From 735fb7895bce19d301769e9901d5f4659a7bae46 Mon Sep 17 00:00:00 2001 From: LilFishyChan Date: Tue, 17 Mar 2026 16:22:47 +0800 Subject: [PATCH 01/14] fix(SysTrayItem): anchor popup menu to icon instead of fixed coordinates - Improved anchor positioning to make the expanded menu follow the icon dynamically rather than using fixed absolute coordinates. - Replaced manual `rect` calculations with direct `item: root` binding. - Updated `gravity` and `edges` logic to properly support both vertical and horizontal bar orientations. --- .../quickshell/ii/modules/ii/bar/SysTray.qml | 5 ++++ .../ii/modules/ii/bar/SysTrayItem.qml | 25 +++++++++++-------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/dots/.config/quickshell/ii/modules/ii/bar/SysTray.qml b/dots/.config/quickshell/ii/modules/ii/bar/SysTray.qml index 56d33e0d4..69d98105a 100644 --- a/dots/.config/quickshell/ii/modules/ii/bar/SysTray.qml +++ b/dots/.config/quickshell/ii/modules/ii/bar/SysTray.qml @@ -29,6 +29,11 @@ Item { } function setExtraWindowAndGrabFocus(window) { + if (root.activeMenu && root.activeMenu !== window) { + if (typeof root.activeMenu.close === "function") + root.activeMenu.close(); + root.activeMenu = null; + } root.activeMenu = window; root.grabFocus(); } diff --git a/dots/.config/quickshell/ii/modules/ii/bar/SysTrayItem.qml b/dots/.config/quickshell/ii/modules/ii/bar/SysTrayItem.qml index 6230de0f8..d85b5fe6b 100644 --- a/dots/.config/quickshell/ii/modules/ii/bar/SysTrayItem.qml +++ b/dots/.config/quickshell/ii/modules/ii/bar/SysTrayItem.qml @@ -26,7 +26,11 @@ MouseArea { item.activate(); break; case Qt.RightButton: - if (item.hasMenu) menu.open(); + if (item.hasMenu) + if (menu.active && menu.item && typeof menu.item.close === "function") + menu.item.close(); + else + menu.open(); break; } event.accepted = true; @@ -44,15 +48,16 @@ MouseArea { sourceComponent: SysTrayMenu { Component.onCompleted: this.open(); trayItemMenuHandle: root.item.menu - anchor { - window: root.QsWindow.window - rect.x: root.x + (Config.options.bar.vertical ? 0 : QsWindow.window?.width) - rect.y: root.y + (Config.options.bar.vertical ? QsWindow.window?.height : 0) - rect.height: root.height - rect.width: root.width - edges: Config.options.bar.bottom ? (Edges.Top | Edges.Left) : (Edges.Bottom | Edges.Right) - gravity: Config.options.bar.bottom ? (Edges.Top | Edges.Left) : (Edges.Bottom | Edges.Right) - } + anchor { + window: root.QsWindow.window + item: root + gravity: Config.options.bar.vertical + ? (Config.options.bar.bottom ? Edges.Left : Edges.Right) + : (Config.options.bar.bottom ? Edges.Top : Edges.Bottom) + edges: Config.options.bar.vertical + ? (Config.options.bar.bottom ? Edges.Left : Edges.Right) + : (Config.options.bar.bottom ? Edges.Top : Edges.Bottom) + } onMenuOpened: (window) => root.menuOpened(window); onMenuClosed: { root.menuClosed(); From bd923a0f8876eb029ed16ff3e6f3d325d754aa12 Mon Sep 17 00:00:00 2001 From: LilFishyChan Date: Tue, 17 Mar 2026 17:07:59 +0800 Subject: [PATCH 02/14] feat(SysTray): add pinning functionality to system tray items --- .../ii/modules/ii/bar/SysTrayItem.qml | 1 + .../ii/modules/ii/bar/SysTrayMenu.qml | 47 +++++++++++++++++++ .../quickshell/ii/services/TrayService.qml | 21 +++++++-- 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/dots/.config/quickshell/ii/modules/ii/bar/SysTrayItem.qml b/dots/.config/quickshell/ii/modules/ii/bar/SysTrayItem.qml index d85b5fe6b..45ce89258 100644 --- a/dots/.config/quickshell/ii/modules/ii/bar/SysTrayItem.qml +++ b/dots/.config/quickshell/ii/modules/ii/bar/SysTrayItem.qml @@ -48,6 +48,7 @@ MouseArea { sourceComponent: SysTrayMenu { Component.onCompleted: this.open(); trayItemMenuHandle: root.item.menu + trayItemId: root.item.id anchor { window: root.QsWindow.window item: root diff --git a/dots/.config/quickshell/ii/modules/ii/bar/SysTrayMenu.qml b/dots/.config/quickshell/ii/modules/ii/bar/SysTrayMenu.qml index 8dc6dc608..cba4cf615 100644 --- a/dots/.config/quickshell/ii/modules/ii/bar/SysTrayMenu.qml +++ b/dots/.config/quickshell/ii/modules/ii/bar/SysTrayMenu.qml @@ -9,6 +9,7 @@ import Quickshell PopupWindow { id: root required property QsMenuHandle trayItemMenuHandle + property string trayItemId: "" property real popupBackgroundMargin: 0 signal menuClosed @@ -173,6 +174,52 @@ PopupWindow { } } } + RippleButton { + id: pinEntry + buttonRadius: popupBackground.radius - popupBackground.padding + horizontalPadding: 12 + implicitWidth: contentItem.implicitWidth + horizontalPadding * 2 + implicitHeight: 36 + Layout.topMargin: 0 + Layout.bottomMargin: 0 + Layout.fillWidth: true + + visible: root.trayItemId !== undefined && root.trayItemId.length > 0 + + releaseAction: () => { + TrayService.togglePin(root.trayItemId); + root.close(); + } + + contentItem: RowLayout { + anchors { + verticalCenter: parent.verticalCenter + left: parent.left + right: parent.right + leftMargin: pinEntry.horizontalPadding + rightMargin: pinEntry.horizontalPadding + } + spacing: 8 + + MaterialSymbol { + iconSize: 18 + text: "push_pin" + } + + StyledText { + Layout.fillWidth: true + text: TrayService.isPinned(root.trayItemId) ? Translation.tr("Unpin") : Translation.tr("Pin") + } + } + } + + Rectangle { + Layout.fillWidth: true + implicitHeight: 1 + color: Appearance.colors.colSubtext + Layout.topMargin: 4 + Layout.bottomMargin: 4 + } Repeater { id: menuEntriesRepeater diff --git a/dots/.config/quickshell/ii/services/TrayService.qml b/dots/.config/quickshell/ii/services/TrayService.qml index a874c6c11..363b0fbde 100644 --- a/dots/.config/quickshell/ii/services/TrayService.qml +++ b/dots/.config/quickshell/ii/services/TrayService.qml @@ -33,12 +33,25 @@ Singleton { function unpin(itemId) { Config.options.tray.pinnedItems = Config.options.tray.pinnedItems.filter(id => id !== itemId); } + function isPinned(itemId) { + for (var i = 0; i < root.pinnedItems.length; i++) { + if (root.pinnedItems[i].id === itemId) + return true; + } + return false; + } + function togglePin(itemId) { - var pins = Config.options.tray.pinnedItems; - if (pins.includes(itemId)) { - unpin(itemId) + if (root.isPinned(itemId)) { + if (!root.invertPins) + unpin(itemId); + else + pin(itemId); } else { - pin(itemId) + if (!root.invertPins) + pin(itemId); + else + unpin(itemId); } } From a574baacc55c8ace544c25d4da9bbbadf389222d Mon Sep 17 00:00:00 2001 From: LilFishyChan Date: Tue, 17 Mar 2026 17:08:12 +0800 Subject: [PATCH 03/14] feat(translations): add "Pin" and "Unpin" labels to English and Chinese translations --- dots/.config/quickshell/ii/translations/en_US.json | 4 +++- dots/.config/quickshell/ii/translations/zh_CN.json | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/dots/.config/quickshell/ii/translations/en_US.json b/dots/.config/quickshell/ii/translations/en_US.json index 2fbdc8e6d..82cae2c08 100644 --- a/dots/.config/quickshell/ii/translations/en_US.json +++ b/dots/.config/quickshell/ii/translations/en_US.json @@ -604,5 +604,7 @@ "Recognize music": "Recognize music", "Stroke width": "Stroke width", "Use varying shapes for password characters": "Use varying shapes for password characters", - "Battery full": "Battery full" + "Battery full": "Battery full", + "Pin": "Pin", + "Unpin": "Unpin" } \ No newline at end of file diff --git a/dots/.config/quickshell/ii/translations/zh_CN.json b/dots/.config/quickshell/ii/translations/zh_CN.json index 3b4c17d69..fd5d77266 100644 --- a/dots/.config/quickshell/ii/translations/zh_CN.json +++ b/dots/.config/quickshell/ii/translations/zh_CN.json @@ -718,5 +718,7 @@ "No applications": "没有应用", "Creativity": "创意", "Move left": "左移", - "Pin to Start": "固定到“开始”屏幕" + "Pin to Start": "固定到“开始”屏幕", + "Pin": "固定", + "Unpin": "取消固定" } \ No newline at end of file From a831fa92e8755c6f4f233b1296d515a7d073180b Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 19 Mar 2026 09:13:27 +0100 Subject: [PATCH 04/14] hyprconfigurator: atomic writes --- .../ii/scripts/hyprland/hyprconfigurator.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/dots/.config/quickshell/ii/scripts/hyprland/hyprconfigurator.py b/dots/.config/quickshell/ii/scripts/hyprland/hyprconfigurator.py index 7d582a821..3c5961273 100755 --- a/dots/.config/quickshell/ii/scripts/hyprland/hyprconfigurator.py +++ b/dots/.config/quickshell/ii/scripts/hyprland/hyprconfigurator.py @@ -2,6 +2,7 @@ import argparse import re import os +import tempfile def edit_hyprland_config(file_path, set_args, reset_args): try: @@ -54,8 +55,19 @@ def edit_hyprland_config(file_path, set_args, reset_args): new_lines[-1] += '\n' new_lines.append(f"{key} = {value}\n") - with open(file_path, 'w') as file: - file.writelines(new_lines) + dir_name = os.path.dirname(os.path.abspath(file_path)) + temp_path = None + try: + with tempfile.NamedTemporaryFile(mode='w', dir=dir_name, delete=False) as temp_file: + temp_file.writelines(new_lines) + temp_path = temp_file.name + os.chmod(temp_path, os.stat(file_path).st_mode) + os.replace(temp_path, file_path) + except Exception as e: + if temp_path and os.path.exists(temp_path): + os.remove(temp_path) + print(f"Error saving file: {e}") + return for key in reset_set: print(f"Removed '{key}' from '{file_path}'") From 74ce3378d06226da6d43c615d6513e37a3a62008 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 19 Mar 2026 09:24:53 +0100 Subject: [PATCH 05/14] add missing import in AnnotationSourceButton --- .../ii/sidebarLeft/aiChat/AnnotationSourceButton.qml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/dots/.config/quickshell/ii/modules/ii/sidebarLeft/aiChat/AnnotationSourceButton.qml b/dots/.config/quickshell/ii/modules/ii/sidebarLeft/aiChat/AnnotationSourceButton.qml index bd75a5d42..828cc0356 100644 --- a/dots/.config/quickshell/ii/modules/ii/sidebarLeft/aiChat/AnnotationSourceButton.qml +++ b/dots/.config/quickshell/ii/modules/ii/sidebarLeft/aiChat/AnnotationSourceButton.qml @@ -1,10 +1,9 @@ -import qs.modules.common -import qs.modules.common.widgets -import qs.modules.common.functions -import qs.services import QtQuick import QtQuick.Layouts -import Quickshell.Hyprland + +import qs +import qs.modules.common +import qs.modules.common.widgets RippleButton { id: root From 7cf704d4509a74bff26a27c60680ce50928af9e0 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 19 Mar 2026 12:39:12 +0100 Subject: [PATCH 06/14] refactor GameModeToggle's conf opt fetching --- .../modules/common/models/NestableObject.qml | 6 ++ .../models/hyprland/HyprlandConfigOption.qml | 63 +++++++++++++++++++ .../models/quickToggles/GameModeToggle.qml | 15 +++-- .../quickshell/ii/services/HyprlandConfig.qml | 16 ++++- 4 files changed, 90 insertions(+), 10 deletions(-) create mode 100644 dots/.config/quickshell/ii/modules/common/models/NestableObject.qml create mode 100644 dots/.config/quickshell/ii/modules/common/models/hyprland/HyprlandConfigOption.qml diff --git a/dots/.config/quickshell/ii/modules/common/models/NestableObject.qml b/dots/.config/quickshell/ii/modules/common/models/NestableObject.qml new file mode 100644 index 000000000..50af20cb0 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/models/NestableObject.qml @@ -0,0 +1,6 @@ +import QtQuick + +// QtObject that allows stuff to be freely declared inside +QtObject { + default property list data +} diff --git a/dots/.config/quickshell/ii/modules/common/models/hyprland/HyprlandConfigOption.qml b/dots/.config/quickshell/ii/modules/common/models/hyprland/HyprlandConfigOption.qml new file mode 100644 index 000000000..0422aab02 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/models/hyprland/HyprlandConfigOption.qml @@ -0,0 +1,63 @@ +pragma ComponentBehavior: Bound +import QtQml +import QtQuick +import Quickshell.Io +import qs.services +import "../" + +NestableObject { + id: root + + required property string key + property alias fetching: fetchProc.running + property bool set + property var value + + Component.onCompleted: fetch() + + Connections { + target: HyprlandConfig + function onReloaded() { + root.fetch(); + } + } + + function fetch() { + fetchProc.command = fetchProc.baseCommand.concat([root.key]); + fetchProc.running = true; + } + + function setValue(newValue) { + HyprlandConfig.set(root.key, newValue) + } + + function reset() { + HyprlandConfig.reset(root.key) + } + + Process { + id: fetchProc + property list baseCommand: ["hyprctl", "getoption", "-j"] + stdout: StdioCollector { + onStreamFinished: { + if (text == "no such option") + return; + try { + const obj = JSON.parse(text); + // Note that the value is returned as "": + // It's the only field that isn't always in the same key so we put it in an else + for (const key in obj) { + if (key == "option") + continue; + else if (key == "set") + root.set = obj[key]; + else + root.value = obj[key]; + } + } catch (e) { + console.log(`[HyprlandConfigOption] Failed to fetch option "${root.key}":\n - Output: ${text.trim()}\n - Error: ${e}`); + } + } + } + } +} diff --git a/dots/.config/quickshell/ii/modules/common/models/quickToggles/GameModeToggle.qml b/dots/.config/quickshell/ii/modules/common/models/quickToggles/GameModeToggle.qml index a1d6db88e..79dd98665 100644 --- a/dots/.config/quickshell/ii/modules/common/models/quickToggles/GameModeToggle.qml +++ b/dots/.config/quickshell/ii/modules/common/models/quickToggles/GameModeToggle.qml @@ -1,11 +1,12 @@ import QtQuick import Quickshell.Io +import qs.modules.common.models.hyprland import qs.services QuickToggleModel { id: root name: Translation.tr("Game mode") - toggled: toggled + toggled: !confOpt.value icon: "gamepad" mainAction: () => { @@ -34,13 +35,11 @@ QuickToggleModel { ]); } } - Process { - id: fetchActiveState - running: true - command: ["bash", "-c", `test "$(hyprctl getoption animations:enabled -j | jq ".int")" -ne 0`] - onExited: (exitCode, exitStatus) => { - root.toggled = exitCode !== 0; // Inverted because enabled = nonzero exit - } + + HyprlandConfigOption { + id: confOpt + key: "animations:enabled" } + tooltipText: Translation.tr("Game mode") } diff --git a/dots/.config/quickshell/ii/services/HyprlandConfig.qml b/dots/.config/quickshell/ii/services/HyprlandConfig.qml index 50cbe9d9b..cf5eb0e30 100644 --- a/dots/.config/quickshell/ii/services/HyprlandConfig.qml +++ b/dots/.config/quickshell/ii/services/HyprlandConfig.qml @@ -3,7 +3,7 @@ pragma ComponentBehavior: Bound import QtQuick import Quickshell -import Quickshell.Io +import Quickshell.Hyprland import qs.modules.common import qs.modules.common.functions @@ -14,6 +14,8 @@ import qs.modules.common.functions Singleton { id: root + signal reloaded() + readonly property string configuratorScriptPath: Quickshell.shellPath("scripts/hyprland/hyprconfigurator.py") readonly property string shellOverridesPath: FileUtils.trimFileProtocol(`${Directories.config}/hypr/hyprland/shellOverrides/main.conf`) @@ -39,7 +41,7 @@ Singleton { ]) } - function resetMany(keys: var) { + function resetMany(keys: list) { let args = "" for (let i = 0; i < keys.length; i++) { args += `--reset "${keys[i]}" ` @@ -48,4 +50,14 @@ Singleton { `${root.configuratorScriptPath} --file ${root.shellOverridesPath} ${args}` // ]) } + + Connections { + target: Hyprland + + function onRawEvent(event) { + if (event.name == "configreloaded") { + root.reloaded() + } + } + } } From be1838e40debcfbc06755e07eb3cc89a3cac7b5e Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 19 Mar 2026 12:40:05 +0100 Subject: [PATCH 07/14] add anti-flashbang with shader --- .../quickToggles/AntiFlashbangToggle.qml | 4 +- .../nightLight/NightLightDialog.qml | 26 ++++++++-- .../services/HyprlandAntiFlashbangShader.qml | 38 +++++++++++++++ .../anti-flashbang.glsl | 47 +++++++++++++++++++ 4 files changed, 109 insertions(+), 6 deletions(-) create mode 100644 dots/.config/quickshell/ii/services/HyprlandAntiFlashbangShader.qml create mode 100644 dots/.config/quickshell/ii/services/hyprlandAntiFlashbangShader/anti-flashbang.glsl diff --git a/dots/.config/quickshell/ii/modules/common/models/quickToggles/AntiFlashbangToggle.qml b/dots/.config/quickshell/ii/modules/common/models/quickToggles/AntiFlashbangToggle.qml index 088434ad7..64f8c4e72 100644 --- a/dots/.config/quickshell/ii/modules/common/models/quickToggles/AntiFlashbangToggle.qml +++ b/dots/.config/quickshell/ii/modules/common/models/quickToggles/AntiFlashbangToggle.qml @@ -8,10 +8,10 @@ QuickToggleModel { name: Translation.tr("Anti-flashbang") tooltipText: Translation.tr("Anti-flashbang") icon: "flash_off" - toggled: Config.options.light.antiFlashbang.enable + toggled: HyprlandAntiFlashbangShader.enabled mainAction: () => { - Config.options.light.antiFlashbang.enable = !Config.options.light.antiFlashbang.enable; + HyprlandAntiFlashbangShader.toggle() } hasMenu: true } diff --git a/dots/.config/quickshell/ii/modules/ii/sidebarRight/nightLight/NightLightDialog.qml b/dots/.config/quickshell/ii/modules/ii/sidebarRight/nightLight/NightLightDialog.qml index f0286fc38..e6f7509f2 100644 --- a/dots/.config/quickshell/ii/modules/ii/sidebarRight/nightLight/NightLightDialog.qml +++ b/dots/.config/quickshell/ii/modules/ii/sidebarRight/nightLight/NightLightDialog.qml @@ -42,7 +42,7 @@ WindowDialog { right: parent.right } iconSize: Appearance.font.pixelSize.larger - buttonIcon: "lightbulb" + buttonIcon: "check" text: Translation.tr("Enable now") checked: Hyprsunset.active onCheckedChanged: { @@ -102,14 +102,32 @@ WindowDialog { right: parent.right } iconSize: Appearance.font.pixelSize.larger - buttonIcon: "flash_off" - text: Translation.tr("Enable") + buttonIcon: "filter" + text: Translation.tr("Content adjustment") + checked: HyprlandAntiFlashbangShader.enabled + onCheckedChanged: { + if (checked) HyprlandAntiFlashbangShader.enable() + else HyprlandAntiFlashbangShader.disable() + } + StyledToolTip { + text: Translation.tr("Dims screen content as needed.

Pros: Immediately responsive
Cons: Expensive and can hurt color accuracy

Uses a Hyprland screen shader") + } + } + + ConfigSwitch { + anchors { + left: parent.left + right: parent.right + } + iconSize: Appearance.font.pixelSize.larger + buttonIcon: "light_mode" + text: Translation.tr("Brightness adjustment") checked: Config.options.light.antiFlashbang.enable onCheckedChanged: { Config.options.light.antiFlashbang.enable = checked; } StyledToolTip { - text: Translation.tr("Example use case: eroge on one workspace, dark Discord window on another") + text: Translation.tr("Adapts the display (physical screen) brightness

Pros: Less expensive, retains colors
Cons: Not immediately responsive

Adjusts display brightness after each Hyprland IPC event") } } } diff --git a/dots/.config/quickshell/ii/services/HyprlandAntiFlashbangShader.qml b/dots/.config/quickshell/ii/services/HyprlandAntiFlashbangShader.qml new file mode 100644 index 000000000..eea2123b9 --- /dev/null +++ b/dots/.config/quickshell/ii/services/HyprlandAntiFlashbangShader.qml @@ -0,0 +1,38 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import QtQuick +import Quickshell + +import qs.modules.common.models.hyprland + +Singleton { + id: root + + readonly property string shaderPath: Quickshell.shellPath("services/hyprlandAntiFlashbangShader/anti-flashbang.glsl") + property bool enabled: confOpt.value == shaderPath + + function enable() { + HyprlandConfig.setMany({ + "decoration:screen_shader": root.shaderPath, + "debug:damage_tracking": 1, // Turn off dmg tracking to prevent weird flashes. 1 = monitor only + }); + } + + function disable() { + HyprlandConfig.resetMany([ + "decoration:screen_shader", + "debug:damage_tracking" + ]); + } + + function toggle() { + if (root.enabled) disable() + else enable() + } + + HyprlandConfigOption { + id: confOpt + key: "decoration:screen_shader" + } +} diff --git a/dots/.config/quickshell/ii/services/hyprlandAntiFlashbangShader/anti-flashbang.glsl b/dots/.config/quickshell/ii/services/hyprlandAntiFlashbangShader/anti-flashbang.glsl new file mode 100644 index 000000000..32e6ad2ed --- /dev/null +++ b/dots/.config/quickshell/ii/services/hyprlandAntiFlashbangShader/anti-flashbang.glsl @@ -0,0 +1,47 @@ +#version 300 es +precision highp float; + +in vec2 v_texcoord; +uniform sampler2D tex; +out vec4 fragColor; + +float overlayOpacityForBrightness(float x) { + // Note: range 0 to 1 + + // Will a fancy curve help?... I'll have to experiment more at night + // float y = pow(x, 2.0) * 0.75; + // float y = (1.0 - exp(-x))*1.15; + // float y = (1.0 - exp(-pow((x-0.15), 0.6)))*1.18; + float y = x*0.75; + + return min(max(y, 0.001), 1.0); +} + +void main() { + // 1. Get the current pixel color + vec4 pixColor = texture(tex, v_texcoord); + + // 2. Calculate average screen brightness + vec3 totalRGB = vec3(0.0); + float samples = 0.0; + + // We use a nested loop to create a 10x10 grid (100 samples) + // This is dense enough to catch small icons/text but light enough to run fast. + for(float x = 0.05; x < 1.0; x += 0.1) { + for(float y = 0.05; y < 1.0; y += 0.1) { + totalRGB += texture(tex, vec2(x, y)).rgb; + samples++; + } + } + + vec3 avgColor = totalRGB / samples; + float globalBrightness = dot(avgColor, vec3(0.2126, 0.7152, 0.0722)); + + // 3. Get the specific opacity for this brightness level + float opacity = overlayOpacityForBrightness(globalBrightness); + + // 4. Apply the "black overlay" effect + vec3 outColor = mix(pixColor.rgb, vec3(0.0), opacity); + + fragColor = vec4(outColor, pixColor.a); +} From 4a9d6a26652f3bfcdb9ebf0c08a9f9a3118ec813 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 19 Mar 2026 16:42:24 +0100 Subject: [PATCH 08/14] update quickshell version --- .../illogical-impulse-quickshell-git/PKGBUILD | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/sdata/dist-arch/illogical-impulse-quickshell-git/PKGBUILD b/sdata/dist-arch/illogical-impulse-quickshell-git/PKGBUILD index 3828b9838..477e26494 100644 --- a/sdata/dist-arch/illogical-impulse-quickshell-git/PKGBUILD +++ b/sdata/dist-arch/illogical-impulse-quickshell-git/PKGBUILD @@ -1,4 +1,4 @@ -_commit='6e17efab83d3a5ad5d6e59bc08d26095c6660502' +_commit='7511545ee20664e3b8b8d3322c0ffe7567c56f7a' # Useful links: # https://git.outfoxxed.me/quickshell/quickshell/commits/branch/master # https://aur.archlinux.org/packages/quickshell-git @@ -16,16 +16,16 @@ url='https://git.outfoxxed.me/quickshell/quickshell' options=(!strip) license=('LGPL-3.0-only') depends=( + 'cpptrace' + 'jemalloc' + 'mesa' 'qt6-declarative' 'qt6-base' - 'jemalloc' 'qt6-svg' + 'libdrm' 'libpipewire' 'libxcb' 'wayland' - 'libdrm' - 'mesa' - 'google-breakpad' # NOTE: Below are custom dependencies of illogical-impulse qt6-5compat qt6-avif-image-plugin @@ -44,15 +44,15 @@ depends=( syntax-highlighting ) makedepends=( - 'spirv-tools' - 'qt6-shadertools' - 'wayland' - 'wayland-protocols' 'cli11' - 'ninja' 'cmake' 'git' + 'ninja' + 'qt6-shadertools' + 'spirv-tools' 'vulkan-headers' + 'wayland' + 'wayland-protocols' ) provides=("$_pkgname") conflicts=("$_pkgname") From 7a01602c5e8129595dab29d99de5d635591e4df7 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 19 Mar 2026 18:19:10 +0100 Subject: [PATCH 09/14] fix weird indentation --- .../ii/modules/ii/bar/SysTrayItem.qml | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/dots/.config/quickshell/ii/modules/ii/bar/SysTrayItem.qml b/dots/.config/quickshell/ii/modules/ii/bar/SysTrayItem.qml index 45ce89258..859d04217 100644 --- a/dots/.config/quickshell/ii/modules/ii/bar/SysTrayItem.qml +++ b/dots/.config/quickshell/ii/modules/ii/bar/SysTrayItem.qml @@ -1,3 +1,4 @@ +pragma ComponentBehavior: Bound import QtQuick import Quickshell import Quickshell.Services.SystemTray @@ -49,16 +50,16 @@ MouseArea { Component.onCompleted: this.open(); trayItemMenuHandle: root.item.menu trayItemId: root.item.id - anchor { - window: root.QsWindow.window - item: root - gravity: Config.options.bar.vertical - ? (Config.options.bar.bottom ? Edges.Left : Edges.Right) - : (Config.options.bar.bottom ? Edges.Top : Edges.Bottom) - edges: Config.options.bar.vertical - ? (Config.options.bar.bottom ? Edges.Left : Edges.Right) - : (Config.options.bar.bottom ? Edges.Top : Edges.Bottom) - } + anchor { + window: root.QsWindow.window + item: root + gravity: Config.options.bar.vertical + ? (Config.options.bar.bottom ? Edges.Left : Edges.Right) + : (Config.options.bar.bottom ? Edges.Top : Edges.Bottom) + edges: Config.options.bar.vertical + ? (Config.options.bar.bottom ? Edges.Left : Edges.Right) + : (Config.options.bar.bottom ? Edges.Top : Edges.Bottom) + } onMenuOpened: (window) => root.menuOpened(window); onMenuClosed: { root.menuClosed(); From fd2d69e4076d42abebed184a5a2173e698f56327 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 19 Mar 2026 18:27:49 +0100 Subject: [PATCH 10/14] SysTrayMenu: hide pin button when in subpage, remove unnecessary close() --- .../quickshell/ii/modules/ii/bar/SysTrayMenu.qml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/dots/.config/quickshell/ii/modules/ii/bar/SysTrayMenu.qml b/dots/.config/quickshell/ii/modules/ii/bar/SysTrayMenu.qml index cba4cf615..03c734b9d 100644 --- a/dots/.config/quickshell/ii/modules/ii/bar/SysTrayMenu.qml +++ b/dots/.config/quickshell/ii/modules/ii/bar/SysTrayMenu.qml @@ -1,3 +1,5 @@ +pragma ComponentBehavior: Bound + import qs.services import qs.modules.common import qs.modules.common.widgets @@ -184,12 +186,8 @@ PopupWindow { Layout.bottomMargin: 0 Layout.fillWidth: true - visible: root.trayItemId !== undefined && root.trayItemId.length > 0 - - releaseAction: () => { - TrayService.togglePin(root.trayItemId); - root.close(); - } + visible: root.trayItemId !== undefined && root.trayItemId.length > 0 && stackView.depth === 1 + releaseAction: () => TrayService.togglePin(root.trayItemId); contentItem: RowLayout { anchors { From 0362b21f5acc8c118309174c9ffb3694829e6da5 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 19 Mar 2026 18:28:08 +0100 Subject: [PATCH 11/14] tray service: revert togglePin() for fewer if cases --- dots/.config/quickshell/ii/services/TrayService.qml | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/dots/.config/quickshell/ii/services/TrayService.qml b/dots/.config/quickshell/ii/services/TrayService.qml index 363b0fbde..31966345c 100644 --- a/dots/.config/quickshell/ii/services/TrayService.qml +++ b/dots/.config/quickshell/ii/services/TrayService.qml @@ -42,16 +42,11 @@ Singleton { } function togglePin(itemId) { - if (root.isPinned(itemId)) { - if (!root.invertPins) - unpin(itemId); - else - pin(itemId); + var pins = Config.options.tray.pinnedItems; + if (pins.includes(itemId)) { + unpin(itemId) } else { - if (!root.invertPins) - pin(itemId); - else - unpin(itemId); + pin(itemId) } } From 33db15f99198c9548bc15f77fdf56b63f56b73aa Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 22 Mar 2026 08:08:34 +0100 Subject: [PATCH 12/14] vrr off (#3118) --- dots/.config/hypr/hyprland/general.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dots/.config/hypr/hyprland/general.conf b/dots/.config/hypr/hyprland/general.conf index 071515e4c..563b59f2a 100644 --- a/dots/.config/hypr/hyprland/general.conf +++ b/dots/.config/hypr/hyprland/general.conf @@ -140,7 +140,7 @@ misc { disable_hyprland_logo = true disable_splash_rendering = true vfr = 1 - vrr = 1 + vrr = 0 mouse_move_enables_dpms = true key_press_enables_dpms = true animate_manual_resizes = false From 6e769779764d476abea3c6c8a195b73b0988679a Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 22 Mar 2026 10:10:01 +0100 Subject: [PATCH 13/14] region selector: breathing border --- .../RectCornersSelectionDetails.qml | 40 +- .../ii/regionSelector/RegionSelection.qml | 483 ++++++++++-------- .../ii/regionSelector/RegionSelector.qml | 4 + 3 files changed, 282 insertions(+), 245 deletions(-) diff --git a/dots/.config/quickshell/ii/modules/ii/regionSelector/RectCornersSelectionDetails.qml b/dots/.config/quickshell/ii/modules/ii/regionSelector/RectCornersSelectionDetails.qml index 3682e7d51..7bc7669d1 100644 --- a/dots/.config/quickshell/ii/modules/ii/regionSelector/RectCornersSelectionDetails.qml +++ b/dots/.config/quickshell/ii/modules/ii/regionSelector/RectCornersSelectionDetails.qml @@ -14,11 +14,14 @@ Item { required property color overlayColor property bool showAimLines: Config.options.regionSelector.rect.showAimLines + property bool breathingBorderOnly: false + // Overlay to darken screen // Base dark overlay around region Rectangle { id: darkenOverlay z: 1 + visible: !root.breathingBorderOnly anchors { left: parent.left top: parent.top @@ -32,25 +35,6 @@ Item { border.width: Math.max(root.width, root.height) } - // Selection border - // Rectangle { - // id: selectionBorder - // z: 1 - // anchors { - // left: parent.left - // top: parent.top - // leftMargin: root.regionX - // topMargin: root.regionY - // } - // width: root.regionWidth - // height: root.regionHeight - // color: "transparent" - // border.color: root.color - // border.width: 2 - // // radius: root.standardRounding - // radius: 0 // TODO: figure out how to make the overlay thing work with rounding - // } - DashedBorder { id: selectionBorder z: 9 @@ -64,13 +48,23 @@ Item { height: Math.round(root.regionHeight) + borderWidth * 2 color: root.color - dashLength: 6 - gapLength: 3 + dashLength: 8 + gapLength: 4 borderWidth: 1 + + // Breathing + opacity: 0.9 + SequentialAnimation on opacity { + running: root.breathingBorderOnly + loops: Animation.Infinite + NumberAnimation { from: 0.9; to: 0.3; duration: 1200; easing.type: Easing.InOutQuad } + NumberAnimation { from: 0.3; to: 0.9; duration: 1200; easing.type: Easing.InOutQuad } + } } StyledText { z: 2 + visible: !root.breathingBorderOnly anchors { top: selectionBorder.bottom right: selectionBorder.right @@ -82,7 +76,7 @@ Item { // Coord lines Rectangle { // Vertical - visible: root.showAimLines + visible: root.showAimLines && !root.breathingBorderOnly opacity: 0.2 z: 2 x: root.mouseX @@ -94,7 +88,7 @@ Item { color: root.color } Rectangle { // Horizontal - visible: root.showAimLines + visible: root.showAimLines && !root.breathingBorderOnly opacity: 0.2 z: 2 y: root.mouseY diff --git a/dots/.config/quickshell/ii/modules/ii/regionSelector/RegionSelection.qml b/dots/.config/quickshell/ii/modules/ii/regionSelector/RegionSelection.qml index 5f5ed79ab..4f96f2506 100644 --- a/dots/.config/quickshell/ii/modules/ii/regionSelector/RegionSelection.qml +++ b/dots/.config/quickshell/ii/modules/ii/regionSelector/RegionSelection.qml @@ -27,13 +27,17 @@ PanelWindow { bottom: true } + // Modes // TODO: Ask: sidebar AI enum SnipAction { Copy, Edit, Search, CharRecognition, Record, RecordWithSound } enum SelectionMode { RectCorners, Circle } + enum Phase { Select, Post } property var action: RegionSelection.SnipAction.Copy property var selectionMode: RegionSelection.SelectionMode.RectCorners + property var phase: RegionSelection.Phase.Select signal dismiss() + // Styles property string screenshotDir: Directories.screenshotTemp property color overlayColor: ColorUtils.transparentize("#000000", 0.4) property color brightText: Appearance.m3colors.darkmode ? Appearance.colors.colOnLayer0 : Appearance.colors.colLayer0 @@ -46,6 +50,10 @@ PanelWindow { property color imageBorderColor: brightTertiary property color imageFillColor: ColorUtils.transparentize(imageBorderColor, 0.85) property color onBorderColor: "#ff000000" + property real targetRegionOpacity: Config.options.regionSelector.targetRegions.opacity + property bool contentRegionOpacity: Config.options.regionSelector.targetRegions.contentRegionOpacity + + // Vars for indicators readonly property var windows: [...HyprlandData.windowList].sort((a, b) => { // Sort floating=true windows before others if (a.floating === b.floating) return 0; @@ -54,6 +62,7 @@ PanelWindow { readonly property var layers: HyprlandData.layers readonly property real falsePositivePreventionRatio: 0.5 + // Screen & interaction vars readonly property HyprlandMonitor hyprlandMonitor: Hyprland.monitorFor(screen) readonly property real monitorScale: hyprlandMonitor.scale readonly property real monitorOffsetX: hyprlandMonitor.x @@ -105,13 +114,13 @@ PanelWindow { return offsetAdjustedLayers; } + // Config property bool isCircleSelection: (root.selectionMode === RegionSelection.SelectionMode.Circle) property bool enableWindowRegions: Config.options.regionSelector.targetRegions.windows && !isCircleSelection property bool enableLayerRegions: Config.options.regionSelector.targetRegions.layers && !isCircleSelection property bool enableContentRegions: Config.options.regionSelector.targetRegions.content - property real targetRegionOpacity: Config.options.regionSelector.targetRegions.opacity - property bool contentRegionOpacity: Config.options.regionSelector.targetRegions.contentRegionOpacity + // Target property real targetedRegionX: -1 property real targetedRegionY: -1 property real targetedRegionWidth: 0 @@ -175,6 +184,7 @@ PanelWindow { property real regionX: Math.min(dragStartX, draggingX) property real regionY: Math.min(dragStartY, draggingY) + // Screenshot stuff TempScreenshotProcess { id: screenshotProc running: true @@ -247,6 +257,7 @@ PanelWindow { } } + // Execution after selection function snip() { // Validity check if (root.regionWidth <= 0 || root.regionHeight <= 0) { @@ -277,21 +288,27 @@ PanelWindow { screenshotAction, // screenshotDir ) - snipProc.command = command; - - // Image post-processing - snipProc.startDetached(); - root.dismiss(); + Quickshell.execDetached(command); + if (root.action == RegionSelection.SnipAction.Record || root.action == RegionSelection.SnipAction.RecordWithSound) { + root.phase = RegionSelection.Phase.Post + } else { + root.dismiss(); + } } - Process { - id: snipProc + // Only clickable in Selection phase + mask: Region { + item: switch(root.phase) { + case RegionSelection.Phase.Select: return mouseArea; + case RegionSelection.Phase.Post: return null; + } } - ScreencopyView { + ScreencopyView { // For freezing anchors.fill: parent live: false captureSource: root.screen + visible: root.phase === RegionSelection.Phase.Select focus: root.visible Keys.onPressed: (event) => { // Esc to close @@ -299,220 +316,242 @@ PanelWindow { root.dismiss(); } } + } - MouseArea { - id: mouseArea - anchors.fill: parent - cursorShape: Qt.CrossCursor - acceptedButtons: Qt.LeftButton | Qt.RightButton - hoverEnabled: true + MouseArea { + id: mouseArea + anchors.fill: parent + cursorShape: Qt.CrossCursor + acceptedButtons: Qt.LeftButton | Qt.RightButton + hoverEnabled: true - // Controls - onPressed: (mouse) => { - root.dragStartX = mouse.x; - root.dragStartY = mouse.y; - root.draggingX = mouse.x; - root.draggingY = mouse.y; - root.dragging = true; - root.mouseButton = mouse.button; - } - onReleased: (mouse) => { - // Detect if it was a click -> Try to select targeted region - if (root.draggingX === root.dragStartX && root.draggingY === root.dragStartY) { - if (root.targetedRegionValid()) { - root.setRegionToTargeted(); - } - } - // Circle dragging? - else if (root.selectionMode === RegionSelection.SelectionMode.Circle) { - const padding = Config.options.regionSelector.circle.padding + Config.options.regionSelector.circle.strokeWidth / 2; - const dragPoints = (root.points.length > 0) ? root.points : [{ x: mouseArea.mouseX, y: mouseArea.mouseY }]; - const maxX = Math.max(...dragPoints.map(p => p.x)); - const minX = Math.min(...dragPoints.map(p => p.x)); - const maxY = Math.max(...dragPoints.map(p => p.y)); - const minY = Math.min(...dragPoints.map(p => p.y)); - root.regionX = minX - padding; - root.regionY = minY - padding; - root.regionWidth = maxX - minX + padding * 2; - root.regionHeight = maxY - minY + padding * 2; - } - root.snip(); - } - onPositionChanged: (mouse) => { - root.updateTargetedRegion(mouse.x, mouse.y); - if (!root.dragging) return; - root.draggingX = mouse.x; - root.draggingY = mouse.y; - root.dragDiffX = mouse.x - root.dragStartX; - root.dragDiffY = mouse.y - root.dragStartY; - root.points.push({ x: mouse.x, y: mouse.y }); - } - - Loader { - z: 2 - anchors.fill: parent - active: root.selectionMode === RegionSelection.SelectionMode.RectCorners - sourceComponent: RectCornersSelectionDetails { - regionX: root.regionX - regionY: root.regionY - regionWidth: root.regionWidth - regionHeight: root.regionHeight - mouseX: mouseArea.mouseX - mouseY: mouseArea.mouseY - color: root.selectionBorderColor - overlayColor: root.overlayColor - } - } - - Loader { - z: 2 - anchors.fill: parent - active: root.selectionMode === RegionSelection.SelectionMode.Circle - sourceComponent: CircleSelectionDetails { - color: root.selectionBorderColor - overlayColor: root.overlayColor - points: root.points - } - } - - CursorGuide { - z: 9999 - x: root.dragging ? root.regionX + root.regionWidth : mouseArea.mouseX - y: root.dragging ? root.regionY + root.regionHeight : mouseArea.mouseY - action: root.action - selectionMode: root.selectionMode - } - - // Window regions - Repeater { - model: ScriptModel { - values: root.enableWindowRegions ? root.windowRegions : [] - } - delegate: TargetRegion { - z: 2 - required property var modelData - clientDimensions: modelData - showIcon: true - targeted: !root.draggedAway && - (root.targetedRegionX === modelData.at[0] - && root.targetedRegionY === modelData.at[1] - && root.targetedRegionWidth === modelData.size[0] - && root.targetedRegionHeight === modelData.size[1]) - - opacity: root.draggedAway ? 0 : root.targetRegionOpacity - borderColor: root.windowBorderColor - fillColor: targeted ? root.windowFillColor : "transparent" - text: `${modelData.class}` - radius: Appearance.rounding.windowRounding - } - } - - // Layer regions - Repeater { - model: ScriptModel { - values: root.enableLayerRegions ? root.layerRegions : [] - } - delegate: TargetRegion { - z: 3 - required property var modelData - clientDimensions: modelData - targeted: !root.draggedAway && - (root.targetedRegionX === modelData.at[0] - && root.targetedRegionY === modelData.at[1] - && root.targetedRegionWidth === modelData.size[0] - && root.targetedRegionHeight === modelData.size[1]) - - opacity: root.draggedAway ? 0 : root.targetRegionOpacity - borderColor: root.windowBorderColor - fillColor: targeted ? root.windowFillColor : "transparent" - text: `${modelData.namespace}` - radius: Appearance.rounding.windowRounding - } - } - - // Content regions - Repeater { - model: ScriptModel { - values: root.enableContentRegions ? root.imageRegions : [] - } - delegate: TargetRegion { - z: 4 - required property var modelData - clientDimensions: modelData - targeted: !root.draggedAway && - (root.targetedRegionX === modelData.at[0] - && root.targetedRegionY === modelData.at[1] - && root.targetedRegionWidth === modelData.size[0] - && root.targetedRegionHeight === modelData.size[1]) - - opacity: root.draggedAway ? 0 : root.contentRegionOpacity - borderColor: root.imageBorderColor - fillColor: targeted ? root.imageFillColor : "transparent" - text: Translation.tr("Content region") - } - } - - // Controls - Row { - id: regionSelectionControls - z: 10 - anchors { - horizontalCenter: parent.horizontalCenter - bottom: parent.bottom - bottomMargin: -height - } - opacity: 0 - Connections { - target: root - function onVisibleChanged() { - if (!visible) return; - regionSelectionControls.anchors.bottomMargin = 8; - regionSelectionControls.opacity = 1; - } - } - Behavior on opacity { - animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) - } - Behavior on anchors.bottomMargin { - animation: Appearance.animation.elementMove.numberAnimation.createObject(this) - } - spacing: 6 - - OptionsToolbar { - Synchronizer on action { - property alias source: root.action - } - Synchronizer on selectionMode { - property alias source: root.selectionMode - } - onDismiss: root.dismiss(); - } - Item { - anchors { - verticalCenter: parent.verticalCenter - } - implicitWidth: closeFab.implicitWidth - implicitHeight: closeFab.implicitHeight - StyledRectangularShadow { - target: closeFab - radius: closeFab.buttonRadius - } - FloatingActionButton { - id: closeFab - baseSize: 48 - iconText: "close" - onClicked: root.dismiss(); - StyledToolTip { - text: Translation.tr("Close") - } - colBackground: Appearance.colors.colTertiaryContainer - colBackgroundHover: Appearance.colors.colTertiaryContainerHover - colRipple: Appearance.colors.colTertiaryContainerActive - colOnBackground: Appearance.colors.colOnTertiaryContainer - } - } - } - + // Controls + onPressed: (mouse) => { + root.dragStartX = mouse.x; + root.dragStartY = mouse.y; + root.draggingX = mouse.x; + root.draggingY = mouse.y; + root.dragging = true; + root.mouseButton = mouse.button; } + onReleased: (mouse) => { + // Detect if it was a click -> Try to select targeted region + if (root.draggingX === root.dragStartX && root.draggingY === root.dragStartY) { + if (root.targetedRegionValid()) { + root.setRegionToTargeted(); + } + } + // Circle dragging? + else if (root.selectionMode === RegionSelection.SelectionMode.Circle) { + const padding = Config.options.regionSelector.circle.padding + Config.options.regionSelector.circle.strokeWidth / 2; + const dragPoints = (root.points.length > 0) ? root.points : [{ x: mouseArea.mouseX, y: mouseArea.mouseY }]; + const maxX = Math.max(...dragPoints.map(p => p.x)); + const minX = Math.min(...dragPoints.map(p => p.x)); + const maxY = Math.max(...dragPoints.map(p => p.y)); + const minY = Math.min(...dragPoints.map(p => p.y)); + root.regionX = minX - padding; + root.regionY = minY - padding; + root.regionWidth = maxX - minX + padding * 2; + root.regionHeight = maxY - minY + padding * 2; + } + root.snip(); + } + onPositionChanged: (mouse) => { + root.updateTargetedRegion(mouse.x, mouse.y); + if (!root.dragging) return; + root.draggingX = mouse.x; + root.draggingY = mouse.y; + root.dragDiffX = mouse.x - root.dragStartX; + root.dragDiffY = mouse.y - root.dragStartY; + root.points.push({ x: mouse.x, y: mouse.y }); + } + + Loader { + z: 2 + anchors.fill: parent + active: root.selectionMode === RegionSelection.SelectionMode.RectCorners + sourceComponent: RectCornersSelectionDetails { + regionX: root.regionX + regionY: root.regionY + regionWidth: root.regionWidth + regionHeight: root.regionHeight + mouseX: mouseArea.mouseX + mouseY: mouseArea.mouseY + color: root.selectionBorderColor + overlayColor: root.overlayColor + breathingBorderOnly: root.phase === RegionSelection.Phase.Post + } + } + + Loader { + z: 2 + anchors.fill: parent + active: root.selectionMode === RegionSelection.SelectionMode.Circle + sourceComponent: CircleSelectionDetails { + color: root.selectionBorderColor + overlayColor: root.overlayColor + points: root.points + } + } + + // The thing to the bottom-right with an icon + CursorGuide { + z: 9999 + visible: root.phase === RegionSelection.Phase.Select + x: root.dragging ? root.regionX + root.regionWidth : mouseArea.mouseX + y: root.dragging ? root.regionY + root.regionHeight : mouseArea.mouseY + action: root.action + selectionMode: root.selectionMode + } + + // Window regions + Repeater { + model: ScriptModel { + values: { + if (root.phase === RegionSelection.Phase.Select && root.enableWindowRegions) { + return root.windowRegions + } else { + return [] + } + } + } + delegate: TargetRegion { + z: 2 + required property var modelData + clientDimensions: modelData + showIcon: true + targeted: !root.draggedAway && // + (root.targetedRegionX === modelData.at[0] // + && root.targetedRegionY === modelData.at[1] // + && root.targetedRegionWidth === modelData.size[0] // + && root.targetedRegionHeight === modelData.size[1]) + + opacity: root.draggedAway ? 0 : root.targetRegionOpacity + borderColor: root.windowBorderColor + fillColor: targeted ? root.windowFillColor : "transparent" + text: `${modelData.class}` + radius: Appearance.rounding.windowRounding + } + } + + // Layer regions + Repeater { + model: ScriptModel { + values: { + if (root.phase === RegionSelection.Phase.Select && root.enableLayerRegions) { + return root.layerRegions + } else { + return [] + } + } + } + delegate: TargetRegion { + z: 3 + required property var modelData + clientDimensions: modelData + targeted: !root.draggedAway && + (root.targetedRegionX === modelData.at[0] + && root.targetedRegionY === modelData.at[1] + && root.targetedRegionWidth === modelData.size[0] + && root.targetedRegionHeight === modelData.size[1]) + + opacity: root.draggedAway ? 0 : root.targetRegionOpacity + borderColor: root.windowBorderColor + fillColor: targeted ? root.windowFillColor : "transparent" + text: `${modelData.namespace}` + radius: Appearance.rounding.windowRounding + } + } + + // Content regions + Repeater { + model: ScriptModel { + values: { + if (root.phase === RegionSelection.Phase.Select && root.enableContentRegions) { + return root.imageRegions + } else { + return [] + } + } + } + delegate: TargetRegion { + z: 4 + required property var modelData + clientDimensions: modelData + targeted: !root.draggedAway && + (root.targetedRegionX === modelData.at[0] + && root.targetedRegionY === modelData.at[1] + && root.targetedRegionWidth === modelData.size[0] + && root.targetedRegionHeight === modelData.size[1]) + + opacity: root.draggedAway ? 0 : root.contentRegionOpacity + borderColor: root.imageBorderColor + fillColor: targeted ? root.imageFillColor : "transparent" + text: Translation.tr("Content region") + } + } + + // Controls + Row { + id: regionSelectionControls + z: 10 + visible: root.phase === RegionSelection.Phase.Select + anchors { + horizontalCenter: parent.horizontalCenter + bottom: parent.bottom + bottomMargin: -height + } + opacity: 0 + Connections { + target: root + function onVisibleChanged() { + if (!visible) return; + regionSelectionControls.anchors.bottomMargin = 8; + regionSelectionControls.opacity = 1; + } + } + Behavior on opacity { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + Behavior on anchors.bottomMargin { + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) + } + spacing: 6 + + OptionsToolbar { + Synchronizer on action { + property alias source: root.action + } + Synchronizer on selectionMode { + property alias source: root.selectionMode + } + onDismiss: root.dismiss(); + } + Item { + anchors { + verticalCenter: parent.verticalCenter + } + implicitWidth: closeFab.implicitWidth + implicitHeight: closeFab.implicitHeight + StyledRectangularShadow { + target: closeFab + radius: closeFab.buttonRadius + } + FloatingActionButton { + id: closeFab + baseSize: 48 + iconText: "close" + onClicked: root.dismiss(); + StyledToolTip { + text: Translation.tr("Close") + } + colBackground: Appearance.colors.colTertiaryContainer + colBackgroundHover: Appearance.colors.colTertiaryContainerHover + colRipple: Appearance.colors.colTertiaryContainerActive + colOnBackground: Appearance.colors.colOnTertiaryContainer + } + } + } + } } diff --git a/dots/.config/quickshell/ii/modules/ii/regionSelector/RegionSelector.qml b/dots/.config/quickshell/ii/modules/ii/regionSelector/RegionSelector.qml index 40440366a..61525e527 100644 --- a/dots/.config/quickshell/ii/modules/ii/regionSelector/RegionSelector.qml +++ b/dots/.config/quickshell/ii/modules/ii/regionSelector/RegionSelector.qml @@ -65,12 +65,16 @@ Scope { function record() { root.action = RegionSelection.SnipAction.Record root.selectionMode = RegionSelection.SelectionMode.RectCorners + // If already open then re-trigger to stop recording + if (GlobalStates.regionSelectorOpen) GlobalStates.regionSelectorOpen = false GlobalStates.regionSelectorOpen = true } function recordWithSound() { root.action = RegionSelection.SnipAction.RecordWithSound root.selectionMode = RegionSelection.SelectionMode.RectCorners + // If already open then re-trigger to stop recording + if (GlobalStates.regionSelectorOpen) GlobalStates.regionSelectorOpen = false GlobalStates.regionSelectorOpen = true } From 58a122a3b49a27cba2f40504b1d883039699d000 Mon Sep 17 00:00:00 2001 From: Anh <124384272+vananh2801@users.noreply.github.com> Date: Sun, 22 Mar 2026 20:13:01 +0700 Subject: [PATCH 14/14] Add XWayland configuration to fix blurry fonts while using fractional scale It will disable windows scaling on xwayland software to prevent blurry fonts. Source: https://wiki.hypr.land/Configuring/XWayland/ --- dots/.config/hypr/hyprland/general.conf | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dots/.config/hypr/hyprland/general.conf b/dots/.config/hypr/hyprland/general.conf index 563b59f2a..9a0649c8c 100644 --- a/dots/.config/hypr/hyprland/general.conf +++ b/dots/.config/hypr/hyprland/general.conf @@ -166,3 +166,6 @@ cursor { hotspot_padding = 1 } +xwayland { + force_zero_scaling = true +}