From 4ea7401190ad744e6cf105d98fed5cc34c92c08a Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 20 Oct 2025 21:03:03 +0200 Subject: [PATCH 01/53] circle to search --- dots/.config/hypr/hyprland/keybinds.conf | 1 + .../quickshell/ii/modules/common/Config.qml | 2 + .../regionSelector/CircleSelectionDetails.qml | 48 ++ .../RectCornersSelectionDetails.qml | 84 +++ .../regionSelector/RegionSelection.qml | 563 +++++++++++++++++ .../modules/regionSelector/RegionSelector.qml | 565 +----------------- .../modules/regionSelector/TargetRegion.qml | 69 +++ 7 files changed, 790 insertions(+), 542 deletions(-) create mode 100644 dots/.config/quickshell/ii/modules/regionSelector/CircleSelectionDetails.qml create mode 100644 dots/.config/quickshell/ii/modules/regionSelector/RectCornersSelectionDetails.qml create mode 100644 dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml create mode 100644 dots/.config/quickshell/ii/modules/regionSelector/TargetRegion.qml diff --git a/dots/.config/hypr/hyprland/keybinds.conf b/dots/.config/hypr/hyprland/keybinds.conf index 38fc05caf..74343d85b 100644 --- a/dots/.config/hypr/hyprland/keybinds.conf +++ b/dots/.config/hypr/hyprland/keybinds.conf @@ -60,6 +60,7 @@ bindd = Super, V, Copy clipboard history entry, exec, qs -c $qsConfig ipc call T bindd = Super, Period, Copy an emoji, exec, qs -c $qsConfig ipc call TEST_ALIVE || pkill fuzzel || ~/.config/hypr/hyprland/scripts/fuzzel-emoji.sh copy # [hidden] Emoji >> clipboard (fallback) bind = Super+Shift, S, global, quickshell:regionScreenshot # Screen snip bind = Super+Shift, S, exec, qs -c $qsConfig ipc call TEST_ALIVE || pidof slurp || hyprshot --freeze --clipboard-only --mode region --silent # [hidden] Screen snip (fallback) +bind = Super+Shift, A, global, quickshell:regionSearch # Circle to Search # 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/dots/.config/quickshell/ii/modules/common/Config.qml b/dots/.config/quickshell/ii/modules/common/Config.qml index 44b262b96..f93171584 100644 --- a/dots/.config/quickshell/ii/modules/common/Config.qml +++ b/dots/.config/quickshell/ii/modules/common/Config.qml @@ -355,6 +355,8 @@ Singleton { property JsonObject search: JsonObject { property int nonAppResultDelay: 30 // This prevents lagging when typing property string engineBaseUrl: "https://www.google.com/search?q=" + property string imageSearchEngineBaseUrl: "https://lens.google.com/uploadbyurl?url=" + property string fileUploadApiEndpoint: "https://uguu.se/upload" property list excludedSites: ["quora.com", "facebook.com"] property bool sloppy: false // Uses levenshtein distance based scoring instead of fuzzy sort. Very weird. property JsonObject prefix: JsonObject { diff --git a/dots/.config/quickshell/ii/modules/regionSelector/CircleSelectionDetails.qml b/dots/.config/quickshell/ii/modules/regionSelector/CircleSelectionDetails.qml new file mode 100644 index 000000000..bea353d1f --- /dev/null +++ b/dots/.config/quickshell/ii/modules/regionSelector/CircleSelectionDetails.qml @@ -0,0 +1,48 @@ +import qs.modules.common.widgets +import QtQuick +import QtQuick.Shapes +import Quickshell + +Item { + id: root + required property color color + required property color overlayColor + required property list points + property int strokeWidth: 10 + + function updatePoints() { + if (!root.dragging) return; + root.points.push({ x: root.mouseX, y: root.mouseY }); + } + + Rectangle { + id: darkenOverlay + z: 1 + anchors.fill: parent + color: root.overlayColor + } + + Shape { + id: shape + z: 2 + anchors.fill: parent + layer.enabled: true + layer.smooth: true + preferredRendererType: Shape.CurveRenderer + + ShapePath { + id: shapePath + strokeWidth: root.strokeWidth + pathHints: ShapePath.PathLinear + fillColor: "transparent" + strokeColor: root.color + capStyle: ShapePath.RoundCap + joinStyle: ShapePath.RoundJoin + + PathPolyline { + path: root.points + } + } + } + +} diff --git a/dots/.config/quickshell/ii/modules/regionSelector/RectCornersSelectionDetails.qml b/dots/.config/quickshell/ii/modules/regionSelector/RectCornersSelectionDetails.qml new file mode 100644 index 000000000..b4e417c8d --- /dev/null +++ b/dots/.config/quickshell/ii/modules/regionSelector/RectCornersSelectionDetails.qml @@ -0,0 +1,84 @@ +import qs.modules.common.widgets +import QtQuick + +Item { + id: root + required property real regionX + required property real regionY + required property real regionWidth + required property real regionHeight + required property real mouseX + required property real mouseY + required property color color + required property color overlayColor + + // Overlay to darken screen + // Base dark overlay around region + Rectangle { + id: darkenOverlay + z: 1 + anchors { + left: parent.left + top: parent.top + leftMargin: root.regionX - darkenOverlay.border.width + topMargin: root.regionY - darkenOverlay.border.width + } + width: root.regionWidth + darkenOverlay.border.width * 2 + height: root.regionHeight + darkenOverlay.border.width * 2 + color: "transparent" + border.color: root.overlayColor + 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 + } + + StyledText { + z: 2 + anchors { + top: selectionBorder.bottom + right: selectionBorder.right + margins: 8 + } + color: root.color + text: `${Math.round(root.regionWidth)} x ${Math.round(root.regionHeight)}` + } + + // Coord lines + Rectangle { // Vertical + z: 2 + x: root.mouseX + anchors { + top: parent.top + bottom: parent.bottom + } + width: 1 + color: root.color + } + Rectangle { // Horizontal + z: 2 + y: root.mouseY + anchors { + left: parent.left + right: parent.right + } + height: 1 + color: root.color + } +} diff --git a/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml b/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml new file mode 100644 index 000000000..32549fbc0 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml @@ -0,0 +1,563 @@ +pragma ComponentBehavior: Bound +import qs +import qs.modules.common +import qs.modules.common.functions +import qs.modules.common.widgets +import qs.services +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Qt5Compat.GraphicalEffects +import Quickshell +import Quickshell.Io +import Quickshell.Wayland +import Quickshell.Widgets +import Quickshell.Hyprland + +PanelWindow { + id: root + visible: false + WlrLayershell.namespace: "quickshell:regionSelector" + WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive + exclusionMode: ExclusionMode.Ignore + anchors { + left: true + right: true + top: true + bottom: true + } + + // TODO: Ask: sidebar AI; Ocr: tesseract + enum SnipAction { Copy, Edit, Search } + enum SelectionMode { RectCorners, Circle } + property var action: RegionSelection.SnipAction.Copy + property var selectionMode: RegionSelection.SelectionMode.RectCorners + signal dismiss() + + property string screenshotDir: Directories.screenshotTemp + property string imageSearchEngineBaseUrl: Config.options.search.imageSearchEngineBaseUrl + property string fileUploadApiEndpoint: Config.options.search.fileUploadApiEndpoint + 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: "#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].sort((a, b) => { + // Sort floating=true windows before others + if (a.floating === b.floating) return 0; + return a.floating ? -1 : 1; + }) + readonly property var layers: HyprlandData.layers + readonly property real falsePositivePreventionRatio: 0.5 + + readonly property HyprlandMonitor hyprlandMonitor: Hyprland.monitorFor(screen) + 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-${screen.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 list points: [] + property var mouseButton: null + property var imageRegions: [] + readonly property list windowRegions: filterWindowRegionsByLayers( + root.windows.filter(w => w.workspace.id === root.activeWorkspaceId), + root.layerRegions + ).map(window => { + return { + at: [window.at[0] - root.monitorOffsetX, window.at[1] - root.monitorOffsetY], + size: [window.size[0], window.size[1]], + class: window.class, + title: window.title, + } + }) + readonly property list layerRegions: { + const layersOfThisMonitor = root.layers[root.hyprlandMonitor.name] + const topLayers = layersOfThisMonitor?.levels["2"] + if (!topLayers) return []; + const nonBarTopLayers = topLayers + .filter(layer => !(layer.namespace.includes(":bar") || layer.namespace.includes(":verticalBar") || layer.namespace.includes(":dock"))) + .map(layer => { + return { + at: [layer.x, layer.y], + size: [layer.w, layer.h], + namespace: layer.namespace, + } + }) + const offsetAdjustedLayers = nonBarTopLayers.map(layer => { + return { + at: [layer.at[0] - root.monitorOffsetX, layer.at[1] - root.monitorOffsetY], + size: layer.size, + namespace: layer.namespace, + } + }); + return offsetAdjustedLayers; + } + + 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 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 = root.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) { + root.targetedRegionX = clickedRegion.at[0]; + root.targetedRegionY = clickedRegion.at[1]; + root.targetedRegionWidth = clickedRegion.size[0]; + root.targetedRegionHeight = clickedRegion.size[1]; + return; + } + + // Layer regions + const clickedLayer = root.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) { + root.targetedRegionX = clickedLayer.at[0]; + root.targetedRegionY = clickedLayer.at[1]; + root.targetedRegionWidth = clickedLayer.size[0]; + root.targetedRegionHeight = clickedLayer.size[1]; + return; + } + + // Window regions + const clickedWindow = root.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) { + root.targetedRegionX = clickedWindow.at[0]; + root.targetedRegionY = clickedWindow.at[1]; + root.targetedRegionWidth = clickedWindow.size[0]; + root.targetedRegionHeight = clickedWindow.size[1]; + return; + } + + root.targetedRegionX = -1; + root.targetedRegionY = -1; + root.targetedRegionWidth = 0; + root.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) + + Process { + id: screenshotProcess + running: true + command: ["bash", "-c", `mkdir -p '${StringUtils.shellSingleQuoteEscape(root.screenshotDir)}' && grim -o '${StringUtils.shellSingleQuoteEscape(root.screen.name)}' '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}'`] + onExited: (exitCode, exitStatus) => { + root.visible = true; + imageDetectionProcess.running = true; + } + } + + Process { + id: imageDetectionProcess + command: ["bash", "-c", `${Directories.scriptPath}/images/find-regions-venv.sh ` + + `--hyprctl ` + + `--image '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}' ` + + `--max-width ${Math.round(root.screen.width * root.falsePositivePreventionRatio)} ` + + `--max-height ${Math.round(root.screen.height * root.falsePositivePreventionRatio)} `] + stdout: StdioCollector { + id: imageDimensionCollector + onStreamFinished: { + imageRegions = filterImageRegions( + JSON.parse(imageDimensionCollector.text), + root.windowRegions + ); + } + } + } + + function snip() { + // Validity check + if (root.regionWidth <= 0 || root.regionHeight <= 0) { + console.warn("[Region Selector] Invalid region size, skipping snip."); + root.dismiss(); + } + + // Adjust action + if (root.action === RegionSelection.SnipAction.Copy || root.action === RegionSelection.SnipAction.Edit) { + root.action = root.mouseButton === Qt.RightButton ? RegionSelection.SnipAction.Edit : RegionSelection.SnipAction.Copy; + } + + // Set command for action + const cropBase = `magick ${StringUtils.shellSingleQuoteEscape(root.screenshotPath)} ` + + `-crop ${root.regionWidth * root.monitorScale}x${root.regionHeight * root.monitorScale}+${root.regionX * root.monitorScale}+${root.regionY * root.monitorScale}` + const cropToStdout = `${cropBase} -` + const cropInPlace = `${cropBase} '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}'` + const cleanup = `rm '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}'` + const uploadAndGetUrl = (filePath) => { + return `curl -sF files[]=@'${StringUtils.shellSingleQuoteEscape(filePath)}' ${root.fileUploadApiEndpoint} | jq -r '.files[0].url'` + } + switch (root.action) { + case RegionSelection.SnipAction.Copy: + snipProc.command = ["bash", "-c", `${cropToStdout} | wl-copy && ${cleanup}`] + break; + case RegionSelection.SnipAction.Edit: + snipProc.command = ["bash", "-c", `${cropToStdout} | swappy -f - && ${cleanup}`] + break; + case RegionSelection.SnipAction.Search: + snipProc.command = ["bash", "-c", `${cropInPlace} && xdg-open "${root.imageSearchEngineBaseUrl}$(${uploadAndGetUrl(root.screenshotPath)})"`] + break; + default: + console.warn("[Region Selector] Unknown snip action, skipping snip."); + root.dismiss(); + return; + } + + // Image post-processing + snipProc.startDetached(); + root.dismiss(); + } + + Process { + id: snipProc + } + + ScreencopyView { + anchors.fill: parent + live: false + captureSource: root.screen + + focus: root.visible + Keys.onPressed: (event) => { // Esc to close + if (event.key === Qt.Key_Escape) { + root.dismiss(); + } + } + + 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) => { + // Circle dragging? + if (root.selectionMode === RegionSelection.SelectionMode.Circle) { + const maxX = Math.max(...root.points.map(p => p.x)); + const minX = Math.min(...root.points.map(p => p.x)); + const maxY = Math.max(...root.points.map(p => p.y)); + const minY = Math.min(...root.points.map(p => p.y)); + root.regionX = minX; + root.regionY = minY; + root.regionWidth = maxX - minX; + root.regionHeight = maxY - minY; + } + // Detect if it was a click -> Try to select targeted region + if (root.draggingX === root.dragStartX && root.draggingY === root.dragStartY) { + if (root.targetedRegionX >= 0 && root.targetedRegionY >= 0) { + root.regionX = root.targetedRegionX; + root.regionY = root.targetedRegionY; + root.regionWidth = root.targetedRegionWidth; + root.regionHeight = root.targetedRegionHeight; + } + } + root.snip(); + } + onPositionChanged: (mouse) => { + 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 }); + root.updateTargetedRegion(mouse.x, 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 + } + } + + // Instructions + Rectangle { + z: 9999 + anchors { + top: parent.top + horizontalCenter: parent.horizontalCenter + topMargin: (Appearance.sizes.barHeight - implicitHeight) / 2 + } + + opacity: root.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 + + Row { + id: instructionsRow + anchors.centerIn: parent + spacing: 4 + MaterialSymbol { + id: screenshotRegionIcon + // anchors.centerIn: parent + iconSize: Appearance.font.pixelSize.larger + text: switch(root.selectionMode) { + case RegionSelection.SelectionMode.RectCorners: + return "crop_free" + break; + case RegionSelection.SelectionMode.Circle: + return "gesture" + break; + default: + return "crop_free" + } + color: root.genericContentForeground + } + StyledText { + anchors.verticalCenter: parent.verticalCenter + text: { + var instructionText = ""; + var actionText = ""; + if (root.selectionMode === RegionSelection.SelectionMode.RectCorners) { + instructionText = Translation.tr("Drag or click a region"); + } else if (root.selectionMode === RegionSelection.SelectionMode.Circle) { + instructionText = Translation.tr("Circle"); + } + switch (root.action) { + case RegionSelection.SnipAction.Copy: + case RegionSelection.SnipAction.Edit: + actionText = Translation.tr(" | LMB: Copy • RMB: Edit"); + break; + case RegionSelection.SnipAction.Search: + actionText = Translation.tr(" to search"); + break; + default: + actionText = ""; + } + return instructionText + actionText; + } + color: root.genericContentForeground + } + } + } + + // Window regions + Repeater { + model: ScriptModel { + values: root.windowRegions + } + delegate: TargetRegion { + z: 2 + required property var 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 : 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 + } + } + + // Layer regions + Repeater { + model: ScriptModel { + values: root.layerRegions + } + delegate: TargetRegion { + z: 3 + required property var 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 : 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 + } + } + + // Image regions + Repeater { + model: ScriptModel { + values: Config.options.screenshotTool.showContentRegions ? root.imageRegions : [] + } + delegate: TargetRegion { + z: 4 + required property var 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 : 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/dots/.config/quickshell/ii/modules/regionSelector/RegionSelector.qml b/dots/.config/quickshell/ii/modules/regionSelector/RegionSelector.qml index 6eecc365e..336c9fd6b 100644 --- a/dots/.config/quickshell/ii/modules/regionSelector/RegionSelector.qml +++ b/dots/.config/quickshell/ii/modules/regionSelector/RegionSelector.qml @@ -16,83 +16,13 @@ import Quickshell.Hyprland Scope { id: root - property string screenshotDir: Directories.screenshotTemp - 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: "#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].sort((a, b) => { - // Sort floating=true windows before others - if (a.floating === b.floating) return 0; - return a.floating ? -1 : 1; - }) - readonly property var layers: HyprlandData.layers - readonly property real falsePositivePreventionRatio: 0.5 function dismiss() { GlobalStates.regionSelectorOpen = false } - component TargetRegion: Rectangle { - id: regionRect - property bool showIcon: false - 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: regionInfoRow.implicitWidth + horizontalPadding * 2 - implicitHeight: regionInfoRow.implicitHeight + verticalPadding * 2 - Row { - id: regionInfoRow - anchors.centerIn: parent - spacing: 4 - - Loader { - id: regionIconLoader - active: regionRect.showIcon - visible: active - sourceComponent: IconImage { - implicitSize: Appearance.font.pixelSize.larger - source: Quickshell.iconPath(AppSearch.guessIcon(regionRect.text), "image-missing") - } - } - - StyledText { - id: regionText - text: regionRect.text - color: root.genericContentForeground - } - } - } - } + property var action: RegionSelection.SnipAction.Copy + property var selectionMode: RegionSelection.SelectionMode.RectCorners Variants { model: Quickshell.screens @@ -101,478 +31,24 @@ Scope { required property var modelData active: GlobalStates.regionSelectorOpen - sourceComponent: PanelWindow { - id: panelWindow + sourceComponent: RegionSelection { screen: regionSelectorLoader.modelData - visible: false - WlrLayershell.namespace: "quickshell:regionSelector" - WlrLayershell.layer: WlrLayer.Overlay - WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive - exclusionMode: ExclusionMode.Ignore - anchors { - left: true - right: true - top: true - bottom: true - } - - readonly property HyprlandMonitor hyprlandMonitor: Hyprland.monitorFor(screen) - 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-${screen.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: [] - 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"] - if (!topLayers) return []; - const nonBarTopLayers = topLayers - .filter(layer => !(layer.namespace.includes(":bar") || layer.namespace.includes(":verticalBar") || layer.namespace.includes(":dock"))) - .map(layer => { - return { - at: [layer.x, layer.y], - size: [layer.w, layer.h], - namespace: layer.namespace, - } - }) - 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 - 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 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]; - }); - if (clickedRegion) { - panelWindow.targetedRegionX = clickedRegion.at[0]; - panelWindow.targetedRegionY = clickedRegion.at[1]; - panelWindow.targetedRegionWidth = clickedRegion.size[0]; - panelWindow.targetedRegionHeight = clickedRegion.size[1]; - 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]; - }); - 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) - - Process { - id: screenshotProcess - running: true - command: ["bash", "-c", `mkdir -p '${StringUtils.shellSingleQuoteEscape(root.screenshotDir)}' && grim -o '${StringUtils.shellSingleQuoteEscape(panelWindow.screen.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-venv.sh ` - + `--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 = 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."); - root.dismiss(); - } - snipProc.startDetached(); - root.dismiss(); - } - 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 { - anchors.fill: parent - live: false - captureSource: panelWindow.screen - - focus: panelWindow.visible - Keys.onPressed: (event) => { // Esc to close - if (event.key === Qt.Key_Escape) { - root.dismiss(); - } - } - - 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: darkenOverlay - z: 1 - anchors { - left: parent.left - top: parent.top - leftMargin: panelWindow.regionX - darkenOverlay.border.width - topMargin: panelWindow.regionY - darkenOverlay.border.width - } - width: panelWindow.regionWidth + darkenOverlay.border.width * 2 - height: panelWindow.regionHeight + darkenOverlay.border.width * 2 - color: "transparent" - // border.color: root.selectionBorderColor - border.color: root.overlayColor - border.width: Math.max(panelWindow.width, panelWindow.height) - radius: root.standardRounding - } - Rectangle { - id: selectionBorder - 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 - radius: 0 // TODO: figure out how to make the overlay thing work with rounding - } - StyledText { - z: 2 - anchors { - top: selectionBorder.bottom - right: selectionBorder.right - margins: 8 - } - color: root.selectionBorderColor - text: `${Math.round(panelWindow.regionWidth)} x ${Math.round(panelWindow.regionHeight)}` - } - - // Instructions - Rectangle { - z: 9999 - 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 - - Row { - id: instructionsRow - anchors.centerIn: parent - spacing: 4 - MaterialSymbol { - id: screenshotRegionIcon - // anchors.centerIn: parent - iconSize: Appearance.font.pixelSize.larger - text: "screenshot_region" - color: root.genericContentForeground - } - StyledText { - anchors.verticalCenter: parent.verticalCenter - text: Translation.tr("Drag or click a region • LMB: Copy • RMB: Edit") - color: root.genericContentForeground - } - } - } - - // Window regions - Repeater { - model: ScriptModel { - values: panelWindow.windowRegions - } - delegate: TargetRegion { - z: 2 - required property var modelData - showIcon: true - 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 - } - } - - // Layer regions - 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 - } - } - - // Image regions - Repeater { - model: ScriptModel { - values: Config.options.screenshotTool.showContentRegions ? panelWindow.imageRegions : [] - } - delegate: TargetRegion { - z: 4 - 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" - } - } - } - } + onDismiss: root.dismiss() + action: root.action + selectionMode: root.selectionMode } } } function screenshot() { + root.action = RegionSelection.SnipAction.Copy + root.selectionMode = RegionSelection.SelectionMode.RectCorners + GlobalStates.regionSelectorOpen = true + } + + function search() { + root.action = RegionSelection.SnipAction.Search + root.selectionMode = RegionSelection.SelectionMode.Circle GlobalStates.regionSelectorOpen = true } @@ -582,14 +58,19 @@ Scope { function screenshot() { root.screenshot() } + function search() { + root.search() + } } GlobalShortcut { name: "regionScreenshot" description: "Takes a screenshot of the selected region" - - onPressed: { - root.screenshot() - } + onPressed: root.screenshot() + } + GlobalShortcut { + name: "regionSearch" + description: "Searches the selected region" + onPressed: root.search() } } diff --git a/dots/.config/quickshell/ii/modules/regionSelector/TargetRegion.qml b/dots/.config/quickshell/ii/modules/regionSelector/TargetRegion.qml new file mode 100644 index 000000000..0fc2774fb --- /dev/null +++ b/dots/.config/quickshell/ii/modules/regionSelector/TargetRegion.qml @@ -0,0 +1,69 @@ +pragma ComponentBehavior: Bound +import qs +import qs.modules.common +import qs.modules.common.functions +import qs.modules.common.widgets +import qs.services +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Qt5Compat.GraphicalEffects +import Quickshell +import Quickshell.Io +import Quickshell.Wayland +import Quickshell.Widgets +import Quickshell.Hyprland + +Rectangle { + id: regionRect + property bool showIcon: false + 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: regionInfoRow.implicitWidth + horizontalPadding * 2 + implicitHeight: regionInfoRow.implicitHeight + verticalPadding * 2 + Row { + id: regionInfoRow + anchors.centerIn: parent + spacing: 4 + + Loader { + id: regionIconLoader + active: regionRect.showIcon + visible: active + sourceComponent: IconImage { + implicitSize: Appearance.font.pixelSize.larger + source: Quickshell.iconPath(AppSearch.guessIcon(regionRect.text), "image-missing") + } + } + + StyledText { + id: regionText + text: regionRect.text + color: root.genericContentForeground + } + } + } +} \ No newline at end of file From cc605e24d93a3a23996f975658b5859e229dd40b Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 20 Oct 2025 21:09:27 +0200 Subject: [PATCH 02/53] region selector: fix target regions --- .../ii/modules/regionSelector/RegionSelection.qml | 9 +++++++-- .../ii/modules/regionSelector/TargetRegion.qml | 8 +++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml b/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml index 32549fbc0..7f83c4e33 100644 --- a/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml +++ b/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml @@ -48,7 +48,6 @@ PanelWindow { property color imageBorderColor: "#ddf1d1ff" property color imageFillColor: "#33f1d1ff" property color onBorderColor: "#ff000000" - property real standardRounding: 4 readonly property var windows: [...HyprlandData.windowList].sort((a, b) => { // Sort floating=true windows before others if (a.floating === b.floating) return 0; @@ -357,13 +356,13 @@ PanelWindow { 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 }); - root.updateTargetedRegion(mouse.x, mouse.y); } Loader { @@ -478,6 +477,8 @@ PanelWindow { && root.targetedRegionWidth === modelData.size[0] && root.targetedRegionHeight === modelData.size[1]) + colBackground: root.genericContentColor + colForeground: root.genericContentForeground opacity: root.draggedAway ? 0 : 1 visible: opacity > 0 Behavior on opacity { @@ -510,6 +511,8 @@ PanelWindow { && root.targetedRegionWidth === modelData.size[0] && root.targetedRegionHeight === modelData.size[1]) + colBackground: root.genericContentColor + colForeground: root.genericContentForeground opacity: root.draggedAway ? 0 : 1 visible: opacity > 0 Behavior on opacity { @@ -542,6 +545,8 @@ PanelWindow { && root.targetedRegionWidth === modelData.size[0] && root.targetedRegionHeight === modelData.size[1]) + colBackground: root.genericContentColor + colForeground: root.genericContentForeground opacity: root.draggedAway ? 0 : 1 visible: opacity > 0 Behavior on opacity { diff --git a/dots/.config/quickshell/ii/modules/regionSelector/TargetRegion.qml b/dots/.config/quickshell/ii/modules/regionSelector/TargetRegion.qml index 0fc2774fb..49a755ab0 100644 --- a/dots/.config/quickshell/ii/modules/regionSelector/TargetRegion.qml +++ b/dots/.config/quickshell/ii/modules/regionSelector/TargetRegion.qml @@ -16,6 +16,8 @@ import Quickshell.Hyprland Rectangle { id: regionRect + required property color colBackground + required property color colForeground property bool showIcon: false property bool targeted: false property color borderColor @@ -26,14 +28,14 @@ Rectangle { color: fillColor border.color: borderColor border.width: targeted ? 3 : 1 - radius: root.standardRounding + radius: 4 Rectangle { id: regionLabelBackground property real verticalPadding: 5 property real horizontalPadding: 10 radius: 10 - color: root.genericContentColor + color: regionRect.colBackground border.width: 1 border.color: Appearance.m3colors.m3outlineVariant anchors { @@ -62,7 +64,7 @@ Rectangle { StyledText { id: regionText text: regionRect.text - color: root.genericContentForeground + color: regionRect.colForeground } } } From 075a21a9db24969830fd6e0ff7b3c6d7a3495ebc Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 20 Oct 2025 22:00:08 +0200 Subject: [PATCH 03/53] make google lens tool use normal region selection by default --- dots/.config/hypr/hyprland/keybinds.conf | 2 +- .../quickshell/ii/modules/common/Config.qml | 7 +++++-- .../ii/modules/regionSelector/RegionSelection.qml | 8 ++++---- .../ii/modules/regionSelector/RegionSelector.qml | 6 +++++- .../ii/modules/settings/ServicesConfig.qml | 14 ++++++++++++++ 5 files changed, 29 insertions(+), 8 deletions(-) diff --git a/dots/.config/hypr/hyprland/keybinds.conf b/dots/.config/hypr/hyprland/keybinds.conf index 74343d85b..469555874 100644 --- a/dots/.config/hypr/hyprland/keybinds.conf +++ b/dots/.config/hypr/hyprland/keybinds.conf @@ -60,7 +60,7 @@ bindd = Super, V, Copy clipboard history entry, exec, qs -c $qsConfig ipc call T bindd = Super, Period, Copy an emoji, exec, qs -c $qsConfig ipc call TEST_ALIVE || pkill fuzzel || ~/.config/hypr/hyprland/scripts/fuzzel-emoji.sh copy # [hidden] Emoji >> clipboard (fallback) bind = Super+Shift, S, global, quickshell:regionScreenshot # Screen snip bind = Super+Shift, S, exec, qs -c $qsConfig ipc call TEST_ALIVE || pidof slurp || hyprshot --freeze --clipboard-only --mode region --silent # [hidden] Screen snip (fallback) -bind = Super+Shift, A, global, quickshell:regionSearch # Circle to Search +bind = Super+Shift, A, global, quickshell:regionSearch # Google Lens # 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/dots/.config/quickshell/ii/modules/common/Config.qml b/dots/.config/quickshell/ii/modules/common/Config.qml index f93171584..8f18bd0f6 100644 --- a/dots/.config/quickshell/ii/modules/common/Config.qml +++ b/dots/.config/quickshell/ii/modules/common/Config.qml @@ -355,8 +355,6 @@ Singleton { property JsonObject search: JsonObject { property int nonAppResultDelay: 30 // This prevents lagging when typing property string engineBaseUrl: "https://www.google.com/search?q=" - property string imageSearchEngineBaseUrl: "https://lens.google.com/uploadbyurl?url=" - property string fileUploadApiEndpoint: "https://uguu.se/upload" property list excludedSites: ["quora.com", "facebook.com"] property bool sloppy: false // Uses levenshtein distance based scoring instead of fuzzy sort. Very weird. property JsonObject prefix: JsonObject { @@ -369,6 +367,11 @@ Singleton { property string shellCommand: "$" property string webSearch: "?" } + property JsonObject imageSearch: JsonObject { + property string imageSearchEngineBaseUrl: "https://lens.google.com/uploadbyurl?url=" + property string fileUploadApiEndpoint: "https://uguu.se/upload" + property bool useCircleSelection: false + } } property JsonObject sidebar: JsonObject { diff --git a/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml b/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml index 7f83c4e33..b22ce4a58 100644 --- a/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml +++ b/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml @@ -36,8 +36,8 @@ PanelWindow { signal dismiss() property string screenshotDir: Directories.screenshotTemp - property string imageSearchEngineBaseUrl: Config.options.search.imageSearchEngineBaseUrl - property string fileUploadApiEndpoint: Config.options.search.fileUploadApiEndpoint + property string imageSearchEngineBaseUrl: Config.options.search.imageSearch.imageSearchEngineBaseUrl + property string fileUploadApiEndpoint: Config.options.search.imageSearch.fileUploadApiEndpoint property color overlayColor: "#77111111" property color genericContentColor: Qt.alpha(root.overlayColor, 0.9) property color genericContentForeground: "#ddffffff" @@ -424,13 +424,13 @@ PanelWindow { iconSize: Appearance.font.pixelSize.larger text: switch(root.selectionMode) { case RegionSelection.SelectionMode.RectCorners: - return "crop_free" + return "activity_zone" break; case RegionSelection.SelectionMode.Circle: return "gesture" break; default: - return "crop_free" + return "activity_zone" } color: root.genericContentForeground } diff --git a/dots/.config/quickshell/ii/modules/regionSelector/RegionSelector.qml b/dots/.config/quickshell/ii/modules/regionSelector/RegionSelector.qml index 336c9fd6b..7cfd37349 100644 --- a/dots/.config/quickshell/ii/modules/regionSelector/RegionSelector.qml +++ b/dots/.config/quickshell/ii/modules/regionSelector/RegionSelector.qml @@ -48,7 +48,11 @@ Scope { function search() { root.action = RegionSelection.SnipAction.Search - root.selectionMode = RegionSelection.SelectionMode.Circle + if (Config.options.search.imageSearch.useCircleSelection) { + root.selectionMode = RegionSelection.SelectionMode.Circle + } else { + root.selectionMode = RegionSelection.SelectionMode.RectCorners + } GlobalStates.regionSelectorOpen = true } diff --git a/dots/.config/quickshell/ii/modules/settings/ServicesConfig.qml b/dots/.config/quickshell/ii/modules/settings/ServicesConfig.qml index fae5bf4de..8b6c1ff2a 100644 --- a/dots/.config/quickshell/ii/modules/settings/ServicesConfig.qml +++ b/dots/.config/quickshell/ii/modules/settings/ServicesConfig.qml @@ -147,5 +147,19 @@ ContentPage { } } } + ContentSubsection { + title: Translation.tr("Google Lens") + + ConfigSelectionArray { + currentValue: Config.options.search.imageSearch.useCircleSelection ? "circle" : "rectangles" + onSelected: newValue => { + Config.options.search.imageSearch.useCircleSelection = (newValue === "circle"); + } + options: [ + { icon: "activity_zone", value: "rectangles", displayName: Translation.tr("Rectangular selection") }, + { icon: "gesture", value: "circle", displayName: Translation.tr("Circle to Search") } + ] + } + } } } From 54fe878580b4696a733d933b8fc0eac02696d978 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 20 Oct 2025 22:25:23 +0200 Subject: [PATCH 04/53] right sidebar: containerize top row elements --- .../sidebarRight/SidebarRightContent.qml | 69 +++++++++++++------ 1 file changed, 47 insertions(+), 22 deletions(-) diff --git a/dots/.config/quickshell/ii/modules/sidebarRight/SidebarRightContent.qml b/dots/.config/quickshell/ii/modules/sidebarRight/SidebarRightContent.qml index 1eee4d335..239377142 100644 --- a/dots/.config/quickshell/ii/modules/sidebarRight/SidebarRightContent.qml +++ b/dots/.config/quickshell/ii/modules/sidebarRight/SidebarRightContent.qml @@ -18,7 +18,7 @@ import "./volumeMixer/" Item { id: root property int sidebarWidth: Appearance.sizes.sidebarWidth - property int sidebarPadding: 12 + property int sidebarPadding: 10 property string settingsQmlPath: Quickshell.shellPath("settings.qml") property bool showWifiDialog: false property bool showBluetoothDialog: false @@ -62,7 +62,8 @@ Item { SystemButtonRow { Layout.fillHeight: false - Layout.margins: 10 + Layout.fillWidth: true + // Layout.margins: 10 Layout.topMargin: 5 Layout.bottomMargin: 0 } @@ -200,30 +201,54 @@ Item { } } - component SystemButtonRow: RowLayout { - spacing: 10 + component SystemButtonRow: Item { + implicitHeight: Math.max(uptimeContainer.implicitHeight, systemButtonsRow.implicitHeight) - CustomIcon { - id: distroIcon - width: 25 - height: 25 - source: SystemInfo.distroIcon - colorize: true - color: Appearance.colors.colOnLayer0 - } - - StyledText { - font.pixelSize: Appearance.font.pixelSize.normal - color: Appearance.colors.colOnLayer0 - text: Translation.tr("Up %1").arg(DateTime.uptime) - textFormat: Text.MarkdownText - } - - Item { - Layout.fillWidth: true + Rectangle { + id: uptimeContainer + anchors { + top: parent.top + bottom: parent.bottom + left: parent.left + } + color: Appearance.colors.colLayer1 + radius: height / 2 + implicitWidth: uptimeRow.implicitWidth + 24 + implicitHeight: uptimeRow.implicitHeight + 8 + + Row { + id: uptimeRow + anchors.centerIn: parent + spacing: 8 + CustomIcon { + id: distroIcon + anchors.verticalCenter: parent.verticalCenter + width: 25 + height: 25 + source: SystemInfo.distroIcon + colorize: true + color: Appearance.colors.colOnLayer0 + } + StyledText { + anchors.verticalCenter: parent.verticalCenter + font.pixelSize: Appearance.font.pixelSize.normal + color: Appearance.colors.colOnLayer0 + text: Translation.tr("Up %1").arg(DateTime.uptime) + textFormat: Text.MarkdownText + } + } } ButtonGroup { + id: systemButtonsRow + anchors { + top: parent.top + bottom: parent.bottom + right: parent.right + } + color: Appearance.colors.colLayer1 + padding: 4 + QuickToggleButton { toggled: root.editMode visible: Config.options.sidebar.quickToggles.style === "android" From d5b1e9f40c14c632264b1406eab0d3ceff73e6b2 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 20 Oct 2025 22:56:33 +0200 Subject: [PATCH 05/53] sidebar: quick toggles: smoother size change --- .../ii/modules/common/widgets/GroupButton.qml | 4 ++++ .../quickToggles/AndroidQuickPanel.qml | 17 ++++++++++------- .../androidStyle/AndroidQuickToggleButton.qml | 15 +++++++++++++++ 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/dots/.config/quickshell/ii/modules/common/widgets/GroupButton.qml b/dots/.config/quickshell/ii/modules/common/widgets/GroupButton.qml index bc02e77e1..6ebe3341f 100644 --- a/dots/.config/quickshell/ii/modules/common/widgets/GroupButton.qml +++ b/dots/.config/quickshell/ii/modules/common/widgets/GroupButton.qml @@ -22,6 +22,8 @@ Button { property bool bounce: true property real baseWidth: contentItem.implicitWidth + horizontalPadding * 2 property real baseHeight: contentItem.implicitHeight + verticalPadding * 2 + property bool enableImplicitWidthAnimation: true + property bool enableImplicitHeightAnimation: true property real clickedWidth: baseWidth + (isAtSide ? 10 : 20) property real clickedHeight: baseHeight property var parentGroup: root.parent @@ -61,10 +63,12 @@ Button { } Behavior on implicitWidth { + enabled: root.enableImplicitWidthAnimation animation: Appearance.animation.clickBounce.numberAnimation.createObject(this) } Behavior on implicitHeight { + enabled: root.enableImplicitHeightAnimation animation: Appearance.animation.clickBounce.numberAnimation.createObject(this) } diff --git a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/AndroidQuickPanel.qml b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/AndroidQuickPanel.qml index 5aa95ad35..eb7793fba 100644 --- a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/AndroidQuickPanel.qml +++ b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/AndroidQuickPanel.qml @@ -73,14 +73,14 @@ AbstractQuickPanel { Repeater { id: usedRowsRepeater model: ScriptModel { - values: root.toggleRows + values: Array(root.toggleRows.length) } delegate: ButtonGroup { id: toggleRow - required property var modelData required property int index + property var modelData: root.toggleRows[index] property int startingIndex: { - const rows = usedRowsRepeater.model.values; + const rows = root.toggleRows; let sum = 0; for (let i = 0; i < index; i++) { sum += rows[i].length; @@ -91,7 +91,8 @@ AbstractQuickPanel { Repeater { model: ScriptModel { - values: toggleRow.modelData + values: toggleRow?.modelData ?? [] + objectProp: "type" } delegate: AndroidToggleDelegateChooser { startingIndex: toggleRow.startingIndex @@ -131,16 +132,18 @@ AbstractQuickPanel { Repeater { model: ScriptModel { - values: root.unusedToggleRows + values: Array(root.unusedToggleRows.length) } delegate: ButtonGroup { id: unusedToggleRow - required property var modelData + required property int index + property var modelData: root.unusedToggleRows[index] spacing: root.spacing Repeater { model: ScriptModel { - values: unusedToggleRow.modelData + values: unusedToggleRow?.modelData ?? [] + objectProp: "type" } delegate: AndroidToggleDelegateChooser { startingIndex: -1 diff --git a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidQuickToggleButton.qml b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidQuickToggleButton.qml index ee9d58f45..e7f6929e1 100644 --- a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidQuickToggleButton.qml +++ b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidQuickToggleButton.qml @@ -23,6 +23,21 @@ GroupButton { baseHeight: root.baseCellHeight property bool editMode: false + enableImplicitWidthAnimation: !editMode + enableImplicitHeightAnimation: !editMode + Behavior on baseWidth { + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) + } + Behavior on baseHeight { + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) + } + opacity: 0 + Component.onCompleted: { + opacity = 1 + } + Behavior on opacity { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } signal openMenu() From fe3e5de51845e2c0c04ec5fdbbcc4eaaa1861d7f Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 20 Oct 2025 23:12:58 +0200 Subject: [PATCH 06/53] sidebar: quick toggles: larger icon size when 1 cell wide --- .../quickToggles/androidStyle/AndroidQuickToggleButton.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidQuickToggleButton.qml b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidQuickToggleButton.qml index e7f6929e1..18848c21d 100644 --- a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidQuickToggleButton.qml +++ b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidQuickToggleButton.qml @@ -80,7 +80,7 @@ GroupButton { MaterialSymbol { anchors.centerIn: parent fill: root.toggled ? 1 : 0 - iconSize: Appearance.font.pixelSize.huge + iconSize: root.expandedSize ? 22 : 24 color: root.colIcon text: root.buttonIcon } From 854edba825df256d9fd4c9f7e621dfc67fa97665 Mon Sep 17 00:00:00 2001 From: jwihardi Date: Mon, 20 Oct 2025 18:42:59 -0400 Subject: [PATCH 07/53] added live quickshell ebuild with specific commit --- .../quickshell-9999.ebuild | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 sdist/gentoo/illogical-impulse-quickshell-git/quickshell-9999.ebuild diff --git a/sdist/gentoo/illogical-impulse-quickshell-git/quickshell-9999.ebuild b/sdist/gentoo/illogical-impulse-quickshell-git/quickshell-9999.ebuild new file mode 100644 index 000000000..c4b653bb3 --- /dev/null +++ b/sdist/gentoo/illogical-impulse-quickshell-git/quickshell-9999.ebuild @@ -0,0 +1,91 @@ +# Copyright 1999-2025 Gentoo Authors +# Distributed under the terms of the GNU General Public License v2 + +EAPI=8 + +inherit cmake + +DESCRIPTION="Toolkit for building desktop widgets using QtQuick" +HOMEPAGE="https://quickshell.org/" + +if [[ "${PV}" = *9999 ]]; then + inherit git-r3 + EGIT_REPO_URI="https://github.com/quickshell-mirror/${PN^}.git" + EGIT_COMMIT="00858812f25b748d08b075a0d284093685fa3ffd" +else + SRC_URI="https://github.com/quickshell-mirror/${PN}/archive/refs/tags/v${PV}.tar.gz -> ${P}.tar.gz" + KEYWORDS="~amd64" +fi + +LICENSE="LGPL-3" +SLOT="0" +# Upstream recommends leaving all build options enabled by default +IUSE="+breakpad +jemalloc +sockets +wayland +layer-shell +session-lock +toplevel-management +screencopy +X +pipewire +tray +mpris +pam +hyprland +hyprland-global-shortcuts +hyprland-focus-grab +i3 +i3-ipc +bluetooth" + +RDEPEND=" + dev-qt/qtbase:6 + dev-qt/qtsvg:6 + jemalloc? ( dev-libs/jemalloc ) + wayland? ( + dev-libs/wayland + dev-qt/qtwayland:6 + ) + screencopy? ( + x11-libs/libdrm + media-libs/mesa + ) + X? ( x11-libs/libxcb ) + pipewire? ( media-video/pipewire ) + mpris? ( dev-qt/qtdbus ) + pam? ( sys-libs/pam ) + bluetooth? ( net-wireless/bluez ) + + + +" +DEPEND="${RDEPEND}" +BDEPEND=" + || ( >=sys-devel/gcc-14:* >=llvm-core/clang-17:* ) + + dev-util/spirv-tools + dev-qt/qtshadertools:6 + wayland? ( + dev-util/wayland-scanner + dev-libs/wayland-protocols + ) + dev-cpp/cli11 + dev-build/ninja + dev-build/cmake + dev-vcs/git + virtual/pkgconfig + breakpad? ( dev-util/breakpad ) + +" + +src_configure(){ + mycmakeargs=( + -DCMAKE_BUILD_TYPE=RelWithDebInfo + -DDISTRIBUTOR="Gentoo GURU" + -DINSTALL_QML_PREFIX="lib64/qt6/qml" + -DCRASH_REPORTER=$(usex breakpad ON OFF) + -DUSE_JEMALLOC=$(usex jemalloc ON OFF) + -DSOCKETS=$(usex sockets ON OFF) + -DWAYLAND=$(usex wayland ON OFF) + -DWAYLAND_WLR_LAYERSHELL=$(usex layer-shell ON OFF) + -DWAYLAND_SESSION_LOCK=$(usex session-lock ON OFF) + -DWAYLAND_TOPLEVEL_MANAGEMENT=$(usex toplevel-management ON OFF) + -DSCREENCOPY=$(usex screencopy ON OFF) + -DX11=$(usex X ON OFF) + -DSERVICE_PIPEWIRE=$(usex pipewire ON OFF) + -DSERVICE_STATUS_NOTIFIER=$(usex tray ON OFF) + -DSERVICE_MPRIS=$(usex mpris ON OFF) + -DSERVICE_PAM=$(usex pam ON OFF) + -DHYPRLAND=$(usex hyprland ON OFF) + -DHYPRLAND_GLOBAL_SHORTCUTS=$(usex hyprland-global-shortcuts) + -DHYPRLAND_FOCUS_GRAB=$(usex hyprland-focus-grab) + -DI3=$(usex i3 ON OFF) + -DI3_IPC=$(usex i3-ipc ON OFF) + -DBLUETOOTH=$(usex bluetooth ON OFF) + ) + cmake_src_configure +} From bf96099e4696f8b3aa3764a071e55abfed62a439 Mon Sep 17 00:00:00 2001 From: jwihardi Date: Mon, 20 Oct 2025 19:14:38 -0400 Subject: [PATCH 08/53] fixed package name --- ...logical-impulse-quickshell-git-9999.ebuild | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 sdist/gentoo/illogical-impulse-quickshell-git/illogical-impulse-quickshell-git-9999.ebuild diff --git a/sdist/gentoo/illogical-impulse-quickshell-git/illogical-impulse-quickshell-git-9999.ebuild b/sdist/gentoo/illogical-impulse-quickshell-git/illogical-impulse-quickshell-git-9999.ebuild new file mode 100644 index 000000000..d5106355a --- /dev/null +++ b/sdist/gentoo/illogical-impulse-quickshell-git/illogical-impulse-quickshell-git-9999.ebuild @@ -0,0 +1,86 @@ +# Copyright 1999-2025 Gentoo Authors +# Distributed under the terms of the GNU General Public License v2 + +EAPI=8 + +inherit cmake git-r3 + +DESCRIPTION="Toolkit for building desktop widgets using QtQuick" +HOMEPAGE="https://quickshell.org/" + +EGIT_REPO_URI="https://github.com/quickshell-mirror/quickshell.git" +EGIT_COMMIT="00858812f25b748d08b075a0d284093685fa3ffd" + +KEYWORDS="~amd64 ~arm64 ~x86" +LICENSE="LGPL-3" +SLOT="0" +# Upstream recommends leaving all build options enabled by default +IUSE="+breakpad +jemalloc +sockets +wayland +layer-shell +session-lock +toplevel-management +screencopy +X +pipewire +tray +mpris +pam +hyprland +hyprland-global-shortcuts +hyprland-focus-grab +i3 +i3-ipc +bluetooth" + +RDEPEND=" + dev-qt/qtbase:6 + dev-qt/qtsvg:6 + jemalloc? ( dev-libs/jemalloc ) + wayland? ( + dev-libs/wayland + dev-qt/qtwayland:6 + ) + screencopy? ( + x11-libs/libdrm + media-libs/mesa + ) + X? ( x11-libs/libxcb ) + pipewire? ( media-video/pipewire ) + mpris? ( dev-qt/qtdbus ) + pam? ( sys-libs/pam ) + bluetooth? ( net-wireless/bluez ) + + + +" +DEPEND="${RDEPEND}" +BDEPEND=" + || ( >=sys-devel/gcc-14:* >=llvm-core/clang-17:* ) + + dev-util/spirv-tools + dev-qt/qtshadertools:6 + wayland? ( + dev-util/wayland-scanner + dev-libs/wayland-protocols + ) + dev-cpp/cli11 + dev-build/ninja + dev-build/cmake + dev-vcs/git + virtual/pkgconfig + breakpad? ( dev-util/breakpad ) + +" + +src_configure(){ + mycmakeargs=( + -DCMAKE_BUILD_TYPE=RelWithDebInfo + -DDISTRIBUTOR="Gentoo GURU" + -DINSTALL_QML_PREFIX="lib64/qt6/qml" + -DCRASH_REPORTER=$(usex breakpad ON OFF) + -DUSE_JEMALLOC=$(usex jemalloc ON OFF) + -DSOCKETS=$(usex sockets ON OFF) + -DWAYLAND=$(usex wayland ON OFF) + -DWAYLAND_WLR_LAYERSHELL=$(usex layer-shell ON OFF) + -DWAYLAND_SESSION_LOCK=$(usex session-lock ON OFF) + -DWAYLAND_TOPLEVEL_MANAGEMENT=$(usex toplevel-management ON OFF) + -DSCREENCOPY=$(usex screencopy ON OFF) + -DX11=$(usex X ON OFF) + -DSERVICE_PIPEWIRE=$(usex pipewire ON OFF) + -DSERVICE_STATUS_NOTIFIER=$(usex tray ON OFF) + -DSERVICE_MPRIS=$(usex mpris ON OFF) + -DSERVICE_PAM=$(usex pam ON OFF) + -DHYPRLAND=$(usex hyprland ON OFF) + -DHYPRLAND_GLOBAL_SHORTCUTS=$(usex hyprland-global-shortcuts) + -DHYPRLAND_FOCUS_GRAB=$(usex hyprland-focus-grab) + -DI3=$(usex i3 ON OFF) + -DI3_IPC=$(usex i3-ipc ON OFF) + -DBLUETOOTH=$(usex bluetooth ON OFF) + ) + cmake_src_configure +} From 4de08c438b08db6846376d6c436f623407930223 Mon Sep 17 00:00:00 2001 From: jwihardi <84292598+jwihardi@users.noreply.github.com> Date: Mon, 20 Oct 2025 19:16:27 -0400 Subject: [PATCH 09/53] Delete sdist/gentoo/illogical-impulse-quickshell-git/quickshell-9999.ebuild --- .../quickshell-9999.ebuild | 91 ------------------- 1 file changed, 91 deletions(-) delete mode 100644 sdist/gentoo/illogical-impulse-quickshell-git/quickshell-9999.ebuild diff --git a/sdist/gentoo/illogical-impulse-quickshell-git/quickshell-9999.ebuild b/sdist/gentoo/illogical-impulse-quickshell-git/quickshell-9999.ebuild deleted file mode 100644 index c4b653bb3..000000000 --- a/sdist/gentoo/illogical-impulse-quickshell-git/quickshell-9999.ebuild +++ /dev/null @@ -1,91 +0,0 @@ -# Copyright 1999-2025 Gentoo Authors -# Distributed under the terms of the GNU General Public License v2 - -EAPI=8 - -inherit cmake - -DESCRIPTION="Toolkit for building desktop widgets using QtQuick" -HOMEPAGE="https://quickshell.org/" - -if [[ "${PV}" = *9999 ]]; then - inherit git-r3 - EGIT_REPO_URI="https://github.com/quickshell-mirror/${PN^}.git" - EGIT_COMMIT="00858812f25b748d08b075a0d284093685fa3ffd" -else - SRC_URI="https://github.com/quickshell-mirror/${PN}/archive/refs/tags/v${PV}.tar.gz -> ${P}.tar.gz" - KEYWORDS="~amd64" -fi - -LICENSE="LGPL-3" -SLOT="0" -# Upstream recommends leaving all build options enabled by default -IUSE="+breakpad +jemalloc +sockets +wayland +layer-shell +session-lock +toplevel-management +screencopy +X +pipewire +tray +mpris +pam +hyprland +hyprland-global-shortcuts +hyprland-focus-grab +i3 +i3-ipc +bluetooth" - -RDEPEND=" - dev-qt/qtbase:6 - dev-qt/qtsvg:6 - jemalloc? ( dev-libs/jemalloc ) - wayland? ( - dev-libs/wayland - dev-qt/qtwayland:6 - ) - screencopy? ( - x11-libs/libdrm - media-libs/mesa - ) - X? ( x11-libs/libxcb ) - pipewire? ( media-video/pipewire ) - mpris? ( dev-qt/qtdbus ) - pam? ( sys-libs/pam ) - bluetooth? ( net-wireless/bluez ) - - - -" -DEPEND="${RDEPEND}" -BDEPEND=" - || ( >=sys-devel/gcc-14:* >=llvm-core/clang-17:* ) - - dev-util/spirv-tools - dev-qt/qtshadertools:6 - wayland? ( - dev-util/wayland-scanner - dev-libs/wayland-protocols - ) - dev-cpp/cli11 - dev-build/ninja - dev-build/cmake - dev-vcs/git - virtual/pkgconfig - breakpad? ( dev-util/breakpad ) - -" - -src_configure(){ - mycmakeargs=( - -DCMAKE_BUILD_TYPE=RelWithDebInfo - -DDISTRIBUTOR="Gentoo GURU" - -DINSTALL_QML_PREFIX="lib64/qt6/qml" - -DCRASH_REPORTER=$(usex breakpad ON OFF) - -DUSE_JEMALLOC=$(usex jemalloc ON OFF) - -DSOCKETS=$(usex sockets ON OFF) - -DWAYLAND=$(usex wayland ON OFF) - -DWAYLAND_WLR_LAYERSHELL=$(usex layer-shell ON OFF) - -DWAYLAND_SESSION_LOCK=$(usex session-lock ON OFF) - -DWAYLAND_TOPLEVEL_MANAGEMENT=$(usex toplevel-management ON OFF) - -DSCREENCOPY=$(usex screencopy ON OFF) - -DX11=$(usex X ON OFF) - -DSERVICE_PIPEWIRE=$(usex pipewire ON OFF) - -DSERVICE_STATUS_NOTIFIER=$(usex tray ON OFF) - -DSERVICE_MPRIS=$(usex mpris ON OFF) - -DSERVICE_PAM=$(usex pam ON OFF) - -DHYPRLAND=$(usex hyprland ON OFF) - -DHYPRLAND_GLOBAL_SHORTCUTS=$(usex hyprland-global-shortcuts) - -DHYPRLAND_FOCUS_GRAB=$(usex hyprland-focus-grab) - -DI3=$(usex i3 ON OFF) - -DI3_IPC=$(usex i3-ipc ON OFF) - -DBLUETOOTH=$(usex bluetooth ON OFF) - ) - cmake_src_configure -} From 9242b93558ae45d08d87b9cd5d42ef1a5b562faf Mon Sep 17 00:00:00 2001 From: jwihardi Date: Mon, 20 Oct 2025 20:35:43 -0400 Subject: [PATCH 10/53] quickshell, widgets, install, useflag (breakpad), keywords - updated --- ...cal-impulse-quickshell-git-9999-r1.ebuild} | 0 ...> illogical-impulse-widgets-1.0-r2.ebuild} | 7 +- .../quickshell-9999.ebuild | 84 ------------------- sdist/gentoo/install-deps.sh | 2 +- sdist/gentoo/keywords | 4 + sdist/gentoo/useflags | 2 +- 6 files changed, 11 insertions(+), 88 deletions(-) rename sdist/gentoo/illogical-impulse-quickshell-git/{illogical-impulse-quickshell-git-9999.ebuild => illogical-impulse-quickshell-git-9999-r1.ebuild} (100%) rename sdist/gentoo/illogical-impulse-widgets/{illogical-impulse-widgets-1.0-r1.ebuild => illogical-impulse-widgets-1.0-r2.ebuild} (82%) delete mode 100644 sdist/gentoo/illogical-impulse-widgets/quickshell-9999.ebuild diff --git a/sdist/gentoo/illogical-impulse-quickshell-git/illogical-impulse-quickshell-git-9999.ebuild b/sdist/gentoo/illogical-impulse-quickshell-git/illogical-impulse-quickshell-git-9999-r1.ebuild similarity index 100% rename from sdist/gentoo/illogical-impulse-quickshell-git/illogical-impulse-quickshell-git-9999.ebuild rename to sdist/gentoo/illogical-impulse-quickshell-git/illogical-impulse-quickshell-git-9999-r1.ebuild diff --git a/sdist/gentoo/illogical-impulse-widgets/illogical-impulse-widgets-1.0-r1.ebuild b/sdist/gentoo/illogical-impulse-widgets/illogical-impulse-widgets-1.0-r2.ebuild similarity index 82% rename from sdist/gentoo/illogical-impulse-widgets/illogical-impulse-widgets-1.0-r1.ebuild rename to sdist/gentoo/illogical-impulse-widgets/illogical-impulse-widgets-1.0-r2.ebuild index e2b878a74..fe6109223 100644 --- a/sdist/gentoo/illogical-impulse-widgets/illogical-impulse-widgets-1.0-r1.ebuild +++ b/sdist/gentoo/illogical-impulse-widgets/illogical-impulse-widgets-1.0-r2.ebuild @@ -15,8 +15,11 @@ DEPEND="" RDEPEND=" gui-apps/fuzzel dev-libs/glib - gui-apps/quickshell + media-gfx/imagemagick + gui-apps/hypridle + gui-libs/hyprutils + gui-apps/hyprlock + gui-apps/hyprpicker app-i18n/translate-shell gui-apps/wlogout - media-gfx/imagemagick " diff --git a/sdist/gentoo/illogical-impulse-widgets/quickshell-9999.ebuild b/sdist/gentoo/illogical-impulse-widgets/quickshell-9999.ebuild deleted file mode 100644 index 89a092516..000000000 --- a/sdist/gentoo/illogical-impulse-widgets/quickshell-9999.ebuild +++ /dev/null @@ -1,84 +0,0 @@ -# Copyright 1999-2025 Gentoo Authors -# Distributed under the terms of the GNU General Public License v2 - -EAPI=8 - -inherit cmake - -DESCRIPTION="Toolkit for building desktop widgets using QtQuick" -HOMEPAGE="https://quickshell.org/" - -if [[ "${PV}" = *9999 ]]; then - inherit git-r3 - EGIT_REPO_URI="https://github.com/quickshell-mirror/${PN^}.git" -else - SRC_URI="https://github.com/quickshell-mirror/${PN}/archive/refs/tags/v${PV}.tar.gz -> ${P}.tar.gz" -fi - -LICENSE="LGPL-3" -SLOT="0" -KEYWORDS="~amd64 ~arm64 ~x86" -# Upstream recommends leaving all build options enabled by default -IUSE="+breakpad +jemalloc +sockets +wayland +layer-shell +session-lock +toplevel-management +screencopy +X +pipewire +tray +mpris +pam +hyprland +hyprland-global-shortcuts +hyprland-focus-grab +i3 +i3-ipc +bluetooth" - -RDEPEND=" - dev-qt/qtbase:6 - dev-qt/qtsvg:6 - jemalloc? ( dev-libs/jemalloc ) - wayland? ( - dev-libs/wayland - dev-qt/qtwayland:6 - ) - screencopy? ( - x11-libs/libdrm - media-libs/mesa - ) - X? ( x11-libs/libxcb ) - pipewire? ( media-video/pipewire ) - mpris? ( dev-qt/qtdbus ) - pam? ( sys-libs/pam ) - bluetooth? ( net-wireless/bluez ) -" -DEPEND="${RDEPEND}" -BDEPEND=" - || ( >=sys-devel/gcc-14:* >=llvm-core/clang-17:* ) - dev-build/cmake - dev-build/ninja - virtual/pkgconfig - dev-cpp/cli11 - dev-util/spirv-tools - dev-qt/qtshadertools:6 - breakpad? ( dev-util/breakpad ) - wayland? ( - dev-util/wayland-scanner - dev-libs/wayland-protocols - ) -" - -src_configure(){ - mycmakeargs=( - -DCMAKE_BUILD_TYPE=RelWithDebInfo - -DDISTRIBUTOR="Gentoo GURU" - -DINSTALL_QML_PREFIX="lib64/qt6/qml" - -DCRASH_REPORTER=$(usex breakpad ON OFF) - -DUSE_JEMALLOC=$(usex jemalloc ON OFF) - -DSOCKETS=$(usex sockets ON OFF) - -DWAYLAND=$(usex wayland ON OFF) - -DWAYLAND_WLR_LAYERSHELL=$(usex layer-shell ON OFF) - -DWAYLAND_SESSION_LOCK=$(usex session-lock ON OFF) - -DWAYLAND_TOPLEVEL_MANAGEMENT=$(usex toplevel-management ON OFF) - -DSCREENCOPY=$(usex screencopy ON OFF) - -DX11=$(usex X ON OFF) - -DSERVICE_PIPEWIRE=$(usex pipewire ON OFF) - -DSERVICE_STATUS_NOTIFIER=$(usex tray ON OFF) - -DSERVICE_MPRIS=$(usex mpris ON OFF) - -DSERVICE_PAM=$(usex pam ON OFF) - -DHYPRLAND=$(usex hyprland ON OFF) - -DHYPRLAND_GLOBAL_SHORTCUTS=$(usex hyprland-global-shortcuts) - -DHYPRLAND_FOCUS_GRAB=$(usex hyprland-focus-grab) - -DI3=$(usex i3 ON OFF) - -DI3_IPC=$(usex i3-ipc ON OFF) - -DBLUETOOTH=$(usex bluetooth ON OFF) - ) - cmake_src_configure -} diff --git a/sdist/gentoo/install-deps.sh b/sdist/gentoo/install-deps.sh index 949bbf8eb..7be8f30a9 100644 --- a/sdist/gentoo/install-deps.sh +++ b/sdist/gentoo/install-deps.sh @@ -37,7 +37,7 @@ fi arch=$(portageq envvar ACCEPT_KEYWORDS) # Exclude hyprland, will deal with that separately -metapkgs=(illogical-impulse-{audio,backlight,basic,bibata-modern-classic-bin,fonts-themes,hyprland,kde,microtex-git,oneui4-icons-git,portal,python,screencapture,toolkit,widgets}) +metapkgs=(illogical-impulse-{audio,backlight,basic,bibata-modern-classic-bin,fonts-themes,hyprland,kde,microtex-git,oneui4-icons-git,portal,python,quickshell-git,screencapture,toolkit,widgets}) ebuild_dir="/var/db/repos/localrepo" diff --git a/sdist/gentoo/keywords b/sdist/gentoo/keywords index 9716ace7a..e6043f0f7 100644 --- a/sdist/gentoo/keywords +++ b/sdist/gentoo/keywords @@ -9,6 +9,7 @@ app-misc/illogical-impulse-microtex-git app-misc/illogical-impulse-oneui4-icons-git app-misc/illogical-impulse-portal app-misc/illogical-impulse-python +gui-misc/illogical-impulse-quickshell-git app-misc/illogical-impulse-screencapture app-misc/illogical-impulse-toolkit app-misc/illogical-impulse-widgets @@ -39,3 +40,6 @@ gui-libs/hyprland-qt-support ** gui-libs/hyprland-qtutils ** gui-wm/hyprland ** x11-libs/libxkbcommon +dev-util/breakpad +dev-libs/linux-syscall-support +dev-embedded/libdisasm diff --git a/sdist/gentoo/useflags b/sdist/gentoo/useflags index c3e12ed8e..e7c9755fc 100644 --- a/sdist/gentoo/useflags +++ b/sdist/gentoo/useflags @@ -111,7 +111,7 @@ sys-power/upower introspection gui-apps/fuzzel png svg dev-libs/glib dbus elf introspection mime xattr # ngl idk about nm-connection-editor. Works fine without -gui-apps/quickshell -X -i3 -i3-ipc -breakpad bluetooth hyprland hyprland-focus-grab hyprland-global-shortcuts jemalloc layer-shell mpris pam pipewire screencopy session-lock sockets toplevel-management tray wayland +gui-apps/quickshell -X -i3 -i3-ipc breakpad bluetooth hyprland hyprland-focus-grab hyprland-global-shortcuts jemalloc layer-shell mpris pam pipewire screencopy session-lock sockets toplevel-management tray wayland #app-i18n/translate-shell (nothing needed) #gui-apps/wlogout (no use flags) media-gfx/imagemagick xml From 3f030805d137d7fbdab08d981b0c94d206b497d9 Mon Sep 17 00:00:00 2001 From: jwihardi <84292598+jwihardi@users.noreply.github.com> Date: Mon, 20 Oct 2025 21:15:35 -0400 Subject: [PATCH 11/53] gui-misc -> app-misc --- sdist/gentoo/keywords | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdist/gentoo/keywords b/sdist/gentoo/keywords index e6043f0f7..a10d1111e 100644 --- a/sdist/gentoo/keywords +++ b/sdist/gentoo/keywords @@ -9,7 +9,7 @@ app-misc/illogical-impulse-microtex-git app-misc/illogical-impulse-oneui4-icons-git app-misc/illogical-impulse-portal app-misc/illogical-impulse-python -gui-misc/illogical-impulse-quickshell-git +app-misc/illogical-impulse-quickshell-git app-misc/illogical-impulse-screencapture app-misc/illogical-impulse-toolkit app-misc/illogical-impulse-widgets From 3cd323cb1a9a7a96f9881aad85f267225a6f0d7a Mon Sep 17 00:00:00 2001 From: clsty Date: Tue, 21 Oct 2025 09:42:29 +0800 Subject: [PATCH 12/53] Add --skip-quickshell for install-files.sh --- sdata/options/install.sh | 6 ++++-- sdata/step/3.install-files.sh | 11 +++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/sdata/options/install.sh b/sdata/options/install.sh index 0093e106b..fa3f2b4e3 100644 --- a/sdata/options/install.sh +++ b/sdata/options/install.sh @@ -14,11 +14,12 @@ Options for install: --skip-allsetups Skip the whole process setting up permissions/services etc --skip-allfiles Skip the whole process copying configuration files -s, --skip-sysupdate Skip system package upgrade e.g. \"sudo pacman -Syu\" + --skip-quickshell Skip installing the config for Quickshell --skip-hyprland Skip installing the config for Hyprland --skip-fish Skip installing the config for Fish --skip-plasmaintg Skip installing plasma-browser-integration --skip-miscconf Skip copying the dirs and files to \".configs\" except for - AGS, Fish and Hyprland + Quickshell, Fish and Hyprland --exp-files Use experimental script for the third step copying files --fontset (Unavailable yet) Use a set of pre-defined font and config --via-nix (Unavailable yet) Use Nix to install dependencies @@ -32,7 +33,7 @@ cleancache(){ # `man getopt` to see more para=$(getopt \ -o hfk:cs \ - -l help,force,fontset:,clean,skip-allgreeting,skip-alldeps,skip-allsetups,skip-allfiles,skip-sysupdate,skip-fish,skip-hyprland,skip-plasmaintg,skip-miscconf,exp-files,via-nix \ + -l help,force,fontset:,clean,skip-allgreeting,skip-alldeps,skip-allsetups,skip-allfiles,skip-sysupdate,skip-quickshell,skip-fish,skip-hyprland,skip-plasmaintg,skip-miscconf,exp-files,via-nix \ -n "$0" -- "$@") [ $? != 0 ] && echo "$0: Error when getopt, please recheck parameters." && exit 1 ##################################################################################### @@ -64,6 +65,7 @@ while true ; do -s|--skip-sysupdate) SKIP_SYSUPDATE=true;shift;; --skip-hyprland) SKIP_HYPRLAND=true;shift;; --skip-fish) SKIP_FISH=true;shift;; + --skip-quickshell) SKIP_QUICKSHELL=true;shift;; --skip-miscconf) SKIP_MISCCONF=true;shift;; --skip-plasmaintg) SKIP_PLASMAINTG=true;shift;; --exp-files) EXPERIMENTAL_FILES_SCRIPT=true;shift;; diff --git a/sdata/step/3.install-files.sh b/sdata/step/3.install-files.sh index 50b4b5692..a0d389570 100644 --- a/sdata/step/3.install-files.sh +++ b/sdata/step/3.install-files.sh @@ -84,11 +84,11 @@ esac # original dotfiles and new ones in the SAME DIRECTORY # (eg. in ~/.config/hypr) won't be mixed together -# MISC (For dots/.config/* but not fish, not Hyprland) +# MISC (For dots/.config/* but not quickshell, not fish, not Hyprland) case $SKIP_MISCCONF in true) sleep 0;; *) - for i in $(find dots/.config/ -mindepth 1 -maxdepth 1 ! -name 'fish' ! -name 'hypr' -exec basename {} \;); do + for i in $(find dots/.config/ -mindepth 1 -maxdepth 1 ! -name 'quickshell' ! -name 'fish' ! -name 'hypr' -exec basename {} \;); do # i="dots/.config/$i" echo "[$0]: Found target: dots/.config/$i" if [ -d "dots/.config/$i" ];then warning_rsync; v rsync -av --delete "dots/.config/$i/" "$XDG_CONFIG_HOME/$i/" @@ -98,6 +98,13 @@ case $SKIP_MISCCONF in ;; esac +case $SKIP_QUICKSHELL in + true) sleep 0;; + *) + warning_rsync; v rsync -av --delete dots/.config/quickshell/ "$XDG_CONFIG_HOME"/quickshell/ + ;; +esac + case $SKIP_FISH in true) sleep 0;; *) From 18c11899cbbc78d480838e97d165629f154de2fb Mon Sep 17 00:00:00 2001 From: "Celestial.y" Date: Tue, 21 Oct 2025 13:40:03 +0800 Subject: [PATCH 13/53] Update package-installers.sh --- sdata/lib/package-installers.sh | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sdata/lib/package-installers.sh b/sdata/lib/package-installers.sh index c89d8009e..54da9833d 100644 --- a/sdata/lib/package-installers.sh +++ b/sdata/lib/package-installers.sh @@ -1,6 +1,5 @@ # This script depends on `functions.sh' . -# This is NOT a script for execution, but for loading functions, so NOT need execution permission or shebang. -# NOTE that you NOT need to `cd ..' because the `$0' is NOT this file, but the script file which will source this file. +# This script is not for direct execution, instead it should be sourced by other script. It does not need execution permission or shebang. # shellcheck shell=bash @@ -105,5 +104,5 @@ install-python-packages(){ x uv venv --prompt .venv $(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV) -p 3.12 x source $(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate x uv pip install -r sdata/uv/requirements.txt - x deactivate # We don't need the virtual environment anymore + x deactivate } From fa52acad2775910dc64dd9a522e5034e0b5b61b7 Mon Sep 17 00:00:00 2001 From: clsty Date: Tue, 21 Oct 2025 14:49:09 +0800 Subject: [PATCH 14/53] Add auto-close-issue workflow (WIP) --- .github/workflows/auto-close-issue.yml | 104 +++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 .github/workflows/auto-close-issue.yml diff --git a/.github/workflows/auto-close-issue.yml b/.github/workflows/auto-close-issue.yml new file mode 100644 index 000000000..e6bb394f8 --- /dev/null +++ b/.github/workflows/auto-close-issue.yml @@ -0,0 +1,104 @@ +on: + issues: + types: [edited] + +name: Close issues when the "ticked without reading" checkbox is checked + +permissions: + issues: write + +jobs: + detect-and-close: + name: Detect checkbox and close + runs-on: ubuntu-latest + steps: + - name: Check for the "ticked without reading" checkbox and close if present + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + OWNER: ${{ github.repository_owner }} + REPO: ${{ github.event.repository.name }} + ISSUE_NUMBER: ${{ github.event.issue.number }} + ISSUE_BODY: ${{ toJson(github.event.issue.body) }} + ISSUE_USER: ${{ github.event.issue.user.login }} + run: | + set -euo pipefail + + # Normalize the JSON-encoded body into plain text + BODY=$(printf '%s' "$ISSUE_BODY" | sed -E 's/^"(.*)"$/\1/' | sed 's/\\"/"/g' | sed 's/\\n/\n/g') + + echo "Checking issue #${ISSUE_NUMBER} for the target checked checkbox..." + # Match a checked markdown checkbox followed by the exact label text (case-insensitive). + # Example matching line: "- [x] I've ticked the checkboxes without reading their contents" + if printf '%s' "$BODY" | grep -Eiq '\[x\].*I('"'"'|`)?ve ticked the checkboxes without reading their contents'; then + echo "Target checkbox is checked. Proceeding to comment and close the issue." + + # GraphQL: get the issue node id + read -r -d '' QUERY_GET_ID <<'GRAPHQL' +query($owner: String!, $name: String!, $number: Int!) { + repository(owner: $owner, name: $name) { + issue(number: $number) { + id + } + } +} +GRAPHQL + + GET_ID_PAYLOAD=$(jq -n --arg q "$QUERY_GET_ID" --arg owner "$OWNER" --arg name "$REPO" --argjson number "$ISSUE_NUMBER" '{query:$q, variables:{owner:$owner, name:$name, number:$number}}') + + RES=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" -H "Content-Type: application/json" -d "$GET_ID_PAYLOAD" https://api.github.com/graphql) + ISSUE_ID=$(printf '%s' "$RES" | jq -r '.data.repository.issue.id // empty') + + if [ -z "$ISSUE_ID" ]; then + echo "Failed to get issue id from GraphQL response:" + printf '%s\n' "$RES" + exit 1 + fi + echo "Issue node id: $ISSUE_ID" + + # Prepare the comment body (English) and mention the issue author + COMMENT_BODY="Hi @${ISSUE_USER} — I noticed you checked \"I've ticked the checkboxes without reading their contents\" in the issue template. To help others assist you effectively, please read the template and provide the requested diagnostic information (Step 2 & Step 3) and then reopen or resubmit the issue. I will close this issue now (stateReason=NOT_PLANNED). If you update the issue with the required information, we can re-evaluate. Thank you!" + + # addComment mutation + read -r -d '' MUT_ADD_COMMENT <<'GRAPHQL' +mutation($id: ID!, $body: String!) { + addComment(input: {subjectId: $id, body: $body}) { + clientMutationId + } +} +GRAPHQL + + ADD_COMMENT_PAYLOAD=$(jq -n --arg q "$MUT_ADD_COMMENT" --arg id "$ISSUE_ID" --arg body "$COMMENT_BODY" '{query:$q, variables:{id:$id, body:$body}}') + + RES_COMMENT=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" -H "Content-Type: application/json" -d "$ADD_COMMENT_PAYLOAD" https://api.github.com/graphql) + ERR_COMMENT=$(printf '%s' "$RES_COMMENT" | jq -r '.errors[]?.message // empty') + if [ -n "$ERR_COMMENT" ]; then + echo "addComment error: $ERR_COMMENT" + printf '%s\n' "$RES_COMMENT" + exit 1 + fi + echo "Comment posted." + + # updateIssue mutation to close with NOT_PLANNED + read -r -d '' MUT_UPDATE_ISSUE <<'GRAPHQL' +mutation($id: ID!) { + updateIssue(input: {id: $id, state: CLOSED, stateReason: NOT_PLANNED}) { + issue { + number + } + } +} +GRAPHQL + + UPDATE_PAYLOAD=$(jq -n --arg q "$MUT_UPDATE_ISSUE" --arg id "$ISSUE_ID" '{query:$q, variables:{id:$id}}') + + RES_UPDATE=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" -H "Content-Type: application/json" -d "$UPDATE_PAYLOAD" https://api.github.com/graphql) + ERR_UPDATE=$(printf '%s' "$RES_UPDATE" | jq -r '.errors[]?.message // empty') + if [ -n "$ERR_UPDATE" ]; then + echo "updateIssue error: $ERR_UPDATE" + printf '%s\n' "$RES_UPDATE" + exit 1 + fi + echo "Issue closed with reason NOT_PLANNED." + else + echo "Checkbox not present/checked. Nothing to do." + fi From 00526116cc81b3c681ada2220d97edea2ef7aa9d Mon Sep 17 00:00:00 2001 From: clsty Date: Tue, 21 Oct 2025 14:53:43 +0800 Subject: [PATCH 15/53] Try to fix auto-close-issue.yml --- .github/workflows/auto-close-issue.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/auto-close-issue.yml b/.github/workflows/auto-close-issue.yml index e6bb394f8..1ef76812e 100644 --- a/.github/workflows/auto-close-issue.yml +++ b/.github/workflows/auto-close-issue.yml @@ -1,6 +1,6 @@ on: issues: - types: [edited] + types: [opened, edited] name: Close issues when the "ticked without reading" checkbox is checked From 2ddfc77b66a2a916764be191dfe6ff4ba8184fe3 Mon Sep 17 00:00:00 2001 From: clsty Date: Tue, 21 Oct 2025 14:58:15 +0800 Subject: [PATCH 16/53] Try to fix auto-close-issue.yml --- .github/workflows/auto-close-issue.yml | 44 ++++++-------------------- 1 file changed, 9 insertions(+), 35 deletions(-) diff --git a/.github/workflows/auto-close-issue.yml b/.github/workflows/auto-close-issue.yml index 1ef76812e..7f81d9f50 100644 --- a/.github/workflows/auto-close-issue.yml +++ b/.github/workflows/auto-close-issue.yml @@ -1,6 +1,6 @@ on: issues: - types: [opened, edited] + types: [edited] name: Close issues when the "ticked without reading" checkbox is checked @@ -9,10 +9,9 @@ permissions: jobs: detect-and-close: - name: Detect checkbox and close runs-on: ubuntu-latest steps: - - name: Check for the "ticked without reading" checkbox and close if present + - name: Detect checked "ticked without reading" checkbox and close env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} OWNER: ${{ github.repository_owner }} @@ -29,21 +28,12 @@ jobs: echo "Checking issue #${ISSUE_NUMBER} for the target checked checkbox..." # Match a checked markdown checkbox followed by the exact label text (case-insensitive). # Example matching line: "- [x] I've ticked the checkboxes without reading their contents" - if printf '%s' "$BODY" | grep -Eiq '\[x\].*I('"'"'|`)?ve ticked the checkboxes without reading their contents'; then + if printf '%s' "$BODY" | grep -Eiq '\[x\].*I'"'"'ve ticked the checkboxes without reading their contents'; then echo "Target checkbox is checked. Proceeding to comment and close the issue." - # GraphQL: get the issue node id - read -r -d '' QUERY_GET_ID <<'GRAPHQL' -query($owner: String!, $name: String!, $number: Int!) { - repository(owner: $owner, name: $name) { - issue(number: $number) { - id - } - } -} -GRAPHQL - - GET_ID_PAYLOAD=$(jq -n --arg q "$QUERY_GET_ID" --arg owner "$OWNER" --arg name "$REPO" --argjson number "$ISSUE_NUMBER" '{query:$q, variables:{owner:$owner, name:$name, number:$number}}') + # GraphQL query (single-line string to avoid YAML/heredoc quoting issues) + QUERY='query($owner: String!, $name: String!, $number: Int!) { repository(owner: $owner, name: $name) { issue(number: $number) { id } } }' + GET_ID_PAYLOAD=$(jq -n --arg q "$QUERY" --arg owner "$OWNER" --arg name "$REPO" --argjson number "$ISSUE_NUMBER" '{query:$q, variables:{owner:$owner, name:$name, number:$number}}') RES=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" -H "Content-Type: application/json" -d "$GET_ID_PAYLOAD" https://api.github.com/graphql) ISSUE_ID=$(printf '%s' "$RES" | jq -r '.data.repository.issue.id // empty') @@ -56,17 +46,10 @@ GRAPHQL echo "Issue node id: $ISSUE_ID" # Prepare the comment body (English) and mention the issue author - COMMENT_BODY="Hi @${ISSUE_USER} — I noticed you checked \"I've ticked the checkboxes without reading their contents\" in the issue template. To help others assist you effectively, please read the template and provide the requested diagnostic information (Step 2 & Step 3) and then reopen or resubmit the issue. I will close this issue now (stateReason=NOT_PLANNED). If you update the issue with the required information, we can re-evaluate. Thank you!" + COMMENT_BODY="Hi @${ISSUE_USER} — I noticed you checked \"I've ticked the checkboxes without reading their contents\" in the issue template. To help others assist you effectively, please read the template and provide the requested diagnostic information (Step 2 & Step 3). I will close this issue now (stateReason=NOT_PLANNED). If you update the issue with the required information, we can re-evaluate. Thank you!" # addComment mutation - read -r -d '' MUT_ADD_COMMENT <<'GRAPHQL' -mutation($id: ID!, $body: String!) { - addComment(input: {subjectId: $id, body: $body}) { - clientMutationId - } -} -GRAPHQL - + MUT_ADD_COMMENT='mutation($id: ID!, $body: String!) { addComment(input: {subjectId: $id, body: $body}) { clientMutationId } }' ADD_COMMENT_PAYLOAD=$(jq -n --arg q "$MUT_ADD_COMMENT" --arg id "$ISSUE_ID" --arg body "$COMMENT_BODY" '{query:$q, variables:{id:$id, body:$body}}') RES_COMMENT=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" -H "Content-Type: application/json" -d "$ADD_COMMENT_PAYLOAD" https://api.github.com/graphql) @@ -79,16 +62,7 @@ GRAPHQL echo "Comment posted." # updateIssue mutation to close with NOT_PLANNED - read -r -d '' MUT_UPDATE_ISSUE <<'GRAPHQL' -mutation($id: ID!) { - updateIssue(input: {id: $id, state: CLOSED, stateReason: NOT_PLANNED}) { - issue { - number - } - } -} -GRAPHQL - + MUT_UPDATE_ISSUE='mutation($id: ID!) { updateIssue(input: {id: $id, state: CLOSED, stateReason: NOT_PLANNED}) { issue { number } } }' UPDATE_PAYLOAD=$(jq -n --arg q "$MUT_UPDATE_ISSUE" --arg id "$ISSUE_ID" '{query:$q, variables:{id:$id}}') RES_UPDATE=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" -H "Content-Type: application/json" -d "$UPDATE_PAYLOAD" https://api.github.com/graphql) From e8937e203016c125e8708134679b547938567b2b Mon Sep 17 00:00:00 2001 From: clsty Date: Tue, 21 Oct 2025 15:01:16 +0800 Subject: [PATCH 17/53] Try to fix auto-close-issue.yml --- .github/workflows/auto-close-issue.yml | 72 ++++++++++++++++++++------ 1 file changed, 57 insertions(+), 15 deletions(-) diff --git a/.github/workflows/auto-close-issue.yml b/.github/workflows/auto-close-issue.yml index 7f81d9f50..4f8479fb7 100644 --- a/.github/workflows/auto-close-issue.yml +++ b/.github/workflows/auto-close-issue.yml @@ -26,53 +26,95 @@ jobs: BODY=$(printf '%s' "$ISSUE_BODY" | sed -E 's/^"(.*)"$/\1/' | sed 's/\\"/"/g' | sed 's/\\n/\n/g') echo "Checking issue #${ISSUE_NUMBER} for the target checked checkbox..." - # Match a checked markdown checkbox followed by the exact label text (case-insensitive). - # Example matching line: "- [x] I've ticked the checkboxes without reading their contents" - if printf '%s' "$BODY" | grep -Eiq '\[x\].*I'"'"'ve ticked the checkboxes without reading their contents'; then + # Look for the exact label text in a checked markdown checkbox (case-insensitive). + if printf '%s' "$BODY" | grep -Fiq "I've ticked the checkboxes without reading their contents"; then echo "Target checkbox is checked. Proceeding to comment and close the issue." - # GraphQL query (single-line string to avoid YAML/heredoc quoting issues) + # --- Get issue node id via GraphQL (logged for debugging) --- QUERY='query($owner: String!, $name: String!, $number: Int!) { repository(owner: $owner, name: $name) { issue(number: $number) { id } } }' GET_ID_PAYLOAD=$(jq -n --arg q "$QUERY" --arg owner "$OWNER" --arg name "$REPO" --argjson number "$ISSUE_NUMBER" '{query:$q, variables:{owner:$owner, name:$name, number:$number}}') + echo "GraphQL: fetching issue node id..." RES=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" -H "Content-Type: application/json" -d "$GET_ID_PAYLOAD" https://api.github.com/graphql) + echo "GraphQL response (get id):" + printf '%s\n' "$RES" + ISSUE_ID=$(printf '%s' "$RES" | jq -r '.data.repository.issue.id // empty') if [ -z "$ISSUE_ID" ]; then - echo "Failed to get issue id from GraphQL response:" - printf '%s\n' "$RES" + echo "Failed to get issue id from GraphQL response. Aborting." exit 1 fi echo "Issue node id: $ISSUE_ID" - # Prepare the comment body (English) and mention the issue author + # --- Post a comment to the issue --- COMMENT_BODY="Hi @${ISSUE_USER} — I noticed you checked \"I've ticked the checkboxes without reading their contents\" in the issue template. To help others assist you effectively, please read the template and provide the requested diagnostic information (Step 2 & Step 3). I will close this issue now (stateReason=NOT_PLANNED). If you update the issue with the required information, we can re-evaluate. Thank you!" - # addComment mutation MUT_ADD_COMMENT='mutation($id: ID!, $body: String!) { addComment(input: {subjectId: $id, body: $body}) { clientMutationId } }' ADD_COMMENT_PAYLOAD=$(jq -n --arg q "$MUT_ADD_COMMENT" --arg id "$ISSUE_ID" --arg body "$COMMENT_BODY" '{query:$q, variables:{id:$id, body:$body}}') + echo "GraphQL: adding comment..." RES_COMMENT=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" -H "Content-Type: application/json" -d "$ADD_COMMENT_PAYLOAD" https://api.github.com/graphql) + echo "GraphQL response (add comment):" + printf '%s\n' "$RES_COMMENT" + ERR_COMMENT=$(printf '%s' "$RES_COMMENT" | jq -r '.errors[]?.message // empty') if [ -n "$ERR_COMMENT" ]; then echo "addComment error: $ERR_COMMENT" - printf '%s\n' "$RES_COMMENT" exit 1 fi echo "Comment posted." - # updateIssue mutation to close with NOT_PLANNED - MUT_UPDATE_ISSUE='mutation($id: ID!) { updateIssue(input: {id: $id, state: CLOSED, stateReason: NOT_PLANNED}) { issue { number } } }' + # --- Attempt to close via GraphQL updateIssue --- + MUT_UPDATE_ISSUE='mutation($id: ID!) { updateIssue(input: {id: $id, state: CLOSED, stateReason: NOT_PLANNED}) { issue { number, state, stateReason } } }' UPDATE_PAYLOAD=$(jq -n --arg q "$MUT_UPDATE_ISSUE" --arg id "$ISSUE_ID" '{query:$q, variables:{id:$id}}') + echo "GraphQL: updating issue (close with NOT_PLANNED)..." RES_UPDATE=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" -H "Content-Type: application/json" -d "$UPDATE_PAYLOAD" https://api.github.com/graphql) + echo "GraphQL response (update issue):" + printf '%s\n' "$RES_UPDATE" + ERR_UPDATE=$(printf '%s' "$RES_UPDATE" | jq -r '.errors[]?.message // empty') + UPDATED_STATE=$(printf '%s' "$RES_UPDATE" | jq -r '.data.updateIssue.issue.state // empty') + UPDATED_REASON=$(printf '%s' "$RES_UPDATE" | jq -r '.data.updateIssue.issue.stateReason // empty') + if [ -n "$ERR_UPDATE" ]; then - echo "updateIssue error: $ERR_UPDATE" - printf '%s\n' "$RES_UPDATE" - exit 1 + echo "GraphQL updateIssue returned errors: $ERR_UPDATE" fi - echo "Issue closed with reason NOT_PLANNED." + + if [ "$UPDATED_STATE" = "CLOSED" ] && [ -n "$UPDATED_REASON" ]; then + echo "Issue closed via GraphQL: state=$UPDATED_STATE, stateReason=$UPDATED_REASON" + else + echo "GraphQL update did not confirm the issue is closed. Falling back to REST API PATCH to ensure the issue is closed." + + # REST fallback to close the issue with state_reason "not_planned" + REST_PAYLOAD=$(jq -n --arg state "closed" --arg sr "not_planned" '{state:$state, state_reason:$sr}') + echo "REST: PATCH /repos/$OWNER/$REPO/issues/$ISSUE_NUMBER payload: $REST_PAYLOAD" + RES_REST=$(curl -s -w "\n%{http_code}" -X PATCH \ + -H "Authorization: Bearer $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + -H "Content-Type: application/json" \ + -d "$REST_PAYLOAD" \ + "https://api.github.com/repos/$OWNER/$REPO/issues/$ISSUE_NUMBER") + + # separate body and status + HTTP_STATUS=$(printf '%s' "$RES_REST" | tail -n1) + RESP_BODY=$(printf '%s' "$RES_REST" | sed '$d') + + echo "REST response body:" + printf '%s\n' "$RESP_BODY" + echo "REST HTTP status: $HTTP_STATUS" + + if [ "$HTTP_STATUS" -ge 200 ] && [ "$HTTP_STATUS" -lt 300 ]; then + CLOSED_STATE=$(printf '%s' "$RESP_BODY" | jq -r '.state // empty') + CLOSED_REASON=$(printf '%s' "$RESP_BODY" | jq -r '.state_reason // empty') + echo "Issue closed via REST: state=$CLOSED_STATE, state_reason=$CLOSED_REASON" + else + echo "REST fallback failed to close the issue. See REST response above." + exit 1 + fi + fi + else echo "Checkbox not present/checked. Nothing to do." fi From 1842ab790e27961c84f8eb0e67e7b46089c81d5a Mon Sep 17 00:00:00 2001 From: clsty Date: Tue, 21 Oct 2025 15:04:10 +0800 Subject: [PATCH 18/53] Try to fix auto-close-issue.yml --- .github/workflows/auto-close-issue.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/auto-close-issue.yml b/.github/workflows/auto-close-issue.yml index 4f8479fb7..778140ff2 100644 --- a/.github/workflows/auto-close-issue.yml +++ b/.github/workflows/auto-close-issue.yml @@ -27,7 +27,7 @@ jobs: echo "Checking issue #${ISSUE_NUMBER} for the target checked checkbox..." # Look for the exact label text in a checked markdown checkbox (case-insensitive). - if printf '%s' "$BODY" | grep -Fiq "I've ticked the checkboxes without reading their contents"; then + if printf '%s' "$BODY" | grep -Fiq "- [x] I've ticked the checkboxes without reading their contents"; then echo "Target checkbox is checked. Proceeding to comment and close the issue." # --- Get issue node id via GraphQL (logged for debugging) --- From b557586a62ce02c201448e130acd1a7530908610 Mon Sep 17 00:00:00 2001 From: clsty Date: Tue, 21 Oct 2025 15:13:12 +0800 Subject: [PATCH 19/53] Try to fix auto-close-issue.yml --- .github/workflows/auto-close-issue.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/auto-close-issue.yml b/.github/workflows/auto-close-issue.yml index 778140ff2..ca92712c5 100644 --- a/.github/workflows/auto-close-issue.yml +++ b/.github/workflows/auto-close-issue.yml @@ -27,7 +27,7 @@ jobs: echo "Checking issue #${ISSUE_NUMBER} for the target checked checkbox..." # Look for the exact label text in a checked markdown checkbox (case-insensitive). - if printf '%s' "$BODY" | grep -Fiq "- [x] I've ticked the checkboxes without reading their contents"; then + if printf '%s' "$BODY" | grep -Fiq -- "- [x] I've ticked the checkboxes without reading their contents"; then echo "Target checkbox is checked. Proceeding to comment and close the issue." # --- Get issue node id via GraphQL (logged for debugging) --- From a79201ebd74f15f35a1aab6c27237feef7496946 Mon Sep 17 00:00:00 2001 From: clsty Date: Tue, 21 Oct 2025 15:24:06 +0800 Subject: [PATCH 20/53] Improve auto-close-issue.yml --- .github/workflows/auto-close-issue.yml | 61 +++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 7 deletions(-) diff --git a/.github/workflows/auto-close-issue.yml b/.github/workflows/auto-close-issue.yml index ca92712c5..4c75f11e6 100644 --- a/.github/workflows/auto-close-issue.yml +++ b/.github/workflows/auto-close-issue.yml @@ -11,7 +11,7 @@ jobs: detect-and-close: runs-on: ubuntu-latest steps: - - name: Detect checked "ticked without reading" checkbox and close + - name: Detect checked "ticked without reading" checkbox, comment, close and lock env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} OWNER: ${{ github.repository_owner }} @@ -26,11 +26,11 @@ jobs: BODY=$(printf '%s' "$ISSUE_BODY" | sed -E 's/^"(.*)"$/\1/' | sed 's/\\"/"/g' | sed 's/\\n/\n/g') echo "Checking issue #${ISSUE_NUMBER} for the target checked checkbox..." - # Look for the exact label text in a checked markdown checkbox (case-insensitive). + # Use -- to stop option parsing so the leading - in the pattern isn't treated as an option if printf '%s' "$BODY" | grep -Fiq -- "- [x] I've ticked the checkboxes without reading their contents"; then - echo "Target checkbox is checked. Proceeding to comment and close the issue." + echo "Target checkbox is checked. Proceeding to comment, close and lock the issue." - # --- Get issue node id via GraphQL (logged for debugging) --- + # --- Get issue node id via GraphQL --- QUERY='query($owner: String!, $name: String!, $number: Int!) { repository(owner: $owner, name: $name) { issue(number: $number) { id } } }' GET_ID_PAYLOAD=$(jq -n --arg q "$QUERY" --arg owner "$OWNER" --arg name "$REPO" --argjson number "$ISSUE_NUMBER" '{query:$q, variables:{owner:$owner, name:$name, number:$number}}') @@ -49,7 +49,6 @@ jobs: # --- Post a comment to the issue --- COMMENT_BODY="Hi @${ISSUE_USER} — I noticed you checked \"I've ticked the checkboxes without reading their contents\" in the issue template. To help others assist you effectively, please read the template and provide the requested diagnostic information (Step 2 & Step 3). I will close this issue now (stateReason=NOT_PLANNED). If you update the issue with the required information, we can re-evaluate. Thank you!" - MUT_ADD_COMMENT='mutation($id: ID!, $body: String!) { addComment(input: {subjectId: $id, body: $body}) { clientMutationId } }' ADD_COMMENT_PAYLOAD=$(jq -n --arg q "$MUT_ADD_COMMENT" --arg id "$ISSUE_ID" --arg body "$COMMENT_BODY" '{query:$q, variables:{id:$id, body:$body}}') @@ -78,12 +77,15 @@ jobs: UPDATED_STATE=$(printf '%s' "$RES_UPDATE" | jq -r '.data.updateIssue.issue.state // empty') UPDATED_REASON=$(printf '%s' "$RES_UPDATE" | jq -r '.data.updateIssue.issue.stateReason // empty') + CLOSED_OK=false + if [ -n "$ERR_UPDATE" ]; then echo "GraphQL updateIssue returned errors: $ERR_UPDATE" fi - if [ "$UPDATED_STATE" = "CLOSED" ] && [ -n "$UPDATED_REASON" ]; then + if [ "$UPDATED_STATE" = "CLOSED" ]; then echo "Issue closed via GraphQL: state=$UPDATED_STATE, stateReason=$UPDATED_REASON" + CLOSED_OK=true else echo "GraphQL update did not confirm the issue is closed. Falling back to REST API PATCH to ensure the issue is closed." @@ -97,7 +99,6 @@ jobs: -d "$REST_PAYLOAD" \ "https://api.github.com/repos/$OWNER/$REPO/issues/$ISSUE_NUMBER") - # separate body and status HTTP_STATUS=$(printf '%s' "$RES_REST" | tail -n1) RESP_BODY=$(printf '%s' "$RES_REST" | sed '$d') @@ -109,12 +110,58 @@ jobs: CLOSED_STATE=$(printf '%s' "$RESP_BODY" | jq -r '.state // empty') CLOSED_REASON=$(printf '%s' "$RESP_BODY" | jq -r '.state_reason // empty') echo "Issue closed via REST: state=$CLOSED_STATE, state_reason=$CLOSED_REASON" + if [ "$CLOSED_STATE" = "closed" ]; then + CLOSED_OK=true + fi else echo "REST fallback failed to close the issue. See REST response above." exit 1 fi fi + # --- Attempt to lock the conversation (GraphQL first, then REST fallback) --- + if [ "$CLOSED_OK" = "true" ]; then + echo "Attempting to lock the conversation via GraphQL with reason NO_REASON..." + + MUT_LOCK='mutation($id: ID!, $reason: LockReason) { lockLockable(input:{lockableId:$id, lockReason:$reason}) { clientMutationId } }' + LOCK_PAYLOAD=$(jq -n --arg q "$MUT_LOCK" --arg id "$ISSUE_ID" --arg reason "NO_REASON" '{query:$q, variables:{id:$id, reason:$reason}}') + + RES_LOCK=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" -H "Content-Type: application/json" -d "$LOCK_PAYLOAD" https://api.github.com/graphql) + echo "GraphQL response (lock):" + printf '%s\n' "$RES_LOCK" + + LOCK_ERR=$(printf '%s' "$RES_LOCK" | jq -r '.errors[]?.message // empty') + + if [ -n "$LOCK_ERR" ]; then + echo "GraphQL lockLockable returned errors: $LOCK_ERR" + echo "Falling back to REST API to lock the conversation (no explicit reason)." + + # REST fallback to lock the issue (no lock_reason to indicate "no reason") + RES_REST_LOCK=$(curl -s -w "\n%{http_code}" -X PUT \ + -H "Authorization: Bearer $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/$OWNER/$REPO/issues/$ISSUE_NUMBER/lock" -d '{}') + + HTTP_STATUS_LOCK=$(printf '%s' "$RES_REST_LOCK" | tail -n1) + RESP_BODY_LOCK=$(printf '%s' "$RES_REST_LOCK" | sed '$d') + + echo "REST lock response body:" + printf '%s\n' "$RESP_BODY_LOCK" + echo "REST lock HTTP status: $HTTP_STATUS_LOCK" + + if [ "$HTTP_STATUS_LOCK" -ge 200 ] && [ "$HTTP_STATUS_LOCK" -lt 300 ]; then + echo "Issue conversation locked via REST (no explicit reason)." + else + echo "REST fallback failed to lock the conversation. See REST response above." + exit 1 + fi + else + echo "Lock via GraphQL succeeded (or returned no errors)." + fi + else + echo "Issue was not successfully closed; skipping lock." + fi + else echo "Checkbox not present/checked. Nothing to do." fi From f37fa1b071419c3e39c0c48dd8f69bc3720f27af Mon Sep 17 00:00:00 2001 From: clsty Date: Tue, 21 Oct 2025 15:25:45 +0800 Subject: [PATCH 21/53] Finish adding auto-close-issue.yml --- .github/workflows/auto-close-issue.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/auto-close-issue.yml b/.github/workflows/auto-close-issue.yml index 4c75f11e6..a3d0039bc 100644 --- a/.github/workflows/auto-close-issue.yml +++ b/.github/workflows/auto-close-issue.yml @@ -1,6 +1,6 @@ on: issues: - types: [edited] + types: [opened] name: Close issues when the "ticked without reading" checkbox is checked From b1921b78471b374574f9ac626ee476a1d0cd9049 Mon Sep 17 00:00:00 2001 From: clsty Date: Tue, 21 Oct 2025 15:42:35 +0800 Subject: [PATCH 22/53] Update message --- .github/workflows/auto-close-issue.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/auto-close-issue.yml b/.github/workflows/auto-close-issue.yml index a3d0039bc..35afe6e4b 100644 --- a/.github/workflows/auto-close-issue.yml +++ b/.github/workflows/auto-close-issue.yml @@ -48,7 +48,7 @@ jobs: echo "Issue node id: $ISSUE_ID" # --- Post a comment to the issue --- - COMMENT_BODY="Hi @${ISSUE_USER} — I noticed you checked \"I've ticked the checkboxes without reading their contents\" in the issue template. To help others assist you effectively, please read the template and provide the requested diagnostic information (Step 2 & Step 3). I will close this issue now (stateReason=NOT_PLANNED). If you update the issue with the required information, we can re-evaluate. Thank you!" + COMMENT_BODY="Hi @${ISSUE_USER} — I noticed you checked \"I've ticked the checkboxes without reading their contents\" in the issue template. To help others assist you effectively, please read the template and provide the requested diagnostic information (Step 2 & Step 3). I will close this issue now. If you create a new issue with the required information, we can re-evaluate. Thank you!" MUT_ADD_COMMENT='mutation($id: ID!, $body: String!) { addComment(input: {subjectId: $id, body: $body}) { clientMutationId } }' ADD_COMMENT_PAYLOAD=$(jq -n --arg q "$MUT_ADD_COMMENT" --arg id "$ISSUE_ID" --arg body "$COMMENT_BODY" '{query:$q, variables:{id:$id, body:$body}}') From 1ba6b761f02f759779ff79aa672aa98f8375ac92 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 21 Oct 2025 13:22:55 +0200 Subject: [PATCH 23/53] allow corner hover open for horizontal bar --- .../quickshell/ii/modules/screenCorners/ScreenCorners.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/dots/.config/quickshell/ii/modules/screenCorners/ScreenCorners.qml b/dots/.config/quickshell/ii/modules/screenCorners/ScreenCorners.qml index 67bd899ef..55173b664 100644 --- a/dots/.config/quickshell/ii/modules/screenCorners/ScreenCorners.qml +++ b/dots/.config/quickshell/ii/modules/screenCorners/ScreenCorners.qml @@ -64,7 +64,6 @@ Scope { id: sidebarCornerOpenInteractionLoader active: { if (!Config.options.sidebar.cornerOpen.enable) return false; - if (!Config.options.bar.vertical && Config.options.sidebar.cornerOpen.bottom == Config.options.bar.bottom) return false; if (cornerPanelWindow.fullscreen) return false; return (Config.options.sidebar.cornerOpen.bottom == cornerWidget.isBottom); } From 1830aeba187beb3bb48eddeb1de2abd429a0f805 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 21 Oct 2025 13:27:15 +0200 Subject: [PATCH 24/53] make left sidebar padding consistent with right sidebar --- dots/.config/quickshell/ii/modules/sidebarLeft/SidebarLeft.qml | 1 - .../quickshell/ii/modules/sidebarLeft/SidebarLeftContent.qml | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/dots/.config/quickshell/ii/modules/sidebarLeft/SidebarLeft.qml b/dots/.config/quickshell/ii/modules/sidebarLeft/SidebarLeft.qml index 8d0a43a05..6d8009f0b 100644 --- a/dots/.config/quickshell/ii/modules/sidebarLeft/SidebarLeft.qml +++ b/dots/.config/quickshell/ii/modules/sidebarLeft/SidebarLeft.qml @@ -10,7 +10,6 @@ import Quickshell.Hyprland Scope { // Scope id: root - property int sidebarPadding: 15 property bool detach: false property Component contentComponent: SidebarLeftContent {} property Item sidebarContent diff --git a/dots/.config/quickshell/ii/modules/sidebarLeft/SidebarLeftContent.qml b/dots/.config/quickshell/ii/modules/sidebarLeft/SidebarLeftContent.qml index 37014b4b7..ae19f07a8 100644 --- a/dots/.config/quickshell/ii/modules/sidebarLeft/SidebarLeftContent.qml +++ b/dots/.config/quickshell/ii/modules/sidebarLeft/SidebarLeftContent.qml @@ -9,6 +9,7 @@ import Qt5Compat.GraphicalEffects Item { id: root required property var scopeRoot + property int sidebarPadding: 10 anchors.fill: parent property bool aiChatEnabled: Config.options.policies.ai !== 0 property bool translatorEnabled: Config.options.sidebar.translator.enable From c50a505cdb931fe1e985535744036bcadc23156e Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 21 Oct 2025 16:06:16 +0200 Subject: [PATCH 25/53] fix screenshot button --- dots/.config/quickshell/ii/modules/bar/UtilButtons.qml | 2 +- .../quickToggles/androidStyle/AndroidScreenSnipToggle.qml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/dots/.config/quickshell/ii/modules/bar/UtilButtons.qml b/dots/.config/quickshell/ii/modules/bar/UtilButtons.qml index 2930329c1..d7f73a4e3 100644 --- a/dots/.config/quickshell/ii/modules/bar/UtilButtons.qml +++ b/dots/.config/quickshell/ii/modules/bar/UtilButtons.qml @@ -25,7 +25,7 @@ Item { visible: Config.options.bar.utilButtons.showScreenSnip sourceComponent: CircleUtilButton { Layout.alignment: Qt.AlignVCenter - onClicked: Quickshell.execDetached(["qs", "-p", Quickshell.shellPath("screenshot.qml")]) + onClicked: Hyprland.dispatch("global quickshell:regionScreenshot") MaterialSymbol { horizontalAlignment: Qt.AlignHCenter fill: 1 diff --git a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidScreenSnipToggle.qml b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidScreenSnipToggle.qml index c00570e87..5a22b616a 100644 --- a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidScreenSnipToggle.qml +++ b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidScreenSnipToggle.qml @@ -4,6 +4,7 @@ import qs.modules.common.widgets import qs.services import QtQuick import Quickshell +import Quickshell.Hyprland AndroidQuickToggleButton { id: root @@ -22,7 +23,7 @@ AndroidQuickToggleButton { interval: 300 repeat: false onTriggered: { - Quickshell.execDetached(["qs", "-p", Quickshell.shellPath("screenshot.qml")]) + Hyprland.dispatch("global quickshell:regionScreenshot") } } From 778620c312306405c36eb36043726da249607023 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 21 Oct 2025 16:40:53 +0200 Subject: [PATCH 26/53] translation?service?:safer?access??? --- dots/.config/quickshell/ii/services/Translation.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dots/.config/quickshell/ii/services/Translation.qml b/dots/.config/quickshell/ii/services/Translation.qml index 71e176869..c504ba21c 100644 --- a/dots/.config/quickshell/ii/services/Translation.qml +++ b/dots/.config/quickshell/ii/services/Translation.qml @@ -79,7 +79,7 @@ Singleton { // Special cases if (!text) return ""; var key = text.toString(); - if (root.isLoading || (!root.translations.hasOwnProperty(key) && !root.generatedTranslations.hasOwnProperty(key))) + if (root.isLoading || (!root?.translations?.hasOwnProperty(key) && !root?.generatedTranslations?.hasOwnProperty(key))) return key; // Normal cases From 94102cec979ff42347d1132168bbe9240cad5545 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 21 Oct 2025 17:43:15 +0200 Subject: [PATCH 27/53] fix kill dialog not writing "always" properly (#2232) --- dots/.config/quickshell/ii/killDialog.qml | 3 ++- dots/.config/quickshell/ii/modules/common/Config.qml | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/dots/.config/quickshell/ii/killDialog.qml b/dots/.config/quickshell/ii/killDialog.qml index ff36fb5e6..cf961ef38 100644 --- a/dots/.config/quickshell/ii/killDialog.qml +++ b/dots/.config/quickshell/ii/killDialog.qml @@ -36,6 +36,7 @@ ApplicationWindow { Component.onCompleted: { Config.readWriteDelay = 0; + Config.blockWrites = true; MaterialThemeLoader.reapplyTheme(); } @@ -90,8 +91,8 @@ ApplicationWindow { } onClicked: { Quickshell.execDetached(["killall", ...conflictGroup.programs]) - conflictGroup.visible = false conflictGroup.alwaysSelected() + conflictGroup.visible = false } } RippleButton { diff --git a/dots/.config/quickshell/ii/modules/common/Config.qml b/dots/.config/quickshell/ii/modules/common/Config.qml index 8f18bd0f6..26a7e48a0 100644 --- a/dots/.config/quickshell/ii/modules/common/Config.qml +++ b/dots/.config/quickshell/ii/modules/common/Config.qml @@ -10,6 +10,7 @@ Singleton { property alias options: configOptionsJsonAdapter property bool ready: false property int readWriteDelay: 50 // milliseconds + property bool blockWrites: false function setNestedValue(nestedKey, value) { let keys = nestedKey.split("."); @@ -63,6 +64,7 @@ Singleton { id: configFileView path: root.filePath watchChanges: true + blockWrites: root.blockWrites onFileChanged: fileReloadTimer.restart() onAdapterUpdated: fileWriteTimer.restart() onLoaded: root.ready = true From d5bccd9bb1d5a9283897f327d1f7ad4ac34d62db Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 21 Oct 2025 17:51:55 +0200 Subject: [PATCH 28/53] hide code: entries from cheatsheet for zoom keybinds --- dots/.config/hypr/hyprland/keybinds.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dots/.config/hypr/hyprland/keybinds.conf b/dots/.config/hypr/hyprland/keybinds.conf index 469555874..5e6ff1e80 100644 --- a/dots/.config/hypr/hyprland/keybinds.conf +++ b/dots/.config/hypr/hyprland/keybinds.conf @@ -226,8 +226,8 @@ binde = Super, Equal, exec, qs -c $qsConfig ipc call zoom zoomIn # Zoom in binde = Super, Minus, exec, qs -c $qsConfig ipc call TEST_ALIVE || ~/.config/hypr/hyprland/scripts/zoom.sh decrease 0.1 # [hidden] Zoom out binde = Super, Equal, exec, qs -c $qsConfig ipc call TEST_ALIVE || ~/.config/hypr/hyprland/scripts/zoom.sh increase 0.1 # [hidden] Zoom in # Zoom with keypad -binde = Super, code:82, exec, qs -c $qsConfig ipc call zoom zoomOut # Zoom out -binde = Super, code:86, exec, qs -c $qsConfig ipc call zoom zoomIn # Zoom in +binde = Super, code:82, exec, qs -c $qsConfig ipc call zoom zoomOut # [hidden] Zoom out +binde = Super, code:86, exec, qs -c $qsConfig ipc call zoom zoomIn # [hidden] Zoom in binde = Super, code:82, exec, qs -c $qsConfig ipc call TEST_ALIVE || ~/.config/hypr/hyprland/scripts/zoom.sh decrease 0.1 # [hidden] Zoom out binde = Super, code:86, exec, qs -c $qsConfig ipc call TEST_ALIVE || ~/.config/hypr/hyprland/scripts/zoom.sh increase 0.1 # [hidden] Zoom in From a99f6cac5e0f0d371d70b2fdee1f883b0fdca785 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 21 Oct 2025 20:10:52 +0200 Subject: [PATCH 29/53] sidebar: fix weird anim on toggle buttons --- .../quickshell/ii/modules/common/widgets/GroupButton.qml | 2 ++ .../quickToggles/androidStyle/AndroidQuickToggleButton.qml | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/dots/.config/quickshell/ii/modules/common/widgets/GroupButton.qml b/dots/.config/quickshell/ii/modules/common/widgets/GroupButton.qml index 6ebe3341f..ed93eb77f 100644 --- a/dots/.config/quickshell/ii/modules/common/widgets/GroupButton.qml +++ b/dots/.config/quickshell/ii/modules/common/widgets/GroupButton.qml @@ -79,7 +79,9 @@ Button { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } + property alias mouseArea: buttonMouseArea MouseArea { + id: buttonMouseArea anchors.fill: parent cursorShape: Qt.PointingHandCursor acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton diff --git a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidQuickToggleButton.qml b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidQuickToggleButton.qml index 18848c21d..9165bcf56 100644 --- a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidQuickToggleButton.qml +++ b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidQuickToggleButton.qml @@ -23,8 +23,8 @@ GroupButton { baseHeight: root.baseCellHeight property bool editMode: false - enableImplicitWidthAnimation: !editMode - enableImplicitHeightAnimation: !editMode + enableImplicitWidthAnimation: !editMode && root.mouseArea.containsMouse + enableImplicitHeightAnimation: !editMode && root.mouseArea.containsMouse Behavior on baseWidth { animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } From 4031925e11b57e202e2ec6d18a532c58bdb1aae5 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 21 Oct 2025 20:49:42 +0200 Subject: [PATCH 30/53] fix "no active player" popup positioning for vertical bar --- .../quickshell/ii/modules/mediaControls/MediaControls.qml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/dots/.config/quickshell/ii/modules/mediaControls/MediaControls.qml b/dots/.config/quickshell/ii/modules/mediaControls/MediaControls.qml index ea90be154..62426a36f 100644 --- a/dots/.config/quickshell/ii/modules/mediaControls/MediaControls.qml +++ b/dots/.config/quickshell/ii/modules/mediaControls/MediaControls.qml @@ -159,7 +159,13 @@ Scope { } Item { // No player placeholder - Layout.fillWidth: true + Layout.alignment: { + if (mediaControlsRoot.anchors.left) return Qt.AlignLeft; + if (mediaControlsRoot.anchors.right) return Qt.AlignRight; + return Qt.AlignHCenter; + } + Layout.leftMargin: Appearance.sizes.hyprlandGapsOut + Layout.rightMargin: Appearance.sizes.hyprlandGapsOut visible: root.meaningfulPlayers.length === 0 implicitWidth: placeholderBackground.implicitWidth + Appearance.sizes.elevationMargin implicitHeight: placeholderBackground.implicitHeight + Appearance.sizes.elevationMargin From f2e4508cfc9303331d49fb1ca9a7464456f61949 Mon Sep 17 00:00:00 2001 From: Filip Janus Date: Tue, 21 Oct 2025 22:36:56 +0200 Subject: [PATCH 31/53] refactor import to be compatible with new version of quickshell --- .../ii/modules/background/Background.qml | 2 +- .../background/cookieClock/CookieClock.qml | 4 +- .../quickshell/ii/modules/bar/BarContent.qml | 2 +- .../ii/modules/bar/weather/WeatherPopup.qml | 4 +- .../indicators/BrightnessIndicator.qml | 2 +- .../indicators/VolumeIndicator.qml | 2 +- .../ii/modules/sidebarLeft/AiChat.qml | 2 +- .../ii/modules/sidebarLeft/Anime.qml | 2 +- .../ii/modules/sidebarLeft/Translator.qml | 2 +- .../sidebarLeft/anime/BooruResponse.qml | 4 +- .../sidebarRight/BottomWidgetGroup.qml | 8 ++-- .../sidebarRight/CenterWidgetGroup.qml | 4 +- .../sidebarRight/SidebarRightContent.qml | 10 ++--- .../quickToggles/AndroidQuickPanel.qml | 2 +- .../quickToggles/ClassicQuickPanel.qml | 2 +- .../classicStyle/NetworkToggle.qml | 2 +- .../modules/verticalBar/BatteryIndicator.qml | 2 +- .../ii/modules/verticalBar/Resources.qml | 2 +- .../verticalBar/VerticalBarContent.qml | 2 +- .../verticalBar/VerticalClockWidget.qml | 2 +- .../verticalBar/VerticalDateWidget.qml | 2 +- .../ii/modules/verticalBar/VerticalMedia.qml | 2 +- dots/.config/quickshell/ii/services/Ai.qml | 2 +- .../quickshell/ii/services/Network.qml | 2 +- dots/.config/quickshell/ii/shell.qml | 40 +++++++++---------- 25 files changed, 55 insertions(+), 55 deletions(-) diff --git a/dots/.config/quickshell/ii/modules/background/Background.qml b/dots/.config/quickshell/ii/modules/background/Background.qml index c997d847b..4aed1c08c 100644 --- a/dots/.config/quickshell/ii/modules/background/Background.qml +++ b/dots/.config/quickshell/ii/modules/background/Background.qml @@ -13,7 +13,7 @@ import Quickshell.Io import Quickshell.Wayland import Quickshell.Hyprland -import "./cookieClock" +import qs.modules.background.cookieClock Variants { id: root diff --git a/dots/.config/quickshell/ii/modules/background/cookieClock/CookieClock.qml b/dots/.config/quickshell/ii/modules/background/cookieClock/CookieClock.qml index 9b5ae0265..d08056909 100644 --- a/dots/.config/quickshell/ii/modules/background/cookieClock/CookieClock.qml +++ b/dots/.config/quickshell/ii/modules/background/cookieClock/CookieClock.qml @@ -9,8 +9,8 @@ import QtQuick.Layouts import Qt5Compat.GraphicalEffects import Quickshell.Io -import "./dateIndicator" -import "./minuteMarks" +import qs.modules.background.cookieClock.dateIndicator +import qs.modules.background.cookieClock.minuteMarks Item { id: root diff --git a/dots/.config/quickshell/ii/modules/bar/BarContent.qml b/dots/.config/quickshell/ii/modules/bar/BarContent.qml index 7e8ca9e77..77ed8db69 100644 --- a/dots/.config/quickshell/ii/modules/bar/BarContent.qml +++ b/dots/.config/quickshell/ii/modules/bar/BarContent.qml @@ -1,4 +1,4 @@ -import "./weather" +import qs.modules.bar.weather import QtQuick import QtQuick.Layouts import Quickshell diff --git a/dots/.config/quickshell/ii/modules/bar/weather/WeatherPopup.qml b/dots/.config/quickshell/ii/modules/bar/weather/WeatherPopup.qml index bd93c2662..0c06932f7 100644 --- a/dots/.config/quickshell/ii/modules/bar/weather/WeatherPopup.qml +++ b/dots/.config/quickshell/ii/modules/bar/weather/WeatherPopup.qml @@ -4,7 +4,7 @@ import qs.modules.common.widgets import QtQuick import QtQuick.Layouts -import "../" +import qs.modules.bar StyledPopup { id: root @@ -101,4 +101,4 @@ StyledPopup { } } } -} \ No newline at end of file +} diff --git a/dots/.config/quickshell/ii/modules/onScreenDisplay/indicators/BrightnessIndicator.qml b/dots/.config/quickshell/ii/modules/onScreenDisplay/indicators/BrightnessIndicator.qml index f127440f8..30661b911 100644 --- a/dots/.config/quickshell/ii/modules/onScreenDisplay/indicators/BrightnessIndicator.qml +++ b/dots/.config/quickshell/ii/modules/onScreenDisplay/indicators/BrightnessIndicator.qml @@ -2,7 +2,7 @@ import qs.services import QtQuick import Quickshell import Quickshell.Hyprland -import "../" +import qs.modules.onScreenDisplay OsdValueIndicator { id: root diff --git a/dots/.config/quickshell/ii/modules/onScreenDisplay/indicators/VolumeIndicator.qml b/dots/.config/quickshell/ii/modules/onScreenDisplay/indicators/VolumeIndicator.qml index 7f7b5f47f..487befdac 100644 --- a/dots/.config/quickshell/ii/modules/onScreenDisplay/indicators/VolumeIndicator.qml +++ b/dots/.config/quickshell/ii/modules/onScreenDisplay/indicators/VolumeIndicator.qml @@ -1,6 +1,6 @@ import qs.services import QtQuick -import "../" +import qs.modules.onScreenDisplay OsdValueIndicator { id: osdValues diff --git a/dots/.config/quickshell/ii/modules/sidebarLeft/AiChat.qml b/dots/.config/quickshell/ii/modules/sidebarLeft/AiChat.qml index 280c57583..6d00752bc 100644 --- a/dots/.config/quickshell/ii/modules/sidebarLeft/AiChat.qml +++ b/dots/.config/quickshell/ii/modules/sidebarLeft/AiChat.qml @@ -3,7 +3,7 @@ import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions -import "./aiChat/" +import qs.modules.sidebarLeft.aiChat import QtQuick import QtQuick.Controls import QtQuick.Layouts diff --git a/dots/.config/quickshell/ii/modules/sidebarLeft/Anime.qml b/dots/.config/quickshell/ii/modules/sidebarLeft/Anime.qml index 42c314a10..cab251cb8 100644 --- a/dots/.config/quickshell/ii/modules/sidebarLeft/Anime.qml +++ b/dots/.config/quickshell/ii/modules/sidebarLeft/Anime.qml @@ -3,7 +3,7 @@ import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions -import "./anime/" +import qs.modules.sidebarLeft.anime import QtQuick import QtQuick.Controls import QtQuick.Layouts diff --git a/dots/.config/quickshell/ii/modules/sidebarLeft/Translator.qml b/dots/.config/quickshell/ii/modules/sidebarLeft/Translator.qml index 18a8cf5b9..41f4fffab 100644 --- a/dots/.config/quickshell/ii/modules/sidebarLeft/Translator.qml +++ b/dots/.config/quickshell/ii/modules/sidebarLeft/Translator.qml @@ -2,7 +2,7 @@ import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions -import "./translator/" +import qs.modules.sidebarLeft.translator import QtQuick import QtQuick.Layouts import Quickshell diff --git a/dots/.config/quickshell/ii/modules/sidebarLeft/anime/BooruResponse.qml b/dots/.config/quickshell/ii/modules/sidebarLeft/anime/BooruResponse.qml index cfb2a7f9d..7c9f7fd09 100644 --- a/dots/.config/quickshell/ii/modules/sidebarLeft/anime/BooruResponse.qml +++ b/dots/.config/quickshell/ii/modules/sidebarLeft/anime/BooruResponse.qml @@ -3,7 +3,7 @@ import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions -import "../" +import qs.modules.sidebarLeft import qs.services import QtQuick import QtQuick.Controls @@ -287,4 +287,4 @@ Rectangle { } } } -} \ No newline at end of file +} diff --git a/dots/.config/quickshell/ii/modules/sidebarRight/BottomWidgetGroup.qml b/dots/.config/quickshell/ii/modules/sidebarRight/BottomWidgetGroup.qml index a7a895af1..96313289a 100644 --- a/dots/.config/quickshell/ii/modules/sidebarRight/BottomWidgetGroup.qml +++ b/dots/.config/quickshell/ii/modules/sidebarRight/BottomWidgetGroup.qml @@ -1,9 +1,9 @@ import qs.modules.common import qs.modules.common.widgets import qs.services -import "./calendar" -import "./todo" -import "./pomodoro" +import qs.modules.sidebarRight.calendar +import qs.modules.sidebarRight.todo +import qs.modules.sidebarRight.pomodoro import QtQuick import QtQuick.Layouts @@ -248,4 +248,4 @@ Rectangle { anchors.margins: 5 } } -} \ No newline at end of file +} diff --git a/dots/.config/quickshell/ii/modules/sidebarRight/CenterWidgetGroup.qml b/dots/.config/quickshell/ii/modules/sidebarRight/CenterWidgetGroup.qml index 85d3b823a..007006ca9 100644 --- a/dots/.config/quickshell/ii/modules/sidebarRight/CenterWidgetGroup.qml +++ b/dots/.config/quickshell/ii/modules/sidebarRight/CenterWidgetGroup.qml @@ -1,8 +1,8 @@ import qs.modules.common import qs.modules.common.widgets import qs.services -import "./notifications" -import "./volumeMixer" +import qs.modules.sidebarRight.notifications +import qs.modules.sidebarRight.volumeMixer import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Controls diff --git a/dots/.config/quickshell/ii/modules/sidebarRight/SidebarRightContent.qml b/dots/.config/quickshell/ii/modules/sidebarRight/SidebarRightContent.qml index 239377142..cf228d365 100644 --- a/dots/.config/quickshell/ii/modules/sidebarRight/SidebarRightContent.qml +++ b/dots/.config/quickshell/ii/modules/sidebarRight/SidebarRightContent.qml @@ -9,11 +9,11 @@ import Quickshell import Quickshell.Bluetooth import Quickshell.Hyprland -import "./quickToggles/" -import "./quickToggles/classicStyle/" -import "./wifiNetworks/" -import "./bluetoothDevices/" -import "./volumeMixer/" +import qs.modules.sidebarRight.quickToggles +import qs.modules.sidebarRight.quickToggles.classicStyle +import qs.modules.sidebarRight.wifiNetworks +import qs.modules.sidebarRight.bluetoothDevices +import qs.modules.sidebarRight.volumeMixer Item { id: root diff --git a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/AndroidQuickPanel.qml b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/AndroidQuickPanel.qml index eb7793fba..f307c7a51 100644 --- a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/AndroidQuickPanel.qml +++ b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/AndroidQuickPanel.qml @@ -6,7 +6,7 @@ import QtQuick.Layouts import Quickshell import Quickshell.Bluetooth -import "./androidStyle/" +import qs.modules.sidebarRight.quickToggles.androidStyle AbstractQuickPanel { id: root diff --git a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/ClassicQuickPanel.qml b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/ClassicQuickPanel.qml index c6855dfaa..cf52886a0 100644 --- a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/ClassicQuickPanel.qml +++ b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/ClassicQuickPanel.qml @@ -5,7 +5,7 @@ import QtQuick import QtQuick.Layouts import Quickshell.Bluetooth -import "./classicStyle/" +import qs.modules.sidebarRight.quickToggles.classicStyle AbstractQuickPanel { id: root diff --git a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/classicStyle/NetworkToggle.qml b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/classicStyle/NetworkToggle.qml index 5fd8e3e8d..cc3ac3fca 100644 --- a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/classicStyle/NetworkToggle.qml +++ b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/classicStyle/NetworkToggle.qml @@ -2,7 +2,7 @@ import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions -import "../" +import qs.modules.sidebarRight.quickToggles import qs import QtQuick import Quickshell diff --git a/dots/.config/quickshell/ii/modules/verticalBar/BatteryIndicator.qml b/dots/.config/quickshell/ii/modules/verticalBar/BatteryIndicator.qml index 27d704884..49afde713 100644 --- a/dots/.config/quickshell/ii/modules/verticalBar/BatteryIndicator.qml +++ b/dots/.config/quickshell/ii/modules/verticalBar/BatteryIndicator.qml @@ -3,7 +3,7 @@ import qs.modules.common.widgets import qs.services import QtQuick import QtQuick.Layouts -import "./../bar" as Bar +import qs.modules.verticalBar.bar as Bar MouseArea { id: root diff --git a/dots/.config/quickshell/ii/modules/verticalBar/Resources.qml b/dots/.config/quickshell/ii/modules/verticalBar/Resources.qml index e4bba0187..430596104 100644 --- a/dots/.config/quickshell/ii/modules/verticalBar/Resources.qml +++ b/dots/.config/quickshell/ii/modules/verticalBar/Resources.qml @@ -2,7 +2,7 @@ import qs.services import qs.modules.common import QtQuick import QtQuick.Layouts -import "../bar" as Bar +import qs.modules.verticalBar.bar as Bar MouseArea { id: root diff --git a/dots/.config/quickshell/ii/modules/verticalBar/VerticalBarContent.qml b/dots/.config/quickshell/ii/modules/verticalBar/VerticalBarContent.qml index e594a0ae4..46ae164ca 100644 --- a/dots/.config/quickshell/ii/modules/verticalBar/VerticalBarContent.qml +++ b/dots/.config/quickshell/ii/modules/verticalBar/VerticalBarContent.qml @@ -8,7 +8,7 @@ import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions -import "../bar" as Bar +import qs.modules.verticalBar.bar as Bar Item { // Bar content region id: root diff --git a/dots/.config/quickshell/ii/modules/verticalBar/VerticalClockWidget.qml b/dots/.config/quickshell/ii/modules/verticalBar/VerticalClockWidget.qml index 1c19f2828..0aabf9ecf 100644 --- a/dots/.config/quickshell/ii/modules/verticalBar/VerticalClockWidget.qml +++ b/dots/.config/quickshell/ii/modules/verticalBar/VerticalClockWidget.qml @@ -3,7 +3,7 @@ import qs.modules.common.widgets import qs.services import QtQuick import QtQuick.Layouts -import "../bar" as Bar +import qs.modules.verticalBar.bar as Bar Item { id: root diff --git a/dots/.config/quickshell/ii/modules/verticalBar/VerticalDateWidget.qml b/dots/.config/quickshell/ii/modules/verticalBar/VerticalDateWidget.qml index 8ff9386c3..07255de78 100644 --- a/dots/.config/quickshell/ii/modules/verticalBar/VerticalDateWidget.qml +++ b/dots/.config/quickshell/ii/modules/verticalBar/VerticalDateWidget.qml @@ -4,7 +4,7 @@ import qs.services import QtQuick import QtQuick.Shapes import QtQuick.Layouts -import "../bar" as Bar +import qs.modules.verticalBar.bar as Bar Item { // Full hitbox id: root diff --git a/dots/.config/quickshell/ii/modules/verticalBar/VerticalMedia.qml b/dots/.config/quickshell/ii/modules/verticalBar/VerticalMedia.qml index 24eed8b4f..81786be7a 100644 --- a/dots/.config/quickshell/ii/modules/verticalBar/VerticalMedia.qml +++ b/dots/.config/quickshell/ii/modules/verticalBar/VerticalMedia.qml @@ -8,7 +8,7 @@ import QtQuick import QtQuick.Layouts import Quickshell.Services.Mpris -import "../bar" as Bar +import qs.modules.verticalBar.bar as Bar MouseArea { id: root diff --git a/dots/.config/quickshell/ii/services/Ai.qml b/dots/.config/quickshell/ii/services/Ai.qml index e348d4459..26657b0d1 100644 --- a/dots/.config/quickshell/ii/services/Ai.qml +++ b/dots/.config/quickshell/ii/services/Ai.qml @@ -7,7 +7,7 @@ import Quickshell import Quickshell.Io import Quickshell.Wayland import QtQuick -import "./ai/" +import qs.services.ai /** * Basic service to handle LLM chats. Supports Google's and OpenAI's API formats. diff --git a/dots/.config/quickshell/ii/services/Network.qml b/dots/.config/quickshell/ii/services/Network.qml index 181e76cf4..7d16a9450 100644 --- a/dots/.config/quickshell/ii/services/Network.qml +++ b/dots/.config/quickshell/ii/services/Network.qml @@ -6,7 +6,7 @@ pragma ComponentBehavior: Bound import Quickshell import Quickshell.Io import QtQuick -import "./network" +import qs.services.network /** * Network service with nmcli. diff --git a/dots/.config/quickshell/ii/shell.qml b/dots/.config/quickshell/ii/shell.qml index 1dd1627f4..9cb90f102 100644 --- a/dots/.config/quickshell/ii/shell.qml +++ b/dots/.config/quickshell/ii/shell.qml @@ -7,30 +7,30 @@ //@ pragma Env QT_SCALE_FACTOR=1 -import "./modules/common/" -import "./modules/background/" -import "./modules/bar/" -import "./modules/cheatsheet/" -import "./modules/crosshair/" -import "./modules/dock/" -import "./modules/lock/" -import "./modules/mediaControls/" -import "./modules/notificationPopup/" -import "./modules/onScreenDisplay/" -import "./modules/onScreenKeyboard/" -import "./modules/overview/" -import "./modules/regionSelector/" -import "./modules/screenCorners/" -import "./modules/sessionScreen/" -import "./modules/sidebarLeft/" -import "./modules/sidebarRight/" -import "./modules/verticalBar/" -import "./modules/wallpaperSelector/" +import qs.modules.common +import qs.modules.background +import qs.modules.bar +import qs.modules.cheatsheet +import qs.modules.crosshair +import qs.modules.dock +import qs.modules.lock +import qs.modules.mediaControls +import qs.modules.notificationPopup +import qs.modules.onScreenDisplay +import qs.modules.onScreenKeyboard +import qs.modules.overview +import qs.modules.regionSelector +import qs.modules.screenCorners +import qs.modules.sessionScreen +import qs.modules.sidebarLeft +import qs.modules.sidebarRight +import qs.modules.verticalBar +import qs.modules.wallpaperSelector import QtQuick import QtQuick.Window import Quickshell -import "./services/" +import qs.services ShellRoot { // Enable/disable modules here. False = not loaded at all, so rest assured From ecd7a225e97126cb21ae1b7fb4f00f1df9069f82 Mon Sep 17 00:00:00 2001 From: Filip Janus Date: Tue, 21 Oct 2025 22:43:54 +0200 Subject: [PATCH 32/53] fix bar module reference --- .../quickshell/ii/modules/verticalBar/BatteryIndicator.qml | 2 +- dots/.config/quickshell/ii/modules/verticalBar/Resources.qml | 2 +- .../quickshell/ii/modules/verticalBar/VerticalBarContent.qml | 2 +- .../quickshell/ii/modules/verticalBar/VerticalClockWidget.qml | 2 +- .../quickshell/ii/modules/verticalBar/VerticalDateWidget.qml | 2 +- .../.config/quickshell/ii/modules/verticalBar/VerticalMedia.qml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dots/.config/quickshell/ii/modules/verticalBar/BatteryIndicator.qml b/dots/.config/quickshell/ii/modules/verticalBar/BatteryIndicator.qml index 49afde713..b134b12fe 100644 --- a/dots/.config/quickshell/ii/modules/verticalBar/BatteryIndicator.qml +++ b/dots/.config/quickshell/ii/modules/verticalBar/BatteryIndicator.qml @@ -3,7 +3,7 @@ import qs.modules.common.widgets import qs.services import QtQuick import QtQuick.Layouts -import qs.modules.verticalBar.bar as Bar +import qs.modules.bar as Bar MouseArea { id: root diff --git a/dots/.config/quickshell/ii/modules/verticalBar/Resources.qml b/dots/.config/quickshell/ii/modules/verticalBar/Resources.qml index 430596104..ddbb1c399 100644 --- a/dots/.config/quickshell/ii/modules/verticalBar/Resources.qml +++ b/dots/.config/quickshell/ii/modules/verticalBar/Resources.qml @@ -2,7 +2,7 @@ import qs.services import qs.modules.common import QtQuick import QtQuick.Layouts -import qs.modules.verticalBar.bar as Bar +import qs.modules.bar as Bar MouseArea { id: root diff --git a/dots/.config/quickshell/ii/modules/verticalBar/VerticalBarContent.qml b/dots/.config/quickshell/ii/modules/verticalBar/VerticalBarContent.qml index 46ae164ca..ac6be80cd 100644 --- a/dots/.config/quickshell/ii/modules/verticalBar/VerticalBarContent.qml +++ b/dots/.config/quickshell/ii/modules/verticalBar/VerticalBarContent.qml @@ -8,7 +8,7 @@ import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions -import qs.modules.verticalBar.bar as Bar +import qs.modules.bar as Bar Item { // Bar content region id: root diff --git a/dots/.config/quickshell/ii/modules/verticalBar/VerticalClockWidget.qml b/dots/.config/quickshell/ii/modules/verticalBar/VerticalClockWidget.qml index 0aabf9ecf..391d2e78c 100644 --- a/dots/.config/quickshell/ii/modules/verticalBar/VerticalClockWidget.qml +++ b/dots/.config/quickshell/ii/modules/verticalBar/VerticalClockWidget.qml @@ -3,7 +3,7 @@ import qs.modules.common.widgets import qs.services import QtQuick import QtQuick.Layouts -import qs.modules.verticalBar.bar as Bar +import qs.modules.bar as Bar Item { id: root diff --git a/dots/.config/quickshell/ii/modules/verticalBar/VerticalDateWidget.qml b/dots/.config/quickshell/ii/modules/verticalBar/VerticalDateWidget.qml index 07255de78..aaf17ca4f 100644 --- a/dots/.config/quickshell/ii/modules/verticalBar/VerticalDateWidget.qml +++ b/dots/.config/quickshell/ii/modules/verticalBar/VerticalDateWidget.qml @@ -4,7 +4,7 @@ import qs.services import QtQuick import QtQuick.Shapes import QtQuick.Layouts -import qs.modules.verticalBar.bar as Bar +import qs.modules.bar as Bar Item { // Full hitbox id: root diff --git a/dots/.config/quickshell/ii/modules/verticalBar/VerticalMedia.qml b/dots/.config/quickshell/ii/modules/verticalBar/VerticalMedia.qml index 81786be7a..7a512564a 100644 --- a/dots/.config/quickshell/ii/modules/verticalBar/VerticalMedia.qml +++ b/dots/.config/quickshell/ii/modules/verticalBar/VerticalMedia.qml @@ -8,7 +8,7 @@ import QtQuick import QtQuick.Layouts import Quickshell.Services.Mpris -import qs.modules.verticalBar.bar as Bar +import qs.modules.bar as Bar MouseArea { id: root From fddb7ecc056f9c3f51d244df1d769e74c81f4ce4 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 21 Oct 2025 22:58:26 +0200 Subject: [PATCH 33/53] remove redundant imports --- .../quickshell/ii/modules/common/functions/Fuzzy.qml | 2 +- .../quickshell/ii/modules/common/functions/Levendist.qml | 2 +- .../ii/modules/common/widgets/NotificationAppIcon.qml | 2 +- .../ii/modules/common/widgets/NotificationGroup.qml | 2 +- .../quickshell/ii/modules/regionSelector/TargetRegion.qml | 8 -------- .../ii/modules/sidebarLeft/anime/BooruResponse.qml | 1 - .../ii/modules/sidebarRight/calendar/CalendarWidget.qml | 2 +- 7 files changed, 5 insertions(+), 14 deletions(-) diff --git a/dots/.config/quickshell/ii/modules/common/functions/Fuzzy.qml b/dots/.config/quickshell/ii/modules/common/functions/Fuzzy.qml index 7a132ada1..00891ed36 100644 --- a/dots/.config/quickshell/ii/modules/common/functions/Fuzzy.qml +++ b/dots/.config/quickshell/ii/modules/common/functions/Fuzzy.qml @@ -1,6 +1,6 @@ pragma Singleton import Quickshell -import "./fuzzysort.js" as FuzzySort +import "fuzzysort.js" as FuzzySort /** * Wrapper for FuzzySort to play nicely with Quickshell's imports diff --git a/dots/.config/quickshell/ii/modules/common/functions/Levendist.qml b/dots/.config/quickshell/ii/modules/common/functions/Levendist.qml index a327c3c78..0d6a37481 100644 --- a/dots/.config/quickshell/ii/modules/common/functions/Levendist.qml +++ b/dots/.config/quickshell/ii/modules/common/functions/Levendist.qml @@ -1,6 +1,6 @@ pragma Singleton import Quickshell -import "./levendist.js" as Levendist +import "levendist.js" as Levendist /** * Wrapper for levendist.js to play nicely with Quickshell's imports diff --git a/dots/.config/quickshell/ii/modules/common/widgets/NotificationAppIcon.qml b/dots/.config/quickshell/ii/modules/common/widgets/NotificationAppIcon.qml index 9155bd879..8635f4f78 100644 --- a/dots/.config/quickshell/ii/modules/common/widgets/NotificationAppIcon.qml +++ b/dots/.config/quickshell/ii/modules/common/widgets/NotificationAppIcon.qml @@ -1,5 +1,5 @@ import qs.modules.common -import "./notification_utils.js" as NotificationUtils +import "notification_utils.js" as NotificationUtils import Qt5Compat.GraphicalEffects import QtQuick import Quickshell diff --git a/dots/.config/quickshell/ii/modules/common/widgets/NotificationGroup.qml b/dots/.config/quickshell/ii/modules/common/widgets/NotificationGroup.qml index 6e8e1cecc..b4c96978b 100644 --- a/dots/.config/quickshell/ii/modules/common/widgets/NotificationGroup.qml +++ b/dots/.config/quickshell/ii/modules/common/widgets/NotificationGroup.qml @@ -1,7 +1,7 @@ import qs.services import qs.modules.common import qs.modules.common.functions -import "./notification_utils.js" as NotificationUtils +import "notification_utils.js" as NotificationUtils import QtQuick import QtQuick.Layouts import Quickshell diff --git a/dots/.config/quickshell/ii/modules/regionSelector/TargetRegion.qml b/dots/.config/quickshell/ii/modules/regionSelector/TargetRegion.qml index 49a755ab0..627e84c70 100644 --- a/dots/.config/quickshell/ii/modules/regionSelector/TargetRegion.qml +++ b/dots/.config/quickshell/ii/modules/regionSelector/TargetRegion.qml @@ -1,18 +1,10 @@ pragma ComponentBehavior: Bound -import qs import qs.modules.common -import qs.modules.common.functions import qs.modules.common.widgets import qs.services import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import Qt5Compat.GraphicalEffects import Quickshell -import Quickshell.Io -import Quickshell.Wayland import Quickshell.Widgets -import Quickshell.Hyprland Rectangle { id: regionRect diff --git a/dots/.config/quickshell/ii/modules/sidebarLeft/anime/BooruResponse.qml b/dots/.config/quickshell/ii/modules/sidebarLeft/anime/BooruResponse.qml index 7c9f7fd09..9258dbc9d 100644 --- a/dots/.config/quickshell/ii/modules/sidebarLeft/anime/BooruResponse.qml +++ b/dots/.config/quickshell/ii/modules/sidebarLeft/anime/BooruResponse.qml @@ -4,7 +4,6 @@ import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import qs.modules.sidebarLeft -import qs.services import QtQuick import QtQuick.Controls import QtQuick.Layouts diff --git a/dots/.config/quickshell/ii/modules/sidebarRight/calendar/CalendarWidget.qml b/dots/.config/quickshell/ii/modules/sidebarRight/calendar/CalendarWidget.qml index 66055acae..002a9e31f 100644 --- a/dots/.config/quickshell/ii/modules/sidebarRight/calendar/CalendarWidget.qml +++ b/dots/.config/quickshell/ii/modules/sidebarRight/calendar/CalendarWidget.qml @@ -1,7 +1,7 @@ import qs.services import qs.modules.common import qs.modules.common.widgets -import "./calendar_layout.js" as CalendarLayout +import "calendar_layout.js" as CalendarLayout import QtQuick import QtQuick.Layouts From 8e222eb40db88997283d95f5b1567b06a04b7551 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 21 Oct 2025 23:05:01 +0200 Subject: [PATCH 34/53] update qs version --- sdist/arch/illogical-impulse-quickshell-git/PKGBUILD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdist/arch/illogical-impulse-quickshell-git/PKGBUILD b/sdist/arch/illogical-impulse-quickshell-git/PKGBUILD index f84d63079..f018b1564 100644 --- a/sdist/arch/illogical-impulse-quickshell-git/PKGBUILD +++ b/sdist/arch/illogical-impulse-quickshell-git/PKGBUILD @@ -1,4 +1,4 @@ -_commit='00858812f25b748d08b075a0d284093685fa3ffd' +_commit='3e2ce40b18af943f9ba370ed73565e9f487663ef' # Useful links: # https://git.outfoxxed.me/quickshell/quickshell/commits/branch/master # https://aur.archlinux.org/packages/quickshell-git From 0718546167fc7c1386c0c10a529fba328ac8044f Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 22 Oct 2025 01:27:29 +0200 Subject: [PATCH 35/53] refractor icon toolbar buttons --- .../common/widgets/IconToolbarButton.qml | 21 +++++++++++ .../ii/modules/lock/LockSurface.qml | 26 +++----------- .../WallpaperSelectorContent.qml | 36 +++++-------------- 3 files changed, 33 insertions(+), 50 deletions(-) create mode 100644 dots/.config/quickshell/ii/modules/common/widgets/IconToolbarButton.qml diff --git a/dots/.config/quickshell/ii/modules/common/widgets/IconToolbarButton.qml b/dots/.config/quickshell/ii/modules/common/widgets/IconToolbarButton.qml new file mode 100644 index 000000000..8532d0cd4 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/widgets/IconToolbarButton.qml @@ -0,0 +1,21 @@ +import QtQuick +import QtQuick.Layouts +import qs.modules.common + +ToolbarButton { + id: iconBtn + implicitWidth: height + + colBackgroundToggled: Appearance.colors.colSecondaryContainer + colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover + colRippleToggled: Appearance.colors.colSecondaryContainerActive + + contentItem: MaterialSymbol { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + iconSize: 22 + text: iconBtn.text + color: iconBtn.toggled ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnSurfaceVariant + } +} diff --git a/dots/.config/quickshell/ii/modules/lock/LockSurface.qml b/dots/.config/quickshell/ii/modules/lock/LockSurface.qml index 7a4340705..5feba6c72 100644 --- a/dots/.config/quickshell/ii/modules/lock/LockSurface.qml +++ b/dots/.config/quickshell/ii/modules/lock/LockSurface.qml @@ -234,26 +234,26 @@ MouseArea { color: (Battery.isLow && !Battery.isCharging) ? Appearance.colors.colError : Appearance.colors.colOnSurfaceVariant } - ActionToolbarIconButton { + IconToolbarButton { id: sleepButton onClicked: Session.suspend() text: "dark_mode" } - PasswordGuardedActionToolbarIconButton { + PasswordGuardedIconToolbarButton { id: powerButton text: "power_settings_new" targetAction: LockContext.ActionEnum.Poweroff } - PasswordGuardedActionToolbarIconButton { + PasswordGuardedIconToolbarButton { id: rebootButton text: "restart_alt" targetAction: LockContext.ActionEnum.Reboot } } - component PasswordGuardedActionToolbarIconButton: ActionToolbarIconButton { + component PasswordGuardedIconToolbarButton: IconToolbarButton { id: guardedBtn required property var targetAction @@ -273,24 +273,6 @@ MouseArea { } } - component ActionToolbarIconButton: ToolbarButton { - id: iconBtn - implicitWidth: height - - colBackgroundToggled: Appearance.colors.colSecondaryContainer - colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover - colRippleToggled: Appearance.colors.colSecondaryContainerActive - - contentItem: MaterialSymbol { - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - iconSize: 24 - text: iconBtn.text - color: iconBtn.toggled ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnSurfaceVariant - } - } - component IconAndTextPair: Row { id: pair required property string icon diff --git a/dots/.config/quickshell/ii/modules/wallpaperSelector/WallpaperSelectorContent.qml b/dots/.config/quickshell/ii/modules/wallpaperSelector/WallpaperSelectorContent.qml index 9d89dfd7d..a093d38ca 100644 --- a/dots/.config/quickshell/ii/modules/wallpaperSelector/WallpaperSelectorContent.qml +++ b/dots/.config/quickshell/ii/modules/wallpaperSelector/WallpaperSelectorContent.qml @@ -316,7 +316,7 @@ MouseArea { bottomMargin: 8 } - ToolbarButton { + IconToolbarButton { implicitWidth: height onClicked: { Wallpapers.openFallbackPicker(root.useDarkMode); @@ -327,42 +327,27 @@ MouseArea { GlobalStates.wallpaperSelectorOpen = false; Config.options.wallpaperSelector.useSystemFileDialog = true } - contentItem: MaterialSymbol { - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - text: "open_in_new" - iconSize: Appearance.font.pixelSize.larger - } + text: "open_in_new" StyledToolTip { text: Translation.tr("Use the system file picker instead\nRight-click to make this the default behavior") } } - ToolbarButton { + IconToolbarButton { implicitWidth: height onClicked: { Wallpapers.randomFromCurrentFolder(); } - contentItem: MaterialSymbol { - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - text: "ifl" - iconSize: Appearance.font.pixelSize.larger - } + text: "ifl" StyledToolTip { text: Translation.tr("Pick random from this folder") } } - ToolbarButton { + IconToolbarButton { implicitWidth: height onClicked: root.useDarkMode = !root.useDarkMode - contentItem: MaterialSymbol { - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - text: root.useDarkMode ? "dark_mode" : "light_mode" - iconSize: Appearance.font.pixelSize.larger - } + text: root.useDarkMode ? "dark_mode" : "light_mode" StyledToolTip { text: Translation.tr("Click to toggle light/dark mode\n(applied when wallpaper is chosen)") } @@ -403,17 +388,12 @@ MouseArea { } } - ToolbarButton { + IconToolbarButton { implicitWidth: height onClicked: { GlobalStates.wallpaperSelectorOpen = false; } - contentItem: MaterialSymbol { - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - text: "cancel_presentation" - iconSize: Appearance.font.pixelSize.larger - } + text: "close" StyledToolTip { text: Translation.tr("Cancel wallpaper selection") } From bce8b6f9a83e01577acecdb73466c9f0222282dc Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 22 Oct 2025 01:28:11 +0200 Subject: [PATCH 36/53] make sidebar classic toggle button icons have size consistent with toolbar --- .../quickToggles/classicStyle/QuickToggleButton.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/classicStyle/QuickToggleButton.qml b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/classicStyle/QuickToggleButton.qml index 25a53de1a..11ca7cb97 100644 --- a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/classicStyle/QuickToggleButton.qml +++ b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/classicStyle/QuickToggleButton.qml @@ -14,7 +14,7 @@ GroupButton { contentItem: MaterialSymbol { anchors.centerIn: parent - iconSize: 20 + iconSize: 22 fill: toggled ? 1 : 0 color: toggled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer1 horizontalAlignment: Text.AlignHCenter From 7e4cbaf5dfceaeae1999dbc5b369d943c40538b1 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 22 Oct 2025 01:43:13 +0200 Subject: [PATCH 37/53] revamp region selector --- .../quickshell/ii/modules/common/Config.qml | 19 +- .../widgets/IconAndTextToolbarButton.qml | 33 +++ .../regionSelector/CircleSelectionDetails.qml | 3 +- .../regionSelector/RegionSelection.qml | 224 ++++++++++-------- .../modules/regionSelector/TargetRegion.qml | 63 ++--- .../ii/modules/settings/InterfaceConfig.qml | 98 ++++++-- .../ii/modules/settings/ServicesConfig.qml | 14 -- 7 files changed, 290 insertions(+), 164 deletions(-) create mode 100644 dots/.config/quickshell/ii/modules/common/widgets/IconAndTextToolbarButton.qml diff --git a/dots/.config/quickshell/ii/modules/common/Config.qml b/dots/.config/quickshell/ii/modules/common/Config.qml index 26a7e48a0..44ae213b3 100644 --- a/dots/.config/quickshell/ii/modules/common/Config.qml +++ b/dots/.config/quickshell/ii/modules/common/Config.qml @@ -350,6 +350,20 @@ Singleton { property real columns: 5 } + property JsonObject regionSelector: JsonObject { + property JsonObject targetRegions: JsonObject { + property bool windows: true + property bool layers: false + property bool content: true + property bool showLabel: false + property real opacity: 0.6 + } + property JsonObject circle: JsonObject { + property int strokeWidth: 6 + property int padding: 40 + } + } + property JsonObject resources: JsonObject { property int updateInterval: 3000 } @@ -371,7 +385,6 @@ Singleton { } property JsonObject imageSearch: JsonObject { property string imageSearchEngineBaseUrl: "https://lens.google.com/uploadbyurl?url=" - property string fileUploadApiEndpoint: "https://uguu.se/upload" property bool useCircleSelection: false } } @@ -460,10 +473,6 @@ Singleton { property int arbitraryRaceConditionDelay: 20 // milliseconds } - property JsonObject screenshotTool: JsonObject { - property bool showContentRegions: true - } - property JsonObject workSafety: JsonObject { property JsonObject enable: JsonObject { property bool wallpaper: true diff --git a/dots/.config/quickshell/ii/modules/common/widgets/IconAndTextToolbarButton.qml b/dots/.config/quickshell/ii/modules/common/widgets/IconAndTextToolbarButton.qml new file mode 100644 index 000000000..45f90f8a1 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/widgets/IconAndTextToolbarButton.qml @@ -0,0 +1,33 @@ +import QtQuick +import QtQuick.Layouts +import qs.modules.common + +ToolbarButton { + id: iconBtn + required property string iconText + + colBackgroundToggled: Appearance.colors.colSecondaryContainer + colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover + colRippleToggled: Appearance.colors.colSecondaryContainerActive + property color colText: toggled ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnSurfaceVariant + + contentItem: Row { + anchors.centerIn: parent + spacing: 6 + + MaterialSymbol { + anchors.verticalCenter: parent.verticalCenter + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + iconSize: 22 + text: iconBtn.iconText + color: iconBtn.colText + } + StyledText { + visible: iconBtn.iconText.length > 0 && iconBtn.text.length > 0 + anchors.verticalCenter: parent.verticalCenter + color: iconBtn.colText + text: iconBtn.text + } + } +} diff --git a/dots/.config/quickshell/ii/modules/regionSelector/CircleSelectionDetails.qml b/dots/.config/quickshell/ii/modules/regionSelector/CircleSelectionDetails.qml index bea353d1f..c0f823fbe 100644 --- a/dots/.config/quickshell/ii/modules/regionSelector/CircleSelectionDetails.qml +++ b/dots/.config/quickshell/ii/modules/regionSelector/CircleSelectionDetails.qml @@ -1,3 +1,4 @@ +import qs.modules.common import qs.modules.common.widgets import QtQuick import QtQuick.Shapes @@ -8,7 +9,7 @@ Item { required property color color required property color overlayColor required property list points - property int strokeWidth: 10 + property int strokeWidth: Config.options.regionSelector.circle.strokeWidth function updatePoints() { if (!root.dragging) return; diff --git a/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml b/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml index b22ce4a58..ce6385331 100644 --- a/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml +++ b/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml @@ -37,16 +37,16 @@ PanelWindow { property string screenshotDir: Directories.screenshotTemp property string imageSearchEngineBaseUrl: Config.options.search.imageSearch.imageSearchEngineBaseUrl - property string fileUploadApiEndpoint: Config.options.search.imageSearch.fileUploadApiEndpoint + property string fileUploadApiEndpoint: "https://uguu.se/upload" property color overlayColor: "#77111111" property color genericContentColor: Qt.alpha(root.overlayColor, 0.9) property color genericContentForeground: "#ddffffff" - property color selectionBorderColor: "#ddf1f1f1" + property color selectionBorderColor: ColorUtils.mix(Appearance.colors.colOnLayer0, Appearance.colors.colSecondary, 0.5) property color selectionFillColor: "#33ffffff" - property color windowBorderColor: "#dda0c0da" - property color windowFillColor: "#22a0c0da" - property color imageBorderColor: "#ddf1d1ff" - property color imageFillColor: "#33f1d1ff" + property color windowBorderColor: Appearance.colors.colSecondary + property color windowFillColor: ColorUtils.transparentize(windowBorderColor, 0.85) + property color imageBorderColor: Appearance.colors.colTertiary + property color imageFillColor: ColorUtils.transparentize(imageBorderColor, 0.85) property color onBorderColor: "#ff000000" readonly property var windows: [...HyprlandData.windowList].sort((a, b) => { // Sort floating=true windows before others @@ -107,10 +107,25 @@ PanelWindow { return offsetAdjustedLayers; } + 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 real targetedRegionX: -1 property real targetedRegionY: -1 property real targetedRegionWidth: 0 property real targetedRegionHeight: 0 + function targetedRegionValid() { + return (root.targetedRegionX >= 0 && root.targetedRegionY >= 0) + } + function setRegionToTargeted() { + root.regionX = root.targetedRegionX; + root.regionY = root.targetedRegionY; + root.regionWidth = root.targetedRegionWidth; + root.regionHeight = root.targetedRegionHeight; + } function intersectionOverUnion(regionA, regionB) { // region: { at: [x, y], size: [w, h] } @@ -265,6 +280,12 @@ PanelWindow { root.dismiss(); } + // Clamp region to screen bounds + root.regionX = Math.max(0, Math.min(root.regionX, root.screen.width - root.regionWidth)); + root.regionY = Math.max(0, Math.min(root.regionY, root.screen.height - root.regionHeight)); + root.regionWidth = Math.max(0, Math.min(root.regionWidth, root.screen.width - root.regionX)); + root.regionHeight = Math.max(0, Math.min(root.regionHeight, root.screen.height - root.regionY)); + // Adjust action if (root.action === RegionSelection.SnipAction.Copy || root.action === RegionSelection.SnipAction.Edit) { root.action = root.mouseButton === Qt.RightButton ? RegionSelection.SnipAction.Edit : RegionSelection.SnipAction.Copy; @@ -335,22 +356,29 @@ PanelWindow { onReleased: (mouse) => { // Circle dragging? if (root.selectionMode === RegionSelection.SelectionMode.Circle) { - const maxX = Math.max(...root.points.map(p => p.x)); - const minX = Math.min(...root.points.map(p => p.x)); - const maxY = Math.max(...root.points.map(p => p.y)); - const minY = Math.min(...root.points.map(p => p.y)); - root.regionX = minX; - root.regionY = minY; - root.regionWidth = maxX - minX; - root.regionHeight = maxY - minY; + 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; + if (root.targetedRegionValid() && imageRegions.find(region => { + return (region.at[0] === root.targetedRegionX + && region.at[1] === root.targetedRegionY + && region.size[0] === root.targetedRegionWidth + && region.size[1] === root.targetedRegionHeight) + })) { + root.setRegionToTargeted(); + } } // Detect if it was a click -> Try to select targeted region - if (root.draggingX === root.dragStartX && root.draggingY === root.dragStartY) { - if (root.targetedRegionX >= 0 && root.targetedRegionY >= 0) { - root.regionX = root.targetedRegionX; - root.regionY = root.targetedRegionY; - root.regionWidth = root.targetedRegionWidth; - root.regionHeight = root.targetedRegionHeight; + else if (root.draggingX === root.dragStartX && root.draggingY === root.dragStartY) { + if (root.targetedRegionValid()) { + root.setRegionToTargeted(); } } root.snip(); @@ -392,80 +420,10 @@ PanelWindow { } } - // Instructions - Rectangle { - z: 9999 - anchors { - top: parent.top - horizontalCenter: parent.horizontalCenter - topMargin: (Appearance.sizes.barHeight - implicitHeight) / 2 - } - - opacity: root.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 - - Row { - id: instructionsRow - anchors.centerIn: parent - spacing: 4 - MaterialSymbol { - id: screenshotRegionIcon - // anchors.centerIn: parent - iconSize: Appearance.font.pixelSize.larger - text: switch(root.selectionMode) { - case RegionSelection.SelectionMode.RectCorners: - return "activity_zone" - break; - case RegionSelection.SelectionMode.Circle: - return "gesture" - break; - default: - return "activity_zone" - } - color: root.genericContentForeground - } - StyledText { - anchors.verticalCenter: parent.verticalCenter - text: { - var instructionText = ""; - var actionText = ""; - if (root.selectionMode === RegionSelection.SelectionMode.RectCorners) { - instructionText = Translation.tr("Drag or click a region"); - } else if (root.selectionMode === RegionSelection.SelectionMode.Circle) { - instructionText = Translation.tr("Circle"); - } - switch (root.action) { - case RegionSelection.SnipAction.Copy: - case RegionSelection.SnipAction.Edit: - actionText = Translation.tr(" | LMB: Copy • RMB: Edit"); - break; - case RegionSelection.SnipAction.Search: - actionText = Translation.tr(" to search"); - break; - default: - actionText = ""; - } - return instructionText + actionText; - } - color: root.genericContentForeground - } - } - } - // Window regions Repeater { model: ScriptModel { - values: root.windowRegions + values: root.enableWindowRegions ? root.windowRegions : [] } delegate: TargetRegion { z: 2 @@ -479,7 +437,7 @@ PanelWindow { colBackground: root.genericContentColor colForeground: root.genericContentForeground - opacity: root.draggedAway ? 0 : 1 + opacity: root.draggedAway ? 0 : root.targetRegionOpacity visible: opacity > 0 Behavior on opacity { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) @@ -500,7 +458,7 @@ PanelWindow { // Layer regions Repeater { model: ScriptModel { - values: root.layerRegions + values: root.enableLayerRegions ? root.layerRegions : [] } delegate: TargetRegion { z: 3 @@ -513,7 +471,7 @@ PanelWindow { colBackground: root.genericContentColor colForeground: root.genericContentForeground - opacity: root.draggedAway ? 0 : 1 + opacity: root.draggedAway ? 0 : root.targetRegionOpacity visible: opacity > 0 Behavior on opacity { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) @@ -531,10 +489,10 @@ PanelWindow { } } - // Image regions + // Content regions Repeater { model: ScriptModel { - values: Config.options.screenshotTool.showContentRegions ? root.imageRegions : [] + values: root.enableContentRegions ? root.imageRegions : [] } delegate: TargetRegion { z: 4 @@ -547,7 +505,7 @@ PanelWindow { colBackground: root.genericContentColor colForeground: root.genericContentForeground - opacity: root.draggedAway ? 0 : 1 + opacity: root.draggedAway ? 0 : root.targetRegionOpacity visible: opacity > 0 Behavior on opacity { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) @@ -560,7 +518,77 @@ PanelWindow { borderColor: root.imageBorderColor fillColor: targeted ? root.imageFillColor : "transparent" border.width: targeted ? 4 : 2 - text: "Content region" + text: Translation.tr("Content region") + } + } + + // Options toolbar + Toolbar { + id: toolbar + z: 9999 + anchors { + horizontalCenter: parent.horizontalCenter + bottom: parent.bottom + bottomMargin: -height + } + opacity: 0 + Connections { + target: root + function onVisibleChanged() { + if (!visible) return; + toolbar.anchors.bottomMargin = 8; + toolbar.opacity = 1; + } + } + Behavior on opacity { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + Behavior on anchors.bottomMargin { + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) + } + + MaterialCookie { + Layout.fillHeight: true + Layout.leftMargin: 2 + Layout.rightMargin: 2 + implicitSize: 36 // Intentionally smaller because this one is brighter than others + sides: 10 + amplitude: implicitSize / 44 + color: Appearance.colors.colPrimary + MaterialSymbol { + anchors.centerIn: parent + iconSize: 22 + color: Appearance.colors.colOnPrimary + text: switch (root.action) { + case RegionSelection.SnipAction.Copy: + case RegionSelection.SnipAction.Edit: + return "content_cut"; + case RegionSelection.SnipAction.Search: + return "image_search"; + default: + return ""; + } + } + } + + IconAndTextToolbarButton { + iconText: "activity_zone" + text: Translation.tr("Rect") + toggled: root.selectionMode === RegionSelection.SelectionMode.RectCorners + onClicked: root.selectionMode = RegionSelection.SelectionMode.RectCorners + } + + IconAndTextToolbarButton { + iconText: "gesture" + text: Translation.tr("Circle") + toggled: root.selectionMode === RegionSelection.SelectionMode.Circle + onClicked: root.selectionMode = RegionSelection.SelectionMode.Circle + } + + IconToolbarButton { + text: "close" + colBackground: Appearance.colors.colLayer3 + onClicked: root.dismiss(); } } } diff --git a/dots/.config/quickshell/ii/modules/regionSelector/TargetRegion.qml b/dots/.config/quickshell/ii/modules/regionSelector/TargetRegion.qml index 627e84c70..f1043d13b 100644 --- a/dots/.config/quickshell/ii/modules/regionSelector/TargetRegion.qml +++ b/dots/.config/quickshell/ii/modules/regionSelector/TargetRegion.qml @@ -7,9 +7,10 @@ import Quickshell import Quickshell.Widgets Rectangle { - id: regionRect + id: root required property color colBackground required property color colForeground + property bool showLabel: Config.options.regionSelector.targetRegions.showLabel property bool showIcon: false property bool targeted: false property color borderColor @@ -22,41 +23,45 @@ Rectangle { border.width: targeted ? 3 : 1 radius: 4 - Rectangle { - id: regionLabelBackground - property real verticalPadding: 5 - property real horizontalPadding: 10 - radius: 10 - color: regionRect.colBackground - border.width: 1 - border.color: Appearance.m3colors.m3outlineVariant + Loader { anchors { top: parent.top left: parent.left - topMargin: regionRect.textPadding - leftMargin: regionRect.textPadding + topMargin: root.textPadding + leftMargin: root.textPadding } - implicitWidth: regionInfoRow.implicitWidth + horizontalPadding * 2 - implicitHeight: regionInfoRow.implicitHeight + verticalPadding * 2 - Row { - id: regionInfoRow - anchors.centerIn: parent - spacing: 4 + + active: root.showLabel + sourceComponent: Rectangle { + property real verticalPadding: 5 + property real horizontalPadding: 10 + radius: 10 + color: root.colBackground + border.width: 1 + border.color: Appearance.m3colors.m3outlineVariant + implicitWidth: regionInfoRow.implicitWidth + horizontalPadding * 2 + implicitHeight: regionInfoRow.implicitHeight + verticalPadding * 2 - Loader { - id: regionIconLoader - active: regionRect.showIcon - visible: active - sourceComponent: IconImage { - implicitSize: Appearance.font.pixelSize.larger - source: Quickshell.iconPath(AppSearch.guessIcon(regionRect.text), "image-missing") + Row { + id: regionInfoRow + anchors.centerIn: parent + spacing: 4 + + Loader { + id: regionIconLoader + active: root.showIcon + visible: active + sourceComponent: IconImage { + implicitSize: Appearance.font.pixelSize.larger + source: Quickshell.iconPath(AppSearch.guessIcon(root.text), "image-missing") + } } - } - StyledText { - id: regionText - text: regionRect.text - color: regionRect.colForeground + StyledText { + id: regionText + text: root.text + color: root.colForeground + } } } } diff --git a/dots/.config/quickshell/ii/modules/settings/InterfaceConfig.qml b/dots/.config/quickshell/ii/modules/settings/InterfaceConfig.qml index 72469843c..ac900ccbb 100644 --- a/dots/.config/quickshell/ii/modules/settings/InterfaceConfig.qml +++ b/dots/.config/quickshell/ii/modules/settings/InterfaceConfig.qml @@ -574,6 +574,87 @@ ContentPage { } } + ContentSection { + icon: "screenshot_frame_2" + title: Translation.tr("Region selector (screen snipping/Google Lens)") + + ContentSubsection { + title: Translation.tr("Hint target regions") + ConfigRow { + ConfigSwitch { + buttonIcon: "select_window" + text: Translation.tr('Windows') + checked: Config.options.regionSelector.targetRegions.windows + onCheckedChanged: { + Config.options.regionSelector.targetRegions.windows = checked; + } + } + ConfigSwitch { + buttonIcon: "right_panel_open" + text: Translation.tr('Layers') + checked: Config.options.regionSelector.targetRegions.layers + onCheckedChanged: { + Config.options.regionSelector.targetRegions.layers = checked; + } + } + ConfigSwitch { + buttonIcon: "nearby" + text: Translation.tr('Content') + checked: Config.options.regionSelector.targetRegions.content + onCheckedChanged: { + Config.options.regionSelector.targetRegions.content = checked; + } + StyledToolTip { + text: Translation.tr("Could be images or parts of the screen that have some containment.\nMight not always be accurate.\nThis is done with an image processing algorithm run locally and no AI is used.") + } + } + } + } + + ContentSubsection { + title: Translation.tr("Google Lens") + + ConfigSelectionArray { + currentValue: Config.options.search.imageSearch.useCircleSelection ? "circle" : "rectangles" + onSelected: newValue => { + Config.options.search.imageSearch.useCircleSelection = (newValue === "circle"); + } + options: [ + { icon: "activity_zone", value: "rectangles", displayName: Translation.tr("Rectangular selection") }, + { icon: "gesture", value: "circle", displayName: Translation.tr("Circle to Search") } + ] + } + } + + ContentSubsection { + title: Translation.tr("Circle selection") + + ConfigSpinBox { + icon: "eraser_size_3" + text: Translation.tr("Stroke width") + value: Config.options.regionSelector.circle.strokeWidth + from: 1 + to: 20 + stepSize: 1 + onValueChanged: { + Config.options.regionSelector.circle.strokeWidth = value; + } + } + + ConfigSpinBox { + icon: "screenshot_frame_2" + text: Translation.tr("Padding") + value: Config.options.regionSelector.circle.padding + from: 0 + to: 100 + stepSize: 5 + onValueChanged: { + Config.options.regionSelector.circle.padding = value; + } + } + } + } + ContentSection { icon: "side_navigation" title: Translation.tr("Sidebars") @@ -848,23 +929,6 @@ ContentPage { } } - ContentSection { - icon: "screenshot_frame_2" - title: Translation.tr("Screenshot tool") - - ConfigSwitch { - buttonIcon: "nearby" - text: Translation.tr('Show regions of potential interest') - checked: Config.options.screenshotTool.showContentRegions - onCheckedChanged: { - Config.options.screenshotTool.showContentRegions = checked; - } - StyledToolTip { - text: Translation.tr("Such regions could be images or parts of the screen that have some containment.\nMight not always be accurate.\nThis is done with an image processing algorithm run locally and no AI is used.") - } - } - } - ContentSection { icon: "wallpaper_slideshow" title: Translation.tr("Wallpaper selector") diff --git a/dots/.config/quickshell/ii/modules/settings/ServicesConfig.qml b/dots/.config/quickshell/ii/modules/settings/ServicesConfig.qml index 8b6c1ff2a..fae5bf4de 100644 --- a/dots/.config/quickshell/ii/modules/settings/ServicesConfig.qml +++ b/dots/.config/quickshell/ii/modules/settings/ServicesConfig.qml @@ -147,19 +147,5 @@ ContentPage { } } } - ContentSubsection { - title: Translation.tr("Google Lens") - - ConfigSelectionArray { - currentValue: Config.options.search.imageSearch.useCircleSelection ? "circle" : "rectangles" - onSelected: newValue => { - Config.options.search.imageSearch.useCircleSelection = (newValue === "circle"); - } - options: [ - { icon: "activity_zone", value: "rectangles", displayName: Translation.tr("Rectangular selection") }, - { icon: "gesture", value: "circle", displayName: Translation.tr("Circle to Search") } - ] - } - } } } From ed0289df3bab18b47954a5cf171b066662f06f76 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 22 Oct 2025 01:46:52 +0200 Subject: [PATCH 38/53] adjust screenshot regions opacity --- dots/.config/quickshell/ii/modules/common/Config.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dots/.config/quickshell/ii/modules/common/Config.qml b/dots/.config/quickshell/ii/modules/common/Config.qml index 44ae213b3..aab7931fb 100644 --- a/dots/.config/quickshell/ii/modules/common/Config.qml +++ b/dots/.config/quickshell/ii/modules/common/Config.qml @@ -356,7 +356,7 @@ Singleton { property bool layers: false property bool content: true property bool showLabel: false - property real opacity: 0.6 + property real opacity: 0.3 } property JsonObject circle: JsonObject { property int strokeWidth: 6 From c4f81e7027c20d71da2863ea1f5b55c9ddd89e2d Mon Sep 17 00:00:00 2001 From: clsty Date: Wed, 22 Oct 2025 08:23:45 +0800 Subject: [PATCH 39/53] Update quickshell version (gentoo) --- .../illogical-impulse-quickshell-git-9999-r1.ebuild | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdist/gentoo/illogical-impulse-quickshell-git/illogical-impulse-quickshell-git-9999-r1.ebuild b/sdist/gentoo/illogical-impulse-quickshell-git/illogical-impulse-quickshell-git-9999-r1.ebuild index d5106355a..d87712f46 100644 --- a/sdist/gentoo/illogical-impulse-quickshell-git/illogical-impulse-quickshell-git-9999-r1.ebuild +++ b/sdist/gentoo/illogical-impulse-quickshell-git/illogical-impulse-quickshell-git-9999-r1.ebuild @@ -9,7 +9,7 @@ DESCRIPTION="Toolkit for building desktop widgets using QtQuick" HOMEPAGE="https://quickshell.org/" EGIT_REPO_URI="https://github.com/quickshell-mirror/quickshell.git" -EGIT_COMMIT="00858812f25b748d08b075a0d284093685fa3ffd" +EGIT_COMMIT="3e2ce40b18af943f9ba370ed73565e9f487663ef" KEYWORDS="~amd64 ~arm64 ~x86" LICENSE="LGPL-3" From 973d0c0c07582d30372e5a798523b7dd2fc23064 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 22 Oct 2025 09:22:58 +0200 Subject: [PATCH 40/53] make region selector more readable in light theme --- .../quickshell/ii/modules/common/Config.qml | 1 + .../ii/modules/regionSelector/RegionSelection.qml | 14 +++++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/dots/.config/quickshell/ii/modules/common/Config.qml b/dots/.config/quickshell/ii/modules/common/Config.qml index aab7931fb..7d5a93ea4 100644 --- a/dots/.config/quickshell/ii/modules/common/Config.qml +++ b/dots/.config/quickshell/ii/modules/common/Config.qml @@ -357,6 +357,7 @@ Singleton { property bool content: true property bool showLabel: false property real opacity: 0.3 + property real contentRegionOpacity: 0.8 } property JsonObject circle: JsonObject { property int strokeWidth: 6 diff --git a/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml b/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml index ce6385331..a2be366ad 100644 --- a/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml +++ b/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml @@ -38,14 +38,17 @@ PanelWindow { property string screenshotDir: Directories.screenshotTemp property string imageSearchEngineBaseUrl: Config.options.search.imageSearch.imageSearchEngineBaseUrl property string fileUploadApiEndpoint: "https://uguu.se/upload" - property color overlayColor: "#77111111" + property color overlayColor: "#88111111" property color genericContentColor: Qt.alpha(root.overlayColor, 0.9) property color genericContentForeground: "#ddffffff" - property color selectionBorderColor: ColorUtils.mix(Appearance.colors.colOnLayer0, Appearance.colors.colSecondary, 0.5) + property color brightText: Appearance.m3colors.darkmode ? Appearance.colors.colOnLayer0 : Appearance.colors.colLayer0 + property color brightSecondary: Appearance.m3colors.darkmode ? Appearance.colors.colSecondary : Appearance.colors.colOnSecondary + property color brightTertiary: Appearance.m3colors.darkmode ? Appearance.colors.colTertiary : Qt.lighter(Appearance.colors.colPrimary) + property color selectionBorderColor: ColorUtils.mix(brightText, brightSecondary, 0.5) property color selectionFillColor: "#33ffffff" - property color windowBorderColor: Appearance.colors.colSecondary + property color windowBorderColor: brightSecondary property color windowFillColor: ColorUtils.transparentize(windowBorderColor, 0.85) - property color imageBorderColor: Appearance.colors.colTertiary + property color imageBorderColor: brightTertiary property color imageFillColor: ColorUtils.transparentize(imageBorderColor, 0.85) property color onBorderColor: "#ff000000" readonly property var windows: [...HyprlandData.windowList].sort((a, b) => { @@ -112,6 +115,7 @@ PanelWindow { 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 property real targetedRegionX: -1 property real targetedRegionY: -1 @@ -505,7 +509,7 @@ PanelWindow { colBackground: root.genericContentColor colForeground: root.genericContentForeground - opacity: root.draggedAway ? 0 : root.targetRegionOpacity + opacity: root.draggedAway ? 0 : root.contentRegionOpacity visible: opacity > 0 Behavior on opacity { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) From c1393ce7c76cb82c151521d44f7d9643239652c9 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 22 Oct 2025 17:46:30 +0200 Subject: [PATCH 41/53] region selector: animate icon on mode change --- .../quickshell/ii/modules/regionSelector/RegionSelection.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml b/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml index a2be366ad..2a3c024ab 100644 --- a/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml +++ b/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml @@ -563,6 +563,7 @@ PanelWindow { anchors.centerIn: parent iconSize: 22 color: Appearance.colors.colOnPrimary + animateChange: true text: switch (root.action) { case RegionSelection.SnipAction.Copy: case RegionSelection.SnipAction.Edit: From 63be9874f405d74b4b3ebc1d3a2747207420184c Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 22 Oct 2025 17:46:41 +0200 Subject: [PATCH 42/53] systeminfo: add gentoo --- .../ii/assets/icons/gentoo-symbolic.svg | 37 +++++++++++++++++++ .../quickshell/ii/services/SystemInfo.qml | 2 + 2 files changed, 39 insertions(+) create mode 100644 dots/.config/quickshell/ii/assets/icons/gentoo-symbolic.svg diff --git a/dots/.config/quickshell/ii/assets/icons/gentoo-symbolic.svg b/dots/.config/quickshell/ii/assets/icons/gentoo-symbolic.svg new file mode 100644 index 000000000..741be9b6b --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/gentoo-symbolic.svg @@ -0,0 +1,37 @@ + + + + + + diff --git a/dots/.config/quickshell/ii/services/SystemInfo.qml b/dots/.config/quickshell/ii/services/SystemInfo.qml index a8da8e191..1d5c0bf6e 100644 --- a/dots/.config/quickshell/ii/services/SystemInfo.qml +++ b/dots/.config/quickshell/ii/services/SystemInfo.qml @@ -70,6 +70,8 @@ Singleton { case "debian": case "raspbian": case "kali": distroIcon = "debian-symbolic"; break; + case "funtoo": + case "gentoo": distroIcon = "gentoo-symbolic"; break; default: distroIcon = "linux-symbolic"; break; } if (textOsRelease.toLowerCase().includes("nyarch")) { From 0ac96e02ab770a1100046dc47c58a46a22f890ab Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 22 Oct 2025 22:13:23 +0200 Subject: [PATCH 43/53] brightness service: more edging --- dots/.config/quickshell/ii/services/Brightness.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dots/.config/quickshell/ii/services/Brightness.qml b/dots/.config/quickshell/ii/services/Brightness.qml index 3f9d93767..26c7be6cd 100644 --- a/dots/.config/quickshell/ii/services/Brightness.qml +++ b/dots/.config/quickshell/ii/services/Brightness.qml @@ -14,6 +14,7 @@ import QtQuick */ Singleton { id: root + property real minimumBrightnessAllowed: 0.00001 // Setting to 0 would kind of turn off the screen. We don't want that. signal brightnessChanged() @@ -125,7 +126,7 @@ Singleton { } function setBrightness(value: real): void { - value = Math.max(0.01, Math.min(1, value)); + value = Math.max(root.minimumBrightnessAllowed, Math.min(1, value)); monitor.brightness = value; setTimer.restart(); } From 586d7d4f9bb2d4e1581ac43cb75d1516f566daa6 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 22 Oct 2025 22:15:26 +0200 Subject: [PATCH 44/53] sidebar: unfuck non-default quick toggles configurations --- .../ii/modules/sidebarRight/quickToggles/AndroidQuickPanel.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/AndroidQuickPanel.qml b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/AndroidQuickPanel.qml index f307c7a51..a3c94d7b7 100644 --- a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/AndroidQuickPanel.qml +++ b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/AndroidQuickPanel.qml @@ -23,7 +23,7 @@ AbstractQuickPanel { readonly property list availableToggleTypes: ["network", "bluetooth", "idleInhibitor", "easyEffects", "nightLight", "darkMode", "cloudflareWarp", "gameMode", "screenSnip", "colorPicker", "onScreenKeyboard", "mic", "audio", "notifications", "powerProfile"] readonly property int columns: Config.options.sidebar.quickToggles.android.columns - readonly property list toggles: Config.options.sidebar.quickToggles.android.toggles + readonly property list toggles: Config.ready ? Config.options.sidebar.quickToggles.android.toggles : [] readonly property list toggleRows: toggleRowsForList(toggles) readonly property list unusedToggles: { const types = availableToggleTypes.filter(type => !toggles.some(toggle => (toggle && toggle.type === type))) From 6ed9c9869e24d02aad9944d37ac2c91ff4d5aa06 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 22 Oct 2025 22:16:45 +0200 Subject: [PATCH 45/53] styledslider: make marks with any range --- .../quickshell/ii/modules/common/widgets/StyledSlider.qml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dots/.config/quickshell/ii/modules/common/widgets/StyledSlider.qml b/dots/.config/quickshell/ii/modules/common/widgets/StyledSlider.qml index 5473d4628..a3fe23387 100644 --- a/dots/.config/quickshell/ii/modules/common/widgets/StyledSlider.qml +++ b/dots/.config/quickshell/ii/modules/common/widgets/StyledSlider.qml @@ -73,12 +73,13 @@ Slider { component TrackDot: Rectangle { required property real value + property real normalizedValue: (value - root.from) / (root.to - root.from) anchors.verticalCenter: parent.verticalCenter - x: root.handleMargins + (value * root.effectiveDraggingWidth) - (root.trackDotSize / 2) + x: root.handleMargins + (normalizedValue * root.effectiveDraggingWidth) - (root.trackDotSize / 2) width: root.trackDotSize height: root.trackDotSize radius: Appearance.rounding.full - color: value > root.visualPosition ? root.dotColor : root.dotColorHighlighted + color: normalizedValue > root.visualPosition ? root.dotColor : root.dotColorHighlighted Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) From bb08c61b7628e079edf1803fb438802ee2f8b7a3 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 22 Oct 2025 22:16:58 +0200 Subject: [PATCH 46/53] sidebar: night light dialog --- .../modules/common/widgets/ConfigSwitch.qml | 2 + .../common/widgets/WindowDialogSlider.qml | 43 ++++++ .../sidebarRight/SidebarRightContent.qml | 60 +++++---- .../nightLight/NightLightDialog.qml | 125 ++++++++++++++++++ .../quickToggles/AbstractQuickPanel.qml | 5 +- .../quickToggles/AndroidQuickPanel.qml | 29 ++-- .../androidStyle/AndroidNightLightToggle.qml | 4 +- .../AndroidToggleDelegateChooser.qml | 8 +- .../quickshell/ii/services/Hyprsunset.qml | 15 ++- 9 files changed, 244 insertions(+), 47 deletions(-) create mode 100644 dots/.config/quickshell/ii/modules/common/widgets/WindowDialogSlider.qml create mode 100644 dots/.config/quickshell/ii/modules/sidebarRight/nightLight/NightLightDialog.qml diff --git a/dots/.config/quickshell/ii/modules/common/widgets/ConfigSwitch.qml b/dots/.config/quickshell/ii/modules/common/widgets/ConfigSwitch.qml index 13c43c552..02e0eabad 100644 --- a/dots/.config/quickshell/ii/modules/common/widgets/ConfigSwitch.qml +++ b/dots/.config/quickshell/ii/modules/common/widgets/ConfigSwitch.qml @@ -7,6 +7,7 @@ import QtQuick.Controls RippleButton { id: root property string buttonIcon + property alias iconSize: iconWidget.iconSize Layout.fillWidth: true implicitHeight: contentItem.implicitHeight + 8 * 2 @@ -17,6 +18,7 @@ RippleButton { contentItem: RowLayout { spacing: 10 OptionalMaterialSymbol { + id: iconWidget icon: root.buttonIcon opacity: root.enabled ? 1 : 0.4 iconSize: Appearance.font.pixelSize.larger diff --git a/dots/.config/quickshell/ii/modules/common/widgets/WindowDialogSlider.qml b/dots/.config/quickshell/ii/modules/common/widgets/WindowDialogSlider.qml new file mode 100644 index 000000000..5c6db1443 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/widgets/WindowDialogSlider.qml @@ -0,0 +1,43 @@ +pragma ComponentBehavior: Bound +import qs.modules.common +import qs.modules.common.widgets +import qs.services +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell.Widgets + +Column { + id: root + + property alias text: sliderName.text + property alias from: sliderWidget.from + property alias to: sliderWidget.to + property alias value: sliderWidget.value + property alias tooltipContent: sliderWidget.tooltipContent + property alias stopIndicatorValues: sliderWidget.stopIndicatorValues + + signal moved() + + spacing: -2 + ContentSubsectionLabel { + id: sliderName + visible: text?.length > 0 + text: "" + anchors { + left: parent.left + right: parent.right + } + } + StyledSlider { + id: sliderWidget + anchors { + left: parent.left + right: parent.right + leftMargin: 4 + rightMargin: leftMargin + } + configuration: StyledSlider.Configuration.S + onMoved: root.moved() + } +} diff --git a/dots/.config/quickshell/ii/modules/sidebarRight/SidebarRightContent.qml b/dots/.config/quickshell/ii/modules/sidebarRight/SidebarRightContent.qml index cf228d365..f2aee10fe 100644 --- a/dots/.config/quickshell/ii/modules/sidebarRight/SidebarRightContent.qml +++ b/dots/.config/quickshell/ii/modules/sidebarRight/SidebarRightContent.qml @@ -11,19 +11,22 @@ import Quickshell.Hyprland import qs.modules.sidebarRight.quickToggles import qs.modules.sidebarRight.quickToggles.classicStyle -import qs.modules.sidebarRight.wifiNetworks + import qs.modules.sidebarRight.bluetoothDevices +import qs.modules.sidebarRight.nightLight import qs.modules.sidebarRight.volumeMixer +import qs.modules.sidebarRight.wifiNetworks Item { id: root property int sidebarWidth: Appearance.sizes.sidebarWidth property int sidebarPadding: 10 property string settingsQmlPath: Quickshell.shellPath("settings.qml") - property bool showWifiDialog: false - property bool showBluetoothDialog: false property bool showAudioOutputDialog: false property bool showAudioInputDialog: false + property bool showBluetoothDialog: false + property bool showNightLightDialog: false + property bool showWifiDialog: false property bool editMode: false Connections { @@ -109,18 +112,20 @@ Item { } ToggleDialog { - id: wifiDialogLoader - shownPropertyString: "showWifiDialog" - dialog: WifiDialog {} - onShownChanged: { - if (!shown) return; - Network.enableWifi(); - Network.rescanWifi(); + shownPropertyString: "showAudioOutputDialog" + dialog: VolumeDialog { + isSink: true + } + } + + ToggleDialog { + shownPropertyString: "showAudioInputDialog" + dialog: VolumeDialog { + isSink: false } } ToggleDialog { - id: bluetoothDialogLoader shownPropertyString: "showBluetoothDialog" dialog: BluetoothDialog {} onShownChanged: { @@ -130,23 +135,21 @@ Item { Bluetooth.defaultAdapter.enabled = true; Bluetooth.defaultAdapter.discovering = true; } - } } ToggleDialog { - id: audioOutputDialogLoader - shownPropertyString: "showAudioOutputDialog" - dialog: VolumeDialog { - isSink: true - } + shownPropertyString: "showNightLightDialog" + dialog: NightLightDialog {} } ToggleDialog { - id: audioInputDialogLoader - shownPropertyString: "showAudioInputDialog" - dialog: VolumeDialog { - isSink: false + shownPropertyString: "showWifiDialog" + dialog: WifiDialog {} + onShownChanged: { + if (!shown) return; + Network.enableWifi(); + Network.rescanWifi(); } } @@ -186,18 +189,21 @@ Item { active: Config.options.sidebar.quickToggles.style === styleName Connections { target: quickPanelImplLoader.item - function onOpenWifiDialog() { - root.showWifiDialog = true; - } - function onOpenBluetoothDialog() { - root.showBluetoothDialog = true; - } function onOpenAudioOutputDialog() { root.showAudioOutputDialog = true; } function onOpenAudioInputDialog() { root.showAudioInputDialog = true; } + function onOpenBluetoothDialog() { + root.showBluetoothDialog = true; + } + function onOpenNightLightDialog() { + root.showNightLightDialog = true; + } + function onOpenWifiDialog() { + root.showWifiDialog = true; + } } } diff --git a/dots/.config/quickshell/ii/modules/sidebarRight/nightLight/NightLightDialog.qml b/dots/.config/quickshell/ii/modules/sidebarRight/nightLight/NightLightDialog.qml new file mode 100644 index 000000000..25fb120f0 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/sidebarRight/nightLight/NightLightDialog.qml @@ -0,0 +1,125 @@ +import qs +import qs.services +import qs.modules.common +import qs.modules.common.widgets +import qs.modules.common.functions +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell.Io +import Quickshell +import Quickshell.Wayland +import Quickshell.Hyprland + +WindowDialog { + id: root + property var screen: root.QsWindow.window?.screen + property var brightnessMonitor: Brightness.getMonitorForScreen(screen) + + WindowDialogTitle { + text: Translation.tr("Eye protection") + } + + WindowDialogSectionHeader { + text: Translation.tr("Night Light") + } + + WindowDialogSeparator { + Layout.topMargin: -22 + Layout.leftMargin: 0 + Layout.rightMargin: 0 + } + + Column { + id: nightLightColumn + Layout.topMargin: -16 + Layout.fillWidth: true + Layout.fillHeight: true + + ConfigSwitch { + anchors { + left: parent.left + right: parent.right + } + iconSize: Appearance.font.pixelSize.larger + buttonIcon: "lightbulb" + text: Translation.tr("Enable now") + checked: Hyprsunset.active + onCheckedChanged: { + Hyprsunset.toggle(checked) + } + } + + ConfigSwitch { + anchors { + left: parent.left + right: parent.right + } + iconSize: Appearance.font.pixelSize.larger + buttonIcon: "night_sight_auto" + text: Translation.tr("Automatic") + checked: Config.options.light.night.automatic + onCheckedChanged: { + Config.options.light.night.automatic = checked; + } + } + + WindowDialogSlider { + anchors { + left: parent.left + right: parent.right + leftMargin: 4 + rightMargin: 4 + } + text: Translation.tr("Color temperature") + from: 1000 + to: 20000 + stopIndicatorValues: [6000, to] + value: Config.options.light.night.colorTemperature + onMoved: Config.options.light.night.colorTemperature = value + tooltipContent: `${Math.round(value)}K` + } + } + + WindowDialogSectionHeader { + text: Translation.tr("Brightness") + } + + WindowDialogSeparator { + Layout.topMargin: -22 + Layout.leftMargin: 0 + Layout.rightMargin: 0 + } + + Column { + id: brightnessColumn + Layout.topMargin: -16 + Layout.fillWidth: true + // Layout.fillHeight: true + + WindowDialogSlider { + anchors { + left: parent.left + right: parent.right + leftMargin: 4 + rightMargin: 4 + } + // text: Translation.tr("Brightness") + value: root.brightnessMonitor.brightness + onMoved: root.brightnessMonitor.setBrightness(value) + } + } + + WindowDialogButtonRow { + Layout.fillWidth: true + + Item { + Layout.fillWidth: true + } + + DialogButton { + buttonText: Translation.tr("Done") + onClicked: root.dismiss() + } + } +} diff --git a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/AbstractQuickPanel.qml b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/AbstractQuickPanel.qml index 6f2861409..c6ef4b9c8 100644 --- a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/AbstractQuickPanel.qml +++ b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/AbstractQuickPanel.qml @@ -7,8 +7,9 @@ Rectangle { radius: Appearance.rounding.normal color: Appearance.colors.colLayer1 - signal openWifiDialog() - signal openBluetoothDialog() signal openAudioOutputDialog() signal openAudioInputDialog() + signal openBluetoothDialog() + signal openNightLightDialog() + signal openWifiDialog() } diff --git a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/AndroidQuickPanel.qml b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/AndroidQuickPanel.qml index a3c94d7b7..353107822 100644 --- a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/AndroidQuickPanel.qml +++ b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/AndroidQuickPanel.qml @@ -12,15 +12,23 @@ AbstractQuickPanel { id: root property bool editMode: false Layout.fillWidth: true - implicitHeight: (editMode ? contentItem.implicitHeight : usedRows.implicitHeight) + root.padding * 2 + // Sizes + implicitHeight: (editMode ? contentItem.implicitHeight : usedRows.implicitHeight) + root.padding * 2 Behavior on implicitHeight { animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } - property real spacing: 6 property real padding: 6 + readonly property real baseCellWidth: { + // This is the wrong calculation, but it looks correct in reality??? + // (theoretically spacing should be multiplied by 1 column less) + const availableWidth = root.width - (root.padding * 2) - (root.spacing * (root.columns)) + return availableWidth / root.columns + } + readonly property real baseCellHeight: 56 + // Toggles readonly property list availableToggleTypes: ["network", "bluetooth", "idleInhibitor", "easyEffects", "nightLight", "darkMode", "cloudflareWarp", "gameMode", "screenSnip", "colorPicker", "onScreenKeyboard", "mic", "audio", "notifications", "powerProfile"] readonly property int columns: Config.options.sidebar.quickToggles.android.columns readonly property list toggles: Config.ready ? Config.options.sidebar.quickToggles.android.toggles : [] @@ -30,13 +38,6 @@ AbstractQuickPanel { return types.map(type => { return { type: type, size: 1 } }) } readonly property list unusedToggleRows: toggleRowsForList(unusedToggles) - readonly property real baseCellWidth: { - // This is the wrong calculation, but it looks correct in reality??? - // (theoretically spacing should be multiplied by 1 column less) - const availableWidth = root.width - (root.padding * 2) - (root.spacing * (root.columns)) - return availableWidth / root.columns - } - readonly property real baseCellHeight: 56 function toggleRowsForList(togglesList) { var rows = []; @@ -78,7 +79,10 @@ AbstractQuickPanel { delegate: ButtonGroup { id: toggleRow required property int index - property var modelData: root.toggleRows[index] + property var modelData: { + print(JSON.stringify(root.toggleRows[index])) + return root.toggleRows[index] + } property int startingIndex: { const rows = root.toggleRows; let sum = 0; @@ -100,10 +104,11 @@ AbstractQuickPanel { baseCellWidth: root.baseCellWidth baseCellHeight: root.baseCellHeight spacing: root.spacing - onOpenWifiDialog: root.openWifiDialog() - onOpenBluetoothDialog: root.openBluetoothDialog() onOpenAudioOutputDialog: root.openAudioOutputDialog() onOpenAudioInputDialog: root.openAudioInputDialog() + onOpenBluetoothDialog: root.openBluetoothDialog() + onOpenNightLightDialog: root.openNightLightDialog() + onOpenWifiDialog: root.openWifiDialog() } } } diff --git a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidNightLightToggle.qml b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidNightLightToggle.qml index 378dd0284..699d93dc3 100644 --- a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidNightLightToggle.qml +++ b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidNightLightToggle.qml @@ -19,7 +19,7 @@ AndroidQuickToggleButton { } altAction: () => { - Config.options.light.night.automatic = !Config.options.light.night.automatic + root.openMenu() } Component.onCompleted: { @@ -27,7 +27,7 @@ AndroidQuickToggleButton { } StyledToolTip { - text: Translation.tr("Night Light | Right-click to toggle Auto mode") + text: Translation.tr("Night Light | Right-click to configure") } } diff --git a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidToggleDelegateChooser.qml b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidToggleDelegateChooser.qml index 2cfaa9585..a8f79c843 100644 --- a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidToggleDelegateChooser.qml +++ b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidToggleDelegateChooser.qml @@ -14,10 +14,11 @@ DelegateChooser { required property real baseCellHeight required property real spacing required property int startingIndex - signal openWifiDialog() - signal openBluetoothDialog() signal openAudioOutputDialog() signal openAudioInputDialog() + signal openBluetoothDialog() + signal openNightLightDialog() + signal openWifiDialog() role: "type" @@ -90,6 +91,9 @@ DelegateChooser { baseCellHeight: root.baseCellHeight cellSpacing: root.spacing cellSize: modelData.size + onOpenMenu: { + root.openNightLightDialog() + } } } DelegateChoice { roleValue: "darkMode"; AndroidDarkModeToggle { diff --git a/dots/.config/quickshell/ii/services/Hyprsunset.qml b/dots/.config/quickshell/ii/services/Hyprsunset.qml index 2f7b3569f..2642025ad 100644 --- a/dots/.config/quickshell/ii/services/Hyprsunset.qml +++ b/dots/.config/quickshell/ii/services/Hyprsunset.qml @@ -4,6 +4,7 @@ import QtQuick import qs.modules.common import Quickshell import Quickshell.Io +import Quickshell.Hyprland /** * Simple hyprsunset service with automatic mode. @@ -111,18 +112,28 @@ Singleton { } } - function toggle() { + function toggle(active = undefined) { if (root.manualActive === undefined) { root.manualActive = root.active; root.manualActiveHour = root.clockHour; root.manualActiveMinute = root.clockMinute; } - root.manualActive = !root.manualActive; + root.manualActive = active !== undefined ? active : !root.manualActive; if (root.manualActive) { root.enable(); } else { root.disable(); } } + + // Change temp + Connections { + target: Config.options.light.night + onColorTemperatureChanged: { + if (!root.active) return; + Hyprland.dispatch(`hyprctl hyprsunset temperature ${Config.options.light.night.colorTemperature}`); + Quickshell.execDetached(["hyprctl", "hyprsunset", "temperature", `${Config.options.light.night.colorTemperature}`]); + } + } } From 064488a9c4fb2ed1291cbe669a56446839bac475 Mon Sep 17 00:00:00 2001 From: "Celestial.y" Date: Thu, 23 Oct 2025 04:36:40 +0800 Subject: [PATCH 47/53] Not overwrite other things under quickshell --- sdata/step/3.install-files.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdata/step/3.install-files.sh b/sdata/step/3.install-files.sh index a0d389570..547e3a30a 100644 --- a/sdata/step/3.install-files.sh +++ b/sdata/step/3.install-files.sh @@ -67,7 +67,7 @@ function ask_backup_configs(){ ##################################################################################### # In case some dirs does not exists -v mkdir -p $XDG_BIN_HOME $XDG_CACHE_HOME $XDG_CONFIG_HOME $XDG_DATA_HOME +v mkdir -p $XDG_BIN_HOME $XDG_CACHE_HOME $XDG_CONFIG_HOME/quickshell $XDG_DATA_HOME case $ask in false) sleep 0 ;; @@ -101,7 +101,7 @@ esac case $SKIP_QUICKSHELL in true) sleep 0;; *) - warning_rsync; v rsync -av --delete dots/.config/quickshell/ "$XDG_CONFIG_HOME"/quickshell/ + warning_rsync; v rsync -av --delete dots/.config/quickshell/ii/ "$XDG_CONFIG_HOME"/quickshell/ii/ ;; esac From 8141e15bd97bdcf6bcd791c5b212bb7d93393f3d Mon Sep 17 00:00:00 2001 From: clsty Date: Thu, 23 Oct 2025 05:20:22 +0800 Subject: [PATCH 48/53] Add subcmds; Not skip backup unless input y/n --- sdata/step/3.install-files.sh | 22 ++++++++++++++-------- setup | 31 ++++++++++++++++++++++++++++++- 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/sdata/step/3.install-files.sh b/sdata/step/3.install-files.sh index 547e3a30a..eaea5017a 100644 --- a/sdata/step/3.install-files.sh +++ b/sdata/step/3.install-files.sh @@ -53,15 +53,21 @@ function ask_backup_configs(){ showfun backup_clashing_targets printf "${STY_RED}" printf "Would you like to backup clashing dirs/files under \"$XDG_CONFIG_HOME\" and \"$XDG_DATA_HOME\" to \"$BACKUP_DIR\"?" - read -p "[y/N] " backup_confirm - case $backup_confirm in - [yY][eE][sS]|[yY]) - backup_clashing_targets dots/.config $XDG_CONFIG_HOME "${BACKUP_DIR}/.config" - backup_clashing_targets dots/.local/share $XDG_DATA_HOME "${BACKUP_DIR}/.local/share" - ;; - *) echo "Skipping backup..." ;; - esac printf "${STY_RST}" + while true;do + echo " y = Yes, backup" + echo " n = No, skip to next" + local p; read -p "====> " p + case $p in + [yY]) echo -e "${STY_BLUE}OK, doing backup...${STY_RST}" ;local backup=true;break ;; + [nN]) echo -e "${STY_BLUE}Alright, skipping...${STY_RST}" ;local backup=false;break ;; + *) echo -e "${STY_RED}Please enter [y/n].${STY_RST}";; + esac + done + if $backup;then + backup_clashing_targets dots/.config $XDG_CONFIG_HOME "${BACKUP_DIR}/.config" + backup_clashing_targets dots/.local/share $XDG_DATA_HOME "${BACKUP_DIR}/.local/share" + fi } ##################################################################################### diff --git a/setup b/setup index 88fb487e1..ebbab3988 100755 --- a/setup +++ b/setup @@ -18,6 +18,9 @@ Syntax: Subcommands: install (Default) Install/Reinstall/Update illogical-impulse. + install-deps Run the install step \"1. Install dependencies\" + install-setups Run the install step \"2. Setup for permissions/services etc\" + install-files Run the install step \"3. Copying config files\" exp-uninstall (Experimental) Uninstall illogical-impulse. exp-update (Experimental) Update illogical-impulse without fully reinstall. help Show this help message. @@ -30,7 +33,8 @@ case $1 in # Global help help|--help|-h)showhelp_global;exit;; # Correct subcommand - install|exp-uninstall|exp-update)SCRIPT_SUBCOMMAND=$1;shift;; + install|install-deps|install-setups|install-files|exp-uninstall|exp-update) + SCRIPT_SUBCOMMAND=$1;shift;; # No subcommand -*|"")SCRIPT_SUBCOMMAND=install;; # Wrong subcommand @@ -60,6 +64,31 @@ case ${SCRIPT_SUBCOMMAND} in fi fi ;; + install-deps) + source ./sdata/options/install.sh + if [[ "${SKIP_ALLDEPS}" != true ]]; then + printf "${STY_CYAN}[$0]: 1. Install dependencies\n${STY_RST}" + source ./sdata/step/1.install-deps-selector.sh + fi + ;; + install-setups) + source ./sdata/options/install.sh + if [[ "${SKIP_ALLSETUPS}" != true ]]; then + printf "${STY_CYAN}[$0]: 2. Setup for permissions/services etc\n${STY_RST}" + source ./sdata/step/2.install-setups-selector.sh + fi + ;; + install-files) + source ./sdata/options/install.sh + if [[ "${SKIP_ALLFILES}" != true ]]; then + printf "${STY_CYAN}[$0]: 3. Copying config files\n${STY_RST}" + if [[ "${EXPERIMENTAL_FILES_SCRIPT}" == true ]]; then + source ./sdata/step/3.install-files.experimental.sh + else + source ./sdata/step/3.install-files.sh + fi + fi + ;; exp-uninstall) source ./sdata/options/exp-uninstall.sh source ./sdata/step/exp-uninstall.sh From a4d2a720d0518b11fdd1b70ef9bfdaafce448d21 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 22 Oct 2025 23:55:39 +0200 Subject: [PATCH 49/53] fix deprecated Connections syntax --- dots/.config/quickshell/ii/services/Hyprsunset.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dots/.config/quickshell/ii/services/Hyprsunset.qml b/dots/.config/quickshell/ii/services/Hyprsunset.qml index 2642025ad..8f0e36529 100644 --- a/dots/.config/quickshell/ii/services/Hyprsunset.qml +++ b/dots/.config/quickshell/ii/services/Hyprsunset.qml @@ -130,7 +130,7 @@ Singleton { // Change temp Connections { target: Config.options.light.night - onColorTemperatureChanged: { + function onColorTemperatureChanged() { if (!root.active) return; Hyprland.dispatch(`hyprctl hyprsunset temperature ${Config.options.light.night.colorTemperature}`); Quickshell.execDetached(["hyprctl", "hyprsunset", "temperature", `${Config.options.light.night.colorTemperature}`]); From b35ef9091638a58eec05e259d2d6747ee80f20c7 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 22 Oct 2025 23:56:50 +0200 Subject: [PATCH 50/53] add anti flashbang could be improve with smooth fading --- .../quickshell/ii/modules/common/Config.qml | 3 + .../modules/common/widgets/WindowDialog.qml | 2 +- .../nightLight/NightLightDialog.qml | 36 +++++++++- .../quickToggles/AndroidQuickPanel.qml | 5 +- .../quickshell/ii/services/Brightness.qml | 67 ++++++++++++++++++- 5 files changed, 105 insertions(+), 8 deletions(-) diff --git a/dots/.config/quickshell/ii/modules/common/Config.qml b/dots/.config/quickshell/ii/modules/common/Config.qml index 7d5a93ea4..54562da02 100644 --- a/dots/.config/quickshell/ii/modules/common/Config.qml +++ b/dots/.config/quickshell/ii/modules/common/Config.qml @@ -303,6 +303,9 @@ Singleton { property string to: "06:30" // Format: "HH:mm", 24-hour time property int colorTemperature: 5000 } + property JsonObject antiFlashbang: JsonObject { + property bool enable: false + } } property JsonObject lock: JsonObject { diff --git a/dots/.config/quickshell/ii/modules/common/widgets/WindowDialog.qml b/dots/.config/quickshell/ii/modules/common/widgets/WindowDialog.qml index 92f6353e3..a0ca64e98 100644 --- a/dots/.config/quickshell/ii/modules/common/widgets/WindowDialog.qml +++ b/dots/.config/quickshell/ii/modules/common/widgets/WindowDialog.qml @@ -50,7 +50,7 @@ Rectangle { property real targetY: root.height / 2 - root.backgroundHeight / 2 y: root.show ? targetY : (targetY - root.backgroundAnimationMovementDistance) implicitWidth: 350 - implicitHeight: 0 + implicitHeight: contentColumn.implicitHeight + dialogBackground.radius * 2 Behavior on implicitHeight { NumberAnimation { id: dialogBackgroundHeightAnimation diff --git a/dots/.config/quickshell/ii/modules/sidebarRight/nightLight/NightLightDialog.qml b/dots/.config/quickshell/ii/modules/sidebarRight/nightLight/NightLightDialog.qml index 25fb120f0..535aded59 100644 --- a/dots/.config/quickshell/ii/modules/sidebarRight/nightLight/NightLightDialog.qml +++ b/dots/.config/quickshell/ii/modules/sidebarRight/nightLight/NightLightDialog.qml @@ -34,7 +34,6 @@ WindowDialog { id: nightLightColumn Layout.topMargin: -16 Layout.fillWidth: true - Layout.fillHeight: true ConfigSwitch { anchors { @@ -81,6 +80,39 @@ WindowDialog { } } + WindowDialogSectionHeader { + text: Translation.tr("Anti-flashbang (experimental)") + } + + WindowDialogSeparator { + Layout.topMargin: -22 + Layout.leftMargin: 0 + Layout.rightMargin: 0 + } + + Column { + id: antiFlashbangColumn + Layout.topMargin: -16 + Layout.fillWidth: true + + ConfigSwitch { + anchors { + left: parent.left + right: parent.right + } + iconSize: Appearance.font.pixelSize.larger + buttonIcon: "destruction" + text: Translation.tr("Enable") + 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") + } + } + } + WindowDialogSectionHeader { text: Translation.tr("Brightness") } @@ -95,7 +127,7 @@ WindowDialog { id: brightnessColumn Layout.topMargin: -16 Layout.fillWidth: true - // Layout.fillHeight: true + Layout.fillHeight: true WindowDialogSlider { anchors { diff --git a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/AndroidQuickPanel.qml b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/AndroidQuickPanel.qml index 353107822..a7d743585 100644 --- a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/AndroidQuickPanel.qml +++ b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/AndroidQuickPanel.qml @@ -79,10 +79,7 @@ AbstractQuickPanel { delegate: ButtonGroup { id: toggleRow required property int index - property var modelData: { - print(JSON.stringify(root.toggleRows[index])) - return root.toggleRows[index] - } + property var modelData: root.toggleRows[index] property int startingIndex: { const rows = root.toggleRows; let sum = 0; diff --git a/dots/.config/quickshell/ii/services/Brightness.qml b/dots/.config/quickshell/ii/services/Brightness.qml index 26c7be6cd..ccc7810e2 100644 --- a/dots/.config/quickshell/ii/services/Brightness.qml +++ b/dots/.config/quickshell/ii/services/Brightness.qml @@ -4,6 +4,8 @@ pragma ComponentBehavior: Bound // From https://github.com/caelestia-dots/shell with modifications. // License: GPLv3 +import qs.modules.common +import qs.modules.common.functions import Quickshell import Quickshell.Io import Quickshell.Hyprland @@ -85,6 +87,8 @@ Singleton { } property int rawMaxBrightness: 100 property real brightness + property real brightnessMultiplier: 1.0 + property real multipliedBrightness: Math.max(0, Math.min(1, brightness * brightnessMultiplier)) property bool ready: false onBrightnessChanged: { @@ -120,7 +124,8 @@ Singleton { } function syncBrightness() { - const rounded = Math.round(monitor.brightness * monitor.rawMaxBrightness); + const brightnessValue = monitor.multipliedBrightness + const rounded = Math.round(brightnessValue * monitor.rawMaxBrightness); setProc.command = isDdc ? ["ddcutil", "-b", busNum, "setvcp", "10", rounded] : ["brightnessctl", "--class", "backlight", "s", rounded, "--quiet"]; setProc.startDetached(); } @@ -131,6 +136,11 @@ Singleton { setTimer.restart(); } + function setBrightnessMultiplier(value: real): void { + monitor.brightnessMultiplier = value; + setTimer.restart(); + } + Component.onCompleted: { initialize(); } @@ -146,6 +156,61 @@ Singleton { BrightnessMonitor {} } + // Anti-flashbang + property string screenshotDir: "/tmp/quickshell/brightness/antiflashbang" + function brightnessMultiplierForLightness(x: real): real { + // 6.600135 + 216.360356 * e^(-0.0811129189x) + // Division by 100 is to normalize to [0, 1] + return (6.600135 + 216.360356 * Math.pow(Math.E, -0.0811129189 * x)) / 100.0; + } + Variants { + model: Quickshell.screens + Scope { + id: screenScope + required property var modelData + property string screenName: modelData.name + property string screenshotPath: `${root.screenshotDir}/screenshot-${screenName}.png` + Connections { + enabled: Config.options.light.antiFlashbang.enable + target: Hyprland + function onRawEvent(event) { + if (["workspacev2"].includes(event.name)) { + screenshotTimer.restart(); + } + } + } + + Timer { + id: screenshotTimer + interval: 700 // This is what I have for a Hyprland ws anim + onTriggered: { + screenshotProc.running = false; + screenshotProc.running = true; + } + } + + Process { + id: screenshotProc + command: ["bash", "-c", + `mkdir -p '${StringUtils.shellSingleQuoteEscape(root.screenshotDir)}'` + + ` && grim -o '${StringUtils.shellSingleQuoteEscape(screenScope.screenName)}' '${StringUtils.shellSingleQuoteEscape(screenScope.screenshotPath)}'` + + ` && magick '${StringUtils.shellSingleQuoteEscape(screenScope.screenshotPath)}' -colorspace Gray -format "%[fx:mean*100]" info:` + ] + stdout: StdioCollector { + id: lightnessCollector + onStreamFinished: { + const lightness = lightnessCollector.text + const newMultiplier = root.brightnessMultiplierForLightness(parseFloat(lightness)) + print(lightness, "->", newMultiplier) + Brightness.getMonitorForScreen(screenScope.modelData).setBrightnessMultiplier(newMultiplier) + } + } + } + } + } + + // External trigger points + IpcHandler { target: "brightness" From 7515f77846fb7e87fabf800434325fe331ef42bc Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 23 Oct 2025 00:05:19 +0200 Subject: [PATCH 51/53] region selector: add option to show/hide coord aim lines --- .../.config/quickshell/ii/modules/common/Config.qml | 5 ++++- .../regionSelector/RectCornersSelectionDetails.qml | 6 ++++++ .../ii/modules/settings/InterfaceConfig.qml | 13 +++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/dots/.config/quickshell/ii/modules/common/Config.qml b/dots/.config/quickshell/ii/modules/common/Config.qml index 54562da02..899271a94 100644 --- a/dots/.config/quickshell/ii/modules/common/Config.qml +++ b/dots/.config/quickshell/ii/modules/common/Config.qml @@ -362,9 +362,12 @@ Singleton { property real opacity: 0.3 property real contentRegionOpacity: 0.8 } + property JsonObject rect: JsonObject { + property bool showAimLines: true + } property JsonObject circle: JsonObject { property int strokeWidth: 6 - property int padding: 40 + property int padding: 30 } } diff --git a/dots/.config/quickshell/ii/modules/regionSelector/RectCornersSelectionDetails.qml b/dots/.config/quickshell/ii/modules/regionSelector/RectCornersSelectionDetails.qml index b4e417c8d..b1956186a 100644 --- a/dots/.config/quickshell/ii/modules/regionSelector/RectCornersSelectionDetails.qml +++ b/dots/.config/quickshell/ii/modules/regionSelector/RectCornersSelectionDetails.qml @@ -1,3 +1,4 @@ +import qs.modules.common import qs.modules.common.widgets import QtQuick @@ -11,6 +12,7 @@ Item { required property real mouseY required property color color required property color overlayColor + property bool showAimLines: Config.options.regionSelector.rect.showAimLines // Overlay to darken screen // Base dark overlay around region @@ -62,6 +64,8 @@ Item { // Coord lines Rectangle { // Vertical + visible: root.showAimLines + opacity: 0.5 z: 2 x: root.mouseX anchors { @@ -72,6 +76,8 @@ Item { color: root.color } Rectangle { // Horizontal + visible: root.showAimLines + opacity: 0.5 z: 2 y: root.mouseY anchors { diff --git a/dots/.config/quickshell/ii/modules/settings/InterfaceConfig.qml b/dots/.config/quickshell/ii/modules/settings/InterfaceConfig.qml index ac900ccbb..bd08cfffd 100644 --- a/dots/.config/quickshell/ii/modules/settings/InterfaceConfig.qml +++ b/dots/.config/quickshell/ii/modules/settings/InterfaceConfig.qml @@ -626,6 +626,19 @@ ContentPage { } } + ContentSubsection { + title: Translation.tr("Rectangular selection") + + ConfigSwitch { + buttonIcon: "point_scan" + text: Translation.tr("Show aim lines") + checked: Config.options.regionSelector.rect.showAimLines + onCheckedChanged: { + Config.options.regionSelector.rect.showAimLines = checked; + } + } + } + ContentSubsection { title: Translation.tr("Circle selection") From c620c11ba8b85e6d1d54394d71e533f6cb4ce616 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 23 Oct 2025 00:06:23 +0200 Subject: [PATCH 52/53] region selector: lower aim lines opacity --- .../ii/modules/regionSelector/RectCornersSelectionDetails.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dots/.config/quickshell/ii/modules/regionSelector/RectCornersSelectionDetails.qml b/dots/.config/quickshell/ii/modules/regionSelector/RectCornersSelectionDetails.qml index b1956186a..40069131d 100644 --- a/dots/.config/quickshell/ii/modules/regionSelector/RectCornersSelectionDetails.qml +++ b/dots/.config/quickshell/ii/modules/regionSelector/RectCornersSelectionDetails.qml @@ -65,7 +65,7 @@ Item { // Coord lines Rectangle { // Vertical visible: root.showAimLines - opacity: 0.5 + opacity: 0.2 z: 2 x: root.mouseX anchors { @@ -77,7 +77,7 @@ Item { } Rectangle { // Horizontal visible: root.showAimLines - opacity: 0.5 + opacity: 0.2 z: 2 y: root.mouseY anchors { From b3cabb788bcd7ed8ee94e697f2a3d119d888c10c Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 23 Oct 2025 00:22:35 +0200 Subject: [PATCH 53/53] anti flashbang: clean up when done --- dots/.config/quickshell/ii/services/Brightness.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dots/.config/quickshell/ii/services/Brightness.qml b/dots/.config/quickshell/ii/services/Brightness.qml index ccc7810e2..a71f8f487 100644 --- a/dots/.config/quickshell/ii/services/Brightness.qml +++ b/dots/.config/quickshell/ii/services/Brightness.qml @@ -199,9 +199,9 @@ Singleton { stdout: StdioCollector { id: lightnessCollector onStreamFinished: { + Quickshell.execDetached(["rm", screenScope.screenshotPath]); // Cleanup const lightness = lightnessCollector.text const newMultiplier = root.brightnessMultiplierForLightness(parseFloat(lightness)) - print(lightness, "->", newMultiplier) Brightness.getMonitorForScreen(screenScope.modelData).setBrightnessMultiplier(newMultiplier) } }