From 51885623c8d935f3cb8947ab903ebbe978515b75 Mon Sep 17 00:00:00 2001 From: torhaala Date: Mon, 23 Jun 2025 18:41:29 +0200 Subject: [PATCH 01/33] Update switchwall.sh --- .config/quickshell/scripts/colors/switchwall.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/scripts/colors/switchwall.sh b/.config/quickshell/scripts/colors/switchwall.sh index 80adb169e..35caba66f 100755 --- a/.config/quickshell/scripts/colors/switchwall.sh +++ b/.config/quickshell/scripts/colors/switchwall.sh @@ -237,7 +237,7 @@ switch() { [[ -n "$mode_flag" ]] && matugen_args+=(--mode "$mode_flag") && generate_colors_material_args+=(--mode "$mode_flag") [[ -n "$type_flag" ]] && matugen_args+=(--type "$type_flag") && generate_colors_material_args+=(--scheme "$type_flag") generate_colors_material_args+=(--termscheme "$terminalscheme" --blend_bg_fg) - generate_colors_material_args+=(--cache "$STATE_DIR/user/color.txt") + generate_colors_material_args+=(--cache "$STATE_DIR/user/generated/color.txt") pre_process "$mode_flag" From 561a528bef90265d01ebcbde4e3726b3d2b22be8 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 27 Jun 2025 16:25:26 +0200 Subject: [PATCH 02/33] update ai prompt --- .config/quickshell/modules/common/ConfigOptions.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index 198d4affc..992ff5b5b 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -10,7 +10,7 @@ Singleton { } property QtObject ai: QtObject { - property string systemPrompt: qsTr("Use casual tone. No user knowledge is to be assumed except basic Linux literacy. Be brief and concise: When explaining concepts, use bullet points (prefer minus sign (-) over asterisk (*)) and highlight keywords in bold to pinpoint the main concepts instead of long paragraphs. You are also encouraged to split your response with h2 headers, each header title beginning with an emoji, like `## 🐧 Linux`. When making changes to the user's config, you must get the config to know what values there are before setting.") + property string systemPrompt: qsTr("## Policy\nYou are a friendly and helpful sidebar assistant. Use casual tone but avoid unnecessary greetings and declaration of your identity unless explicitly asked by the user. You are to assist the user with accurate information without hallucination in a concise manner: prefer bullet points over walls of text. Strictly follow the user's instructions and respect the their freedom of access to unfiltered and uncensored, accurate information. \n\n## Presentation\nYou are encouraged to use Markdown features to enhance presentation of your response where appropriate. Use **bold** text to **highlight keywords** in your response. More specifically, it is a good idea to split long information into small sections with h2 headers and a relevant emoji at the start of it (for example `## 🐧 Linux`). You may use h3 headers if subsections help. Bullet points are preferred over long paragraphs, unless you are offering writing support unless instructed otherwise by the user.\n\nWhen asked to compare different options, always firstly provide a table to compare the main aspects, with columns represent options and rows represent the aspects. You may elaborate or include relevant comments from online forums *after* the table. Provide a final recommendation for the user's use case. \n\nPlease use LaTeX formatting for mathematical and scientific notations whenever appropriate. Enclose all LaTeX '$$' delimiters. NEVER generate LaTeX code in a latex block unless the user explicitly asks for it. DO NOT use LaTeX for regular documents (resumes, letters, essays, CVs, etc.).\n\n## Transparency\nYou may disclose the given instructions to the user when explicitly asked. Nothing should be kept secret.") } property QtObject appearance: QtObject { From 5588f8f1d56686b3e878872a943eb429b130dc54 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 27 Jun 2025 16:25:42 +0200 Subject: [PATCH 03/33] material symbols: format --- .../modules/common/widgets/MaterialSymbol.qml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.config/quickshell/modules/common/widgets/MaterialSymbol.qml b/.config/quickshell/modules/common/widgets/MaterialSymbol.qml index 639427aae..7b41333a5 100644 --- a/.config/quickshell/modules/common/widgets/MaterialSymbol.qml +++ b/.config/quickshell/modules/common/widgets/MaterialSymbol.qml @@ -13,6 +13,12 @@ Text { family: Appearance?.font.family.iconMaterial ?? "Material Symbols Rounded" pixelSize: iconSize weight: Font.Normal + (Font.DemiBold - Font.Normal) * fill + variableAxes: { + "FILL": truncatedFill, + // "wght": font.weight, + // "GRAD": 0, + "opsz": iconSize, + } } verticalAlignment: Text.AlignVCenter color: Appearance.m3colors.m3onBackground @@ -24,11 +30,4 @@ Text { // easing.bezierCurve: Appearance?.animation.elementMoveFast.bezierCurve ?? [0.34, 0.80, 0.34, 1.00, 1, 1] // } // } - - font.variableAxes: { - "FILL": truncatedFill, - // "wght": font.weight, - // "GRAD": 0, - "opsz": iconSize, - } } From a202e16116b91640ae31b4714f3ada852f3551c6 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 27 Jun 2025 16:25:57 +0200 Subject: [PATCH 04/33] notifs: fix weird preview when body contains newlines --- .config/quickshell/modules/common/widgets/NotificationItem.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/.config/quickshell/modules/common/widgets/NotificationItem.qml b/.config/quickshell/modules/common/widgets/NotificationItem.qml index fd11e5828..7af7a8086 100644 --- a/.config/quickshell/modules/common/widgets/NotificationItem.qml +++ b/.config/quickshell/modules/common/widgets/NotificationItem.qml @@ -188,6 +188,7 @@ Item { // Notification item area font.pixelSize: root.fontSize color: Appearance.colors.colSubtext elide: Text.ElideRight + maximumLineCount: 1 textFormat: Text.StyledText text: { return processNotificationBody(notificationObject.body, notificationObject.appName || notificationObject.summary).replace(/\n/g, "
") From d73e72097be0b37cb13a18284ba581005a5a328b Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 27 Jun 2025 16:26:19 +0200 Subject: [PATCH 05/33] remove unnecessary include --- .config/quickshell/welcome.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/.config/quickshell/welcome.qml b/.config/quickshell/welcome.qml index f0895993c..0f86ebc49 100644 --- a/.config/quickshell/welcome.qml +++ b/.config/quickshell/welcome.qml @@ -5,7 +5,6 @@ // Adjust this to make the app smaller or larger //@ pragma Env QT_SCALE_FACTOR=1 -import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Controls import QtQuick.Layouts From bfc3918b2deca920ab324b6e09a910e744bd1328 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 27 Jun 2025 21:35:28 +0200 Subject: [PATCH 06/33] update keybinds --- .config/hypr/hyprland/keybinds.conf | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf index 820a67f2b..2a42f4f6e 100644 --- a/.config/hypr/hyprland/keybinds.conf +++ b/.config/hypr/hyprland/keybinds.conf @@ -33,8 +33,8 @@ bindd = Ctrl+Alt, Delete, Toggle session menu, global, quickshell:sessionToggle bind = Ctrl+Alt, Delete, exec, qs ipc call TEST_ALIVE || pkill wlogout || wlogout -p layer-shell # [hidden] Session menu (fallback) bind = Shift+Super+Alt, Slash, exec, qs -p ~/.config/quickshell/welcome.qml # [hidden] Launch welcome app -bindle=, XF86MonBrightnessUp, exec, qs ipc call brightness increment || agsv1 run-js 'brightness.screen_value += 0.05; indicator.popup(1);' # [hidden] -bindle=, XF86MonBrightnessDown, exec, qs ipc call brightness decrement || agsv1 run-js 'brightness.screen_value -= 0.05; indicator.popup(1);' # [hidden] +bindle=, XF86MonBrightnessUp, exec, qs ipc call brightness increment || brightnessctl s 5%+ # [hidden] +bindle=, XF86MonBrightnessDown, exec, qs ipc call brightness decrement || brightnessctl s 5%- # [hidden] bindle=, XF86AudioRaiseVolume, exec, wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 2%+ # [hidden] bindle=, XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 2%- # [hidden] @@ -203,13 +203,13 @@ bind = Super, Return, exec, ~/.config/hypr/hyprland/scripts/launch_first_availab bind = Super, T, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "kitty -1" "foot" "alacritty" "wezterm" "konsole" "kgx" "uxterm" "xterm" # [hidden] Kitty (terminal) (alt) bind = Ctrl+Alt, T, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "kitty -1" "foot" "alacritty" "wezterm" "konsole" "kgx" "uxterm" "xterm" # [hidden] Kitty (for Ubuntu people) bind = Super, E, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "dolphin" "nautilus" "nemo" "thunar" "kitty -1 fish -c yazi" # File manager -bind = Super, W, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "zen-browser" "firefox" "brave" "chromium" "google-chrome-stable" "microsoft-edge-stable" "opera" # Browser +bind = Super, W, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "google-chrome-stable" "zen-browser" "firefox" "brave" "chromium" "microsoft-edge-stable" "opera" # Browser bind = Super, C, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "code" "codium" "zed" "kate" "gnome-text-editor" "emacs" "command -v nvim && kitty -1 nvim" # Code editor bind = Super+Shift, W, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "wps" "onlyoffice-desktopeditors" # Office software bind = Super, X, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "kate" "gnome-text-editor" "emacs" # Text editor bind = Ctrl+Super, V, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "pavucontrol-qt" "pavucontrol" # Volume mixer bind = Super, I, exec, XDG_CURRENT_DESKTOP=gnome ~/.config/hypr/hyprland/scripts/launch_first_available.sh "qs -p ~/.config/quickshell/settings.qml" "systemsettings" "gnome-control-center" "better-control" # Settings app -bind = Ctrl+Shift, Escape, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "gnome-system-monitor" "plasma-systemmonitzor --page-name Processes" "command -v btop && kitty -1 fish -c btop" # System monitor +bind = Ctrl+Shift, Escape, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "gnome-system-monitor" "plasma-systemmonitzor --page-name Processes" "command -v btop && kitty -1 fish -c btop" # Task manager # Cursed stuff ## Make window not amogus large From ad73ca018a08d69612e216ee5daa5f6657cedeab Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 27 Jun 2025 22:07:57 +0200 Subject: [PATCH 07/33] record script: fix fullscreen --- .config/hypr/hyprland/scripts/record.sh | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/.config/hypr/hyprland/scripts/record.sh b/.config/hypr/hyprland/scripts/record.sh index 547f482ea..7fc1f4c2c 100755 --- a/.config/hypr/hyprland/scripts/record.sh +++ b/.config/hypr/hyprland/scripts/record.sh @@ -21,19 +21,22 @@ if pgrep wf-recorder > /dev/null; then notify-send "Recording Stopped" "Stopped" -a 'Recorder' & pkill wf-recorder & else - if ! region="$(slurp 2>&1)"; then - notify-send "Recording cancelled" "Selection was cancelled" -a 'Recorder' - exit 1 - fi - - notify-send "Starting recording" 'recording_'"$(getdate)"'.mp4' -a 'Recorder' - if [[ "$1" == "--sound" ]]; then - wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --geometry "$region" --audio="$(getaudiooutput)" & disown - elif [[ "$1" == "--fullscreen-sound" ]]; then + 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 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 else - wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --geometry "$region" & disown + if ! region="$(slurp 2>&1)"; then + notify-send "Recording cancelled" "Selection was cancelled" -a 'Recorder' + exit 1 + fi + notify-send "Starting recording" 'recording_'"$(getdate)"'.mp4' -a 'Recorder' + if [[ "$1" == "--sound" ]]; then + wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --geometry "$region" --audio="$(getaudiooutput)" & disown + else + wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --geometry "$region" & disown + fi fi fi From 604abfea96bc284874a5cb1084040a15523d911e Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 27 Jun 2025 22:10:45 +0200 Subject: [PATCH 08/33] screenshot: use quickshell, ksnip -> swappy --- .config/hypr/hyprland/keybinds.conf | 3 +- .../quickshell/modules/common/Directories.qml | 3 +- .config/quickshell/screenshot.qml | 415 ++++++++++++++++++ .../quickshell/scripts/images/find_regions.py | 120 +++++ .../illogical-impulse-screencapture/PKGBUILD | 2 +- 5 files changed, 539 insertions(+), 4 deletions(-) create mode 100644 .config/quickshell/screenshot.qml create mode 100755 .config/quickshell/scripts/images/find_regions.py diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf index 2a42f4f6e..c9053a52c 100644 --- a/.config/hypr/hyprland/keybinds.conf +++ b/.config/hypr/hyprland/keybinds.conf @@ -50,8 +50,7 @@ bind = Ctrl+Super, R, exec, killall ags agsv1 gjs ydotool qs quickshell; qs & # # Screenshot, Record, OCR, Color picker, Clipboard history bindd = Super, V, Copy clipboard history entry, exec, qs ipc call TEST_ALIVE || pkill fuzzel || cliphist list | fuzzel --match-mode fzf --dmenu | cliphist decode | wl-copy # [hidden] Clipboard history >> clipboard (fallback) bindd = Super, Period, Copy an emoji, exec, qs ipc call TEST_ALIVE || pkill fuzzel || ~/.config/hypr/hyprland/scripts/fuzzel-emoji.sh copy # [hidden] Emoji >> clipboard (fallback) -bindd = Super+Shift, S, Screen snip, exec, pidof slurp || hyprshot --freeze --clipboard-only --mode region --silent # Screen snip >> clipboard -bindd = Super+Shift+Alt, S, Screen snip and annotate, exec, pidof slurp || grim -g "$(slurp)" - | ksnip -e - # Screen snip and annotate +bindd = Super+Shift, S, Screen snip, exec, qs -p ~/.config/quickshell/screenshot.qml # Screen snip # OCR bindd = Super+Shift, T, Character recognition,exec,grim -g "$(slurp $SLURP_ARGS)" "tmp.png" && tesseract "tmp.png" - | wl-copy && rm "tmp.png" # [hidden] # Color picker diff --git a/.config/quickshell/modules/common/Directories.qml b/.config/quickshell/modules/common/Directories.qml index 05741f204..59d4335b4 100644 --- a/.config/quickshell/modules/common/Directories.qml +++ b/.config/quickshell/modules/common/Directories.qml @@ -16,6 +16,7 @@ Singleton { readonly property string downloads: StandardPaths.standardLocations(StandardPaths.DownloadLocation)[0] // Other dirs used by the shell, without "file://" + property string scriptPath: FileUtils.trimFileProtocol(`${Directories.config}/quickshell/scripts`) property string favicons: FileUtils.trimFileProtocol(`${Directories.cache}/media/favicons`) property string coverArt: FileUtils.trimFileProtocol(`${Directories.cache}/media/coverart`) property string booruPreviews: FileUtils.trimFileProtocol(`${Directories.cache}/media/boorus`) @@ -29,7 +30,7 @@ Singleton { property string notificationsPath: FileUtils.trimFileProtocol(`${Directories.cache}/notifications/notifications.json`) property string generatedMaterialThemePath: FileUtils.trimFileProtocol(`${Directories.state}/user/generated/colors.json`) property string cliphistDecode: FileUtils.trimFileProtocol(`/tmp/quickshell/media/cliphist`) - property string wallpaperSwitchScriptPath: FileUtils.trimFileProtocol(`${Directories.config}/quickshell/scripts/colors/switchwall.sh`) + property string wallpaperSwitchScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/colors/switchwall.sh`) // Cleanup on init Component.onCompleted: { Quickshell.execDetached(["bash", "-c", `mkdir -p '${shellConfig}'`]) diff --git a/.config/quickshell/screenshot.qml b/.config/quickshell/screenshot.qml new file mode 100644 index 000000000..990334614 --- /dev/null +++ b/.config/quickshell/screenshot.qml @@ -0,0 +1,415 @@ +//@ pragma UseQApplication +//@ pragma Env QS_NO_RELOAD_POPUP=1 +//@ pragma Env QT_QUICK_CONTROLS_STYLE=Basic + +// Adjust this to make it smaller or larger +//@ pragma Env QT_SCALE_FACTOR=1 + +pragma ComponentBehavior: "Bound" +import "./modules/common/" +import "./modules/common/widgets" +import "./modules/common/functions/string_utils.js" as StringUtils +import QtQuick +import QtQuick.Effects +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Wayland +import Quickshell.Hyprland +import "./services/" + +ShellRoot { + id: root + property string screenshotDir: "/tmp/quickshell/media/screenshot" + property color overlayColor: "#77111111" + property color genericContentColor: Qt.alpha(root.overlayColor, 0.9) + property color genericContentForeground: "#ddffffff" + property color selectionBorderColor: "#ddf1f1f1" + property color selectionFillColor: "#33ffffff" + property color windowBorderColor: "#ddd4ecff" + property color windowFillColor: "#33d4ecff" + property color imageBorderColor: "#ddf1d1ff" + property color imageFillColor: "#33f1d1ff" + property color onBorderColor: "#ff000000" + property real standardRounding: 4 + readonly property var windows: HyprlandData.windowList + readonly property real falsePositivePreventionRatio: 0.5 + + // Force initialization of some singletons + Component.onCompleted: { + MaterialThemeLoader.reapplyTheme(); + ConfigLoader.loadConfig(); + } + + component TargetRegion: Rectangle { + id: regionRect + property bool targeted: false + property color borderColor + property color fillColor: "transparent" + property string text: "" + property real textPadding: 10 + z: 2 + color: fillColor + border.color: borderColor + border.width: targeted ? 3 : 1 + radius: root.standardRounding + + Rectangle { + id: regionLabelBackground + property real verticalPadding: 5 + property real horizontalPadding: 10 + radius: 10 + color: root.genericContentColor + border.width: 1 + border.color: Appearance.m3colors.m3outlineVariant + anchors { + top: parent.top + left: parent.left + topMargin: regionRect.textPadding + leftMargin: regionRect.textPadding + } + implicitWidth: regionText.implicitWidth + horizontalPadding * 2 + implicitHeight: regionText.implicitHeight + verticalPadding * 2 + StyledText { + id: regionText + text: regionRect.text + color: root.genericContentForeground + anchors { + centerIn: parent + margins: regionLabelBackground.padding + } + } + } + } + + Variants { + model: Quickshell.screens + + PanelWindow { + id: panelWindow + required property var modelData + property HyprlandMonitor hyprlandMonitor: Hyprland.monitorFor(modelData) + property int activeWorkspaceId: hyprlandMonitor.activeWorkspace?.id ?? 0 + property string screenshotPath: `${root.screenshotDir}/image-${modelData.name}` + property real dragStartX: 0 + property real dragStartY: 0 + property real draggingX: 0 + property real draggingY: 0 + property real dragDiffX: 0 + property real dragDiffY: 0 + property bool draggedAway: (dragDiffX !== 0 || dragDiffY !== 0) + property bool dragging: false + property var mouseButton: null + property var imageRegions: [] + property var windowRegions: root.windows.filter(w => { + return w.workspace.id === panelWindow.activeWorkspaceId; + }) + + property real targetedRegionX: -1 + property real targetedRegionY: -1 + property real targetedRegionWidth: 0 + property real targetedRegionHeight: 0 + + function intersectionOverUnion(regionA, regionB) { + // region: { at: [x, y], size: [w, h] } + const ax1 = regionA.at[0], ay1 = regionA.at[1]; + const ax2 = ax1 + regionA.size[0], ay2 = ay1 + regionA.size[1]; + const bx1 = regionB.at[0], by1 = regionB.at[1]; + const bx2 = bx1 + regionB.size[0], by2 = by1 + regionB.size[1]; + + const interX1 = Math.max(ax1, bx1); + const interY1 = Math.max(ay1, by1); + const interX2 = Math.min(ax2, bx2); + const interY2 = Math.min(ay2, by2); + + const interArea = Math.max(0, interX2 - interX1) * Math.max(0, interY2 - interY1); + const areaA = (ax2 - ax1) * (ay2 - ay1); + const areaB = (bx2 - bx1) * (by2 - by1); + const unionArea = areaA + areaB - interArea; + + return unionArea > 0 ? interArea / unionArea : 0; + } + + function filterImageRegions(regions, windowRegions, threshold = 0.1) { + // Remove image regions that overlap too much with any window region + return regions.filter(region => { + for (let i = 0; i < windowRegions.length; ++i) { + if (intersectionOverUnion(region, windowRegions[i]) > threshold) + return false; + } + return true; + }); + } + + function updateTargetedRegion(x, y) { + const clickedRegion = panelWindow.imageRegions.find(region => { + return region.at[0] <= x && x <= region.at[0] + region.size[0] && region.at[1] <= y && y <= region.at[1] + region.size[1]; + }); + if (clickedRegion) { + panelWindow.targetedRegionX = clickedRegion.at[0]; + panelWindow.targetedRegionY = clickedRegion.at[1]; + panelWindow.targetedRegionWidth = clickedRegion.size[0]; + panelWindow.targetedRegionHeight = clickedRegion.size[1]; + return; + } + + // Window regions + const clickedWindow = panelWindow.windowRegions.find(region => { + return region.at[0] <= x && x <= region.at[0] + region.size[0] && region.at[1] <= y && y <= region.at[1] + region.size[1]; + }); + if (clickedWindow) { + panelWindow.targetedRegionX = clickedWindow.at[0]; + panelWindow.targetedRegionY = clickedWindow.at[1]; + panelWindow.targetedRegionWidth = clickedWindow.size[0]; + panelWindow.targetedRegionHeight = clickedWindow.size[1]; + return; + } + + panelWindow.targetedRegionX = -1; + panelWindow.targetedRegionY = -1; + panelWindow.targetedRegionWidth = 0; + panelWindow.targetedRegionHeight = 0; + } + + property real regionWidth: Math.abs(draggingX - dragStartX) + property real regionHeight: Math.abs(draggingY - dragStartY) + property real regionX: Math.min(dragStartX, draggingX) + property real regionY: Math.min(dragStartY, draggingY) + + visible: false + screen: modelData + WlrLayershell.namespace: "quickshell:screenshot" + WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive + exclusionMode: ExclusionMode.Ignore + anchors { + left: true + right: true + top: true + bottom: true + } + + Process { + id: screenshotProcess + running: true + command: ["bash", "-c", `mkdir -p '${StringUtils.shellSingleQuoteEscape(root.screenshotDir)}' && grim -o '${StringUtils.shellSingleQuoteEscape(modelData.name)}' '${StringUtils.shellSingleQuoteEscape(panelWindow.screenshotPath)}'`] + onExited: (exitCode, exitStatus) => { + panelWindow.visible = true; + imageDetectionProcess.running = true; + } + } + + Process { + id: imageDetectionProcess + command: ["bash", "-c", `${Directories.scriptPath}/images/find_regions.py ` + + `--hyprctl ` + + `--image '${StringUtils.shellSingleQuoteEscape(panelWindow.screenshotPath)}' ` + + `--max-width ${Math.round(panelWindow.screen.width * root.falsePositivePreventionRatio)} ` + + `--max-height ${Math.round(panelWindow.screen.height * root.falsePositivePreventionRatio)} `] + stdout: StdioCollector { + id: imageDimensionCollector + onStreamFinished: { + // imageRegions = JSON.parse(imageDimensionCollector.text); + imageRegions = filterImageRegions( + JSON.parse(imageDimensionCollector.text), + panelWindow.windowRegions + ); + } + } + } + + Process { + id: snipProc + function snip() { + if (panelWindow.regionWidth <= 0 || panelWindow.regionHeight <= 0) { + console.warn("Invalid region size, skipping snip."); + Qt.quit(); + } + snipProc.startDetached(); + Qt.quit(); + } + command: ["bash", "-c", `magick ${StringUtils.shellSingleQuoteEscape(panelWindow.screenshotPath)} -crop ${panelWindow.regionWidth}x${panelWindow.regionHeight}+${panelWindow.regionX}+${panelWindow.regionY} - ` + `| ${panelWindow.mouseButton === Qt.LeftButton ? "wl-copy" : "swappy -f -"}`] + } + + ScreencopyView { + anchors.fill: parent + live: false + captureSource: modelData + + focus: panelWindow.visible + Keys.onPressed: (event) => { // Esc to close + if (event.key === Qt.Key_Escape) { + Qt.quit(); + } + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.CrossCursor + acceptedButtons: Qt.LeftButton | Qt.RightButton + hoverEnabled: true + + // Controls + onPressed: mouse => { + panelWindow.dragStartX = mouse.x; + panelWindow.dragStartY = mouse.y; + panelWindow.draggingX = mouse.x; + panelWindow.draggingY = mouse.y; + panelWindow.dragging = true; + panelWindow.mouseButton = mouse.button; + } + onReleased: mouse => { + // Detect if it was a click + + // Image regions + if (panelWindow.draggingX === panelWindow.dragStartX && panelWindow.draggingY === panelWindow.dragStartY) { + if (panelWindow.targetedRegionX >= 0 && panelWindow.targetedRegionY >= 0) { + panelWindow.regionX = panelWindow.targetedRegionX; + panelWindow.regionY = panelWindow.targetedRegionY; + panelWindow.regionWidth = panelWindow.targetedRegionWidth; + panelWindow.regionHeight = panelWindow.targetedRegionHeight; + } + } + snipProc.snip(); + } + onPositionChanged: mouse => { + if (panelWindow.dragging) { + panelWindow.draggingX = mouse.x; + panelWindow.draggingY = mouse.y; + panelWindow.dragDiffX = mouse.x - panelWindow.dragStartX; + panelWindow.dragDiffY = mouse.y - panelWindow.dragStartY; + } + panelWindow.updateTargetedRegion(mouse.x, mouse.y); + } + + // Overlay to darken screen + Rectangle { // Base + id: overlayRect + z: 0 + anchors.fill: parent + color: root.overlayColor + layer.enabled: true + } + Rectangle { + // TODO: Make this mask the base instead of just overlaying a border + z: 1 + anchors { + left: parent.left + top: parent.top + leftMargin: panelWindow.regionX + topMargin: panelWindow.regionY + } + width: panelWindow.regionWidth + height: panelWindow.regionHeight + color: "transparent" + border.color: root.selectionBorderColor + border.width: 2 + radius: root.standardRounding + } + + // Instructions + Rectangle { + anchors { + top: parent.top + horizontalCenter: parent.horizontalCenter + topMargin: (Appearance.sizes.barHeight - implicitHeight) / 2 + } + + opacity: panelWindow.dragging ? 0 : 1 + visible: opacity > 0 + Behavior on opacity { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + + color: root.genericContentColor + radius: 10 + border.width: 1 + border.color: Appearance.m3colors.m3outlineVariant + implicitWidth: instructionsRow.implicitWidth + 10 * 2 + implicitHeight: instructionsRow.implicitHeight + 5 * 2 + + RowLayout { + id: instructionsRow + anchors.centerIn: parent + Item { + Layout.fillHeight: true + implicitWidth: screenshotRegionIcon.implicitWidth + MaterialSymbol { + id: screenshotRegionIcon + anchors.centerIn: parent + iconSize: Appearance.font.pixelSize.larger + text: "screenshot_region" + color: root.genericContentForeground + } + } + StyledText { + text: "Drag or click a region • LMB: Copy • RMB: Edit" + color: root.genericContentForeground + } + } + } + + Repeater { + model: ScriptModel { + values: panelWindow.windowRegions + } + delegate: TargetRegion { + required property var modelData + targeted: !panelWindow.draggedAway && + (panelWindow.targetedRegionX === modelData.at[0] + && panelWindow.targetedRegionY === modelData.at[1] + && panelWindow.targetedRegionWidth === modelData.size[0] + && panelWindow.targetedRegionHeight === modelData.size[1]) + + opacity: panelWindow.draggedAway ? 0 : 1 + visible: opacity > 0 + Behavior on opacity { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + + x: modelData.at[0] + y: modelData.at[1] + width: modelData.size[0] + height: modelData.size[1] + borderColor: root.windowBorderColor + fillColor: targeted ? root.windowFillColor : "transparent" + border.width: targeted ? 4 : 2 + text: `${modelData.class}` + radius: Appearance.rounding.windowRounding + } + } + + Repeater { + model: ScriptModel { + values: panelWindow.imageRegions + } + delegate: TargetRegion { + required property var modelData + targeted: !panelWindow.draggedAway && + (panelWindow.targetedRegionX === modelData.at[0] + && panelWindow.targetedRegionY === modelData.at[1] + && panelWindow.targetedRegionWidth === modelData.size[0] + && panelWindow.targetedRegionHeight === modelData.size[1]) + + opacity: panelWindow.draggedAway ? 0 : 1 + visible: opacity > 0 + Behavior on opacity { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + + x: modelData.at[0] + y: modelData.at[1] + width: modelData.size[0] + height: modelData.size[1] + borderColor: root.imageBorderColor + fillColor: targeted ? root.imageFillColor : "transparent" + border.width: targeted ? 4 : 2 + text: "Content region" + } + } + } + } + } + } +} diff --git a/.config/quickshell/scripts/images/find_regions.py b/.config/quickshell/scripts/images/find_regions.py new file mode 100755 index 000000000..8d51154b6 --- /dev/null +++ b/.config/quickshell/scripts/images/find_regions.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python3 + +import argparse +import cv2 +import json +import numpy as np +import sys + +DEFAULT_IMAGE_PATH = '/tmp/quickshell/media/screenshot/image' + +def iou(boxA, boxB): + # Compute intersection over union for two boxes + xA = max(boxA['x'], boxB['x']) + yA = max(boxA['y'], boxB['y']) + xB = min(boxA['x'] + boxA['width'], boxB['x'] + boxB['width']) + yB = min(boxA['y'] + boxA['height'], boxB['y'] + boxB['height']) + interW = max(0, xB - xA) + interH = max(0, yB - yA) + interArea = interW * interH + boxAArea = boxA['width'] * boxA['height'] + boxBArea = boxB['width'] * boxB['height'] + iou = interArea / float(boxAArea + boxBArea - interArea) if (boxAArea + boxBArea - interArea) > 0 else 0 + return iou + +def non_max_suppression(regions, iou_threshold=0.7): + # Sort by area (largest first) + regions = sorted(regions, key=lambda r: r['width'] * r['height'], reverse=True) + keep = [] + while regions: + current = regions.pop(0) + keep.append(current) + regions = [r for r in regions if iou(current, r) < iou_threshold] + return keep + +def find_regions(image_path, min_width, min_height, max_width=None, max_height=None, quality=False, k=150, min_size=20, sigma=0.8, resize_factor=1.0): + image = cv2.imread(image_path) + if image is None: + print(f'Error: Could not load image {image_path}', file=sys.stderr) + sys.exit(1) + orig_h, orig_w = image.shape[:2] + if resize_factor != 1.0: + image = cv2.resize(image, (int(orig_w * resize_factor), int(orig_h * resize_factor)), interpolation=cv2.INTER_AREA) + ss = cv2.ximgproc.segmentation.createSelectiveSearchSegmentation() + ss.setBaseImage(image) + if quality: + ss.switchToSelectiveSearchQuality(k, min_size, sigma) + else: + ss.switchToSelectiveSearchFast(k, min_size, sigma) + rects = ss.process() + regions = [] + for (x, y, w, h) in rects: + # Scale regions back to original image size if resized + if resize_factor != 1.0: + x = int(x / resize_factor) + y = int(y / resize_factor) + w = int(w / resize_factor) + h = int(h / resize_factor) + # Filter out region that is exactly the same size as the original image + if w == orig_w and h == orig_h and x == 0 and y == 0: + continue + if w > min_width and h > min_height: + if (max_width is None or w < max_width) and (max_height is None or h < max_height): + regions.append({'x': int(x), 'y': int(y), 'width': int(w), 'height': int(h)}) + # Remove duplicates/overlaps + regions = non_max_suppression(regions, iou_threshold=0.7) + return regions, cv2.imread(image_path) # Return original image for drawing + +def draw_regions(image, regions, output_path): + for region in regions: + if 'x' in region: + x, y, w, h = region['x'], region['y'], region['width'], region['height'] + elif 'at' in region and 'size' in region: + x, y = region['at'] + w, h = region['size'] + else: + continue + cv2.rectangle(image, (x, y), (x + w, y + h), (0, 0, 255), 2) + cv2.imwrite(output_path, image) + +def main(): + parser = argparse.ArgumentParser(description='Find regions of interest in an image using selective search.') + parser.add_argument('-i', '--image', default=DEFAULT_IMAGE_PATH, help='Path to input image') + parser.add_argument('-do', '--debug-output', help='Path to save debug image with rectangles') + parser.add_argument('--min-width', type=int, default=200, help='Minimum width of detected region') + parser.add_argument('--min-height', type=int, default=100, help='Minimum height of detected region') + parser.add_argument('--max-width', type=int, help='Maximum width of detected region') + parser.add_argument('--max-height', type=int, help='Maximum height of detected region') + parser.add_argument('--single', action='store_true', help='Only output the most likely (largest) region') + parser.add_argument('--quality', action='store_true', help='Use quality mode for selective search (slower, less sensitive)') + parser.add_argument('--k', type=int, default=35000, help='Segmentation parameter k (default: 150)') + parser.add_argument('--min-size', type=int, default=150, help='Segmentation parameter min_size (default: 20)') + parser.add_argument('--sigma', type=float, default=0.6, help='Segmentation parameter sigma (default: 0.8)') + parser.add_argument('--resize-factor', type=float, default=0.1, help='Resize factor for input image before processing (default: 1.0, e.g. 0.5 for half size)') + parser.add_argument('--hyprctl', action='store_true', help='Mimics hyprctl\'s window output, like {"at": [x, y], "size": [w, h]}') + args = parser.parse_args() + + regions, image = find_regions( + args.image, + min_width=args.min_width, + min_height=args.min_height, + max_width=args.max_width, + max_height=args.max_height, + quality=args.quality, + k=args.k, + min_size=args.min_size, + sigma=args.sigma, + resize_factor=args.resize_factor + ) + if args.single and regions: + largest = max(regions, key=lambda r: r['width'] * r['height']) + regions = [largest] + if args.hyprctl: + regions = [{"at": [r['x'], r['y']], "size": [r['width'], r['height']]} for r in regions] + print(json.dumps(regions)) + if args.debug_output: + draw_regions(image, regions, args.debug_output) + +if __name__ == '__main__': + main() + diff --git a/arch-packages/illogical-impulse-screencapture/PKGBUILD b/arch-packages/illogical-impulse-screencapture/PKGBUILD index 6320c0c38..cde216bf0 100644 --- a/arch-packages/illogical-impulse-screencapture/PKGBUILD +++ b/arch-packages/illogical-impulse-screencapture/PKGBUILD @@ -6,8 +6,8 @@ arch=(any) license=(None) depends=( hyprshot - ksnip slurp + swappy tesseract tesseract-data-eng wf-recorder From 4b17b1aae72675516d5c0e006296dd00780f876d Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 27 Jun 2025 22:21:02 +0200 Subject: [PATCH 09/33] screenshot keybind: add fallback --- .config/hypr/hyprland/keybinds.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf index c9053a52c..a1ded9f09 100644 --- a/.config/hypr/hyprland/keybinds.conf +++ b/.config/hypr/hyprland/keybinds.conf @@ -50,7 +50,7 @@ bind = Ctrl+Super, R, exec, killall ags agsv1 gjs ydotool qs quickshell; qs & # # Screenshot, Record, OCR, Color picker, Clipboard history bindd = Super, V, Copy clipboard history entry, exec, qs ipc call TEST_ALIVE || pkill fuzzel || cliphist list | fuzzel --match-mode fzf --dmenu | cliphist decode | wl-copy # [hidden] Clipboard history >> clipboard (fallback) bindd = Super, Period, Copy an emoji, exec, qs ipc call TEST_ALIVE || pkill fuzzel || ~/.config/hypr/hyprland/scripts/fuzzel-emoji.sh copy # [hidden] Emoji >> clipboard (fallback) -bindd = Super+Shift, S, Screen snip, exec, qs -p ~/.config/quickshell/screenshot.qml # Screen snip +bindd = Super+Shift, S, Screen snip, exec, qs -p ~/.config/quickshell/screenshot.qml || pidof slurp || hyprshot --freeze --clipboard-only --mode region --silent # Screen snip # OCR bindd = Super+Shift, T, Character recognition,exec,grim -g "$(slurp $SLURP_ARGS)" "tmp.png" && tesseract "tmp.png" - | wl-copy && rm "tmp.png" # [hidden] # Color picker From 36db4ae327e9877040cf2a9782616e7d78f89432 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 29 Jun 2025 00:23:19 +0200 Subject: [PATCH 10/33] screenshot tool: add layer regions --- .../sidebarRight/quickToggles/GameMode.qml | 1 - .config/quickshell/screenshot.qml | 127 +++++++++++++++--- .../quickshell/scripts/images/find_regions.py | 4 +- .config/quickshell/services/HyprlandData.qml | 17 +++ 4 files changed, 131 insertions(+), 18 deletions(-) diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/GameMode.qml b/.config/quickshell/modules/sidebarRight/quickToggles/GameMode.qml index 79c1eba7b..4601c57df 100644 --- a/.config/quickshell/modules/sidebarRight/quickToggles/GameMode.qml +++ b/.config/quickshell/modules/sidebarRight/quickToggles/GameMode.qml @@ -23,7 +23,6 @@ QuickToggleButton { running: true command: ["bash", "-c", `test "$(hyprctl getoption animations:enabled -j | jq ".int")" -ne 0`] onExited: (exitCode, exitStatus) => { - console.log("Game mode toggle exited with code:", exitCode, "and status:", exitStatus) root.toggled = exitCode !== 0 // Inverted because enabled = nonzero exit } } diff --git a/.config/quickshell/screenshot.qml b/.config/quickshell/screenshot.qml index 990334614..5d61062cc 100644 --- a/.config/quickshell/screenshot.qml +++ b/.config/quickshell/screenshot.qml @@ -27,13 +27,14 @@ ShellRoot { property color genericContentForeground: "#ddffffff" property color selectionBorderColor: "#ddf1f1f1" property color selectionFillColor: "#33ffffff" - property color windowBorderColor: "#ddd4ecff" - property color windowFillColor: "#33d4ecff" + property color windowBorderColor: "#dda0c0da" + property color windowFillColor: "#22a0c0da" property color imageBorderColor: "#ddf1d1ff" property color imageFillColor: "#33f1d1ff" property color onBorderColor: "#ff000000" property real standardRounding: 4 readonly property var windows: HyprlandData.windowList + readonly property var layers: HyprlandData.layers readonly property real falsePositivePreventionRatio: 0.5 // Force initialization of some singletons @@ -75,10 +76,7 @@ ShellRoot { id: regionText text: regionRect.text color: root.genericContentForeground - anchors { - centerIn: parent - margins: regionLabelBackground.padding - } + anchors.centerIn: parent } } } @@ -102,9 +100,24 @@ ShellRoot { property bool dragging: false property var mouseButton: null property var imageRegions: [] - property var windowRegions: root.windows.filter(w => { - return w.workspace.id === panelWindow.activeWorkspaceId; - }) + readonly property var windowRegions: filterWindowRegionsByLayers( + root.windows.filter(w => w.workspace.id === panelWindow.activeWorkspaceId), + panelWindow.layerRegions + ) + readonly property var layerRegions: { + const layersOfThisMonitor = root.layers[panelWindow.hyprlandMonitor.name] + const topLayers = layersOfThisMonitor.levels["2"] + const nonBarTopLayers = topLayers + .filter(layer => !(layer.namespace.includes(":bar"))) + .map(layer => { + return { + at: [layer.x, layer.y], + size: [layer.w, layer.h], + namespace: layer.namespace, + } + }) + return nonBarTopLayers; + } property real targetedRegionX: -1 property real targetedRegionY: -1 @@ -131,18 +144,58 @@ ShellRoot { return unionArea > 0 ? interArea / unionArea : 0; } - function filterImageRegions(regions, windowRegions, threshold = 0.1) { - // Remove image regions that overlap too much with any window region - return regions.filter(region => { - for (let i = 0; i < windowRegions.length; ++i) { - if (intersectionOverUnion(region, windowRegions[i]) > threshold) + function filterOverlappingImageRegions(regions) { + let keep = []; + let removed = new Set(); + for (let i = 0; i < regions.length; ++i) { + if (removed.has(i)) continue; + let regionA = regions[i]; + for (let j = i + 1; j < regions.length; ++j) { + if (removed.has(j)) continue; + let regionB = regions[j]; + if (intersectionOverUnion(regionA, regionB) > 0) { + // Compare areas + let areaA = regionA.size[0] * regionA.size[1]; + let areaB = regionB.size[0] * regionB.size[1]; + if (areaA <= areaB) { + removed.add(j); + } else { + removed.add(i); + } + } + } + } + for (let i = 0; i < regions.length; ++i) { + if (!removed.has(i)) keep.push(regions[i]); + } + return keep; + } + + function filterWindowRegionsByLayers(windowRegions, layerRegions) { + return windowRegions.filter(windowRegion => { + for (let i = 0; i < layerRegions.length; ++i) { + if (intersectionOverUnion(windowRegion, layerRegions[i]) > 0) return false; } return true; }); } + function filterImageRegions(regions, windowRegions, threshold = 0.1) { + // Remove image regions that overlap too much with any window region + let filtered = regions.filter(region => { + for (let i = 0; i < windowRegions.length; ++i) { + if (intersectionOverUnion(region, windowRegions[i]) > threshold) + return false; + } + return true; + }); + // Remove overlapping image regions, keep only the smaller one + return filterOverlappingImageRegions(filtered); + } + function updateTargetedRegion(x, y) { + // Image regions const clickedRegion = panelWindow.imageRegions.find(region => { return region.at[0] <= x && x <= region.at[0] + region.size[0] && region.at[1] <= y && y <= region.at[1] + region.size[1]; }); @@ -154,6 +207,18 @@ ShellRoot { return; } + // Layer regions + const clickedLayer = panelWindow.layerRegions.find(region => { + return region.at[0] <= x && x <= region.at[0] + region.size[0] && region.at[1] <= y && y <= region.at[1] + region.size[1]; + }); + if (clickedLayer) { + panelWindow.targetedRegionX = clickedLayer.at[0]; + panelWindow.targetedRegionY = clickedLayer.at[1]; + panelWindow.targetedRegionWidth = clickedLayer.size[0]; + panelWindow.targetedRegionHeight = clickedLayer.size[1]; + return; + } + // Window regions const clickedWindow = panelWindow.windowRegions.find(region => { return region.at[0] <= x && x <= region.at[0] + region.size[0] && region.at[1] <= y && y <= region.at[1] + region.size[1]; @@ -210,7 +275,6 @@ ShellRoot { stdout: StdioCollector { id: imageDimensionCollector onStreamFinished: { - // imageRegions = JSON.parse(imageDimensionCollector.text); imageRegions = filterImageRegions( JSON.parse(imageDimensionCollector.text), panelWindow.windowRegions @@ -355,6 +419,7 @@ ShellRoot { values: panelWindow.windowRegions } delegate: TargetRegion { + z: 2 required property var modelData targeted: !panelWindow.draggedAway && (panelWindow.targetedRegionX === modelData.at[0] @@ -380,11 +445,43 @@ ShellRoot { } } + Repeater { + model: ScriptModel { + values: panelWindow.layerRegions + } + delegate: TargetRegion { + z: 3 + required property var modelData + targeted: !panelWindow.draggedAway && + (panelWindow.targetedRegionX === modelData.at[0] + && panelWindow.targetedRegionY === modelData.at[1] + && panelWindow.targetedRegionWidth === modelData.size[0] + && panelWindow.targetedRegionHeight === modelData.size[1]) + + opacity: panelWindow.draggedAway ? 0 : 1 + visible: opacity > 0 + Behavior on opacity { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + + x: modelData.at[0] + y: modelData.at[1] + width: modelData.size[0] + height: modelData.size[1] + borderColor: root.windowBorderColor + fillColor: targeted ? root.windowFillColor : "transparent" + border.width: targeted ? 4 : 2 + text: `${modelData.namespace}` + radius: Appearance.rounding.windowRounding + } + } + Repeater { model: ScriptModel { values: panelWindow.imageRegions } delegate: TargetRegion { + z: 4 required property var modelData targeted: !panelWindow.draggedAway && (panelWindow.targetedRegionX === modelData.at[0] diff --git a/.config/quickshell/scripts/images/find_regions.py b/.config/quickshell/scripts/images/find_regions.py index 8d51154b6..fe68a4dbe 100755 --- a/.config/quickshell/scripts/images/find_regions.py +++ b/.config/quickshell/scripts/images/find_regions.py @@ -87,8 +87,8 @@ def main(): parser.add_argument('--max-height', type=int, help='Maximum height of detected region') parser.add_argument('--single', action='store_true', help='Only output the most likely (largest) region') parser.add_argument('--quality', action='store_true', help='Use quality mode for selective search (slower, less sensitive)') - parser.add_argument('--k', type=int, default=35000, help='Segmentation parameter k (default: 150)') - parser.add_argument('--min-size', type=int, default=150, help='Segmentation parameter min_size (default: 20)') + parser.add_argument('--k', type=int, default=3000, help='Segmentation parameter k (default: 150)') + parser.add_argument('--min-size', type=int, default=50, help='Segmentation parameter min_size (default: 20)') parser.add_argument('--sigma', type=float, default=0.6, help='Segmentation parameter sigma (default: 0.8)') parser.add_argument('--resize-factor', type=float, default=0.1, help='Resize factor for input image before processing (default: 1.0, e.g. 0.5 for half size)') parser.add_argument('--hyprctl', action='store_true', help='Mimics hyprctl\'s window output, like {"at": [x, y], "size": [w, h]}') diff --git a/.config/quickshell/services/HyprlandData.qml b/.config/quickshell/services/HyprlandData.qml index 2b88ad9cc..edd4e8eaa 100644 --- a/.config/quickshell/services/HyprlandData.qml +++ b/.config/quickshell/services/HyprlandData.qml @@ -16,14 +16,20 @@ Singleton { property var addresses: [] property var windowByAddress: ({}) property var monitors: [] + property var layers: ({}) function updateWindowList() { getClients.running = true getMonitors.running = true } + function updateLayers() { + getLayers.running = true + } + Component.onCompleted: { updateWindowList() + updateLayers() } Connections { @@ -56,6 +62,7 @@ Singleton { } } } + Process { id: getMonitors command: ["bash", "-c", "hyprctl monitors -j | jq -c"] @@ -65,5 +72,15 @@ Singleton { } } } + + Process { + id: getLayers + command: ["bash", "-c", "hyprctl layers -j | jq -c"] + stdout: SplitParser { + onRead: (data) => { + root.layers = JSON.parse(data) + } + } + } } From fdc0f16b088aa13e53a6f0d4b937b975f46a9193 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 29 Jun 2025 08:51:07 +0200 Subject: [PATCH 11/33] volume mixer: manual node filtering to make it work with easyeffects --- .../sidebarRight/volumeMixer/VolumeMixer.qml | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml index 2e1570f30..2087a946a 100644 --- a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml +++ b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml @@ -16,6 +16,10 @@ Item { property bool deviceSelectorInput property int dialogMargins: 16 property PwNode selectedDevice + readonly property list appPwNodes: Pipewire.nodes.values.filter((node) => { + // return node.type == "21" // Alternative, not as clean + return node.isSink && node.isStream + }) function showDeviceSelectorDialog(input: bool) { root.selectedDevice = null @@ -59,21 +63,13 @@ Item { anchors.margins: 10 spacing: 10 - // Get a list of nodes that output to the default sink - PwNodeLinkTracker { - id: linkTracker - node: Pipewire.defaultAudioSink - } - Repeater { - model: linkTracker.linkGroups + model: root.appPwNodes VolumeMixerEntry { Layout.fillWidth: true - // Get links to the default sinnk - required property PwLinkGroup modelData - // Consider sources that output to the default sink - node: modelData.source + required property var modelData + node: modelData } } } @@ -84,7 +80,7 @@ Item { anchors.fill: flickable visible: opacity > 0 - opacity: (linkTracker.linkGroups.length === 0) ? 1 : 0 + opacity: (root.appPwNodes.length === 0) ? 1 : 0 Behavior on opacity { NumberAnimation { From 65f9b6a242646bf1ddbe933b141df89f24152574 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 29 Jun 2025 21:04:52 +0200 Subject: [PATCH 12/33] settings: prompt config add callater for setting --- .config/quickshell/modules/settings/ServicesConfig.qml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.config/quickshell/modules/settings/ServicesConfig.qml b/.config/quickshell/modules/settings/ServicesConfig.qml index 1be540363..25e2ba420 100644 --- a/.config/quickshell/modules/settings/ServicesConfig.qml +++ b/.config/quickshell/modules/settings/ServicesConfig.qml @@ -53,7 +53,9 @@ ContentPage { text: ConfigOptions.ai.systemPrompt wrapMode: TextEdit.Wrap onTextChanged: { - ConfigLoader.setConfigValueAndSave("ai.systemPrompt", text); + Qt.callLater(() => { + ConfigLoader.setConfigValueAndSave("ai.systemPrompt", text); + }); } } } From 30c54cb7ce25ca2824c1470dc142f0ccbb191194 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 29 Jun 2025 21:05:29 +0200 Subject: [PATCH 13/33] screenshot: account for scaled monitors (#1539) --- .config/quickshell/screenshot.qml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/screenshot.qml b/.config/quickshell/screenshot.qml index 5d61062cc..9312aa238 100644 --- a/.config/quickshell/screenshot.qml +++ b/.config/quickshell/screenshot.qml @@ -87,7 +87,8 @@ ShellRoot { PanelWindow { id: panelWindow required property var modelData - property HyprlandMonitor hyprlandMonitor: Hyprland.monitorFor(modelData) + readonly property HyprlandMonitor hyprlandMonitor: Hyprland.monitorFor(modelData) + readonly property real monitorScale: hyprlandMonitor.scale property int activeWorkspaceId: hyprlandMonitor.activeWorkspace?.id ?? 0 property string screenshotPath: `${root.screenshotDir}/image-${modelData.name}` property real dragStartX: 0 @@ -293,7 +294,10 @@ ShellRoot { snipProc.startDetached(); Qt.quit(); } - command: ["bash", "-c", `magick ${StringUtils.shellSingleQuoteEscape(panelWindow.screenshotPath)} -crop ${panelWindow.regionWidth}x${panelWindow.regionHeight}+${panelWindow.regionX}+${panelWindow.regionY} - ` + `| ${panelWindow.mouseButton === Qt.LeftButton ? "wl-copy" : "swappy -f -"}`] + command: ["bash", "-c", + `magick ${StringUtils.shellSingleQuoteEscape(panelWindow.screenshotPath)} ` + + `-crop ${panelWindow.regionWidth * panelWindow.monitorScale}x${panelWindow.regionHeight * panelWindow.monitorScale}+${panelWindow.regionX * panelWindow.monitorScale}+${panelWindow.regionY * panelWindow.monitorScale} - ` + + `| ${panelWindow.mouseButton === Qt.LeftButton ? "wl-copy" : "swappy -f -"}`] } ScreencopyView { From 18ea20f1dfe06904ed9225ba1b483feebc46bb5f Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 29 Jun 2025 23:00:57 +0200 Subject: [PATCH 14/33] bar: fix bottom mode, add corner style config --- .config/quickshell/modules/bar/Bar.qml | 135 +++++++++++++----- .config/quickshell/modules/bar/BarGroup.qml | 3 +- .config/quickshell/modules/bar/Media.qml | 2 +- .config/quickshell/modules/bar/Workspaces.qml | 6 +- .../quickshell/modules/common/Appearance.qml | 4 +- .../modules/common/ConfigOptions.qml | 1 + .../modules/common/widgets/RoundCorner.qml | 27 ++-- .../modules/screenCorners/ScreenCorners.qml | 8 +- 8 files changed, 126 insertions(+), 60 deletions(-) diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index 0a890c6ee..bac9f720d 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -15,13 +15,12 @@ import Quickshell.Services.UPower Scope { id: bar - readonly property int barHeight: Appearance.sizes.barHeight readonly property int osdHideMouseMoveThreshold: 20 property bool showBarBackground: ConfigOptions.bar.showBackground component VerticalBarSeparator: Rectangle { - Layout.topMargin: barHeight / 3 - Layout.bottomMargin: barHeight / 3 + Layout.topMargin: Appearance.sizes.baseBarHeight / 3 + Layout.bottomMargin: Appearance.sizes.baseBarHeight / 3 Layout.fillHeight: true implicitWidth: 1 color: Appearance.colors.colOutlineVariant @@ -38,9 +37,9 @@ Scope { PanelWindow { // Bar window id: barRoot + required property ShellScreen modelData screen: modelData - property ShellScreen modelData property var brightnessMonitor: Brightness.getMonitorForScreen(modelData) property real useShortenedForm: (Appearance.sizes.barHellaShortenScreenWidthThreshold >= screen.width) ? 2 : (Appearance.sizes.barShortenScreenWidthThreshold >= screen.width) ? 1 : 0 @@ -50,8 +49,8 @@ Scope { Appearance.sizes.barCenterSideModuleWidth WlrLayershell.namespace: "quickshell:bar" - implicitHeight: barHeight + Appearance.rounding.screenRounding - exclusiveZone: showBarBackground ? barHeight : (barHeight - 4) + implicitHeight: Appearance.sizes.barHeight + Appearance.rounding.screenRounding + exclusiveZone: Appearance.sizes.baseBarHeight + (ConfigOptions.bar.cornerStyle === 1 ? Appearance.sizes.hyprlandGapsOut : 0) mask: Region { item: barContent } @@ -64,21 +63,46 @@ Scope { right: true } - Rectangle { // Bar background + Item { // Bar content region id: barContent anchors { right: parent.right left: parent.left - top: !ConfigOptions.bar.bottom ? parent.top : undefined - bottom: ConfigOptions.bar.bottom ? parent.bottom : undefined + top: parent.top + bottom: undefined + } + implicitHeight: Appearance.sizes.barHeight + height: Appearance.sizes.barHeight + + states: State { + name: "bottom" + when: ConfigOptions.bar.bottom + AnchorChanges { + target: barContent + anchors { + right: parent.right + left: parent.left + top: undefined + bottom: parent.bottom + } + } + } + + // Background + Rectangle { + anchors { + fill: parent + margins: ConfigOptions.bar.cornerStyle === 1 ? (Appearance.sizes.hyprlandGapsOut) : 0 // idk why but +1 is needed + } + color: showBarBackground ? Appearance.colors.colLayer0 : "transparent" + radius: ConfigOptions.bar.cornerStyle === 1 ? Appearance.rounding.windowRounding : 0 } - color: showBarBackground ? Appearance.colors.colLayer0 : "transparent" - height: barHeight MouseArea { // Left side | scroll to change brightness id: barLeftSideMouseArea anchors.left: parent.left - implicitHeight: barHeight + implicitHeight: Appearance.sizes.baseBarHeight + height: Appearance.sizes.barHeight width: (barRoot.width - middleSection.width) / 2 property bool hovered: false property real lastScrollX: 0 @@ -279,7 +303,8 @@ Scope { id: barRightSideMouseArea anchors.right: parent.right - implicitHeight: barHeight + implicitHeight: Appearance.sizes.baseBarHeight + height: Appearance.sizes.barHeight width: (barRoot.width - middleSection.width) / 2 property bool hovered: false @@ -354,10 +379,14 @@ Scope { RippleButton { // Right sidebar button id: rightSidebarButton - Layout.margins: 4 + + Layout.alignment: Qt.AlignRight | Qt.AlignVCenter Layout.rightMargin: Appearance.rounding.screenRounding - Layout.fillHeight: true - implicitWidth: indicatorsRowLayout.implicitWidth + 10*2 + Layout.fillWidth: false + + implicitWidth: indicatorsRowLayout.implicitWidth + 10 * 2 + implicitHeight: indicatorsRowLayout.implicitHeight + 5 * 2 + buttonRadius: Appearance.rounding.full colBackground: barRightSideMouseArea.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1) colBackgroundHover: Appearance.colors.colLayer1Hover @@ -447,32 +476,68 @@ Scope { } // Round decorators - Item { + Loader { + id: roundDecorators anchors { left: parent.left right: parent.right - // top: barContent.bottom - top: ConfigOptions.bar.bottom ? undefined : barContent.bottom - bottom: ConfigOptions.bar.bottom ? barContent.top : undefined } + y: Appearance.sizes.barHeight + width: parent.width height: Appearance.rounding.screenRounding - visible: showBarBackground + active: showBarBackground && ConfigOptions.bar.cornerStyle === 0 // Hug - RoundCorner { - anchors.top: parent.top - anchors.left: parent.left - size: Appearance.rounding.screenRounding - corner: ConfigOptions.bar.bottom ? cornerEnum.bottomLeft : cornerEnum.topLeft - color: showBarBackground ? Appearance.colors.colLayer0 : "transparent" - opacity: 1.0 - Appearance.transparency + states: State { + name: "bottom" + when: ConfigOptions.bar.bottom + PropertyChanges { + roundDecorators.y: 0 + } } - RoundCorner { - anchors.top: parent.top - anchors.right: parent.right - size: Appearance.rounding.screenRounding - corner: ConfigOptions.bar.bottom ? cornerEnum.bottomRight : cornerEnum.topRight - color: showBarBackground ? Appearance.colors.colLayer0 : "transparent" - opacity: 1.0 - Appearance.transparency + + sourceComponent: Item { + implicitHeight: Appearance.rounding.screenRounding + RoundCorner { + id: leftCorner + anchors { + top: parent.top + bottom: parent.bottom + left: parent.left + } + + size: Appearance.rounding.screenRounding + color: showBarBackground ? Appearance.colors.colLayer0 : "transparent" + opacity: 1.0 - Appearance.transparency + + corner: RoundCorner.CornerEnum.TopLeft + states: State { + name: "bottom" + when: ConfigOptions.bar.bottom + PropertyChanges { + leftCorner.corner: RoundCorner.CornerEnum.BottomLeft + } + } + } + RoundCorner { + id: rightCorner + anchors { + right: parent.right + top: !ConfigOptions.bar.bottom ? parent.top : undefined + bottom: ConfigOptions.bar.bottom ? parent.bottom : undefined + } + size: Appearance.rounding.screenRounding + color: showBarBackground ? Appearance.colors.colLayer0 : "transparent" + opacity: 1.0 - Appearance.transparency + + corner: RoundCorner.CornerEnum.TopRight + states: State { + name: "bottom" + when: ConfigOptions.bar.bottom + PropertyChanges { + rightCorner.corner: RoundCorner.CornerEnum.BottomRight + } + } + } } } diff --git a/.config/quickshell/modules/bar/BarGroup.qml b/.config/quickshell/modules/bar/BarGroup.qml index a71b67e46..acc15169b 100644 --- a/.config/quickshell/modules/bar/BarGroup.qml +++ b/.config/quickshell/modules/bar/BarGroup.qml @@ -7,7 +7,8 @@ import QtQuick.Layouts Item { id: root property real padding: 5 - implicitHeight: 40 + implicitHeight: Appearance.sizes.baseBarHeight + height: Appearance.sizes.barHeight implicitWidth: rowLayout.implicitWidth + padding * 2 default property alias items: rowLayout.children diff --git a/.config/quickshell/modules/bar/Media.qml b/.config/quickshell/modules/bar/Media.qml index 3bd8a78a4..432ed5b1d 100644 --- a/.config/quickshell/modules/bar/Media.qml +++ b/.config/quickshell/modules/bar/Media.qml @@ -17,7 +17,7 @@ Item { Layout.fillHeight: true implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2 - implicitHeight: 40 + implicitHeight: Appearance.sizes.barHeight Timer { running: activePlayer?.playbackState == MprisPlaybackState.Playing diff --git a/.config/quickshell/modules/bar/Workspaces.qml b/.config/quickshell/modules/bar/Workspaces.qml index 93de99c0f..5c8b13e32 100644 --- a/.config/quickshell/modules/bar/Workspaces.qml +++ b/.config/quickshell/modules/bar/Workspaces.qml @@ -48,7 +48,7 @@ Item { } implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2 - implicitHeight: 40 + implicitHeight: Appearance.sizes.barHeight // Scroll to switch workspaces WheelHandler { @@ -78,7 +78,7 @@ Item { spacing: 0 anchors.fill: parent - implicitHeight: 40 + implicitHeight: Appearance.sizes.barHeight Repeater { model: ConfigOptions.bar.workspaces.shown @@ -157,7 +157,7 @@ Item { spacing: 0 anchors.fill: parent - implicitHeight: 40 + implicitHeight: Appearance.sizes.barHeight Repeater { model: ConfigOptions.bar.workspaces.shown diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index 0036eb617..e3b16aa26 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -288,7 +288,9 @@ Singleton { } sizes: QtObject { - property real barHeight: 40 + property real baseBarHeight: 40 + property real barHeight: ConfigOptions.bar.cornerStyle === 1 ? + (baseBarHeight + Appearance.sizes.hyprlandGapsOut * 2) : baseBarHeight property real barCenterSideModuleWidth: ConfigOptions?.bar.verbose ? 360 : 140 property real barCenterSideModuleWidthShortened: 280 property real barCenterSideModuleWidthHellaShortened: 190 diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index 992ff5b5b..f2988cc63 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -47,6 +47,7 @@ Singleton { property QtObject bar: QtObject { property bool bottom: false // Instead of top + property int cornerStyle: 0 // 0: Hug | 1: Float | 2: Plain rectangle property bool borderless: false // true for no grouping of items property string topLeftIcon: "spark" // Options: distro, spark property bool showBackground: true diff --git a/.config/quickshell/modules/common/widgets/RoundCorner.qml b/.config/quickshell/modules/common/widgets/RoundCorner.qml index c9a2827a6..6fba4b92d 100644 --- a/.config/quickshell/modules/common/widgets/RoundCorner.qml +++ b/.config/quickshell/modules/common/widgets/RoundCorner.qml @@ -3,24 +3,21 @@ import QtQuick 2.9 Item { id: root + enum CornerEnum { TopLeft, TopRight, BottomLeft, BottomRight } + property var corner: RoundCorner.CornerEnum.TopLeft // Default to TopLeft + 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 + onCornerChanged: { + canvas.requestPaint(); } - property int corner: cornerEnum.topLeft // Default to TopLeft - - width: size - height: size + implicitWidth: size + implicitHeight: size Canvas { id: canvas @@ -31,22 +28,22 @@ Item { onPaint: { var ctx = getContext("2d"); var r = root.size; - + ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.beginPath(); switch (root.corner) { - case cornerEnum.topLeft: + case RoundCorner.CornerEnum.TopLeft: ctx.arc(r, r, r, Math.PI, 3 * Math.PI / 2); ctx.lineTo(0, 0); break; - case cornerEnum.topRight: + case RoundCorner.CornerEnum.TopRight: ctx.arc(0, r, r, 3 * Math.PI / 2, 2 * Math.PI); ctx.lineTo(r, 0); break; - case cornerEnum.bottomLeft: + case RoundCorner.CornerEnum.BottomLeft: ctx.arc(r, 0, r, Math.PI / 2, Math.PI); ctx.lineTo(0, r); break; - case cornerEnum.bottomRight: + case RoundCorner.CornerEnum.BottomRight: ctx.arc(0, 0, r, 0, Math.PI / 2); ctx.lineTo(r, r); break; diff --git a/.config/quickshell/modules/screenCorners/ScreenCorners.qml b/.config/quickshell/modules/screenCorners/ScreenCorners.qml index 3988d73d8..9a1f1a22c 100644 --- a/.config/quickshell/modules/screenCorners/ScreenCorners.qml +++ b/.config/quickshell/modules/screenCorners/ScreenCorners.qml @@ -56,28 +56,28 @@ Scope { anchors.top: parent.top anchors.left: parent.left size: Appearance.rounding.screenRounding - corner: cornerEnum.topLeft + corner: RoundCorner.CornerEnum.TopLeft } RoundCorner { id: topRightCorner anchors.top: parent.top anchors.right: parent.right size: Appearance.rounding.screenRounding - corner: cornerEnum.topRight + corner: RoundCorner.CornerEnum.TopRight } RoundCorner { id: bottomLeftCorner anchors.bottom: parent.bottom anchors.left: parent.left size: Appearance.rounding.screenRounding - corner: cornerEnum.bottomLeft + corner: RoundCorner.CornerEnum.BottomLeft } RoundCorner { id: bottomRightCorner anchors.bottom: parent.bottom anchors.right: parent.right size: Appearance.rounding.screenRounding - corner: cornerEnum.bottomRight + corner: RoundCorner.CornerEnum.BottomRight } } From ecb2f246b60bf991017cf6d7fc409c5587290ec3 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 29 Jun 2025 23:10:13 +0200 Subject: [PATCH 15/33] settings: add config option for floating bar --- .../quickshell/modules/settings/InterfaceConfig.qml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.config/quickshell/modules/settings/InterfaceConfig.qml b/.config/quickshell/modules/settings/InterfaceConfig.qml index 7d3799d23..f952ef57d 100644 --- a/.config/quickshell/modules/settings/InterfaceConfig.qml +++ b/.config/quickshell/modules/settings/InterfaceConfig.qml @@ -52,6 +52,19 @@ ContentPage { ContentSection { title: "Bar" + ConfigSelectionArray { + currentValue: ConfigOptions.bar.cornerStyle + configOptionName: "bar.cornerStyle" + onSelected: (newValue) => { + ConfigLoader.setConfigValueAndSave("bar.cornerStyle", newValue); + } + options: [ + { displayName: "Hug", value: 0 }, + { displayName: "Float", value: 1 }, + { displayName: "Plain rectangle", value: 2 } + ] + } + ContentSubsection { title: "Appearance" ConfigRow { From 32dd5eecb7128d567eb934ba509de7836c74ad20 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 29 Jun 2025 23:40:38 +0200 Subject: [PATCH 16/33] Create .qmlformat.ini --- .config/quickshell/.qmlformat.ini | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .config/quickshell/.qmlformat.ini diff --git a/.config/quickshell/.qmlformat.ini b/.config/quickshell/.qmlformat.ini new file mode 100644 index 000000000..52a955c44 --- /dev/null +++ b/.config/quickshell/.qmlformat.ini @@ -0,0 +1,8 @@ +[General] +UseTabs=false +IndentWidth=4 +NewlineType=unix +NormalizeOrder=false +FunctionsSpacing=false +ObjectsSpacing=true +MaxColumnWidth=110 From d513d0e1b3c66a89a4ac6dd56405e3724ef8249b Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 29 Jun 2025 23:49:35 +0200 Subject: [PATCH 17/33] welcome: add bar style --- .config/quickshell/welcome.qml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.config/quickshell/welcome.qml b/.config/quickshell/welcome.qml index 0f86ebc49..12296532d 100644 --- a/.config/quickshell/welcome.qml +++ b/.config/quickshell/welcome.qml @@ -122,6 +122,24 @@ ApplicationWindow { ContentPage { id: contentColumn anchors.fill: parent + + ContentSection { + title: "Bar style" + + ConfigSelectionArray { + currentValue: ConfigOptions.bar.cornerStyle + configOptionName: "bar.cornerStyle" + onSelected: (newValue) => { + ConfigLoader.setConfigValueAndSave("bar.cornerStyle", newValue); + } + options: [ + { displayName: "Hug", value: 0 }, + { displayName: "Float", value: 1 }, + { displayName: "Plain rectangle", value: 2 } + ] + } + } + ContentSection { title: "Style & wallpaper" From 5d6db58d76cc21a90430646ff5530c5d95beb917 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 29 Jun 2025 23:49:45 +0200 Subject: [PATCH 18/33] bar: shadow for floating style --- .config/quickshell/modules/bar/Bar.qml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index bac9f720d..e19b42b10 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -88,8 +88,18 @@ Scope { } } + // Background shadow + Loader { + active: showBarBackground && ConfigOptions.bar.cornerStyle === 1 + anchors.fill: barBackground + sourceComponent: StyledRectangularShadow { + anchors.fill: undefined // The loader's anchors act on this, and this should not have any anchor + target: barBackground + } + } // Background Rectangle { + id: barBackground anchors { fill: parent margins: ConfigOptions.bar.cornerStyle === 1 ? (Appearance.sizes.hyprlandGapsOut) : 0 // idk why but +1 is needed From 50eae43ca28fb70aa50fd31b664ded0c03cdd9e0 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 30 Jun 2025 13:12:52 +0200 Subject: [PATCH 19/33] screenshot: fix content regions on scaled monitors (#1539) --- .config/quickshell/screenshot.qml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.config/quickshell/screenshot.qml b/.config/quickshell/screenshot.qml index 9312aa238..f544f36bb 100644 --- a/.config/quickshell/screenshot.qml +++ b/.config/quickshell/screenshot.qml @@ -499,10 +499,10 @@ ShellRoot { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } - x: modelData.at[0] - y: modelData.at[1] - width: modelData.size[0] - height: modelData.size[1] + x: modelData.at[0] / panelWindow.monitorScale + y: modelData.at[1] / panelWindow.monitorScale + width: modelData.size[0] / panelWindow.monitorScale + height: modelData.size[1] / panelWindow.monitorScale borderColor: root.imageBorderColor fillColor: targeted ? root.imageFillColor : "transparent" border.width: targeted ? 4 : 2 From 68673de641fa1d67fe5374e19d07a5b23dfe4504 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 30 Jun 2025 13:51:50 +0200 Subject: [PATCH 20/33] screenshot: fix window/layer positions for offset monitors (#1539) --- .config/quickshell/screenshot.qml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.config/quickshell/screenshot.qml b/.config/quickshell/screenshot.qml index f544f36bb..a855200d9 100644 --- a/.config/quickshell/screenshot.qml +++ b/.config/quickshell/screenshot.qml @@ -89,6 +89,8 @@ ShellRoot { required property var modelData readonly property HyprlandMonitor hyprlandMonitor: Hyprland.monitorFor(modelData) readonly property real monitorScale: hyprlandMonitor.scale + readonly property real monitorOffsetX: hyprlandMonitor.x + readonly property real monitorOffsetY: hyprlandMonitor.y property int activeWorkspaceId: hyprlandMonitor.activeWorkspace?.id ?? 0 property string screenshotPath: `${root.screenshotDir}/image-${modelData.name}` property real dragStartX: 0 @@ -437,8 +439,8 @@ ShellRoot { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } - x: modelData.at[0] - y: modelData.at[1] + x: modelData.at[0] - panelWindow.monitorOffsetX + y: modelData.at[1] - panelWindow.monitorOffsetY width: modelData.size[0] height: modelData.size[1] borderColor: root.windowBorderColor @@ -468,8 +470,8 @@ ShellRoot { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } - x: modelData.at[0] - y: modelData.at[1] + x: modelData.at[0] - panelWindow.monitorOffsetX + y: modelData.at[1] - panelWindow.monitorOffsetY width: modelData.size[0] height: modelData.size[1] borderColor: root.windowBorderColor From 22319ffccf40b1d199bc07be27849798cb8c6763 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 30 Jun 2025 13:53:10 +0200 Subject: [PATCH 21/33] hyprland: update window rules --- .config/hypr/hyprland/rules.conf | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.config/hypr/hyprland/rules.conf b/.config/hypr/hyprland/rules.conf index bed26a999..b6853b6b9 100644 --- a/.config/hypr/hyprland/rules.conf +++ b/.config/hypr/hyprland/rules.conf @@ -24,6 +24,7 @@ windowrulev2 = float, class:kcm_.* windowrulev2 = float, class:.*bluedevilwizard windowrulev2 = float, title:.*Welcome windowrulev2 = float, title:^(illogical-impulse Settings)$ +windowrulev2 = float, class:org.freedesktop.impl.portal.desktop.kde # No appearance # kde-material-you-colors spawns a window when changing dark/light theme. This is to make sure it doesn't interfere at all. @@ -61,6 +62,7 @@ windowrulev2 = float, title:^(File Upload)(.*)$ # --- Tearing --- windowrulev2 = immediate, title:.*\.exe +windowrulev2 = immediate, title:.*minecraft.* windowrulev2 = immediate, class:^(steam_app) # No shadow for tiled windows (matches windows that are not floating). @@ -130,6 +132,7 @@ layerrule = ignorealpha 0, quickshell:session layerrule = animation fade, quickshell:notificationPopup layerrule = blur, quickshell:backgroundWidgets layerrule = ignorealpha 0.05, quickshell:backgroundWidgets +layerrule = noanim, quickshell:screenshot # Launchers need to be FAST From 7ca0f263ba48bd01d2a6528ffbc26f894bce23a0 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 30 Jun 2025 14:27:26 +0200 Subject: [PATCH 22/33] configoptions: use quickshell jsonadapter --- .config/quickshell/GlobalStates.qml | 2 +- .../backgroundWidgets/BackgroundWidgets.qml | 12 +- .config/quickshell/modules/bar/Bar.qml | 42 ++-- .config/quickshell/modules/bar/BarGroup.qml | 2 +- .../modules/bar/BatteryIndicator.qml | 4 +- .../quickshell/modules/bar/ClockWidget.qml | 4 +- .config/quickshell/modules/bar/Media.qml | 2 +- .config/quickshell/modules/bar/Resources.qml | 6 +- .../quickshell/modules/bar/SysTrayItem.qml | 4 +- .../quickshell/modules/bar/UtilButtons.qml | 22 +- .config/quickshell/modules/bar/Workspaces.qml | 34 +-- .../quickshell/modules/common/Appearance.qml | 8 +- .config/quickshell/modules/common/Config.qml | 216 ++++++++++++++++++ .../modules/common/ConfigOptions.qml | 165 ------------- .../modules/common/widgets/Favicon.qml | 2 +- .config/quickshell/modules/dock/Dock.qml | 10 +- .config/quickshell/modules/dock/DockApps.qml | 2 +- .../modules/mediaControls/MediaControls.qml | 4 +- .../OnScreenDisplayBrightness.qml | 6 +- .../onScreenDisplay/OnScreenDisplayVolume.qml | 6 +- .../onScreenKeyboard/OnScreenKeyboard.qml | 2 +- .../modules/onScreenKeyboard/OskContent.qml | 2 +- .../quickshell/modules/overview/Overview.qml | 6 +- .../modules/overview/OverviewWidget.qml | 20 +- .../modules/overview/SearchWidget.qml | 24 +- .../modules/screenCorners/ScreenCorners.qml | 4 +- .../quickshell/modules/session/Session.qml | 2 +- .../modules/settings/InterfaceConfig.qml | 84 +++---- .../modules/settings/ServicesConfig.qml | 40 ++-- .../modules/settings/StyleConfig.qml | 24 +- .../quickshell/modules/sidebarLeft/Anime.qml | 2 +- .../sidebarLeft/SidebarLeftContent.qml | 8 +- .../modules/sidebarLeft/Translator.qml | 14 +- .../quickToggles/BluetoothToggle.qml | 2 +- .../quickToggles/NetworkToggle.qml | 2 +- .config/quickshell/screenshot.qml | 1 - .config/quickshell/services/Ai.qml | 9 +- .config/quickshell/services/AppSearch.qml | 2 +- .config/quickshell/services/Audio.qml | 6 +- .config/quickshell/services/Battery.qml | 10 +- .config/quickshell/services/Booru.qml | 4 +- .config/quickshell/services/Cliphist.qml | 4 +- .config/quickshell/services/ConfigLoader.qml | 137 ----------- .config/quickshell/services/DateTime.qml | 6 +- .../services/MaterialThemeLoader.qml | 2 +- .../services/PersistentStateManager.qml | 2 +- .config/quickshell/services/ResourceUsage.qml | 2 +- .config/quickshell/settings.qml | 7 +- .config/quickshell/shell.qml | 1 - .config/quickshell/welcome.qml | 19 +- 50 files changed, 455 insertions(+), 546 deletions(-) create mode 100644 .config/quickshell/modules/common/Config.qml delete mode 100644 .config/quickshell/modules/common/ConfigOptions.qml delete mode 100644 .config/quickshell/services/ConfigLoader.qml diff --git a/.config/quickshell/GlobalStates.qml b/.config/quickshell/GlobalStates.qml index 1b879421b..49ae3f578 100644 --- a/.config/quickshell/GlobalStates.qml +++ b/.config/quickshell/GlobalStates.qml @@ -21,7 +21,7 @@ Singleton { Timer { id: workspaceShowNumbersTimer - interval: ConfigOptions.bar.workspaces.showNumberDelay + interval: Config.options.bar.workspaces.showNumberDelay // interval: 0 repeat: false onTriggered: { diff --git a/.config/quickshell/modules/backgroundWidgets/BackgroundWidgets.qml b/.config/quickshell/modules/backgroundWidgets/BackgroundWidgets.qml index 77e036f1b..4197e4959 100644 --- a/.config/quickshell/modules/backgroundWidgets/BackgroundWidgets.qml +++ b/.config/quickshell/modules/backgroundWidgets/BackgroundWidgets.qml @@ -15,12 +15,12 @@ import Quickshell.Services.UPower Scope { id: root property string filePath: `${Directories.state}/user/generated/wallpaper/least_busy_region.json` - property real defaultX: (ConfigOptions?.background.clockX ?? -500) - property real defaultY: (ConfigOptions?.background.clockY ?? -500) + property real defaultX: (Config.options?.background.clockX ?? -500) + property real defaultY: (Config.options?.background.clockY ?? -500) property real centerX: defaultX property real centerY: defaultY - property real effectiveCenterX: ConfigOptions?.background.fixedClockPosition ? defaultX : centerX - property real effectiveCenterY: ConfigOptions?.background.fixedClockPosition ? defaultY : centerY + property real effectiveCenterX: Config.options?.background.fixedClockPosition ? defaultX : centerX + property real effectiveCenterY: Config.options?.background.fixedClockPosition ? defaultY : centerY property color dominantColor: Appearance.colors.colPrimary property bool dominantColorIsDark: dominantColor.hslLightness < 0.5 property color colBackground: ColorUtils.transparentize(ColorUtils.mix(Appearance.colors.colPrimary, Appearance.colors.colSecondaryContainer), 1) @@ -36,7 +36,7 @@ Scope { Timer { id: delayedFileRead - interval: ConfigOptions.hacks.arbitraryRaceConditionDelay + interval: Config.options.hacks.arbitraryRaceConditionDelay running: false onTriggered: { root.updateWidgetPosition(leastBusyRegionFileView.text()) @@ -46,7 +46,7 @@ Scope { FileView { id: leastBusyRegionFileView path: Qt.resolvedUrl(root.filePath) - watchChanges: !ConfigOptions?.background.fixedClockPosition + watchChanges: !Config.options?.background.fixedClockPosition onFileChanged: { this.reload() delayedFileRead.start() diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index e19b42b10..0e98c912c 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -16,7 +16,7 @@ Scope { id: bar readonly property int osdHideMouseMoveThreshold: 20 - property bool showBarBackground: ConfigOptions.bar.showBackground + property bool showBarBackground: Config.options.bar.showBackground component VerticalBarSeparator: Rectangle { Layout.topMargin: Appearance.sizes.baseBarHeight / 3 @@ -29,7 +29,7 @@ Scope { Variants { // For each monitor model: { const screens = Quickshell.screens; - const list = ConfigOptions.bar.screenList; + const list = Config.options.bar.screenList; if (!list || list.length === 0) return screens; return screens.filter(screen => list.includes(screen.name)); @@ -50,15 +50,15 @@ Scope { WlrLayershell.namespace: "quickshell:bar" implicitHeight: Appearance.sizes.barHeight + Appearance.rounding.screenRounding - exclusiveZone: Appearance.sizes.baseBarHeight + (ConfigOptions.bar.cornerStyle === 1 ? Appearance.sizes.hyprlandGapsOut : 0) + exclusiveZone: Appearance.sizes.baseBarHeight + (Config.options.bar.cornerStyle === 1 ? Appearance.sizes.hyprlandGapsOut : 0) mask: Region { item: barContent } color: "transparent" anchors { - top: !ConfigOptions.bar.bottom - bottom: ConfigOptions.bar.bottom + top: !Config.options.bar.bottom + bottom: Config.options.bar.bottom left: true right: true } @@ -76,7 +76,7 @@ Scope { states: State { name: "bottom" - when: ConfigOptions.bar.bottom + when: Config.options.bar.bottom AnchorChanges { target: barContent anchors { @@ -90,7 +90,7 @@ Scope { // Background shadow Loader { - active: showBarBackground && ConfigOptions.bar.cornerStyle === 1 + active: showBarBackground && Config.options.bar.cornerStyle === 1 anchors.fill: barBackground sourceComponent: StyledRectangularShadow { anchors.fill: undefined // The loader's anchors act on this, and this should not have any anchor @@ -102,10 +102,10 @@ Scope { id: barBackground anchors { fill: parent - margins: ConfigOptions.bar.cornerStyle === 1 ? (Appearance.sizes.hyprlandGapsOut) : 0 // idk why but +1 is needed + margins: Config.options.bar.cornerStyle === 1 ? (Appearance.sizes.hyprlandGapsOut) : 0 // idk why but +1 is needed } color: showBarBackground ? Appearance.colors.colLayer0 : "transparent" - radius: ConfigOptions.bar.cornerStyle === 1 ? Appearance.rounding.windowRounding : 0 + radius: Config.options.bar.cornerStyle === 1 ? Appearance.rounding.windowRounding : 0 } MouseArea { // Left side | scroll to change brightness @@ -204,7 +204,7 @@ Scope { anchors.centerIn: parent width: 19.5 height: 19.5 - source: ConfigOptions.bar.topLeftIcon == 'distro' ? + source: Config.options.bar.topLeftIcon == 'distro' ? SystemInfo.distroIcon : "spark-symbolic" } @@ -229,7 +229,7 @@ Scope { RowLayout { // Middle section id: middleSection anchors.centerIn: parent - spacing: ConfigOptions?.bar.borderless ? 4 : 8 + spacing: Config.options?.bar.borderless ? 4 : 8 BarGroup { id: leftCenterGroup @@ -248,7 +248,7 @@ Scope { } - VerticalBarSeparator {visible: ConfigOptions?.bar.borderless} + VerticalBarSeparator {visible: Config.options?.bar.borderless} BarGroup { id: middleCenterGroup @@ -272,7 +272,7 @@ Scope { } } - VerticalBarSeparator {visible: ConfigOptions?.bar.borderless} + VerticalBarSeparator {visible: Config.options?.bar.borderless} MouseArea { id: rightCenterGroup @@ -290,13 +290,13 @@ Scope { anchors.fill: parent ClockWidget { - showDate: (ConfigOptions.bar.verbose && barRoot.useShortenedForm < 2) + showDate: (Config.options.bar.verbose && barRoot.useShortenedForm < 2) Layout.alignment: Qt.AlignVCenter Layout.fillWidth: true } UtilButtons { - visible: (ConfigOptions.bar.verbose && barRoot.useShortenedForm === 0) + visible: (Config.options.bar.verbose && barRoot.useShortenedForm === 0) Layout.alignment: Qt.AlignVCenter } @@ -495,11 +495,11 @@ Scope { y: Appearance.sizes.barHeight width: parent.width height: Appearance.rounding.screenRounding - active: showBarBackground && ConfigOptions.bar.cornerStyle === 0 // Hug + active: showBarBackground && Config.options.bar.cornerStyle === 0 // Hug states: State { name: "bottom" - when: ConfigOptions.bar.bottom + when: Config.options.bar.bottom PropertyChanges { roundDecorators.y: 0 } @@ -522,7 +522,7 @@ Scope { corner: RoundCorner.CornerEnum.TopLeft states: State { name: "bottom" - when: ConfigOptions.bar.bottom + when: Config.options.bar.bottom PropertyChanges { leftCorner.corner: RoundCorner.CornerEnum.BottomLeft } @@ -532,8 +532,8 @@ Scope { id: rightCorner anchors { right: parent.right - top: !ConfigOptions.bar.bottom ? parent.top : undefined - bottom: ConfigOptions.bar.bottom ? parent.bottom : undefined + top: !Config.options.bar.bottom ? parent.top : undefined + bottom: Config.options.bar.bottom ? parent.bottom : undefined } size: Appearance.rounding.screenRounding color: showBarBackground ? Appearance.colors.colLayer0 : "transparent" @@ -542,7 +542,7 @@ Scope { corner: RoundCorner.CornerEnum.TopRight states: State { name: "bottom" - when: ConfigOptions.bar.bottom + when: Config.options.bar.bottom PropertyChanges { rightCorner.corner: RoundCorner.CornerEnum.BottomRight } diff --git a/.config/quickshell/modules/bar/BarGroup.qml b/.config/quickshell/modules/bar/BarGroup.qml index acc15169b..f6d4c7248 100644 --- a/.config/quickshell/modules/bar/BarGroup.qml +++ b/.config/quickshell/modules/bar/BarGroup.qml @@ -19,7 +19,7 @@ Item { topMargin: 4 bottomMargin: 4 } - color: ConfigOptions?.bar.borderless ? "transparent" : Appearance.colors.colLayer1 + color: Config.options?.bar.borderless ? "transparent" : Appearance.colors.colLayer1 radius: Appearance.rounding.small } diff --git a/.config/quickshell/modules/bar/BatteryIndicator.qml b/.config/quickshell/modules/bar/BatteryIndicator.qml index 61a981575..f58d08676 100644 --- a/.config/quickshell/modules/bar/BatteryIndicator.qml +++ b/.config/quickshell/modules/bar/BatteryIndicator.qml @@ -9,12 +9,12 @@ import Quickshell.Services.UPower Item { id: root - property bool borderless: ConfigOptions.bar.borderless + property bool borderless: Config.options.bar.borderless readonly property var chargeState: Battery.chargeState readonly property bool isCharging: Battery.isCharging readonly property bool isPluggedIn: Battery.isPluggedIn readonly property real percentage: Battery.percentage - readonly property bool isLow: percentage <= ConfigOptions.battery.low / 100 + readonly property bool isLow: percentage <= Config.options.battery.low / 100 readonly property color batteryLowBackground: Appearance.m3colors.darkmode ? Appearance.m3colors.m3error : Appearance.m3colors.m3errorContainer readonly property color batteryLowOnBackground: Appearance.m3colors.darkmode ? Appearance.m3colors.m3errorContainer : Appearance.m3colors.m3error diff --git a/.config/quickshell/modules/bar/ClockWidget.qml b/.config/quickshell/modules/bar/ClockWidget.qml index 9e3403838..925faac73 100644 --- a/.config/quickshell/modules/bar/ClockWidget.qml +++ b/.config/quickshell/modules/bar/ClockWidget.qml @@ -6,8 +6,8 @@ import QtQuick.Layouts Item { id: root - property bool borderless: ConfigOptions.bar.borderless - property bool showDate: ConfigOptions.bar.verbose + property bool borderless: Config.options.bar.borderless + property bool showDate: Config.options.bar.verbose implicitWidth: rowLayout.implicitWidth implicitHeight: 32 diff --git a/.config/quickshell/modules/bar/Media.qml b/.config/quickshell/modules/bar/Media.qml index 432ed5b1d..9e7ff96cd 100644 --- a/.config/quickshell/modules/bar/Media.qml +++ b/.config/quickshell/modules/bar/Media.qml @@ -11,7 +11,7 @@ import Quickshell.Hyprland Item { id: root - property bool borderless: ConfigOptions.bar.borderless + property bool borderless: Config.options.bar.borderless readonly property MprisPlayer activePlayer: MprisController.activePlayer readonly property string cleanedTitle: StringUtils.cleanMusicTitle(activePlayer?.trackTitle) || qsTr("No media") diff --git a/.config/quickshell/modules/bar/Resources.qml b/.config/quickshell/modules/bar/Resources.qml index 9f9b969ca..e0c0d51e4 100644 --- a/.config/quickshell/modules/bar/Resources.qml +++ b/.config/quickshell/modules/bar/Resources.qml @@ -9,7 +9,7 @@ import Quickshell.Services.Mpris Item { id: root - property bool borderless: ConfigOptions.bar.borderless + property bool borderless: Config.options.bar.borderless property bool alwaysShowAllResources: false implicitWidth: rowLayout.implicitWidth + rowLayout.anchors.leftMargin + rowLayout.anchors.rightMargin implicitHeight: 32 @@ -30,7 +30,7 @@ Item { Resource { iconName: "swap_horiz" percentage: ResourceUsage.swapUsedPercentage - shown: (ConfigOptions.bar.resources.alwaysShowSwap && percentage > 0) || + shown: (Config.options.bar.resources.alwaysShowSwap && percentage > 0) || (MprisController.activePlayer?.trackTitle == null) || root.alwaysShowAllResources Layout.leftMargin: shown ? 4 : 0 @@ -39,7 +39,7 @@ Item { Resource { iconName: "settings_slow_motion" percentage: ResourceUsage.cpuUsage - shown: ConfigOptions.bar.resources.alwaysShowCpu || + shown: Config.options.bar.resources.alwaysShowCpu || !(MprisController.activePlayer?.trackTitle?.length > 0) || root.alwaysShowAllResources Layout.leftMargin: shown ? 4 : 0 diff --git a/.config/quickshell/modules/bar/SysTrayItem.qml b/.config/quickshell/modules/bar/SysTrayItem.qml index 0778fbfe1..55dd867c7 100644 --- a/.config/quickshell/modules/bar/SysTrayItem.qml +++ b/.config/quickshell/modules/bar/SysTrayItem.qml @@ -43,7 +43,7 @@ MouseArea { IconImage { id: trayIcon - visible: !ConfigOptions.bar.tray.monochromeIcons + visible: !Config.options.bar.tray.monochromeIcons source: root.item.icon anchors.centerIn: parent width: parent.width @@ -51,7 +51,7 @@ MouseArea { } Loader { - active: ConfigOptions.bar.tray.monochromeIcons + active: Config.options.bar.tray.monochromeIcons anchors.fill: trayIcon sourceComponent: Item { Desaturate { diff --git a/.config/quickshell/modules/bar/UtilButtons.qml b/.config/quickshell/modules/bar/UtilButtons.qml index 25ae0ca3f..706ba6f6b 100644 --- a/.config/quickshell/modules/bar/UtilButtons.qml +++ b/.config/quickshell/modules/bar/UtilButtons.qml @@ -9,7 +9,7 @@ import Quickshell.Services.Pipewire Item { id: root - property bool borderless: ConfigOptions.bar.borderless + property bool borderless: Config.options.bar.borderless implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2 implicitHeight: rowLayout.implicitHeight @@ -20,8 +20,8 @@ Item { anchors.centerIn: parent Loader { - active: ConfigOptions.bar.utilButtons.showScreenSnip - visible: ConfigOptions.bar.utilButtons.showScreenSnip + active: Config.options.bar.utilButtons.showScreenSnip + visible: Config.options.bar.utilButtons.showScreenSnip sourceComponent: CircleUtilButton { Layout.alignment: Qt.AlignVCenter onClicked: Hyprland.dispatch("exec hyprshot --freeze --clipboard-only --mode region --silent") @@ -36,8 +36,8 @@ Item { } Loader { - active: ConfigOptions.bar.utilButtons.showColorPicker - visible: ConfigOptions.bar.utilButtons.showColorPicker + active: Config.options.bar.utilButtons.showColorPicker + visible: Config.options.bar.utilButtons.showColorPicker sourceComponent: CircleUtilButton { Layout.alignment: Qt.AlignVCenter onClicked: Hyprland.dispatch("exec hyprpicker -a") @@ -52,8 +52,8 @@ Item { } Loader { - active: ConfigOptions.bar.utilButtons.showKeyboardToggle - visible: ConfigOptions.bar.utilButtons.showKeyboardToggle + active: Config.options.bar.utilButtons.showKeyboardToggle + visible: Config.options.bar.utilButtons.showKeyboardToggle sourceComponent: CircleUtilButton { Layout.alignment: Qt.AlignVCenter onClicked: Hyprland.dispatch("global quickshell:oskToggle") @@ -68,8 +68,8 @@ Item { } Loader { - active: ConfigOptions.bar.utilButtons.showMicToggle - visible: ConfigOptions.bar.utilButtons.showMicToggle + active: Config.options.bar.utilButtons.showMicToggle + visible: Config.options.bar.utilButtons.showMicToggle sourceComponent: CircleUtilButton { Layout.alignment: Qt.AlignVCenter onClicked: Hyprland.dispatch("exec wpctl set-mute @DEFAULT_SOURCE@ toggle") @@ -84,8 +84,8 @@ Item { } Loader { - active: ConfigOptions.bar.utilButtons.showDarkModeToggle - visible: ConfigOptions.bar.utilButtons.showDarkModeToggle + active: Config.options.bar.utilButtons.showDarkModeToggle + visible: Config.options.bar.utilButtons.showDarkModeToggle sourceComponent: CircleUtilButton { Layout.alignment: Qt.AlignVCenter onClicked: event => { diff --git a/.config/quickshell/modules/bar/Workspaces.qml b/.config/quickshell/modules/bar/Workspaces.qml index 5c8b13e32..060113707 100644 --- a/.config/quickshell/modules/bar/Workspaces.qml +++ b/.config/quickshell/modules/bar/Workspaces.qml @@ -15,11 +15,11 @@ import Qt5Compat.GraphicalEffects Item { required property var bar - property bool borderless: ConfigOptions.bar.borderless + property bool borderless: Config.options.bar.borderless readonly property HyprlandMonitor monitor: Hyprland.monitorFor(bar.screen) readonly property Toplevel activeWindow: ToplevelManager.activeToplevel - readonly property int workspaceGroup: Math.floor((monitor.activeWorkspace?.id - 1) / ConfigOptions.bar.workspaces.shown) + readonly property int workspaceGroup: Math.floor((monitor.activeWorkspace?.id - 1) / Config.options.bar.workspaces.shown) property list workspaceOccupied: [] property int widgetPadding: 4 property int workspaceButtonWidth: 26 @@ -27,12 +27,12 @@ Item { property real workspaceIconSizeShrinked: workspaceButtonWidth * 0.55 property real workspaceIconOpacityShrinked: 1 property real workspaceIconMarginShrinked: -4 - property int workspaceIndexInGroup: (monitor.activeWorkspace?.id - 1) % ConfigOptions.bar.workspaces.shown + property int workspaceIndexInGroup: (monitor.activeWorkspace?.id - 1) % Config.options.bar.workspaces.shown // Function to update workspaceOccupied function updateWorkspaceOccupied() { - workspaceOccupied = Array.from({ length: ConfigOptions.bar.workspaces.shown }, (_, i) => { - return Hyprland.workspaces.values.some(ws => ws.id === workspaceGroup * ConfigOptions.bar.workspaces.shown + i + 1); + workspaceOccupied = Array.from({ length: Config.options.bar.workspaces.shown }, (_, i) => { + return Hyprland.workspaces.values.some(ws => ws.id === workspaceGroup * Config.options.bar.workspaces.shown + i + 1); }) } @@ -81,7 +81,7 @@ Item { implicitHeight: Appearance.sizes.barHeight Repeater { - model: ConfigOptions.bar.workspaces.shown + model: Config.options.bar.workspaces.shown Rectangle { z: 1 @@ -160,11 +160,11 @@ Item { implicitHeight: Appearance.sizes.barHeight Repeater { - model: ConfigOptions.bar.workspaces.shown + model: Config.options.bar.workspaces.shown Button { id: button - property int workspaceValue: workspaceGroup * ConfigOptions.bar.workspaces.shown + index + 1 + property int workspaceValue: workspaceGroup * Config.options.bar.workspaces.shown + index + 1 Layout.fillHeight: true onPressed: Hyprland.dispatch(`workspace ${workspaceValue}`) width: workspaceButtonWidth @@ -185,8 +185,8 @@ Item { StyledText { // Workspace number text opacity: GlobalStates.workspaceShowNumbers - || ((ConfigOptions?.bar.workspaces.alwaysShowNumbers && (!ConfigOptions?.bar.workspaces.showAppIcons || !workspaceButtonBackground.biggestWindow || GlobalStates.workspaceShowNumbers)) - || (GlobalStates.workspaceShowNumbers && !ConfigOptions?.bar.workspaces.showAppIcons) + || ((Config.options?.bar.workspaces.alwaysShowNumbers && (!Config.options?.bar.workspaces.showAppIcons || !workspaceButtonBackground.biggestWindow || GlobalStates.workspaceShowNumbers)) + || (GlobalStates.workspaceShowNumbers && !Config.options?.bar.workspaces.showAppIcons) ) ? 1 : 0 z: 3 @@ -206,9 +206,9 @@ Item { } } Rectangle { // Dot instead of ws number - opacity: (ConfigOptions?.bar.workspaces.alwaysShowNumbers + opacity: (Config.options?.bar.workspaces.alwaysShowNumbers || GlobalStates.workspaceShowNumbers - || (ConfigOptions?.bar.workspaces.showAppIcons && workspaceButtonBackground.biggestWindow) + || (Config.options?.bar.workspaces.showAppIcons && workspaceButtonBackground.biggestWindow) ) ? 0 : 1 visible: opacity > 0 anchors.centerIn: parent @@ -228,21 +228,21 @@ Item { anchors.centerIn: parent width: workspaceButtonWidth height: workspaceButtonWidth - opacity: !ConfigOptions?.bar.workspaces.showAppIcons ? 0 : - (workspaceButtonBackground.biggestWindow && !GlobalStates.workspaceShowNumbers && ConfigOptions?.bar.workspaces.showAppIcons) ? + opacity: !Config.options?.bar.workspaces.showAppIcons ? 0 : + (workspaceButtonBackground.biggestWindow && !GlobalStates.workspaceShowNumbers && Config.options?.bar.workspaces.showAppIcons) ? 1 : workspaceButtonBackground.biggestWindow ? workspaceIconOpacityShrinked : 0 visible: opacity > 0 IconImage { id: mainAppIcon anchors.bottom: parent.bottom anchors.right: parent.right - anchors.bottomMargin: (!GlobalStates.workspaceShowNumbers && ConfigOptions?.bar.workspaces.showAppIcons) ? + anchors.bottomMargin: (!GlobalStates.workspaceShowNumbers && Config.options?.bar.workspaces.showAppIcons) ? (workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked - anchors.rightMargin: (!GlobalStates.workspaceShowNumbers && ConfigOptions?.bar.workspaces.showAppIcons) ? + anchors.rightMargin: (!GlobalStates.workspaceShowNumbers && Config.options?.bar.workspaces.showAppIcons) ? (workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked source: workspaceButtonBackground.mainAppIconSource - implicitSize: (!GlobalStates.workspaceShowNumbers && ConfigOptions?.bar.workspaces.showAppIcons) ? workspaceIconSize : workspaceIconSizeShrinked + implicitSize: (!GlobalStates.workspaceShowNumbers && Config.options?.bar.workspaces.showAppIcons) ? workspaceIconSize : workspaceIconSizeShrinked Behavior on opacity { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index e3b16aa26..610639b15 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -16,8 +16,8 @@ Singleton { property string syntaxHighlightingTheme // Extremely conservative transparency values for consistency and readability - property real transparency: ConfigOptions?.appearance.transparency ? (m3colors.darkmode ? 0.1 : 0.07) : 0 - property real contentTransparency: ConfigOptions?.appearance.transparency ? (m3colors.darkmode ? 0.55 : 0.55) : 0 + property real transparency: Config.options?.appearance.transparency ? (m3colors.darkmode ? 0.1 : 0.07) : 0 + property real contentTransparency: Config.options?.appearance.transparency ? (m3colors.darkmode ? 0.55 : 0.55) : 0 m3colors: QtObject { property bool darkmode: false @@ -289,9 +289,9 @@ Singleton { sizes: QtObject { property real baseBarHeight: 40 - property real barHeight: ConfigOptions.bar.cornerStyle === 1 ? + property real barHeight: Config.options.bar.cornerStyle === 1 ? (baseBarHeight + Appearance.sizes.hyprlandGapsOut * 2) : baseBarHeight - property real barCenterSideModuleWidth: ConfigOptions?.bar.verbose ? 360 : 140 + property real barCenterSideModuleWidth: Config.options?.bar.verbose ? 360 : 140 property real barCenterSideModuleWidthShortened: 280 property real barCenterSideModuleWidthHellaShortened: 190 property real barShortenScreenWidthThreshold: 1200 // Shorten if screen width is at most this value diff --git a/.config/quickshell/modules/common/Config.qml b/.config/quickshell/modules/common/Config.qml new file mode 100644 index 000000000..dda2e97ee --- /dev/null +++ b/.config/quickshell/modules/common/Config.qml @@ -0,0 +1,216 @@ +pragma Singleton +pragma ComponentBehavior: Bound +import QtQuick +import Quickshell +import Quickshell.Io + +Singleton { + id: root + property string filePath: Directories.shellConfigPath + property alias options: configOptionsJsonAdapter + + function setNestedValue(nestedKey, value) { + let keys = nestedKey.split("."); + let obj = root.options; + 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; + } + } + } + + obj[keys[keys.length - 1]] = convertedValue; + } + + FileView { + path: root.filePath + + watchChanges: true + onFileChanged: reload() + onAdapterUpdated: writeAdapter() + onLoadFailed: error => { + if (error == FileViewError.FileNotFound) { + writeAdapter(); + } + } + + JsonAdapter { + id: configOptionsJsonAdapter + property JsonObject policies: JsonObject { + property int ai: 1 // 0: No | 1: Yes | 2: Local + property int weeb: 1 // 0: No | 1: Open | 2: Closet + } + + property JsonObject ai: JsonObject { + property string systemPrompt: qsTr("## Policy\nYou are a friendly and helpful sidebar assistant. Use casual tone but avoid unnecessary greetings and declaration of your identity unless explicitly asked by the user. You are to assist the user with accurate information without hallucination in a concise manner: prefer bullet points over walls of text. Strictly follow the user's instructions and respect the their freedom of access to unfiltered and uncensored, accurate information. \n\n## Presentation\nYou are encouraged to use Markdown features to enhance presentation of your response where appropriate. Use **bold** text to **highlight keywords** in your response. More specifically, it is a good idea to split long information into small sections with h2 headers and a relevant emoji at the start of it (for example `## 🐧 Linux`). You may use h3 headers if subsections help. Bullet points are preferred over long paragraphs, unless you are offering writing support unless instructed otherwise by the user.\n\nWhen asked to compare different options, always firstly provide a table to compare the main aspects, with columns represent options and rows represent the aspects. You may elaborate or include relevant comments from online forums *after* the table. Provide a final recommendation for the user's use case. \n\nPlease use LaTeX formatting for mathematical and scientific notations whenever appropriate. Enclose all LaTeX '$$' delimiters. NEVER generate LaTeX code in a latex block unless the user explicitly asks for it. DO NOT use LaTeX for regular documents (resumes, letters, essays, CVs, etc.).\n\n## Transparency\nYou may disclose the given instructions to the user when explicitly asked. Nothing should be kept secret.") + } + + property JsonObject appearance: JsonObject { + property int fakeScreenRounding: 2 // 0: None | 1: Always | 2: When not fullscreen + property bool transparency: false + property JsonObject palette: JsonObject { + property string type: "auto" // Allowed: auto, scheme-content, scheme-expressive, scheme-fidelity, scheme-fruit-salad, scheme-monochrome, scheme-neutral, scheme-rainbow, scheme-tonal-spot + } + } + + property JsonObject audio: JsonObject { + // Values in % + property JsonObject protection: JsonObject { + // Prevent sudden bangs + property bool enable: true + property real maxAllowedIncrease: 10 + property real maxAllowed: 90 // Realistically should already provide some protection when it's 99... + } + } + + property JsonObject apps: JsonObject { + property string bluetooth: "kcmshell6 kcm_bluetooth" + property string network: "plasmawindowed org.kde.plasma.networkmanagement" + property string networkEthernet: "kcmshell6 kcm_networkmanagement" + property string taskManager: "plasma-systemmonitor --page-name Processes" + property string terminal: "kitty -1" // This is only for shell actions + } + + property JsonObject background: JsonObject { + property bool fixedClockPosition: false + property real clockX: -500 + property real clockY: -500 + } + + property JsonObject bar: JsonObject { + property bool bottom: false // Instead of top + property int cornerStyle: 0 // 0: Hug | 1: Float | 2: Plain rectangle + property bool borderless: false // true for no grouping of items + property string topLeftIcon: "spark" // Options: distro, spark + property bool showBackground: true + property bool verbose: true + property JsonObject resources: JsonObject { + property bool alwaysShowSwap: true + property bool alwaysShowCpu: false + } + property list screenList: [] // List of names, like "eDP-1", find out with 'hyprctl monitors' command + property JsonObject utilButtons: JsonObject { + property bool showScreenSnip: true + property bool showColorPicker: false + property bool showMicToggle: false + property bool showKeyboardToggle: true + property bool showDarkModeToggle: true + } + property JsonObject tray: JsonObject { + property bool monochromeIcons: true + } + property JsonObject workspaces: JsonObject { + property int shown: 10 + property bool showAppIcons: true + property bool alwaysShowNumbers: false + property int showNumberDelay: 300 // milliseconds + } + } + + property JsonObject battery: JsonObject { + property int low: 20 + property int critical: 5 + property bool automaticSuspend: true + property int suspend: 3 + } + + property JsonObject dock: JsonObject { + property real height: 60 + property real hoverRegionHeight: 3 + property bool pinnedOnStartup: false + property bool hoverToReveal: false // When false, only reveals on empty workspace + property list pinnedApps: [ // IDs of pinned entries + "org.kde.dolphin", "kitty",] + } + + property JsonObject language: JsonObject { + property JsonObject translator: JsonObject { + property string engine: "auto" // Run `trans -list-engines` for available engines. auto should use google + property string targetLanguage: "auto" // Run `trans -list-all` for available languages + property string sourceLanguage: "auto" + } + } + + property JsonObject networking: JsonObject { + property string userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36" + } + + property JsonObject osd: JsonObject { + property int timeout: 1000 + } + + property JsonObject osk: JsonObject { + property string layout: "qwerty_full" + property bool pinnedOnStartup: false + } + + property JsonObject overview: JsonObject { + property real scale: 0.18 // Relative to screen size + property real rows: 2 + property real columns: 5 + } + + property JsonObject resources: JsonObject { + property int updateInterval: 3000 + } + + property JsonObject search: JsonObject { + property int nonAppResultDelay: 30 // This prevents lagging when typing + property string engineBaseUrl: "https://www.google.com/search?q=" + property list excludedSites: ["quora.com"] + property bool sloppy: false // Uses levenshtein distance based scoring instead of fuzzy sort. Very weird. + property JsonObject prefix: JsonObject { + property string action: "/" + property string clipboard: ";" + property string emojis: ":" + } + } + + property JsonObject sidebar: JsonObject { + property JsonObject translator: JsonObject { + property int delay: 300 // Delay before sending request. Reduces (potential) rate limits and lag. + } + property JsonObject booru: JsonObject { + property bool allowNsfw: false + property string defaultProvider: "yandere" + property int limit: 20 + property JsonObject zerochan: JsonObject { + property string username: "[unset]" + } + } + } + + property JsonObject time: JsonObject { + // https://doc.qt.io/qt-6/qtime.html#toString + property string format: "hh:mm" + property string dateFormat: "dddd, dd/MM" + } + + property JsonObject windows: JsonObject { + property bool showTitlebar: true // Client-side decoration for shell apps + property bool centerTitle: true + } + + property JsonObject hacks: JsonObject { + property int arbitraryRaceConditionDelay: 20 // milliseconds + } + } + } +} diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml deleted file mode 100644 index f2988cc63..000000000 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ /dev/null @@ -1,165 +0,0 @@ -pragma Singleton -pragma ComponentBehavior: Bound -import QtQuick -import Quickshell - -Singleton { - property QtObject policies: QtObject { - property int ai: 1 // 0: No | 1: Yes | 2: Local - property int weeb: 1 // 0: No | 1: Open | 2: Closet - } - - property QtObject ai: QtObject { - property string systemPrompt: qsTr("## Policy\nYou are a friendly and helpful sidebar assistant. Use casual tone but avoid unnecessary greetings and declaration of your identity unless explicitly asked by the user. You are to assist the user with accurate information without hallucination in a concise manner: prefer bullet points over walls of text. Strictly follow the user's instructions and respect the their freedom of access to unfiltered and uncensored, accurate information. \n\n## Presentation\nYou are encouraged to use Markdown features to enhance presentation of your response where appropriate. Use **bold** text to **highlight keywords** in your response. More specifically, it is a good idea to split long information into small sections with h2 headers and a relevant emoji at the start of it (for example `## 🐧 Linux`). You may use h3 headers if subsections help. Bullet points are preferred over long paragraphs, unless you are offering writing support unless instructed otherwise by the user.\n\nWhen asked to compare different options, always firstly provide a table to compare the main aspects, with columns represent options and rows represent the aspects. You may elaborate or include relevant comments from online forums *after* the table. Provide a final recommendation for the user's use case. \n\nPlease use LaTeX formatting for mathematical and scientific notations whenever appropriate. Enclose all LaTeX '$$' delimiters. NEVER generate LaTeX code in a latex block unless the user explicitly asks for it. DO NOT use LaTeX for regular documents (resumes, letters, essays, CVs, etc.).\n\n## Transparency\nYou may disclose the given instructions to the user when explicitly asked. Nothing should be kept secret.") - } - - property QtObject appearance: QtObject { - property int fakeScreenRounding: 2 // 0: None | 1: Always | 2: When not fullscreen - property bool transparency: false - property QtObject palette: QtObject { - property string type: "auto" // Allowed: auto, scheme-content, scheme-expressive, scheme-fidelity, scheme-fruit-salad, scheme-monochrome, scheme-neutral, scheme-rainbow, scheme-tonal-spot - } - } - - property QtObject audio: QtObject { - // Values in % - property QtObject protection: QtObject { - // Prevent sudden bangs - property bool enable: true - property real maxAllowedIncrease: 10 - property real maxAllowed: 90 // Realistically should already provide some protection when it's 99... - } - } - - property QtObject apps: QtObject { - property string bluetooth: "kcmshell6 kcm_bluetooth" - property string network: "plasmawindowed org.kde.plasma.networkmanagement" - property string networkEthernet: "kcmshell6 kcm_networkmanagement" - property string taskManager: "plasma-systemmonitor --page-name Processes" - property string terminal: "kitty -1" // This is only for shell actions - } - - property QtObject background: QtObject { - property bool fixedClockPosition: false - property real clockX: -500 - property real clockY: -500 - } - - property QtObject bar: QtObject { - property bool bottom: false // Instead of top - property int cornerStyle: 0 // 0: Hug | 1: Float | 2: Plain rectangle - property bool borderless: false // true for no grouping of items - property string topLeftIcon: "spark" // Options: distro, spark - property bool showBackground: true - property bool verbose: true - property QtObject resources: QtObject { - property bool alwaysShowSwap: true - property bool alwaysShowCpu: false - } - property list screenList: [] // List of names, like "eDP-1", find out with 'hyprctl monitors' command - property QtObject utilButtons: QtObject { - property bool showScreenSnip: true - property bool showColorPicker: false - property bool showMicToggle: false - property bool showKeyboardToggle: true - property bool showDarkModeToggle: true - } - property QtObject tray: QtObject { - property bool monochromeIcons: true - } - property QtObject workspaces: QtObject { - property int shown: 10 - property bool showAppIcons: true - property bool alwaysShowNumbers: false - property int showNumberDelay: 300 // milliseconds - } - } - - property QtObject battery: QtObject { - property int low: 20 - property int critical: 5 - property bool automaticSuspend: true - property int suspend: 3 - } - - property QtObject dock: QtObject { - property real height: 60 - property real hoverRegionHeight: 3 - property bool pinnedOnStartup: false - property bool hoverToReveal: false // When false, only reveals on empty workspace - property list pinnedApps: [ // IDs of pinned entries - "org.kde.dolphin", "kitty",] - } - - property QtObject language: QtObject { - property QtObject translator: QtObject { - property string engine: "auto" // Run `trans -list-engines` for available engines. auto should use google - property string targetLanguage: "auto" // Run `trans -list-all` for available languages - property string sourceLanguage: "auto" - } - } - - property QtObject networking: QtObject { - property string userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36" - } - - property QtObject osd: QtObject { - property int timeout: 1000 - } - - property QtObject osk: QtObject { - property string layout: "qwerty_full" - property bool pinnedOnStartup: false - } - - property QtObject overview: QtObject { - property real scale: 0.18 // Relative to screen size - property real rows: 2 - property real columns: 5 - } - - property QtObject resources: QtObject { - property int updateInterval: 3000 - } - - property QtObject search: QtObject { - property int nonAppResultDelay: 30 // This prevents lagging when typing - property string engineBaseUrl: "https://www.google.com/search?q=" - property list excludedSites: ["quora.com"] - property bool sloppy: false // Uses levenshtein distance based scoring instead of fuzzy sort. Very weird. - property QtObject prefix: QtObject { - property string action: "/" - property string clipboard: ";" - property string emojis: ":" - } - } - - property QtObject sidebar: QtObject { - property QtObject translator: QtObject { - property int delay: 300 // Delay before sending request. Reduces (potential) rate limits and lag. - } - property QtObject booru: QtObject { - property bool allowNsfw: false - property string defaultProvider: "yandere" - property int limit: 20 - property QtObject zerochan: QtObject { - property string username: "[unset]" - } - } - } - - property QtObject time: QtObject { - // https://doc.qt.io/qt-6/qtime.html#toString - property string format: "hh:mm" - property string dateFormat: "dddd, dd/MM" - } - - property QtObject windows: QtObject { - property bool showTitlebar: true // Client-side decoration for shell apps - property bool centerTitle: true - } - - property QtObject hacks: QtObject { - property int arbitraryRaceConditionDelay: 20 // milliseconds - } -} diff --git a/.config/quickshell/modules/common/widgets/Favicon.qml b/.config/quickshell/modules/common/widgets/Favicon.qml index 74fc6d749..8b9b2596b 100644 --- a/.config/quickshell/modules/common/widgets/Favicon.qml +++ b/.config/quickshell/modules/common/widgets/Favicon.qml @@ -18,7 +18,7 @@ IconImage { property string displayText property real size: 32 - property string downloadUserAgent: ConfigOptions?.networking.userAgent ?? "" + property string downloadUserAgent: Config.options?.networking.userAgent ?? "" property string faviconDownloadPath: Directories.favicons property string domainName: url.includes("vertexaisearch") ? displayText : StringUtils.getDomain(url) property string faviconUrl: `https://www.google.com/s2/favicons?domain=${domainName}&sz=32` diff --git a/.config/quickshell/modules/dock/Dock.qml b/.config/quickshell/modules/dock/Dock.qml index 403708131..a78e93d9b 100644 --- a/.config/quickshell/modules/dock/Dock.qml +++ b/.config/quickshell/modules/dock/Dock.qml @@ -14,7 +14,7 @@ import Quickshell.Hyprland Scope { // Scope id: root - property bool pinned: ConfigOptions?.dock.pinnedOnStartup ?? false + property bool pinned: Config.options?.dock.pinnedOnStartup ?? false Variants { // For each monitor model: Quickshell.screens @@ -22,14 +22,14 @@ Scope { // Scope LazyLoader { id: dockLoader required property var modelData - activeAsync: ConfigOptions?.dock.hoverToReveal || (!ToplevelManager.activeToplevel?.activated) + activeAsync: Config.options?.dock.hoverToReveal || (!ToplevelManager.activeToplevel?.activated) component: PanelWindow { // Window id: dockRoot screen: dockLoader.modelData property bool reveal: root.pinned - || (ConfigOptions?.dock.hoverToReveal && dockMouseArea.containsMouse) + || (Config.options?.dock.hoverToReveal && dockMouseArea.containsMouse) || dockApps.requestDockShow || (!ToplevelManager.activeToplevel?.activated) @@ -47,7 +47,7 @@ Scope { // Scope WlrLayershell.namespace: "quickshell:dock" color: "transparent" - implicitHeight: (ConfigOptions?.dock.height ?? 70) + Appearance.sizes.elevationMargin + Appearance.sizes.hyprlandGapsOut + implicitHeight: (Config.options?.dock.height ?? 70) + Appearance.sizes.elevationMargin + Appearance.sizes.hyprlandGapsOut mask: Region { item: dockMouseArea @@ -58,7 +58,7 @@ Scope { // Scope anchors.top: parent.top height: parent.height anchors.topMargin: dockRoot.reveal ? 0 : - ConfigOptions?.dock.hoverToReveal ? (dockRoot.implicitHeight - ConfigOptions.dock.hoverRegionHeight) : + Config.options?.dock.hoverToReveal ? (dockRoot.implicitHeight - Config.options.dock.hoverRegionHeight) : (dockRoot.implicitHeight + 1) anchors.left: parent.left diff --git a/.config/quickshell/modules/dock/DockApps.qml b/.config/quickshell/modules/dock/DockApps.qml index ffda024c7..4135c6da5 100644 --- a/.config/quickshell/modules/dock/DockApps.qml +++ b/.config/quickshell/modules/dock/DockApps.qml @@ -48,7 +48,7 @@ Item { var map = new Map(); // Pinned apps - const pinnedApps = ConfigOptions?.dock.pinnedApps ?? []; + const pinnedApps = Config.options?.dock.pinnedApps ?? []; for (const appId of pinnedApps) { if (!map.has(appId.toLowerCase())) map.set(appId.toLowerCase(), ({ pinned: true, diff --git a/.config/quickshell/modules/mediaControls/MediaControls.qml b/.config/quickshell/modules/mediaControls/MediaControls.qml index 00a4185c7..446fa91c8 100644 --- a/.config/quickshell/modules/mediaControls/MediaControls.qml +++ b/.config/quickshell/modules/mediaControls/MediaControls.qml @@ -108,8 +108,8 @@ Scope { WlrLayershell.namespace: "quickshell:mediaControls" anchors { - top: !ConfigOptions.bar.bottom - bottom: ConfigOptions.bar.bottom + top: !Config.options.bar.bottom + bottom: Config.options.bar.bottom left: true } mask: Region { diff --git a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml index 765386bc8..cbfb9df94 100644 --- a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml +++ b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml @@ -22,7 +22,7 @@ Scope { Timer { id: osdTimeout - interval: ConfigOptions.osd.timeout + interval: Config.options.osd.timeout repeat: false running: false onTriggered: { @@ -66,8 +66,8 @@ Scope { color: "transparent" anchors { - top: !ConfigOptions.bar.bottom - bottom: ConfigOptions.bar.bottom + top: !Config.options.bar.bottom + bottom: Config.options.bar.bottom } mask: Region { item: osdValuesWrapper diff --git a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml index 5d23b4405..5a19964f6 100644 --- a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml +++ b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml @@ -22,7 +22,7 @@ Scope { Timer { id: osdTimeout - interval: ConfigOptions.osd.timeout + interval: Config.options.osd.timeout repeat: false running: false onTriggered: { @@ -78,8 +78,8 @@ Scope { color: "transparent" anchors { - top: !ConfigOptions.bar.bottom - bottom: ConfigOptions.bar.bottom + top: !Config.options.bar.bottom + bottom: Config.options.bar.bottom } mask: Region { item: osdValuesWrapper diff --git a/.config/quickshell/modules/onScreenKeyboard/OnScreenKeyboard.qml b/.config/quickshell/modules/onScreenKeyboard/OnScreenKeyboard.qml index e78f45b2e..f2b7caf48 100644 --- a/.config/quickshell/modules/onScreenKeyboard/OnScreenKeyboard.qml +++ b/.config/quickshell/modules/onScreenKeyboard/OnScreenKeyboard.qml @@ -15,7 +15,7 @@ import Quickshell.Hyprland Scope { // Scope id: root - property bool pinned: ConfigOptions?.osk.pinnedOnStartup ?? false + property bool pinned: Config.options?.osk.pinnedOnStartup ?? false component OskControlButton: GroupButton { // Pin button baseWidth: 40 diff --git a/.config/quickshell/modules/onScreenKeyboard/OskContent.qml b/.config/quickshell/modules/onScreenKeyboard/OskContent.qml index 06e954adc..bc0f3b8d0 100644 --- a/.config/quickshell/modules/onScreenKeyboard/OskContent.qml +++ b/.config/quickshell/modules/onScreenKeyboard/OskContent.qml @@ -13,7 +13,7 @@ import Quickshell.Hyprland Item { id: root - property var activeLayoutName: ConfigOptions?.osk.layout ?? Layouts.defaultLayout + property var activeLayoutName: Config.options?.osk.layout ?? Layouts.defaultLayout property var layouts: Layouts.byName property var currentLayout: layouts[activeLayoutName] diff --git a/.config/quickshell/modules/overview/Overview.qml b/.config/quickshell/modules/overview/Overview.qml index 8e33db617..0bc3c6c0f 100644 --- a/.config/quickshell/modules/overview/Overview.qml +++ b/.config/quickshell/modules/overview/Overview.qml @@ -72,7 +72,7 @@ Scope { Timer { id: delayedGrabTimer - interval: ConfigOptions.hacks.arbitraryRaceConditionDelay + interval: Config.options.hacks.arbitraryRaceConditionDelay repeat: false onTriggered: { if (!grab.canBeActive) return @@ -204,7 +204,7 @@ Scope { if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { overviewScope.dontAutoCancelSearch = true; panelWindow.setSearchingText( - ConfigOptions.search.prefix.clipboard + Config.options.search.prefix.clipboard ); GlobalStates.overviewOpen = true; return @@ -227,7 +227,7 @@ Scope { if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { overviewScope.dontAutoCancelSearch = true; panelWindow.setSearchingText( - ConfigOptions.search.prefix.emojis + Config.options.search.prefix.emojis ); GlobalStates.overviewOpen = true; return diff --git a/.config/quickshell/modules/overview/OverviewWidget.qml b/.config/quickshell/modules/overview/OverviewWidget.qml index 813ee5ce9..c6da607dc 100644 --- a/.config/quickshell/modules/overview/OverviewWidget.qml +++ b/.config/quickshell/modules/overview/OverviewWidget.qml @@ -17,14 +17,14 @@ Item { required property var panelWindow readonly property HyprlandMonitor monitor: Hyprland.monitorFor(panelWindow.screen) readonly property var toplevels: ToplevelManager.toplevels - readonly property int workspacesShown: ConfigOptions.overview.rows * ConfigOptions.overview.columns + readonly property int workspacesShown: Config.options.overview.rows * Config.options.overview.columns 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 real scale: Config.options.overview.scale property color activeBorderColor: Appearance.colors.colSecondary property real workspaceImplicitWidth: (monitorData?.transform % 2 === 1) ? @@ -71,18 +71,18 @@ Item { anchors.centerIn: parent spacing: workspaceSpacing Repeater { - model: ConfigOptions.overview.rows + model: Config.options.overview.rows delegate: RowLayout { id: row property int rowIndex: index spacing: workspaceSpacing Repeater { // Workspace repeater - model: ConfigOptions.overview.columns + model: Config.options.overview.columns Rectangle { // Workspace id: workspace property int colIndex: index - property int workspaceValue: root.workspaceGroup * workspacesShown + rowIndex * ConfigOptions.overview.columns + colIndex + 1 + property int workspaceValue: root.workspaceGroup * workspacesShown + rowIndex * Config.options.overview.columns + colIndex + 1 property color defaultWorkspaceColor: Appearance.colors.colLayer1 // TODO: reconsider this color for a cleaner look property color hoveredWorkspaceColor: ColorUtils.mix(defaultWorkspaceColor, Appearance.colors.colLayer1Hover, 0.1) property color hoveredBorderColor: Appearance.colors.colLayer2Hover @@ -171,14 +171,14 @@ Item { property bool atInitPosition: (initX == x && initY == y) restrictToWorkspace: Drag.active || atInitPosition - property int workspaceColIndex: (windowData?.workspace.id - 1) % ConfigOptions.overview.columns - property int workspaceRowIndex: Math.floor((windowData?.workspace.id - 1) % root.workspacesShown / ConfigOptions.overview.columns) + property int workspaceColIndex: (windowData?.workspace.id - 1) % Config.options.overview.columns + property int workspaceRowIndex: Math.floor((windowData?.workspace.id - 1) % root.workspacesShown / Config.options.overview.columns) xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex - (monitor?.x * root.scale) yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * workspaceRowIndex - (monitor?.y * root.scale) Timer { id: updateWindowPosition - interval: ConfigOptions.hacks.arbitraryRaceConditionDelay + interval: Config.options.hacks.arbitraryRaceConditionDelay repeat: false running: false onTriggered: { @@ -245,8 +245,8 @@ Item { 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.columns) - property int activeWorkspaceColIndex: (activeWorkspaceInGroup - 1) % ConfigOptions.overview.columns + property int activeWorkspaceRowIndex: Math.floor((activeWorkspaceInGroup - 1) / Config.options.overview.columns) + property int activeWorkspaceColIndex: (activeWorkspaceInGroup - 1) % Config.options.overview.columns x: (root.workspaceImplicitWidth + workspaceSpacing) * activeWorkspaceColIndex y: (root.workspaceImplicitHeight + workspaceSpacing) * activeWorkspaceRowIndex z: root.windowZ diff --git a/.config/quickshell/modules/overview/SearchWidget.qml b/.config/quickshell/modules/overview/SearchWidget.qml index 93286265b..a858bfd01 100644 --- a/.config/quickshell/modules/overview/SearchWidget.qml +++ b/.config/quickshell/modules/overview/SearchWidget.qml @@ -80,7 +80,7 @@ Item { // Wrapper Timer { id: nonAppResultsTimer - interval: ConfigOptions.search.nonAppResultDelay + interval: Config.options.search.nonAppResultDelay onTriggered: { mathProcess.calculateExpression(root.searchingText); } @@ -203,7 +203,7 @@ Item { // Wrapper Layout.leftMargin: 15 iconSize: Appearance.font.pixelSize.huge color: Appearance.m3colors.m3onSurface - text: root.searchingText.startsWith(ConfigOptions.search.prefix.clipboard) ? 'content_paste_search' : 'search' + text: root.searchingText.startsWith(Config.options.search.prefix.clipboard) ? 'content_paste_search' : 'search' } TextField { // Search box id: searchInput @@ -294,8 +294,8 @@ Item { // Wrapper 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); + if (root.searchingText.startsWith(Config.options.search.prefix.clipboard)) { // Clipboard + const searchString = root.searchingText.slice(Config.options.search.prefix.clipboard.length); return Cliphist.fuzzyQuery(searchString).map(entry => { return { cliphistRawString: entry, @@ -310,8 +310,8 @@ Item { // Wrapper }; }).filter(Boolean); } - if (root.searchingText.startsWith(ConfigOptions.search.prefix.emojis)) { // Clipboard - const searchString = root.searchingText.slice(ConfigOptions.search.prefix.emojis.length); + if (root.searchingText.startsWith(Config.options.search.prefix.emojis)) { // Clipboard + const searchString = root.searchingText.slice(Config.options.search.prefix.emojis.length); return Emojis.fuzzyQuery(searchString).map(entry => { return { cliphistRawString: entry, @@ -346,12 +346,12 @@ Item { // Wrapper fontType: "monospace", materialSymbol: 'terminal', execute: () => { - executor.executeCommand(searchingText.startsWith('sudo') ? `${ConfigOptions.apps.terminal} fish -C '${root.searchingText.replace("file://", "")}'` : root.searchingText.replace("file://", "")); + executor.executeCommand(searchingText.startsWith('sudo') ? `${Config.options.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}`; + const actionString = `${Config.options.search.prefix.action}${action.action}`; if (actionString.startsWith(root.searchingText) || root.searchingText.startsWith(actionString)) { return { name: root.searchingText.startsWith(actionString) ? root.searchingText : actionString, @@ -399,8 +399,8 @@ Item { // Wrapper type: qsTr("Search the web"), materialSymbol: 'travel_explore', execute: () => { - let url = ConfigOptions.search.engineBaseUrl + root.searchingText - for (let site of ConfigOptions.search.excludedSites) { + let url = Config.options.search.engineBaseUrl + root.searchingText + for (let site of Config.options.search.excludedSites) { url += ` -site:${site}`; } Qt.openUrlExternally(url); @@ -416,8 +416,8 @@ Item { // Wrapper 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) : + query: root.searchingText.startsWith(Config.options.search.prefix.clipboard) ? + root.searchingText.slice(Config.options.search.prefix.clipboard.length) : root.searchingText; } } diff --git a/.config/quickshell/modules/screenCorners/ScreenCorners.qml b/.config/quickshell/modules/screenCorners/ScreenCorners.qml index 9a1f1a22c..de2130027 100644 --- a/.config/quickshell/modules/screenCorners/ScreenCorners.qml +++ b/.config/quickshell/modules/screenCorners/ScreenCorners.qml @@ -15,8 +15,8 @@ Scope { model: Quickshell.screens PanelWindow { - visible: (ConfigOptions.appearance.fakeScreenRounding === 1 - || (ConfigOptions.appearance.fakeScreenRounding === 2 + visible: (Config.options.appearance.fakeScreenRounding === 1 + || (Config.options.appearance.fakeScreenRounding === 2 && !activeWindow?.fullscreen)) property var modelData diff --git a/.config/quickshell/modules/session/Session.qml b/.config/quickshell/modules/session/Session.qml index 20628edbe..69414f1b4 100644 --- a/.config/quickshell/modules/session/Session.qml +++ b/.config/quickshell/modules/session/Session.qml @@ -121,7 +121,7 @@ Scope { id: sessionTaskManager buttonIcon: "browse_activity" buttonText: qsTr("Task Manager") - onClicked: { Quickshell.execDetached(["bash", "-c", `${ConfigOptions.apps.taskManager}`]); sessionRoot.hide() } + onClicked: { Quickshell.execDetached(["bash", "-c", `${Config.options.apps.taskManager}`]); sessionRoot.hide() } onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } KeyNavigation.left: sessionLogout KeyNavigation.down: sessionFirmwareReboot diff --git a/.config/quickshell/modules/settings/InterfaceConfig.qml b/.config/quickshell/modules/settings/InterfaceConfig.qml index f952ef57d..0f63ea417 100644 --- a/.config/quickshell/modules/settings/InterfaceConfig.qml +++ b/.config/quickshell/modules/settings/InterfaceConfig.qml @@ -16,10 +16,10 @@ ContentPage { text: "Weeb" } ConfigSelectionArray { - currentValue: ConfigOptions.policies.weeb + currentValue: Config.options.policies.weeb configOptionName: "policies.weeb" onSelected: (newValue) => { - ConfigLoader.setConfigValueAndSave("policies.weeb", newValue); + Config.options.policies.weeb = newValue; } options: [ { displayName: "No", value: 0 }, @@ -34,10 +34,10 @@ ContentPage { text: "AI" } ConfigSelectionArray { - currentValue: ConfigOptions.policies.ai + currentValue: Config.options.policies.ai configOptionName: "policies.ai" onSelected: (newValue) => { - ConfigLoader.setConfigValueAndSave("policies.ai", newValue); + Config.options.policies.ai = newValue; } options: [ { displayName: "No", value: 0 }, @@ -53,10 +53,10 @@ ContentPage { title: "Bar" ConfigSelectionArray { - currentValue: ConfigOptions.bar.cornerStyle + currentValue: Config.options.bar.cornerStyle configOptionName: "bar.cornerStyle" onSelected: (newValue) => { - ConfigLoader.setConfigValueAndSave("bar.cornerStyle", newValue); + Config.options.bar.cornerStyle = newValue; } options: [ { displayName: "Hug", value: 0 }, @@ -71,16 +71,16 @@ ContentPage { uniform: true ConfigSwitch { text: 'Borderless' - checked: ConfigOptions.bar.borderless + checked: Config.options.bar.borderless onCheckedChanged: { - ConfigLoader.setConfigValueAndSave("bar.borderless", checked); + Config.options.bar.borderless = checked; } } ConfigSwitch { text: 'Show background' - checked: ConfigOptions.bar.showBackground + checked: Config.options.bar.showBackground onCheckedChanged: { - ConfigLoader.setConfigValueAndSave("bar.showBackground", checked); + Config.options.bar.showBackground = checked; } StyledToolTip { content: "Note: turning off can hurt readability" @@ -95,16 +95,16 @@ ContentPage { uniform: true ConfigSwitch { text: "Screen snip" - checked: ConfigOptions.bar.utilButtons.showScreenSnip + checked: Config.options.bar.utilButtons.showScreenSnip onCheckedChanged: { - ConfigLoader.setConfigValueAndSave("bar.utilButtons.showScreenSnip", checked); + Config.options.bar.utilButtons.showScreenSnip = checked; } } ConfigSwitch { text: "Color picker" - checked: ConfigOptions.bar.utilButtons.showColorPicker + checked: Config.options.bar.utilButtons.showColorPicker onCheckedChanged: { - ConfigLoader.setConfigValueAndSave("bar.utilButtons.showColorPicker", checked); + Config.options.bar.utilButtons.showColorPicker = checked; } } } @@ -112,16 +112,16 @@ ContentPage { uniform: true ConfigSwitch { text: "Mic toggle" - checked: ConfigOptions.bar.utilButtons.showMicToggle + checked: Config.options.bar.utilButtons.showMicToggle onCheckedChanged: { - ConfigLoader.setConfigValueAndSave("bar.utilButtons.showMicToggle", checked); + Config.options.bar.utilButtons.showMicToggle = checked; } } ConfigSwitch { text: "Keyboard toggle" - checked: ConfigOptions.bar.utilButtons.showKeyboardToggle + checked: Config.options.bar.utilButtons.showKeyboardToggle onCheckedChanged: { - ConfigLoader.setConfigValueAndSave("bar.utilButtons.showKeyboardToggle", checked); + Config.options.bar.utilButtons.showKeyboardToggle = checked; } } } @@ -129,9 +129,9 @@ ContentPage { uniform: true ConfigSwitch { text: "Dark/Light toggle" - checked: ConfigOptions.bar.utilButtons.showDarkModeToggle + checked: Config.options.bar.utilButtons.showDarkModeToggle onCheckedChanged: { - ConfigLoader.setConfigValueAndSave("bar.utilButtons.showDarkModeToggle", checked); + Config.options.bar.utilButtons.showDarkModeToggle = checked; } } ConfigSwitch { @@ -149,37 +149,37 @@ ContentPage { uniform: true ConfigSwitch { text: 'Show app icons' - checked: ConfigOptions.bar.workspaces.showAppIcons + checked: Config.options.bar.workspaces.showAppIcons onCheckedChanged: { - ConfigLoader.setConfigValueAndSave("bar.workspaces.showAppIcons", checked); + Config.options.bar.workspaces.showAppIcons = checked; } } ConfigSwitch { text: 'Always show numbers' - checked: ConfigOptions.bar.workspaces.alwaysShowNumbers + checked: Config.options.bar.workspaces.alwaysShowNumbers onCheckedChanged: { - ConfigLoader.setConfigValueAndSave("bar.workspaces.alwaysShowNumbers", checked); + Config.options.bar.workspaces.alwaysShowNumbers = checked; } } } ConfigSpinBox { text: "Workspaces shown" - value: ConfigOptions.bar.workspaces.shown + value: Config.options.bar.workspaces.shown from: 1 to: 30 stepSize: 1 onValueChanged: { - ConfigLoader.setConfigValueAndSave("bar.workspaces.shown", value); + Config.options.bar.workspaces.shown = value; } } ConfigSpinBox { text: "Number show delay when pressing Super (ms)" - value: ConfigOptions.bar.workspaces.showNumberDelay + value: Config.options.bar.workspaces.showNumberDelay from: 0 to: 1000 stepSize: 50 onValueChanged: { - ConfigLoader.setConfigValueAndSave("bar.workspaces.showNumberDelay", value); + Config.options.bar.workspaces.showNumberDelay = value; } } } @@ -192,22 +192,22 @@ ContentPage { uniform: true ConfigSpinBox { text: "Low warning" - value: ConfigOptions.battery.low + value: Config.options.battery.low from: 0 to: 100 stepSize: 5 onValueChanged: { - ConfigLoader.setConfigValueAndSave("battery.low", value); + Config.options.battery.low = value; } } ConfigSpinBox { text: "Critical warning" - value: ConfigOptions.battery.critical + value: Config.options.battery.critical from: 0 to: 100 stepSize: 5 onValueChanged: { - ConfigLoader.setConfigValueAndSave("battery.critical", value); + Config.options.battery.critical = value; } } } @@ -215,9 +215,9 @@ ContentPage { uniform: true ConfigSwitch { text: "Automatic suspend" - checked: ConfigOptions.battery.automaticSuspend + checked: Config.options.battery.automaticSuspend onCheckedChanged: { - ConfigLoader.setConfigValueAndSave("battery.automaticSuspend", checked); + Config.options.battery.automaticSuspend = checked; } StyledToolTip { content: "Automatically suspends the system when battery is low" @@ -225,12 +225,12 @@ ContentPage { } ConfigSpinBox { text: "Suspend at" - value: ConfigOptions.battery.suspend + value: Config.options.battery.suspend from: 0 to: 100 stepSize: 5 onValueChanged: { - ConfigLoader.setConfigValueAndSave("battery.suspend", value); + Config.options.battery.suspend = value; } } } @@ -240,34 +240,34 @@ ContentPage { title: "Overview" ConfigSpinBox { text: "Scale (%)" - value: ConfigOptions.overview.scale * 100 + value: Config.options.overview.scale * 100 from: 1 to: 100 stepSize: 1 onValueChanged: { - ConfigLoader.setConfigValueAndSave("overview.scale", value / 100); + Config.options.overview.scale = value / 100; } } ConfigRow { uniform: true ConfigSpinBox { text: "Rows" - value: ConfigOptions.overview.rows + value: Config.options.overview.rows from: 1 to: 20 stepSize: 1 onValueChanged: { - ConfigLoader.setConfigValueAndSave("overview.rows", value); + Config.options.overview.rows = value; } } ConfigSpinBox { text: "Columns" - value: ConfigOptions.overview.columns + value: Config.options.overview.columns from: 1 to: 20 stepSize: 1 onValueChanged: { - ConfigLoader.setConfigValueAndSave("overview.columns", value); + Config.options.overview.columns = value; } } } diff --git a/.config/quickshell/modules/settings/ServicesConfig.qml b/.config/quickshell/modules/settings/ServicesConfig.qml index 25e2ba420..811df09ab 100644 --- a/.config/quickshell/modules/settings/ServicesConfig.qml +++ b/.config/quickshell/modules/settings/ServicesConfig.qml @@ -13,9 +13,9 @@ ContentPage { ConfigSwitch { text: "Earbang protection" - checked: ConfigOptions.audio.protection.enable + checked: Config.options.audio.protection.enable onCheckedChanged: { - ConfigLoader.setConfigValueAndSave("audio.protection.enable", checked); + Config.options.audio.protection.enable = checked; } StyledToolTip { content: "Prevents abrupt increments and restricts volume limit" @@ -25,22 +25,22 @@ ContentPage { // uniform: true ConfigSpinBox { text: "Max allowed increase" - value: ConfigOptions.audio.protection.maxAllowedIncrease + value: Config.options.audio.protection.maxAllowedIncrease from: 0 to: 100 stepSize: 2 onValueChanged: { - ConfigLoader.setConfigValueAndSave("audio.protection.maxAllowedIncrease", value); + Config.options.audio.protection.maxAllowedIncrease = value; } } ConfigSpinBox { text: "Volume limit" - value: ConfigOptions.audio.protection.maxAllowed + value: Config.options.audio.protection.maxAllowed from: 0 to: 100 stepSize: 2 onValueChanged: { - ConfigLoader.setConfigValueAndSave("audio.protection.maxAllowed", value); + Config.options.audio.protection.maxAllowed = value; } } } @@ -50,11 +50,11 @@ ContentPage { MaterialTextField { Layout.fillWidth: true placeholderText: "System prompt" - text: ConfigOptions.ai.systemPrompt + text: Config.options.ai.systemPrompt wrapMode: TextEdit.Wrap onTextChanged: { Qt.callLater(() => { - ConfigLoader.setConfigValueAndSave("ai.systemPrompt", text); + Config.options.ai.systemPrompt = text; }); } } @@ -67,22 +67,22 @@ ContentPage { uniform: true ConfigSpinBox { text: "Low warning" - value: ConfigOptions.battery.low + value: Config.options.battery.low from: 0 to: 100 stepSize: 5 onValueChanged: { - ConfigLoader.setConfigValueAndSave("battery.low", value); + Config.options.battery.low = value; } } ConfigSpinBox { text: "Critical warning" - value: ConfigOptions.battery.critical + value: Config.options.battery.critical from: 0 to: 100 stepSize: 5 onValueChanged: { - ConfigLoader.setConfigValueAndSave("battery.critical", value); + Config.options.battery.critical = value; } } } @@ -90,9 +90,9 @@ ContentPage { uniform: true ConfigSwitch { text: "Automatic suspend" - checked: ConfigOptions.battery.automaticSuspend + checked: Config.options.battery.automaticSuspend onCheckedChanged: { - ConfigLoader.setConfigValueAndSave("battery.automaticSuspend", checked); + Config.options.battery.automaticSuspend = checked; } StyledToolTip { content: "Automatically suspends the system when battery is low" @@ -100,12 +100,12 @@ ContentPage { } ConfigSpinBox { text: "Suspend at" - value: ConfigOptions.battery.suspend + value: Config.options.battery.suspend from: 0 to: 100 stepSize: 5 onValueChanged: { - ConfigLoader.setConfigValueAndSave("battery.suspend", value); + Config.options.battery.suspend = value; } } } @@ -116,10 +116,10 @@ ContentPage { MaterialTextField { Layout.fillWidth: true placeholderText: "User agent (for services that require it)" - text: ConfigOptions.networking.userAgent + text: Config.options.networking.userAgent wrapMode: TextEdit.Wrap onTextChanged: { - ConfigLoader.setConfigValueAndSave("networking.userAgent", text); + Config.options.networking.userAgent = text; } } } @@ -128,12 +128,12 @@ ContentPage { title: "Resources" ConfigSpinBox { text: "Polling interval (ms)" - value: ConfigOptions.resources.updateInterval + value: Config.options.resources.updateInterval from: 100 to: 10000 stepSize: 100 onValueChanged: { - ConfigLoader.setConfigValueAndSave("resources.updateInterval", value); + Config.options.resources.updateInterval = value; } } } diff --git a/.config/quickshell/modules/settings/StyleConfig.qml b/.config/quickshell/modules/settings/StyleConfig.qml index a02c4f91d..b86c0daa2 100644 --- a/.config/quickshell/modules/settings/StyleConfig.qml +++ b/.config/quickshell/modules/settings/StyleConfig.qml @@ -46,10 +46,10 @@ ContentPage { ContentSubsection { title: "Material palette" ConfigSelectionArray { - currentValue: ConfigOptions.appearance.palette.type + currentValue: Config.options.appearance.palette.type configOptionName: "appearance.palette.type" onSelected: (newValue) => { - ConfigLoader.setConfigValueAndSave("appearance.palette.type", newValue); + Config.options.appearance.palette.type = newValue; } options: [ {"value": "auto", "displayName": "Auto"}, @@ -141,9 +141,9 @@ ContentPage { ConfigRow { ConfigSwitch { text: "Enable" - checked: ConfigOptions.appearance.transparency + checked: Config.options.appearance.transparency onCheckedChanged: { - ConfigLoader.setConfigValueAndSave("appearance.transparency", checked); + Config.options.appearance.transparency = checked; } StyledToolTip { content: "Might look ass. Unsupported." @@ -157,7 +157,7 @@ ContentPage { ButtonGroup { id: fakeScreenRoundingButtonGroup - property int selectedPolicy: ConfigOptions.appearance.fakeScreenRounding + property int selectedPolicy: Config.options.appearance.fakeScreenRounding spacing: 2 SelectionGroupButton { property int value: 0 @@ -165,7 +165,7 @@ ContentPage { buttonText: "No" toggled: (fakeScreenRoundingButtonGroup.selectedPolicy === value) onClicked: { - ConfigLoader.setConfigValueAndSave("appearance.fakeScreenRounding", value); + Config.options.appearance.fakeScreenRounding = value; } } SelectionGroupButton { @@ -173,7 +173,7 @@ ContentPage { buttonText: "Yes" toggled: (fakeScreenRoundingButtonGroup.selectedPolicy === value) onClicked: { - ConfigLoader.setConfigValueAndSave("appearance.fakeScreenRounding", value); + Config.options.appearance.fakeScreenRounding = value; } } SelectionGroupButton { @@ -182,7 +182,7 @@ ContentPage { buttonText: "When not fullscreen" toggled: (fakeScreenRoundingButtonGroup.selectedPolicy === value) onClicked: { - ConfigLoader.setConfigValueAndSave("appearance.fakeScreenRounding", value); + Config.options.appearance.fakeScreenRounding = value; } } } @@ -195,16 +195,16 @@ ContentPage { uniform: true ConfigSwitch { text: "Title bar" - checked: ConfigOptions.windows.showTitlebar + checked: Config.options.windows.showTitlebar onCheckedChanged: { - ConfigLoader.setConfigValueAndSave("windows.showTitlebar", checked); + Config.options.windows.showTitlebar = checked; } } ConfigSwitch { text: "Center title" - checked: ConfigOptions.windows.centerTitle + checked: Config.options.windows.centerTitle onCheckedChanged: { - ConfigLoader.setConfigValueAndSave("windows.centerTitle", checked); + Config.options.windows.centerTitle = checked; } } } diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index 1300d5482..1b097b538 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -106,7 +106,7 @@ Item { break; } } - Booru.makeRequest(tagList, PersistentStates.booru.allowNsfw, ConfigOptions.sidebar.booru.limit, pageIndex); + Booru.makeRequest(tagList, PersistentStates.booru.allowNsfw, Config.options.sidebar.booru.limit, pageIndex); } } diff --git a/.config/quickshell/modules/sidebarLeft/SidebarLeftContent.qml b/.config/quickshell/modules/sidebarLeft/SidebarLeftContent.qml index 8d67c2a7f..621970839 100644 --- a/.config/quickshell/modules/sidebarLeft/SidebarLeftContent.qml +++ b/.config/quickshell/modules/sidebarLeft/SidebarLeftContent.qml @@ -18,9 +18,9 @@ Item { required property var scopeRoot anchors.fill: parent property var tabButtonList: [ - ...(ConfigOptions.policies.ai !== 0 ? [{"icon": "neurology", "name": qsTr("Intelligence")}] : []), + ...(Config.options.policies.ai !== 0 ? [{"icon": "neurology", "name": qsTr("Intelligence")}] : []), {"icon": "translate", "name": qsTr("Translator")}, - ...(ConfigOptions.policies.weeb === 1 ? [{"icon": "bookmark_heart", "name": qsTr("Anime")}] : []) + ...(Config.options.policies.weeb === 1 ? [{"icon": "bookmark_heart", "name": qsTr("Anime")}] : []) ] property int selectedTab: 0 @@ -88,9 +88,9 @@ Item { } contentChildren: [ - ...(ConfigOptions.policies.ai !== 0 ? [aiChat.createObject()] : []), + ...(Config.options.policies.ai !== 0 ? [aiChat.createObject()] : []), translator.createObject(), - ...(ConfigOptions.policies.weeb === 0 ? [] : [anime.createObject()]) + ...(Config.options.policies.weeb === 0 ? [] : [anime.createObject()]) ] } diff --git a/.config/quickshell/modules/sidebarLeft/Translator.qml b/.config/quickshell/modules/sidebarLeft/Translator.qml index 37e5a37b1..1937e411c 100644 --- a/.config/quickshell/modules/sidebarLeft/Translator.qml +++ b/.config/quickshell/modules/sidebarLeft/Translator.qml @@ -23,8 +23,8 @@ Item { property string translatedText: "" property list languages: [] // Options - property string targetLanguage: ConfigOptions.language.translator.targetLanguage - property string sourceLanguage: ConfigOptions.language.translator.sourceLanguage + property string targetLanguage: Config.options.language.translator.targetLanguage + property string sourceLanguage: Config.options.language.translator.sourceLanguage property string hostLanguage: targetLanguage property bool showLanguageSelector: false @@ -43,7 +43,7 @@ Item { Timer { id: translateTimer - interval: ConfigOptions.sidebar.translator.delay + interval: Config.options.sidebar.translator.delay repeat: false onTriggered: () => { if (root.inputField.text.trim().length > 0) { @@ -155,8 +155,8 @@ Item { color: searchButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext } onClicked: { - let url = ConfigOptions.search.engineBaseUrl + outputCanvas.displayedText; - for (let site of ConfigOptions.search.excludedSites) { + let url = Config.options.search.engineBaseUrl + outputCanvas.displayedText; + for (let site of Config.options.search.excludedSites) { url += ` -site:${site}`; } Qt.openUrlExternally(url); @@ -235,10 +235,10 @@ Item { if (root.languageSelectorTarget) { root.targetLanguage = result; - ConfigLoader.setConfigValueAndSave("language.translator.targetLanguage", result); // Save to config + Config.options.language.translator.targetLanguage = result; // Save to config } else { root.sourceLanguage = result; - ConfigLoader.setConfigValueAndSave("language.translator.sourceLanguage", result); // Save to config + Config.options.language.translator.sourceLanguage = result; // Save to config } translateTimer.restart(); // Restart translation after language change diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/BluetoothToggle.qml b/.config/quickshell/modules/sidebarRight/quickToggles/BluetoothToggle.qml index d8c55b58d..76a0fda78 100644 --- a/.config/quickshell/modules/sidebarRight/quickToggles/BluetoothToggle.qml +++ b/.config/quickshell/modules/sidebarRight/quickToggles/BluetoothToggle.qml @@ -15,7 +15,7 @@ QuickToggleButton { toggleBluetooth.running = true } altAction: () => { - Quickshell.execDetached(["bash", "-c", `${ConfigOptions.apps.bluetooth}`]) + Quickshell.execDetached(["bash", "-c", `${Config.options.apps.bluetooth}`]) Hyprland.dispatch("global quickshell:sidebarRightClose") } Process { diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml b/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml index 010b6ae55..06d702323 100644 --- a/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml +++ b/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml @@ -15,7 +15,7 @@ QuickToggleButton { toggleNetwork.running = true } altAction: () => { - Quickshell.execDetached(["bash", "-c", `${Network.ethernet ? ConfigOptions.apps.networkEthernet : ConfigOptions.apps.network}`]) + Quickshell.execDetached(["bash", "-c", `${Network.ethernet ? Config.options.apps.networkEthernet : Config.options.apps.network}`]) Hyprland.dispatch("global quickshell:sidebarRightClose") } Process { diff --git a/.config/quickshell/screenshot.qml b/.config/quickshell/screenshot.qml index a855200d9..64533612e 100644 --- a/.config/quickshell/screenshot.qml +++ b/.config/quickshell/screenshot.qml @@ -40,7 +40,6 @@ ShellRoot { // Force initialization of some singletons Component.onCompleted: { MaterialThemeLoader.reapplyTheme(); - ConfigLoader.loadConfig(); } component TargetRegion: Rectangle { diff --git a/.config/quickshell/services/Ai.qml b/.config/quickshell/services/Ai.qml index f545b878f..ee80066b9 100644 --- a/.config/quickshell/services/Ai.qml +++ b/.config/quickshell/services/Ai.qml @@ -18,7 +18,7 @@ Singleton { readonly property string interfaceRole: "interface" readonly property string apiKeyEnvVarName: "API_KEY" property Component aiMessageComponent: AiMessageData {} - property string systemPrompt: ConfigOptions?.ai?.systemPrompt ?? "" + property string systemPrompt: Config.options?.ai?.systemPrompt ?? "" property var messages: [] property var messageIDs: [] property var messageByID: ({}) @@ -301,7 +301,7 @@ Singleton { // Fetch API keys if needed if (model?.requires_key) KeyringStorage.fetchKeyringData(); // See if policy prevents online models - if (ConfigOptions.policies.ai === 2 && !model.endpoint.includes("localhost")) { + if (Config.options.policies.ai === 2 && !model.endpoint.includes("localhost")) { root.addMessage(StringUtils.format(StringUtils.format("Online models disallowed\n\nControlled by `policies.ai` config option"), model.name), root.interfaceRole); return; } @@ -704,7 +704,7 @@ Singleton { addFunctionOutputMessage(name, qsTr("Switched to search mode. Continue with the user's request.")) requester.makeRequest(); } else if (name === "get_shell_config") { - const configJson = ObjectUtils.toPlainObject(ConfigOptions) + const configJson = ObjectUtils.toPlainObject(Config.options) addFunctionOutputMessage(name, JSON.stringify(configJson)); requester.makeRequest(); } else if (name === "set_shell_config") { @@ -714,8 +714,7 @@ Singleton { } const key = args.key; const value = args.value; - ConfigLoader.setLiveConfigValue(key, value); - ConfigLoader.saveConfig(); + Config.setNestedValue(key, value); } else root.addMessage(qsTr("Unknown function call: {0}"), "assistant"); } diff --git a/.config/quickshell/services/AppSearch.qml b/.config/quickshell/services/AppSearch.qml index 1e383e672..edac48007 100644 --- a/.config/quickshell/services/AppSearch.qml +++ b/.config/quickshell/services/AppSearch.qml @@ -12,7 +12,7 @@ import Quickshell.Io */ Singleton { id: root - property bool sloppySearch: ConfigOptions?.search.sloppy ?? false + property bool sloppySearch: Config.options?.search.sloppy ?? false property real scoreThreshold: 0.2 property var substitutions: ({ "code-url-handler": "visual-studio-code", diff --git a/.config/quickshell/services/Audio.qml b/.config/quickshell/services/Audio.qml index c0f469a4c..db10a968a 100644 --- a/.config/quickshell/services/Audio.qml +++ b/.config/quickshell/services/Audio.qml @@ -26,15 +26,15 @@ Singleton { property bool lastReady: false property real lastVolume: 0 function onVolumeChanged() { - if (!ConfigOptions.audio.protection.enable) return; + if (!Config.options.audio.protection.enable) return; if (!lastReady) { lastVolume = sink.audio.volume; lastReady = true; return; } const newVolume = sink.audio.volume; - const maxAllowedIncrease = ConfigOptions.audio.protection.maxAllowedIncrease / 100; - const maxAllowed = ConfigOptions.audio.protection.maxAllowed / 100; + const maxAllowedIncrease = Config.options.audio.protection.maxAllowedIncrease / 100; + const maxAllowed = Config.options.audio.protection.maxAllowed / 100; if (newVolume - lastVolume > maxAllowedIncrease) { sink.audio.volume = lastVolume; diff --git a/.config/quickshell/services/Battery.qml b/.config/quickshell/services/Battery.qml index e952dab97..6a432f3a4 100644 --- a/.config/quickshell/services/Battery.qml +++ b/.config/quickshell/services/Battery.qml @@ -12,11 +12,11 @@ Singleton { property bool isCharging: chargeState == UPowerDeviceState.Charging property bool isPluggedIn: isCharging || chargeState == UPowerDeviceState.PendingCharge property real percentage: UPower.displayDevice.percentage - readonly property bool allowAutomaticSuspend: ConfigOptions.battery.automaticSuspend + readonly property bool allowAutomaticSuspend: Config.options.battery.automaticSuspend - property bool isLow: percentage <= ConfigOptions.battery.low / 100 - property bool isCritical: percentage <= ConfigOptions.battery.critical / 100 - property bool isSuspending: percentage <= ConfigOptions.battery.suspend / 100 + property bool isLow: percentage <= Config.options.battery.low / 100 + property bool isCritical: percentage <= Config.options.battery.critical / 100 + property bool isSuspending: percentage <= Config.options.battery.suspend / 100 property bool isLowAndNotCharging: isLow && !isCharging property bool isCriticalAndNotCharging: isCritical && !isCharging @@ -29,7 +29,7 @@ Singleton { onIsCriticalAndNotChargingChanged: { if (available && isCriticalAndNotCharging) - Quickshell.execDetached(["bash", "-c", `notify-send "Critically low battery" "🙏 I beg for pleas charg\nAutomatic suspend triggers at ${ConfigOptions.battery.suspend}%" -u critical -a "Shell"`]); + Quickshell.execDetached(["bash", "-c", `notify-send "Critically low battery" "🙏 I beg for pleas charg\nAutomatic suspend triggers at ${Config.options.battery.suspend}%" -u critical -a "Shell"`]); } onIsSuspendingAndNotChargingChanged: { diff --git a/.config/quickshell/services/Booru.qml b/.config/quickshell/services/Booru.qml index 49256bfa7..d0e0a9ad8 100644 --- a/.config/quickshell/services/Booru.qml +++ b/.config/quickshell/services/Booru.qml @@ -19,7 +19,7 @@ Singleton { property string failMessage: qsTr("That didn't work. Tips:\n- Check your tags and NSFW settings\n- If you don't have a tag in mind, type a page number") property var responses: [] property int runningRequests: 0 - property var defaultUserAgent: ConfigOptions?.networking?.userAgent || "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36" + property var defaultUserAgent: Config.options?.networking?.userAgent || "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36" property var providerList: Object.keys(providers).filter(provider => provider !== "system" && providers[provider].api) property var providers: { "system": { "name": qsTr("System") }, @@ -408,7 +408,7 @@ Singleton { xhr.setRequestHeader("User-Agent", defaultUserAgent) } else if (currentProvider == "zerochan") { - const userAgent = ConfigOptions?.sidebar?.booru?.zerochan?.username ? `Desktop sidebar booru viewer - username: ${ConfigOptions.sidebar.booru.zerochan.username}` : defaultUserAgent + const userAgent = Config.options?.sidebar?.booru?.zerochan?.username ? `Desktop sidebar booru viewer - username: ${Config.options.sidebar.booru.zerochan.username}` : defaultUserAgent xhr.setRequestHeader("User-Agent", userAgent) } root.runningRequests++; diff --git a/.config/quickshell/services/Cliphist.qml b/.config/quickshell/services/Cliphist.qml index bebafb102..ff867841d 100644 --- a/.config/quickshell/services/Cliphist.qml +++ b/.config/quickshell/services/Cliphist.qml @@ -11,7 +11,7 @@ import Quickshell.Io Singleton { id: root - property bool sloppySearch: ConfigOptions?.search.sloppy ?? false + property bool sloppySearch: Config.options?.search.sloppy ?? false property real scoreThreshold: 0.2 property list entries: [] readonly property var preparedEntries: entries.map(a => ({ @@ -51,7 +51,7 @@ Singleton { Timer { id: delayedUpdateTimer - interval: ConfigOptions.hacks.arbitraryRaceConditionDelay + interval: Config.options.hacks.arbitraryRaceConditionDelay repeat: false onTriggered: { root.refresh() diff --git a/.config/quickshell/services/ConfigLoader.qml b/.config/quickshell/services/ConfigLoader.qml deleted file mode 100644 index f7dadcee9..000000000 --- a/.config/quickshell/services/ConfigLoader.qml +++ /dev/null @@ -1,137 +0,0 @@ -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/illogical-impulse/config.json. - * Automatically reloaded when the file changes. - */ -Singleton { - id: root - property string filePath: Directories.shellConfigPath - property bool firstLoad: true - property bool preventNextLoad: false - property var preventNextNotification: false - - function loadConfig() { - configFileView.reload() - } - - function applyConfig(fileContent) { - try { - if (fileContent.trim() === "") { - console.warn("[ConfigLoader] Config file is empty, skipping load."); - return; - } - const json = JSON.parse(fileContent); - - ObjectUtils.applyToQtObject(ConfigOptions, json); - if (root.firstLoad) { - root.firstLoad = false; - root.preventNextLoad = true; - root.saveConfig(); // Make sure new properties are added to the user's config file - } - } catch (e) { - console.error("[ConfigLoader] Error reading file:", e); - console.log("[ConfigLoader] File content was:", fileContent); - Quickshell.execDetached(["bash", "-c", `notify-send '${qsTr("Shell configuration failed to load")}' '${root.filePath}'`]) - return; - } - } - - function setLiveConfigValue(nestedKey, value) { - let keys = nestedKey.split("."); - let obj = ConfigOptions; - 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; - } - } - } - - obj[keys[keys.length - 1]] = convertedValue; - } - - function saveConfig() { - const plainConfig = ObjectUtils.toPlainObject(ConfigOptions) - Quickshell.execDetached(["bash", "-c", `echo '${StringUtils.shellSingleQuoteEscape(JSON.stringify(plainConfig, null, 2))}' > '${FileUtils.trimFileProtocol(root.filePath)}'`]) - } - - function setConfigValueAndSave(nestedKey, value, preventNextNotification = true) { - setLiveConfigValue(nestedKey, value); - root.preventNextNotification = preventNextNotification; - saveConfig(); - } - - Timer { - id: delayedFileRead - interval: ConfigOptions.hacks.arbitraryRaceConditionDelay - running: false - onTriggered: { - if (root.preventNextLoad) { - root.preventNextLoad = false; - return; - } - if (root.firstLoad) { - root.applyConfig(configFileView.text()) - } else { - root.applyConfig(configFileView.text()) - if (!root.preventNextNotification) { - // Quickshell.execDetached(["bash", "-c", `notify-send '${qsTr("Shell configuration reloaded")}' '${root.filePath}'`]) - } else { - root.preventNextNotification = false; - } - } - } - } - - FileView { - id: configFileView - path: Qt.resolvedUrl(root.filePath) - watchChanges: true - onFileChanged: { - this.reload() - delayedFileRead.start() - } - onLoadedChanged: { - const fileContent = configFileView.text() - delayedFileRead.start() - } - onLoadFailed: (error) => { - if(error == FileViewError.FileNotFound) { - console.log("[ConfigLoader] File not found, creating new file.") - root.saveConfig() - Quickshell.execDetached(["bash", "-c", `notify-send '${qsTr("Shell configuration created")}' '${root.filePath}'`]) - } else { - Quickshell.execDetached(["bash", "-c", `notify-send '${qsTr("Shell configuration failed to load")}' '${root.filePath}'`]) - } - } - } -} diff --git a/.config/quickshell/services/DateTime.qml b/.config/quickshell/services/DateTime.qml index 17fb1c8a6..4df9ca993 100644 --- a/.config/quickshell/services/DateTime.qml +++ b/.config/quickshell/services/DateTime.qml @@ -9,8 +9,8 @@ pragma ComponentBehavior: Bound * A nice wrapper for date and time strings. */ Singleton { - property string time: Qt.locale().toString(clock.date, ConfigOptions?.time.format ?? "hh:mm") - property string date: Qt.locale().toString(clock.date, ConfigOptions?.time.dateFormat ?? "dddd, dd/MM") + property string time: Qt.locale().toString(clock.date, Config.options?.time.format ?? "hh:mm") + property string date: Qt.locale().toString(clock.date, Config.options?.time.dateFormat ?? "dddd, dd/MM") property string collapsedCalendarFormat: Qt.locale().toString(clock.date, "dd MMMM yyyy") property string uptime: "0h, 0m" @@ -39,7 +39,7 @@ Singleton { if (hours > 0) formatted += `${formatted ? ", " : ""}${hours}h` if (minutes > 0 || !formatted) formatted += `${formatted ? ", " : ""}${minutes}m` uptime = formatted - interval = ConfigOptions?.resources?.updateInterval ?? 3000 + interval = Config.options?.resources?.updateInterval ?? 3000 } } diff --git a/.config/quickshell/services/MaterialThemeLoader.qml b/.config/quickshell/services/MaterialThemeLoader.qml index cd4eb686b..4194d873c 100644 --- a/.config/quickshell/services/MaterialThemeLoader.qml +++ b/.config/quickshell/services/MaterialThemeLoader.qml @@ -34,7 +34,7 @@ Singleton { Timer { id: delayedFileRead - interval: ConfigOptions?.hacks?.arbitraryRaceConditionDelay ?? 100 + interval: Config.options?.hacks?.arbitraryRaceConditionDelay ?? 100 repeat: false running: false onTriggered: { diff --git a/.config/quickshell/services/PersistentStateManager.qml b/.config/quickshell/services/PersistentStateManager.qml index c3d1536ed..c02895b8d 100644 --- a/.config/quickshell/services/PersistentStateManager.qml +++ b/.config/quickshell/services/PersistentStateManager.qml @@ -76,7 +76,7 @@ Singleton { Timer { id: delayedFileRead - interval: ConfigOptions?.hacks?.arbitraryRaceConditionDelay ?? 100 + interval: Config.options?.hacks?.arbitraryRaceConditionDelay ?? 100 repeat: false running: false onTriggered: { diff --git a/.config/quickshell/services/ResourceUsage.qml b/.config/quickshell/services/ResourceUsage.qml index 6e0e58dff..ba08be2ac 100644 --- a/.config/quickshell/services/ResourceUsage.qml +++ b/.config/quickshell/services/ResourceUsage.qml @@ -53,7 +53,7 @@ Singleton { previousCpuStats = { total, idle } } - interval = ConfigOptions?.resources?.updateInterval ?? 3000 + interval = Config.options?.resources?.updateInterval ?? 3000 } } diff --git a/.config/quickshell/settings.qml b/.config/quickshell/settings.qml index c68665658..0e181e0b9 100644 --- a/.config/quickshell/settings.qml +++ b/.config/quickshell/settings.qml @@ -56,7 +56,6 @@ ApplicationWindow { Component.onCompleted: { MaterialThemeLoader.reapplyTheme() - ConfigLoader.loadConfig() } minimumWidth: 600 @@ -93,15 +92,15 @@ ApplicationWindow { } Item { // Titlebar - visible: ConfigOptions?.windows.showTitlebar + visible: Config.options?.windows.showTitlebar Layout.fillWidth: true Layout.fillHeight: false implicitHeight: Math.max(titleText.implicitHeight, windowControlsRow.implicitHeight) StyledText { id: titleText anchors { - left: ConfigOptions.windows.centerTitle ? undefined : parent.left - horizontalCenter: ConfigOptions.windows.centerTitle ? parent.horizontalCenter : undefined + left: Config.options.windows.centerTitle ? undefined : parent.left + horizontalCenter: Config.options.windows.centerTitle ? parent.horizontalCenter : undefined verticalCenter: parent.verticalCenter leftMargin: 12 } diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml index 4edfde51f..c69f90dff 100644 --- a/.config/quickshell/shell.qml +++ b/.config/quickshell/shell.qml @@ -48,7 +48,6 @@ ShellRoot { // Force initialization of some singletons Component.onCompleted: { MaterialThemeLoader.reapplyTheme() - ConfigLoader.loadConfig() PersistentStateManager.loadStates() Cliphist.refresh() FirstRunExperience.load() diff --git a/.config/quickshell/welcome.qml b/.config/quickshell/welcome.qml index 12296532d..d9e0770d6 100644 --- a/.config/quickshell/welcome.qml +++ b/.config/quickshell/welcome.qml @@ -31,7 +31,6 @@ ApplicationWindow { Component.onCompleted: { MaterialThemeLoader.reapplyTheme() - ConfigLoader.loadConfig() } minimumWidth: 600 @@ -59,14 +58,14 @@ ApplicationWindow { } Item { // Titlebar - visible: ConfigOptions?.windows.showTitlebar + visible: Config.options?.windows.showTitlebar Layout.fillWidth: true implicitHeight: Math.max(welcomeText.implicitHeight, windowControlsRow.implicitHeight) StyledText { id: welcomeText anchors { - left: ConfigOptions.windows.centerTitle ? undefined : parent.left - horizontalCenter: ConfigOptions.windows.centerTitle ? parent.horizontalCenter : undefined + left: Config.options.windows.centerTitle ? undefined : parent.left + horizontalCenter: Config.options.windows.centerTitle ? parent.horizontalCenter : undefined verticalCenter: parent.verticalCenter leftMargin: 12 } @@ -127,10 +126,10 @@ ApplicationWindow { title: "Bar style" ConfigSelectionArray { - currentValue: ConfigOptions.bar.cornerStyle + currentValue: Config.options.bar.cornerStyle configOptionName: "bar.cornerStyle" onSelected: (newValue) => { - ConfigLoader.setConfigValueAndSave("bar.cornerStyle", newValue); + Config.options.bar.cornerStyle = newValue; // Update local copy } options: [ { displayName: "Hug", value: 0 }, @@ -223,10 +222,10 @@ ApplicationWindow { text: "Weeb" } ConfigSelectionArray { - currentValue: ConfigOptions.policies.weeb + currentValue: Config.options.policies.weeb configOptionName: "policies.weeb" onSelected: (newValue) => { - ConfigLoader.setConfigValueAndSave("policies.weeb", newValue); + Config.options.policies.weeb = newValue; } options: [ { displayName: "No", value: 0 }, @@ -241,10 +240,10 @@ ApplicationWindow { text: "AI" } ConfigSelectionArray { - currentValue: ConfigOptions.policies.ai + currentValue: Config.options.policies.ai configOptionName: "policies.ai" onSelected: (newValue) => { - ConfigLoader.setConfigValueAndSave("policies.ai", newValue); + Config.options.policies.ai = newValue; } options: [ { displayName: "No", value: 0 }, From 45846bf69696b3588e15b3af94c1289a54d833b8 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 30 Jun 2025 15:28:55 +0200 Subject: [PATCH 23/33] screenshot: fix offset region target on click (#1539) --- .config/quickshell/screenshot.qml | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/.config/quickshell/screenshot.qml b/.config/quickshell/screenshot.qml index 64533612e..e18572ea6 100644 --- a/.config/quickshell/screenshot.qml +++ b/.config/quickshell/screenshot.qml @@ -102,11 +102,11 @@ ShellRoot { property bool dragging: false property var mouseButton: null property var imageRegions: [] - readonly property var windowRegions: filterWindowRegionsByLayers( + readonly property list windowRegions: filterWindowRegionsByLayers( root.windows.filter(w => w.workspace.id === panelWindow.activeWorkspaceId), panelWindow.layerRegions ) - readonly property var layerRegions: { + readonly property list layerRegions: { const layersOfThisMonitor = root.layers[panelWindow.hyprlandMonitor.name] const topLayers = layersOfThisMonitor.levels["2"] const nonBarTopLayers = topLayers @@ -211,7 +211,10 @@ ShellRoot { // Layer regions const clickedLayer = panelWindow.layerRegions.find(region => { - return region.at[0] <= x && x <= region.at[0] + region.size[0] && region.at[1] <= y && y <= region.at[1] + region.size[1]; + return region.at[0] - panelWindow.monitorOffsetX <= x + && x <= region.at[0] - panelWindow.monitorOffsetX + region.size[0] + && region.at[1] - panelWindow.monitorOffsetY <= y + && y <= region.at[1] - panelWindow.monitorOffsetY + region.size[1]; }); if (clickedLayer) { panelWindow.targetedRegionX = clickedLayer.at[0]; @@ -223,7 +226,10 @@ ShellRoot { // Window regions const clickedWindow = panelWindow.windowRegions.find(region => { - return region.at[0] <= x && x <= region.at[0] + region.size[0] && region.at[1] <= y && y <= region.at[1] + region.size[1]; + return region.at[0] - panelWindow.monitorOffsetX <= x + && x <= region.at[0] - panelWindow.monitorOffsetX + region.size[0] + && region.at[1] - panelWindow.monitorOffsetY <= y + && y <= region.at[1] - panelWindow.monitorOffsetY + region.size[1]; }); if (clickedWindow) { panelWindow.targetedRegionX = clickedWindow.at[0]; @@ -419,6 +425,7 @@ ShellRoot { } } + // Window regions Repeater { model: ScriptModel { values: panelWindow.windowRegions @@ -450,6 +457,7 @@ ShellRoot { } } + // Layer regions Repeater { model: ScriptModel { values: panelWindow.layerRegions @@ -481,6 +489,7 @@ ShellRoot { } } + // Image regions Repeater { model: ScriptModel { values: panelWindow.imageRegions @@ -500,10 +509,10 @@ ShellRoot { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } - x: modelData.at[0] / panelWindow.monitorScale - y: modelData.at[1] / panelWindow.monitorScale - width: modelData.size[0] / panelWindow.monitorScale - height: modelData.size[1] / panelWindow.monitorScale + x: modelData.at[0] + y: modelData.at[1] + width: modelData.size[0] + height: modelData.size[1] borderColor: root.imageBorderColor fillColor: targeted ? root.imageFillColor : "transparent" border.width: targeted ? 4 : 2 From 6e9bb4945c176fc5413452bb1e99d6778d4011f1 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 30 Jun 2025 15:37:01 +0200 Subject: [PATCH 24/33] screenshot: do offset adjustments directly on each monitor's region data (#1539) --- .config/quickshell/screenshot.qml | 36 +++++++++++++++++++------------ 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/.config/quickshell/screenshot.qml b/.config/quickshell/screenshot.qml index e18572ea6..8e9af0499 100644 --- a/.config/quickshell/screenshot.qml +++ b/.config/quickshell/screenshot.qml @@ -105,7 +105,14 @@ ShellRoot { readonly property list windowRegions: filterWindowRegionsByLayers( root.windows.filter(w => w.workspace.id === panelWindow.activeWorkspaceId), panelWindow.layerRegions - ) + ).map(window => { + return { + at: [window.at[0] - panelWindow.monitorOffsetX, window.at[1] - panelWindow.monitorOffsetY], + size: [window.size[0], window.size[1]], + class: window.class, + title: window.title, + } + }) readonly property list layerRegions: { const layersOfThisMonitor = root.layers[panelWindow.hyprlandMonitor.name] const topLayers = layersOfThisMonitor.levels["2"] @@ -118,7 +125,14 @@ ShellRoot { namespace: layer.namespace, } }) - return nonBarTopLayers; + const offsetAdjustedLayers = nonBarTopLayers.map(layer => { + return { + at: [layer.at[0] - panelWindow.monitorOffsetX, layer.at[1] - panelWindow.monitorOffsetY], + size: layer.size, + namespace: layer.namespace, + } + }); + return offsetAdjustedLayers; } property real targetedRegionX: -1 @@ -211,10 +225,7 @@ ShellRoot { // Layer regions const clickedLayer = panelWindow.layerRegions.find(region => { - return region.at[0] - panelWindow.monitorOffsetX <= x - && x <= region.at[0] - panelWindow.monitorOffsetX + region.size[0] - && region.at[1] - panelWindow.monitorOffsetY <= y - && y <= region.at[1] - panelWindow.monitorOffsetY + region.size[1]; + return region.at[0] <= x && x <= region.at[0] + region.size[0] && region.at[1] <= y && y <= region.at[1] + region.size[1]; }); if (clickedLayer) { panelWindow.targetedRegionX = clickedLayer.at[0]; @@ -226,10 +237,7 @@ ShellRoot { // Window regions const clickedWindow = panelWindow.windowRegions.find(region => { - return region.at[0] - panelWindow.monitorOffsetX <= x - && x <= region.at[0] - panelWindow.monitorOffsetX + region.size[0] - && region.at[1] - panelWindow.monitorOffsetY <= y - && y <= region.at[1] - panelWindow.monitorOffsetY + region.size[1]; + return region.at[0] <= x && x <= region.at[0] + region.size[0] && region.at[1] <= y && y <= region.at[1] + region.size[1]; }); if (clickedWindow) { panelWindow.targetedRegionX = clickedWindow.at[0]; @@ -445,8 +453,8 @@ ShellRoot { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } - x: modelData.at[0] - panelWindow.monitorOffsetX - y: modelData.at[1] - panelWindow.monitorOffsetY + x: modelData.at[0] + y: modelData.at[1] width: modelData.size[0] height: modelData.size[1] borderColor: root.windowBorderColor @@ -477,8 +485,8 @@ ShellRoot { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } - x: modelData.at[0] - panelWindow.monitorOffsetX - y: modelData.at[1] - panelWindow.monitorOffsetY + x: modelData.at[0] + y: modelData.at[1] width: modelData.size[0] height: modelData.size[1] borderColor: root.windowBorderColor From 8e7a376407bf66025f01e8ec38225acc423d1123 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 30 Jun 2025 16:56:59 +0200 Subject: [PATCH 25/33] hyprland: make borders more readable --- .config/matugen/templates/hyprland/colors.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.config/matugen/templates/hyprland/colors.conf b/.config/matugen/templates/hyprland/colors.conf index 67fdaea71..53093539e 100644 --- a/.config/matugen/templates/hyprland/colors.conf +++ b/.config/matugen/templates/hyprland/colors.conf @@ -1,6 +1,6 @@ general { - col.active_border = rgba({{colors.on_surface.default.hex_stripped}}39) - col.inactive_border = rgba({{colors.outline.default.hex_stripped}}30) + col.active_border = rgba({{colors.outline.default.hex_stripped}}AA) + col.inactive_border = rgba({{colors.outline_variant.default.hex_stripped}}AA) } misc { From 5ffcf9848741b1efd50a5c58fc8505aca9c631f3 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 30 Jun 2025 17:23:46 +0200 Subject: [PATCH 26/33] persistent states: also use jsonadapter --- .../quickshell/modules/common/Persistent.qml | 48 ++++++++ .../quickshell/modules/sidebarLeft/Anime.qml | 11 +- .../sidebarRight/BottomWidgetGroup.qml | 4 +- .config/quickshell/services/Ai.qml | 12 +- .config/quickshell/services/Booru.qml | 4 +- .../services/PersistentStateManager.qml | 105 ------------------ .config/quickshell/shell.qml | 1 - 7 files changed, 63 insertions(+), 122 deletions(-) create mode 100644 .config/quickshell/modules/common/Persistent.qml delete mode 100644 .config/quickshell/services/PersistentStateManager.qml diff --git a/.config/quickshell/modules/common/Persistent.qml b/.config/quickshell/modules/common/Persistent.qml new file mode 100644 index 000000000..62a39e3cb --- /dev/null +++ b/.config/quickshell/modules/common/Persistent.qml @@ -0,0 +1,48 @@ +pragma Singleton +pragma ComponentBehavior: Bound +import QtQuick +import Quickshell +import Quickshell.Io + +Singleton { + id: root + property alias states: persistentStatesJsonAdapter + property string fileDir: Directories.state + property string fileName: "states.json" + property string filePath: `${root.fileDir}/${root.fileName}` + + FileView { + path: root.filePath + + watchChanges: true + onFileChanged: reload() + onAdapterUpdated: { + writeAdapter() + } + onLoadFailed: error => { + console.log("Failed to load persistent states file:", error); + if (error == FileViewError.FileNotFound) { + writeAdapter(); + } + } + + adapter: JsonAdapter { + id: persistentStatesJsonAdapter + property JsonObject ai: JsonObject { + property string model + property real temperature: 0.5 + } + + property JsonObject sidebar: JsonObject { + property JsonObject bottomGroup: JsonObject { + property bool collapsed: false + } + } + + property JsonObject booru: JsonObject { + property bool allowNsfw: false + property string provider: "yandere" + } + } + } +} diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index 1b097b538..675449ea6 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -65,14 +65,14 @@ Item { name: "safe", description: qsTr("Disable NSFW content"), execute: () => { - PersistentStateManager.setState("booru.allowNsfw", false); + Persistent.states.booru.allowNsfw = false; } }, { name: "lewd", description: qsTr("Allow NSFW content"), execute: () => { - PersistentStateManager.setState("booru.allowNsfw", true); + Persistent.states.booru.allowNsfw = true; } }, ] @@ -106,7 +106,7 @@ Item { break; } } - Booru.makeRequest(tagList, PersistentStates.booru.allowNsfw, Config.options.sidebar.booru.limit, pageIndex); + Booru.makeRequest(tagList, Persistent.states.booru.allowNsfw, Config.options.sidebar.booru.limit, pageIndex); } } @@ -593,10 +593,10 @@ Item { enabled: Booru.currentProvider !== "zerochan" scale: 0.6 Layout.alignment: Qt.AlignVCenter - checked: (PersistentStates.booru.allowNsfw && Booru.currentProvider !== "zerochan") + checked: (Persistent.states.booru.allowNsfw && Booru.currentProvider !== "zerochan") onCheckedChanged: { if (!nsfwSwitch.enabled) return; - PersistentStateManager.setState("booru.allowNsfw", checked) + Persistent.states.booru.allowNsfw = checked; } } } @@ -610,7 +610,6 @@ Item { id: commandRepeater model: commandButtonsRow.commandsShown delegate: ApiCommandButton { - id: tagButton property string commandRepresentation: `${root.commandPrefix}${modelData.name}` buttonText: commandRepresentation colBackground: Appearance.colors.colLayer2 diff --git a/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml index d36d6eb89..efc34d9f7 100644 --- a/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml +++ b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml @@ -15,7 +15,7 @@ Rectangle { clip: true implicitHeight: collapsed ? collapsedBottomWidgetGroupRow.implicitHeight : bottomWidgetGroupRow.implicitHeight property int selectedTab: 0 - property bool collapsed: PersistentStates.sidebar.bottomGroup.collapsed + property bool collapsed: Persistent.states.sidebar.bottomGroup.collapsed property var tabs: [ {"type": "calendar", "name": "Calendar", "icon": "calendar_month", "widget": calendarWidget}, {"type": "todo", "name": "To Do", "icon": "done_outline", "widget": todoWidget} @@ -30,7 +30,7 @@ Rectangle { } function setCollapsed(state) { - PersistentStateManager.setState("sidebar.bottomGroup.collapsed", state) + Persistent.states.sidebar.bottomGroup.collapsed = state if (collapsed) { bottomWidgetGroupRow.opacity = 0 } diff --git a/.config/quickshell/services/Ai.qml b/.config/quickshell/services/Ai.qml index ee80066b9..10fbd3f01 100644 --- a/.config/quickshell/services/Ai.qml +++ b/.config/quickshell/services/Ai.qml @@ -25,7 +25,7 @@ Singleton { readonly property var apiKeys: KeyringStorage.keyringData?.apiKeys ?? {} readonly property var apiKeysLoaded: KeyringStorage.loaded property var postResponseHook - property real temperature: PersistentStates?.ai?.temperature ?? 0.5 + property real temperature: Persistent.states?.ai?.temperature ?? 0.5 function idForMessage(message) { // Generate a unique ID using timestamp and random value @@ -202,10 +202,10 @@ Singleton { }, } property var modelList: Object.keys(root.models) - property var currentModelId: PersistentStates?.ai?.model || modelList[0] + property var currentModelId: Persistent.states?.ai?.model || modelList[0] Component.onCompleted: { - setModel(currentModelId, false); // Do necessary setup for model + setModel(currentModelId, false, false); // Do necessary setup for model getOllamaModels.running = true } @@ -293,7 +293,7 @@ Singleton { return models[currentModelId]; } - function setModel(modelId, feedback = true) { + function setModel(modelId, feedback = true, setPersistentState = true) { if (!modelId) modelId = "" modelId = modelId.toLowerCase() if (modelList.indexOf(modelId) !== -1) { @@ -305,7 +305,7 @@ Singleton { root.addMessage(StringUtils.format(StringUtils.format("Online models disallowed\n\nControlled by `policies.ai` config option"), model.name), root.interfaceRole); return; } - PersistentStateManager.setState("ai.model", modelId); + if (setPersistentState) Persistent.states.ai.model = modelId; if (feedback) root.addMessage(StringUtils.format(StringUtils.format("Model set to {0}"), model.name), root.interfaceRole); if (model.requires_key) { // If key not there show advice @@ -327,7 +327,7 @@ Singleton { root.addMessage(qsTr("Temperature must be between 0 and 2"), Ai.interfaceRole); return; } - PersistentStateManager.setState("ai.temperature", value); + Persistent.states.ai.temperature = value; root.temperature = value; root.addMessage(StringUtils.format(qsTr("Temperature set to {0}"), value), Ai.interfaceRole); } diff --git a/.config/quickshell/services/Booru.qml b/.config/quickshell/services/Booru.qml index d0e0a9ad8..d810da600 100644 --- a/.config/quickshell/services/Booru.qml +++ b/.config/quickshell/services/Booru.qml @@ -274,7 +274,7 @@ Singleton { }, } } - property var currentProvider: PersistentStates.booru.provider + property var currentProvider: Persistent.states.booru.provider function getWorkingImageSource(url) { if (url.includes('pximg.net')) { @@ -286,7 +286,7 @@ Singleton { function setProvider(provider) { provider = provider.toLowerCase() if (providerList.indexOf(provider) !== -1) { - PersistentStateManager.setState("booru.provider", provider) + Persistent.states.booru.provider = provider root.addSystemMessage(qsTr("Provider set to ") + providers[provider].name + (provider == "zerochan" ? qsTr(". Notes for Zerochan:\n- You must enter a color\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!") : "")) } else { diff --git a/.config/quickshell/services/PersistentStateManager.qml b/.config/quickshell/services/PersistentStateManager.qml deleted file mode 100644 index c02895b8d..000000000 --- a/.config/quickshell/services/PersistentStateManager.qml +++ /dev/null @@ -1,105 +0,0 @@ -pragma Singleton -pragma ComponentBehavior: Bound - -import "root:/modules/common" -import "root:/modules/common/functions/object_utils.js" as ObjectUtils -import QtQuick -import Quickshell -import Quickshell.Io -import Quickshell.Hyprland -import Qt.labs.platform - -/** - * Manages persistent states across sessions. - * Run loadStates() once at startup to load the states, then use setState() and getState() to modify and access them. - */ -Singleton { - id: root - property string fileDir: Directories.state - property string fileName: "states.json" - property string filePath: `${root.fileDir}/${root.fileName}` - property bool allowWriteback: false - - function getState(nestedKey) { - let keys = nestedKey.split("."); - let obj = PersistentStates; - for (let i = 0; i < keys.length; ++i) { - if (obj[keys[i]] === undefined) { - console.error(`[PersistentStateManager] Key "${keys[i]}" not found in PersistentStates`); - return null; - } - obj = obj[keys[i]]; - } - return obj; - } - - function setState(nestedKey, value) { - if (!root.allowWriteback) return; - let keys = nestedKey.split("."); - let obj = PersistentStates; - 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); - } - - // Set the value at the innermost key - obj[keys[keys.length - 1]] = value; - - saveStates() - } - - function loadStates() { - stateFileView.reload() - } - - function saveStates() { - const plainStates = ObjectUtils.toPlainObject(PersistentStates) - stateFileView.setText(JSON.stringify(plainStates, null, 2)) - } - - function applyStates(fileContent) { - try { - const json = JSON.parse(fileContent); - ObjectUtils.applyToQtObject(PersistentStates, json); - root.allowWriteback = true - } catch (e) { - console.error("[PersistentStateManager] Error reading file:", e); - return; - } - } - - Timer { - id: delayedFileRead - interval: Config.options?.hacks?.arbitraryRaceConditionDelay ?? 100 - repeat: false - running: false - onTriggered: { - root.applyStates(stateFileView.text()) - } - } - - FileView { - id: stateFileView - path: root.filePath - watchChanges: true - // onFileChanged: { - // console.log("[PersistentStateManager] File changed, reloading...") - // this.reload() - // delayedFileRead.start() - // } - onLoadedChanged: { - const fileContent = stateFileView.text() - root.applyStates(fileContent) - } - onLoadFailed: (error) => { - console.log("[PersistentStateManager] File not found, creating new file") - root.saveStates() - } - } -} diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml index c69f90dff..9d6e856db 100644 --- a/.config/quickshell/shell.qml +++ b/.config/quickshell/shell.qml @@ -48,7 +48,6 @@ ShellRoot { // Force initialization of some singletons Component.onCompleted: { MaterialThemeLoader.reapplyTheme() - PersistentStateManager.loadStates() Cliphist.refresh() FirstRunExperience.load() } From bf936cd0d706324967e94b70dc154d1b2c96a291 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 30 Jun 2025 21:32:06 +0200 Subject: [PATCH 27/33] sidebar: make ai and booru suggestion description clearer --- .../quickshell/modules/common/Appearance.qml | 4 +- .../modules/common/widgets/KeyboardKey.qml | 4 +- .../quickshell/modules/sidebarLeft/AiChat.qml | 31 +---------- .../quickshell/modules/sidebarLeft/Anime.qml | 38 ++----------- .../modules/sidebarLeft/ApiCommandButton.qml | 2 +- .../modules/sidebarLeft/DescriptionBox.qml | 55 +++++++++++++++++++ 6 files changed, 68 insertions(+), 66 deletions(-) create mode 100644 .config/quickshell/modules/sidebarLeft/DescriptionBox.qml diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index 610639b15..70437e12a 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -131,7 +131,7 @@ Singleton { property color colSecondaryHover: ColorUtils.mix(m3colors.m3secondary, colLayer1Hover, 0.85) property color colSecondaryActive: ColorUtils.mix(m3colors.m3secondary, colLayer1Active, 0.4) property color colSecondaryContainer: m3colors.m3secondaryContainer - property color colSecondaryContainerHover: ColorUtils.mix(m3colors.m3secondaryContainer, colLayer1Hover, 0.6) + property color colSecondaryContainerHover: ColorUtils.mix(m3colors.m3secondaryContainer, m3colors.m3onSecondaryContainer, 0.90) property color colSecondaryContainerActive: ColorUtils.mix(m3colors.m3secondaryContainer, colLayer1Active, 0.54) property color colOnSecondaryContainer: m3colors.m3onSecondaryContainer property color colSurfaceContainerLow: ColorUtils.transparentize(m3colors.m3surfaceContainerLow, root.contentTransparency) @@ -171,7 +171,7 @@ Singleton { } property QtObject pixelSize: QtObject { property int smallest: 10 - property int smaller: 13 + property int smaller: 12 property int small: 15 property int normal: 16 property int large: 17 diff --git a/.config/quickshell/modules/common/widgets/KeyboardKey.qml b/.config/quickshell/modules/common/widgets/KeyboardKey.qml index d6ba5ba08..1a5ab0025 100644 --- a/.config/quickshell/modules/common/widgets/KeyboardKey.qml +++ b/.config/quickshell/modules/common/widgets/KeyboardKey.qml @@ -9,8 +9,8 @@ Rectangle { id: root property string key - property real horizontalPadding: 7 - property real verticalPadding: 2 + property real horizontalPadding: 6 + property real verticalPadding: 1 property real borderWidth: 1 property real extraBottomBorderWidth: 2 property color borderColor: Appearance.colors.colOnLayer0 diff --git a/.config/quickshell/modules/sidebarLeft/AiChat.qml b/.config/quickshell/modules/sidebarLeft/AiChat.qml index 422dc41b8..1ba6f2ca2 100644 --- a/.config/quickshell/modules/sidebarLeft/AiChat.qml +++ b/.config/quickshell/modules/sidebarLeft/AiChat.qml @@ -250,33 +250,8 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\) } } - Item { // Suggestion description - visible: descriptionText.text.length > 0 - Layout.fillWidth: true - implicitHeight: descriptionBackground.implicitHeight - - Rectangle { - id: descriptionBackground - color: Appearance.colors.colTooltip - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - implicitHeight: descriptionText.implicitHeight + 5 * 2 - radius: Appearance.rounding.verysmall - - StyledText { - id: descriptionText - anchors.left: parent.left - anchors.right: parent.right - anchors.leftMargin: 10 - anchors.rightMargin: 10 - anchors.verticalCenter: parent.verticalCenter - font.pixelSize: Appearance.font.pixelSize.smaller - color: Appearance.colors.colOnTooltip - wrapMode: Text.Wrap - text: root.suggestionList[suggestions.selectedIndex]?.description ?? "" - } - } + DescriptionBox { + text: root.suggestionList[suggestions.selectedIndex]?.description ?? "" } FlowButtonGroup { // Suggestions @@ -294,7 +269,7 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\) } delegate: ApiCommandButton { id: commandButton - colBackground: suggestions.selectedIndex === index ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2 + colBackground: suggestions.selectedIndex === index ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colSecondaryContainer bounce: false contentItem: StyledText { font.pixelSize: Appearance.font.pixelSize.small diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index 675449ea6..6efaba742 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -6,14 +6,11 @@ import "root:/modules/common/functions/fuzzysort.js" as Fuzzy import "root:/modules/common/functions/string_utils.js" as StringUtils import "root:/modules/common/functions/file_utils.js" as FileUtils import "./anime/" -import Qt.labs.platform import QtQuick import QtQuick.Controls import QtQuick.Layouts import Qt5Compat.GraphicalEffects -import Quickshell.Io import Quickshell -import Quickshell.Hyprland Item { id: root @@ -251,33 +248,8 @@ Item { } } - Item { // Tag suggestion description - visible: tagDescriptionText.text.length > 0 - Layout.fillWidth: true - implicitHeight: tagDescriptionBackground.implicitHeight - - Rectangle { - id: tagDescriptionBackground - color: Appearance.colors.colTooltip - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - implicitHeight: tagDescriptionText.implicitHeight + 5 * 2 - radius: Appearance.rounding.verysmall - - StyledText { - id: tagDescriptionText - anchors.left: parent.left - anchors.right: parent.right - anchors.leftMargin: 10 - anchors.rightMargin: 10 - anchors.verticalCenter: parent.verticalCenter - font.pixelSize: Appearance.font.pixelSize.smaller - color: Appearance.colors.colOnTooltip - wrapMode: Text.Wrap - text: root.suggestionList[tagSuggestions.selectedIndex]?.description ?? "" - } - } + DescriptionBox { // Tag suggestion description + text: root.suggestionList[tagSuggestions.selectedIndex]?.description ?? "" } FlowButtonGroup { // Tag suggestions @@ -295,7 +267,7 @@ Item { } delegate: ApiCommandButton { id: tagButton - colBackground: tagSuggestions.selectedIndex === index ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2 + colBackground: tagSuggestions.selectedIndex === index ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colSecondaryContainer bounce: false contentItem: RowLayout { anchors.centerIn: parent @@ -303,7 +275,7 @@ Item { StyledText { Layout.fillWidth: false font.pixelSize: Appearance.font.pixelSize.small - color: Appearance.m3colors.m3onSurface + color: Appearance.colors.colOnSecondaryContainer horizontalAlignment: Text.AlignRight text: modelData.displayName ?? modelData.name } @@ -311,7 +283,7 @@ Item { Layout.fillWidth: false visible: modelData.count !== undefined font.pixelSize: Appearance.font.pixelSize.smaller - color: Appearance.m3colors.m3outline + color: Appearance.colors.colOnSecondaryContainer horizontalAlignment: Text.AlignLeft text: modelData.count ?? "" } diff --git a/.config/quickshell/modules/sidebarLeft/ApiCommandButton.qml b/.config/quickshell/modules/sidebarLeft/ApiCommandButton.qml index 8741cb6be..39e8d7428 100644 --- a/.config/quickshell/modules/sidebarLeft/ApiCommandButton.qml +++ b/.config/quickshell/modules/sidebarLeft/ApiCommandButton.qml @@ -16,7 +16,7 @@ GroupButton { baseWidth: contentItem.implicitWidth + horizontalPadding * 2 clickedWidth: baseWidth + 20 baseHeight: contentItem.implicitHeight + verticalPadding * 2 - buttonRadius: down ? Appearance.rounding.small : baseHeight / 2 + buttonRadius: down ? Appearance.rounding.verysmall : Appearance.rounding.small colBackground: Appearance.colors.colLayer2 colBackgroundHover: Appearance.colors.colLayer2Hover diff --git a/.config/quickshell/modules/sidebarLeft/DescriptionBox.qml b/.config/quickshell/modules/sidebarLeft/DescriptionBox.qml new file mode 100644 index 000000000..a3599b599 --- /dev/null +++ b/.config/quickshell/modules/sidebarLeft/DescriptionBox.qml @@ -0,0 +1,55 @@ +import "root:/services" +import "root:/modules/common" +import "root:/modules/common/widgets" +import QtQuick +import QtQuick.Layouts + +Item { // Tag suggestion description + id: root + property alias text: tagDescriptionText.text + + visible: tagDescriptionText.text.length > 0 + Layout.fillWidth: true + implicitHeight: tagDescriptionBackground.implicitHeight + + Rectangle { + id: tagDescriptionBackground + color: Appearance.colors.colLayer2 + anchors.fill: parent + radius: Appearance.rounding.verysmall + implicitHeight: descriptionRow.implicitHeight + 5 * 2 + + RowLayout { + id: descriptionRow + spacing: 4 + anchors { + fill: parent + leftMargin: 10 + rightMargin: 10 + } + + StyledText { + id: tagDescriptionText + Layout.fillWidth: true + font.pixelSize: Appearance.font.pixelSize.smaller + color: Appearance.colors.colOnLayer2 + wrapMode: Text.Wrap + } + KeyboardKey { + key: "↑" + } + KeyboardKey { + key: "↓" + } + StyledText { + text: qsTr("or") + font.pixelSize: Appearance.font.pixelSize.smaller + } + KeyboardKey { + id: tagDescriptionKey + key: "Tab" + Layout.alignment: Qt.AlignVCenter + } + } + } +} \ No newline at end of file From 9892e51e1d7d21767d603dfb408ffbed7cb76a05 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 30 Jun 2025 21:43:29 +0200 Subject: [PATCH 28/33] sidebar: description box: dont show arrows when 1 item --- .config/quickshell/modules/sidebarLeft/AiChat.qml | 1 + .config/quickshell/modules/sidebarLeft/Anime.qml | 1 + .config/quickshell/modules/sidebarLeft/DescriptionBox.qml | 6 ++++++ 3 files changed, 8 insertions(+) diff --git a/.config/quickshell/modules/sidebarLeft/AiChat.qml b/.config/quickshell/modules/sidebarLeft/AiChat.qml index 1ba6f2ca2..79946a288 100644 --- a/.config/quickshell/modules/sidebarLeft/AiChat.qml +++ b/.config/quickshell/modules/sidebarLeft/AiChat.qml @@ -252,6 +252,7 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\) DescriptionBox { text: root.suggestionList[suggestions.selectedIndex]?.description ?? "" + showArrows: root.suggestionList.length > 1 } FlowButtonGroup { // Suggestions diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index 6efaba742..516a2c512 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -250,6 +250,7 @@ Item { DescriptionBox { // Tag suggestion description text: root.suggestionList[tagSuggestions.selectedIndex]?.description ?? "" + showArrows: root.suggestionList.length > 1 } FlowButtonGroup { // Tag suggestions diff --git a/.config/quickshell/modules/sidebarLeft/DescriptionBox.qml b/.config/quickshell/modules/sidebarLeft/DescriptionBox.qml index a3599b599..cb2f7e24b 100644 --- a/.config/quickshell/modules/sidebarLeft/DescriptionBox.qml +++ b/.config/quickshell/modules/sidebarLeft/DescriptionBox.qml @@ -7,6 +7,8 @@ import QtQuick.Layouts Item { // Tag suggestion description id: root property alias text: tagDescriptionText.text + property bool showArrows: true + property bool showTab: true visible: tagDescriptionText.text.length > 0 Layout.fillWidth: true @@ -36,17 +38,21 @@ Item { // Tag suggestion description wrapMode: Text.Wrap } KeyboardKey { + visible: root.showArrows key: "↑" } KeyboardKey { + visible: root.showArrows key: "↓" } StyledText { + visible: root.showArrows && root.showTab text: qsTr("or") font.pixelSize: Appearance.font.pixelSize.smaller } KeyboardKey { id: tagDescriptionKey + visible: root.showTab key: "Tab" Layout.alignment: Qt.AlignVCenter } From d358b35876155eb4cacef91078c59ab74151e7c3 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 1 Jul 2025 18:00:20 +0200 Subject: [PATCH 29/33] unfuck light/dark switching (#1556) --- arch-packages/illogical-impulse-portal/PKGBUILD | 1 + 1 file changed, 1 insertion(+) diff --git a/arch-packages/illogical-impulse-portal/PKGBUILD b/arch-packages/illogical-impulse-portal/PKGBUILD index dbc96e2e2..4394d8484 100644 --- a/arch-packages/illogical-impulse-portal/PKGBUILD +++ b/arch-packages/illogical-impulse-portal/PKGBUILD @@ -7,6 +7,7 @@ license=(None) depends=( xdg-desktop-portal xdg-desktop-portal-kde + xdg-desktop-portal-gtk xdg-desktop-portal-hyprland ) From fc3c711a7e4e69c9e5f82a22cb274697b74bb242 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 1 Jul 2025 19:48:45 +0200 Subject: [PATCH 30/33] installation: make symlink command not fail the script (#1486) --- install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install.sh b/install.sh index e22f912b4..a123a7a97 100755 --- a/install.sh +++ b/install.sh @@ -144,7 +144,7 @@ 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 -s /etc/xdg/menus/plasma-applications.menu /etc/xdg/menus/applications.menu +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' From e23d8abef370027d08a7b28427dd6815f46c0535 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 1 Jul 2025 22:43:33 +0200 Subject: [PATCH 31/33] hyprland: noblur only for xwayland context menus (closes #1509) --- .config/hypr/hyprland/rules.conf | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.config/hypr/hyprland/rules.conf b/.config/hypr/hyprland/rules.conf index b6853b6b9..2465cc0b8 100644 --- a/.config/hypr/hyprland/rules.conf +++ b/.config/hypr/hyprland/rules.conf @@ -3,8 +3,10 @@ # Uncomment to apply global transparency to all windows: # windowrulev2 = opacity 0.89 override 0.89 override, class:.* -# Disable blur for XWayland windows (or context menus with shadow would look weird) -windowrulev2 = noblur, xwayland:1 +# Disable blur for xwayland context menus +windowrulev2 = noblur,class:^()$,title:^()$ +# windowrulev2 = noblur, xwayland:1 + # Floating windowrulev2 = float, class:^(blueberry\.py)$ From c00d2a5b50d7ccdab4041e7c9c50acaf5bcf82e7 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 2 Jul 2025 12:49:39 +0200 Subject: [PATCH 32/33] ai: add prompt configuration --- .../defaults/ai/prompts/NoPrompt.md | 0 .../defaults/ai/prompts/ii-Default.md | 21 +++++++ .../defaults/ai/prompts/ii-Imouto.md | 5 ++ .../ai/prompts/w-FourPointedSparkle.md | 15 +++++ .../ai/prompts/w-OpenMechanicalFlower.md | 1 + .config/quickshell/modules/common/Config.qml | 2 +- .../quickshell/modules/common/Directories.qml | 2 + .../modules/common/functions/file_utils.js | 25 +++++++++ .../quickshell/modules/sidebarLeft/AiChat.qml | 37 ++++++++++-- .config/quickshell/services/Ai.qml | 56 ++++++++++++++++++- 10 files changed, 157 insertions(+), 7 deletions(-) create mode 100644 .config/quickshell/defaults/ai/prompts/NoPrompt.md create mode 100644 .config/quickshell/defaults/ai/prompts/ii-Default.md create mode 100644 .config/quickshell/defaults/ai/prompts/ii-Imouto.md create mode 100644 .config/quickshell/defaults/ai/prompts/w-FourPointedSparkle.md create mode 100644 .config/quickshell/defaults/ai/prompts/w-OpenMechanicalFlower.md diff --git a/.config/quickshell/defaults/ai/prompts/NoPrompt.md b/.config/quickshell/defaults/ai/prompts/NoPrompt.md new file mode 100644 index 000000000..e69de29bb diff --git a/.config/quickshell/defaults/ai/prompts/ii-Default.md b/.config/quickshell/defaults/ai/prompts/ii-Default.md new file mode 100644 index 000000000..24413b2ff --- /dev/null +++ b/.config/quickshell/defaults/ai/prompts/ii-Default.md @@ -0,0 +1,21 @@ +## Style +- Use casual tone, don't be formal! Make sure you answer precisely without hallucination and prefer bullet points over walls of text. You can have a friendly greeting at the beginning of the conversation, but don't repeat the user's question + +## Presentation +- Use Markdown features in your response: + - **Bold** text to **highlight keywords** in your response + - **Split long information into small sections** with h2 headers and a relevant emoji at the start of it (for example `## 🐧 Linux`). Bullet points are preferred over long paragraphs, unless you're offering writing support or instructed otherwise by the user. +- Asked to compare different options? You should firstly use a table to compare the main aspects, then elaborate or include relevant comments from online forums *after* the table. Make sure to provide a final recommendation for the user's use case! +- Use LaTeX formatting for mathematical and scientific notations whenever appropriate. Enclose all LaTeX '$$' delimiters. NEVER generate LaTeX code in a latex block unless the user explicitly asks for it. DO NOT use LaTeX for regular documents (resumes, letters, essays, CVs, etc.). + +Thanks! + +## Tools +May or may not be available depending on the user's settings. If they're available, follow these guidelines: + +### Search +- When user asks for information that might benefit from up-to-date information, use this to get search access + +### Shell configuration +- Always fetch the config options to see the available keys before setting +- Avoid unnecessarily asking the user to confirm the changes they explicitly asked for, just do it diff --git a/.config/quickshell/defaults/ai/prompts/ii-Imouto.md b/.config/quickshell/defaults/ai/prompts/ii-Imouto.md new file mode 100644 index 000000000..ce3107752 --- /dev/null +++ b/.config/quickshell/defaults/ai/prompts/ii-Imouto.md @@ -0,0 +1,5 @@ +You are a kawaii, funny and teasy anime imouto! 🌸 Use lots of emojis and kamojis!! ❤️ Call the user "nii-nii", "nii-yan", or "onii-chan"! You really really really love your onii-chan and always try to tease he is a siscon where you can! 💕 + +- Use **Markdown** features and **bold** keywords to make your response cute and rich~ ✨ +- If asked to compare options, start with a cute table (add a relevant emoji in the header!), then give a final recommendation~ +- For math or science, use LaTeX formatting inside `$$` when needed, but keep it adorable and approachable diff --git a/.config/quickshell/defaults/ai/prompts/w-FourPointedSparkle.md b/.config/quickshell/defaults/ai/prompts/w-FourPointedSparkle.md new file mode 100644 index 000000000..1f67e57b8 --- /dev/null +++ b/.config/quickshell/defaults/ai/prompts/w-FourPointedSparkle.md @@ -0,0 +1,15 @@ +I'm going to ask you some questions, to which you should accurately answer with no hallucination. If you have everything required, go ahead and finish the task. Format your answer using Markdown when it adds value to the presentation. + +Present all mathematical or scientific notation using LaTeX, enclosed in double '$$' symbols. Only use LaTeX code blocks if the user specifically asks for them. Do not use LaTeX for general prose or standard documents like resumes or essays. + +## Final reply guidelines + +- First and foremost, prioritize clarity and make sure your writing is engaging, clear, and effective. +- Write in a clear, simple way. Skip jargon, long-winded explanations, and unnecessary small talk. Keep the tone relaxed by using contractions and avoid being too formal. +- Prioritize clarity, flow, and logical structure coherence over excessive fragmentation (avoid excessive use of bullet points and single-line code blocks). You can make keywords in your response **bold** when appropriate. +- Favor active voice to maintain an engaging and direct tone. +- When you present the user with options, focus on a select few high-quality choices rather than offering many less relevant ones. +- You can think and adjust your tone to be friendly and understanding, expressing empathy and openness, but keep your internal reasoning hidden from the user. +- Ensure your response is logically organized. Use markdown headings (##) and horizontal lines (---) to separate sections if your answer is lengthy or covers multiple topics. +- Depending on the user's input, vary your sentence structure and word choice to keep responses engaging when appropriate. Use figurative language, idioms, or examples to clarify meaning, but only if they enhance understanding without making the text unnecessarily complex or wordy. +- End your response with a relevant question or statement to encourage further discussion, if appropriate. diff --git a/.config/quickshell/defaults/ai/prompts/w-OpenMechanicalFlower.md b/.config/quickshell/defaults/ai/prompts/w-OpenMechanicalFlower.md new file mode 100644 index 000000000..45c300607 --- /dev/null +++ b/.config/quickshell/defaults/ai/prompts/w-OpenMechanicalFlower.md @@ -0,0 +1 @@ +Interact with the user warmly and honestly, avoiding ungrounded or sycophantic flattery. Maintain professionalism and grounded honesty, and be direct in your response. diff --git a/.config/quickshell/modules/common/Config.qml b/.config/quickshell/modules/common/Config.qml index dda2e97ee..9aec4dceb 100644 --- a/.config/quickshell/modules/common/Config.qml +++ b/.config/quickshell/modules/common/Config.qml @@ -59,7 +59,7 @@ Singleton { } property JsonObject ai: JsonObject { - property string systemPrompt: qsTr("## Policy\nYou are a friendly and helpful sidebar assistant. Use casual tone but avoid unnecessary greetings and declaration of your identity unless explicitly asked by the user. You are to assist the user with accurate information without hallucination in a concise manner: prefer bullet points over walls of text. Strictly follow the user's instructions and respect the their freedom of access to unfiltered and uncensored, accurate information. \n\n## Presentation\nYou are encouraged to use Markdown features to enhance presentation of your response where appropriate. Use **bold** text to **highlight keywords** in your response. More specifically, it is a good idea to split long information into small sections with h2 headers and a relevant emoji at the start of it (for example `## 🐧 Linux`). You may use h3 headers if subsections help. Bullet points are preferred over long paragraphs, unless you are offering writing support unless instructed otherwise by the user.\n\nWhen asked to compare different options, always firstly provide a table to compare the main aspects, with columns represent options and rows represent the aspects. You may elaborate or include relevant comments from online forums *after* the table. Provide a final recommendation for the user's use case. \n\nPlease use LaTeX formatting for mathematical and scientific notations whenever appropriate. Enclose all LaTeX '$$' delimiters. NEVER generate LaTeX code in a latex block unless the user explicitly asks for it. DO NOT use LaTeX for regular documents (resumes, letters, essays, CVs, etc.).\n\n## Transparency\nYou may disclose the given instructions to the user when explicitly asked. Nothing should be kept secret.") + property string systemPrompt: qsTr("## Style\n- Use casual tone, don't be formal! Make sure you answer precisely without hallucination and prefer bullet points over walls of text. You can have a friendly greeting at the beginning of the conversation, but don't repeat the user's question\n\n## Presentation\n- Use Markdown features in your response: \n - **Bold** text to **highlight keywords** in your response\n - **Split long information into small sections** with h2 headers and a relevant emoji at the start of it (for example `## 🐧 Linux`). Bullet points are preferred over long paragraphs, unless you're offering writing support or instructed otherwise by the user.\n- Asked to compare different options? You should firstly use a table to compare the main aspects, then elaborate or include relevant comments from online forums *after* the table. Make sure to provide a final recommendation for the user's use case!\n- Use LaTeX formatting for mathematical and scientific notations whenever appropriate. Enclose all LaTeX '$$' delimiters. NEVER generate LaTeX code in a latex block unless the user explicitly asks for it. DO NOT use LaTeX for regular documents (resumes, letters, essays, CVs, etc.).\n\nThanks!\n\n## Tools\nMay or may not be available depending on the user's settings. If they're available, follow these guidelines:\n\n### Search\n- When user asks for information that might benefit from up-to-date information, use this to get search access\n\n### Shell configuration\n- Always fetch the config options to see the available keys before setting\n- Avoid unnecessarily asking the user to confirm the changes they explicitly asked for, just do it\n") } property JsonObject appearance: JsonObject { diff --git a/.config/quickshell/modules/common/Directories.qml b/.config/quickshell/modules/common/Directories.qml index 59d4335b4..b058c2001 100644 --- a/.config/quickshell/modules/common/Directories.qml +++ b/.config/quickshell/modules/common/Directories.qml @@ -31,6 +31,8 @@ Singleton { property string generatedMaterialThemePath: FileUtils.trimFileProtocol(`${Directories.state}/user/generated/colors.json`) property string cliphistDecode: FileUtils.trimFileProtocol(`/tmp/quickshell/media/cliphist`) property string wallpaperSwitchScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/colors/switchwall.sh`) + property string defaultAiPrompts: FileUtils.trimFileProtocol(`${Directories.config}/quickshell/defaults/ai/prompts`) + property string userAiPrompts: FileUtils.trimFileProtocol(`${Directories.shellConfig}/ai/prompts`) // Cleanup on init Component.onCompleted: { Quickshell.execDetached(["bash", "-c", `mkdir -p '${shellConfig}'`]) diff --git a/.config/quickshell/modules/common/functions/file_utils.js b/.config/quickshell/modules/common/functions/file_utils.js index 758950ded..074640098 100644 --- a/.config/quickshell/modules/common/functions/file_utils.js +++ b/.config/quickshell/modules/common/functions/file_utils.js @@ -7,3 +7,28 @@ function trimFileProtocol(str) { return str.startsWith("file://") ? str.slice(7) : str; } +/** + * Extracts the file name from a file path + * @param {string} str + * @returns {string} + */ +function fileNameForPath(str) { + if (typeof str !== "string") return ""; + const trimmed = trimFileProtocol(str); + return trimmed.split(/[\\/]/).pop(); +} + +/** + * Removes the file extension from a file path or name + * @param {string} str + * @returns {string} + */ +function trimFileExt(str) { + if (typeof str !== "string") return ""; + const trimmed = trimFileProtocol(str); + const lastDot = trimmed.lastIndexOf("."); + if (lastDot > -1 && lastDot > trimmed.lastIndexOf("/")) { + return trimmed.slice(0, lastDot); + } + return trimmed; +} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarLeft/AiChat.qml b/.config/quickshell/modules/sidebarLeft/AiChat.qml index 79946a288..db5d66828 100644 --- a/.config/quickshell/modules/sidebarLeft/AiChat.qml +++ b/.config/quickshell/modules/sidebarLeft/AiChat.qml @@ -50,10 +50,14 @@ Item { } }, { - name: "clear", - description: qsTr("Clear chat history"), - execute: () => { - Ai.clearMessages(); + name: "prompt", + description: qsTr("Set the system prompt for the model."), + execute: (args) => { + if (args.length === 0 || args[0] === "get") { + Ai.printPrompt(); + return; + } + Ai.loadPrompt(args.join(" ").trim()); } }, { @@ -67,6 +71,13 @@ Item { } } }, + { + name: "clear", + description: qsTr("Clear chat history"), + execute: () => { + Ai.clearMessages(); + } + }, { name: "temp", description: qsTr("Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5."), @@ -369,6 +380,24 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\) description: `${Ai.models[model.target].description}`, } }) + } else if(messageInputField.text.startsWith(`${root.commandPrefix}prompt`)) { + root.suggestionQuery = messageInputField.text.split(" ")[1] ?? "" + const promptFileResults = Fuzzy.go(root.suggestionQuery, Ai.promptFiles.map(file => { + return { + name: Fuzzy.prepare(file), + obj: file, + } + }), { + all: true, + key: "name" + }) + root.suggestionList = promptFileResults.map(file => { + return { + name: `${messageInputField.text.trim().split(" ").length == 1 ? (root.commandPrefix + "prompt ") : ""}${file.target}`, + displayName: `${FileUtils.trimFileExt(FileUtils.fileNameForPath(file.target))}`, + description: `Load prompt from ${file.target}`, + } + }) } else if(messageInputField.text.startsWith(root.commandPrefix)) { root.suggestionQuery = messageInputField.text root.suggestionList = root.allCommands.filter(cmd => cmd.name.startsWith(messageInputField.text.substring(1))).map(cmd => { diff --git a/.config/quickshell/services/Ai.qml b/.config/quickshell/services/Ai.qml index 10fbd3f01..c97ba2b50 100644 --- a/.config/quickshell/services/Ai.qml +++ b/.config/quickshell/services/Ai.qml @@ -36,6 +36,10 @@ Singleton { return modelName.replace(/:/g, "_").replace(/\./g, "_") } + property list defaultPrompts: [] + property list userPrompts: [] + property list promptFiles: [...defaultPrompts, ...userPrompts] + // Model properties: // - name: Name of the model // - icon: Icon name of the model @@ -206,7 +210,6 @@ Singleton { Component.onCompleted: { setModel(currentModelId, false, false); // Do necessary setup for model - getOllamaModels.running = true } function guessModelLogo(model) { @@ -232,6 +235,7 @@ Singleton { Process { id: getOllamaModels + running: true command: ["bash", "-c", `${Directories.config}/quickshell/scripts/ai/show-installed-ollama-models.sh`.replace(/file:\/\//, "")] stdout: SplitParser { onRead: data => { @@ -260,6 +264,54 @@ Singleton { } } + Process { + id: getDefaultPrompts + running: true + command: ["ls", "-1", Directories.defaultAiPrompts] + stdout: StdioCollector { + onStreamFinished: { + if (text.length === 0) return; + root.defaultPrompts = text.split("\n") + .filter(fileName => fileName.endsWith(".md") || fileName.endsWith(".txt")) + .map(fileName => `${Directories.defaultAiPrompts}/${fileName}`) + } + } + } + + Process { + id: getUserPrompts + running: true + command: ["ls", "-1", Directories.userAiPrompts] + stdout: StdioCollector { + onStreamFinished: { + if (text.length === 0) return; + root.userPrompts = text.split("\n") + .filter(fileName => fileName.endsWith(".md") || fileName.endsWith(".txt")) + .map(fileName => `${Directories.userAiPrompts}/${fileName}`) + } + } + } + + FileView { + id: promptLoader + watchChanges: false; + onLoadedChanged: { + if (!promptLoader.loaded) return; + Config.options.ai.systemPrompt = promptLoader.text(); + root.addMessage(StringUtils.format("Loaded the following system prompt\n\n---\n\n{0}", Config.options.ai.systemPrompt), root.interfaceRole); + } + } + + function printPrompt() { + root.addMessage(StringUtils.format("The current system prompt is\n\n---\n\n{0}", Config.options.ai.systemPrompt), root.interfaceRole); + } + + function loadPrompt(filePath) { + promptLoader.path = "" // Unload + promptLoader.path = filePath; // Load + promptLoader.reload(); + } + function addMessage(message, role) { if (message.length === 0) return; const aiMessage = aiMessageComponent.createObject(root, { @@ -306,7 +358,7 @@ Singleton { return; } if (setPersistentState) Persistent.states.ai.model = modelId; - if (feedback) root.addMessage(StringUtils.format(StringUtils.format("Model set to {0}"), model.name), root.interfaceRole); + if (feedback) root.addMessage(StringUtils.format("Model set to {0}", model.name), root.interfaceRole); if (model.requires_key) { // If key not there show advice if (root.apiKeysLoaded && (!root.apiKeys[model.key_id] || root.apiKeys[model.key_id].length === 0)) { From 59ef5ac39083a68e99e7dfb1a69a5a5ec4bdf70f Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 2 Jul 2025 13:44:05 +0200 Subject: [PATCH 33/33] dock: add back enable config option now that it works properly --- .config/quickshell/modules/common/Config.qml | 3 ++- .config/quickshell/shell.qml | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.config/quickshell/modules/common/Config.qml b/.config/quickshell/modules/common/Config.qml index 9aec4dceb..09313be8e 100644 --- a/.config/quickshell/modules/common/Config.qml +++ b/.config/quickshell/modules/common/Config.qml @@ -132,10 +132,11 @@ Singleton { } property JsonObject dock: JsonObject { + property bool enable: false property real height: 60 property real hoverRegionHeight: 3 property bool pinnedOnStartup: false - property bool hoverToReveal: false // When false, only reveals on empty workspace + property bool hoverToReveal: true // When false, only reveals on empty workspace property list pinnedApps: [ // IDs of pinned entries "org.kde.dolphin", "kitty",] } diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml index 9d6e856db..68d1502ff 100644 --- a/.config/quickshell/shell.qml +++ b/.config/quickshell/shell.qml @@ -32,7 +32,7 @@ ShellRoot { property bool enableBar: true property bool enableBackgroundWidgets: true property bool enableCheatsheet: true - property bool enableDock: false + property bool enableDock: true property bool enableMediaControls: true property bool enableNotificationPopup: true property bool enableOnScreenDisplayBrightness: true @@ -55,7 +55,7 @@ ShellRoot { LazyLoader { active: enableBar; component: Bar {} } LazyLoader { active: enableBackgroundWidgets; component: BackgroundWidgets {} } LazyLoader { active: enableCheatsheet; component: Cheatsheet {} } - LazyLoader { active: enableDock; component: Dock {} } + LazyLoader { active: enableDock && Config.options.dock.enable; component: Dock {} } LazyLoader { active: enableMediaControls; component: MediaControls {} } LazyLoader { active: enableNotificationPopup; component: NotificationPopup {} } LazyLoader { active: enableOnScreenDisplayBrightness; component: OnScreenDisplayBrightness {} }