diff --git a/.github/workflows/auto-close-issue.yml b/.github/workflows/auto-close-issue.yml
new file mode 100644
index 000000000..35afe6e4b
--- /dev/null
+++ b/.github/workflows/auto-close-issue.yml
@@ -0,0 +1,167 @@
+on:
+ issues:
+ types: [opened]
+
+name: Close issues when the "ticked without reading" checkbox is checked
+
+permissions:
+ issues: write
+
+jobs:
+ detect-and-close:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Detect checked "ticked without reading" checkbox, comment, close and lock
+ 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..."
+ # 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, close and lock the issue."
+
+ # --- 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}}')
+
+ 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. Aborting."
+ exit 1
+ fi
+ 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. 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}}')
+
+ 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"
+ exit 1
+ fi
+ echo "Comment posted."
+
+ # --- 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')
+
+ CLOSED_OK=false
+
+ if [ -n "$ERR_UPDATE" ]; then
+ echo "GraphQL updateIssue returned errors: $ERR_UPDATE"
+ fi
+
+ 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."
+
+ # 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")
+
+ 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"
+ 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
diff --git a/dots/.config/hypr/hyprland/keybinds.conf b/dots/.config/hypr/hyprland/keybinds.conf
index 38fc05caf..5e6ff1e80 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 # 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
@@ -225,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
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/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/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/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/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/common/Config.qml b/dots/.config/quickshell/ii/modules/common/Config.qml
index 9714b23f4..e10e7ac38 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
@@ -302,6 +304,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 {
@@ -349,6 +354,24 @@ 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.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: 30
+ }
+ }
+
property JsonObject resources: JsonObject {
property int updateInterval: 3000
}
@@ -368,6 +391,10 @@ Singleton {
property string shellCommand: "$"
property string webSearch: "?"
}
+ property JsonObject imageSearch: JsonObject {
+ property string imageSearchEngineBaseUrl: "https://lens.google.com/uploadbyurl?url="
+ property bool useCircleSelection: false
+ }
}
property JsonObject sidebar: JsonObject {
@@ -454,10 +481,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/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/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/GroupButton.qml b/dots/.config/quickshell/ii/modules/common/widgets/GroupButton.qml
index bc02e77e1..ed93eb77f 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)
}
@@ -75,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/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/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/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/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)
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/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/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/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
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/regionSelector/CircleSelectionDetails.qml b/dots/.config/quickshell/ii/modules/regionSelector/CircleSelectionDetails.qml
new file mode 100644
index 000000000..c0f823fbe
--- /dev/null
+++ b/dots/.config/quickshell/ii/modules/regionSelector/CircleSelectionDetails.qml
@@ -0,0 +1,49 @@
+import qs.modules.common
+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: Config.options.regionSelector.circle.strokeWidth
+
+ 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..40069131d
--- /dev/null
+++ b/dots/.config/quickshell/ii/modules/regionSelector/RectCornersSelectionDetails.qml
@@ -0,0 +1,90 @@
+import qs.modules.common
+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
+ property bool showAimLines: Config.options.regionSelector.rect.showAimLines
+
+ // 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
+ visible: root.showAimLines
+ opacity: 0.2
+ z: 2
+ x: root.mouseX
+ anchors {
+ top: parent.top
+ bottom: parent.bottom
+ }
+ width: 1
+ color: root.color
+ }
+ Rectangle { // Horizontal
+ visible: root.showAimLines
+ opacity: 0.2
+ 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..2a3c024ab
--- /dev/null
+++ b/dots/.config/quickshell/ii/modules/regionSelector/RegionSelection.qml
@@ -0,0 +1,601 @@
+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.imageSearch.imageSearchEngineBaseUrl
+ property string fileUploadApiEndpoint: "https://uguu.se/upload"
+ property color overlayColor: "#88111111"
+ property color genericContentColor: Qt.alpha(root.overlayColor, 0.9)
+ property color genericContentForeground: "#ddffffff"
+ 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: brightSecondary
+ property color windowFillColor: ColorUtils.transparentize(windowBorderColor, 0.85)
+ 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) => {
+ // 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 bool isCircleSelection: (root.selectionMode === RegionSelection.SelectionMode.Circle)
+ property bool enableWindowRegions: Config.options.regionSelector.targetRegions.windows && !isCircleSelection
+ property bool enableLayerRegions: Config.options.regionSelector.targetRegions.layers && !isCircleSelection
+ property bool enableContentRegions: Config.options.regionSelector.targetRegions.content
+ property real targetRegionOpacity: Config.options.regionSelector.targetRegions.opacity
+ property bool contentRegionOpacity: Config.options.regionSelector.targetRegions.contentRegionOpacity
+
+ 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] }
+ 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();
+ }
+
+ // 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;
+ }
+
+ // 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 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
+ else if (root.draggingX === root.dragStartX && root.draggingY === root.dragStartY) {
+ if (root.targetedRegionValid()) {
+ root.setRegionToTargeted();
+ }
+ }
+ root.snip();
+ }
+ onPositionChanged: (mouse) => {
+ root.updateTargetedRegion(mouse.x, mouse.y);
+ if (!root.dragging) return;
+ root.draggingX = mouse.x;
+ root.draggingY = mouse.y;
+ root.dragDiffX = mouse.x - root.dragStartX;
+ root.dragDiffY = mouse.y - root.dragStartY;
+ root.points.push({ x: mouse.x, y: mouse.y });
+ }
+
+ Loader {
+ z: 2
+ anchors.fill: parent
+ active: root.selectionMode === RegionSelection.SelectionMode.RectCorners
+ sourceComponent: RectCornersSelectionDetails {
+ regionX: root.regionX
+ regionY: root.regionY
+ regionWidth: root.regionWidth
+ regionHeight: root.regionHeight
+ mouseX: mouseArea.mouseX
+ mouseY: mouseArea.mouseY
+ color: root.selectionBorderColor
+ overlayColor: root.overlayColor
+ }
+ }
+
+ Loader {
+ z: 2
+ anchors.fill: parent
+ active: root.selectionMode === RegionSelection.SelectionMode.Circle
+ sourceComponent: CircleSelectionDetails {
+ color: root.selectionBorderColor
+ overlayColor: root.overlayColor
+ points: root.points
+ }
+ }
+
+ // Window regions
+ Repeater {
+ model: ScriptModel {
+ values: root.enableWindowRegions ? 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])
+
+ colBackground: root.genericContentColor
+ colForeground: root.genericContentForeground
+ opacity: root.draggedAway ? 0 : root.targetRegionOpacity
+ 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.enableLayerRegions ? 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])
+
+ colBackground: root.genericContentColor
+ colForeground: root.genericContentForeground
+ opacity: root.draggedAway ? 0 : root.targetRegionOpacity
+ 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
+ }
+ }
+
+ // Content regions
+ Repeater {
+ model: ScriptModel {
+ values: root.enableContentRegions ? 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])
+
+ colBackground: root.genericContentColor
+ colForeground: root.genericContentForeground
+ opacity: root.draggedAway ? 0 : root.contentRegionOpacity
+ 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: 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
+ animateChange: true
+ 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/RegionSelector.qml b/dots/.config/quickshell/ii/modules/regionSelector/RegionSelector.qml
index 6eecc365e..7cfd37349 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,28 @@ 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
+ if (Config.options.search.imageSearch.useCircleSelection) {
+ root.selectionMode = RegionSelection.SelectionMode.Circle
+ } else {
+ root.selectionMode = RegionSelection.SelectionMode.RectCorners
+ }
GlobalStates.regionSelectorOpen = true
}
@@ -582,14 +62,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..f1043d13b
--- /dev/null
+++ b/dots/.config/quickshell/ii/modules/regionSelector/TargetRegion.qml
@@ -0,0 +1,68 @@
+pragma ComponentBehavior: Bound
+import qs.modules.common
+import qs.modules.common.widgets
+import qs.services
+import QtQuick
+import Quickshell
+import Quickshell.Widgets
+
+Rectangle {
+ 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
+ property color fillColor: "transparent"
+ property string text: ""
+ property real textPadding: 10
+ z: 2
+ color: fillColor
+ border.color: borderColor
+ border.width: targeted ? 3 : 1
+ radius: 4
+
+ Loader {
+ anchors {
+ top: parent.top
+ left: parent.left
+ topMargin: root.textPadding
+ leftMargin: root.textPadding
+ }
+
+ 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
+
+ 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: root.text
+ color: root.colForeground
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
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);
}
diff --git a/dots/.config/quickshell/ii/modules/settings/InterfaceConfig.qml b/dots/.config/quickshell/ii/modules/settings/InterfaceConfig.qml
index 72469843c..bd08cfffd 100644
--- a/dots/.config/quickshell/ii/modules/settings/InterfaceConfig.qml
+++ b/dots/.config/quickshell/ii/modules/settings/InterfaceConfig.qml
@@ -574,6 +574,100 @@ 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("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")
+
+ 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 +942,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/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/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
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..9258dbc9d 100644
--- a/dots/.config/quickshell/ii/modules/sidebarLeft/anime/BooruResponse.qml
+++ b/dots/.config/quickshell/ii/modules/sidebarLeft/anime/BooruResponse.qml
@@ -3,8 +3,7 @@ import qs.services
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
-import "../"
-import qs.services
+import qs.modules.sidebarLeft
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
@@ -287,4 +286,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 1eee4d335..f2aee10fe 100644
--- a/dots/.config/quickshell/ii/modules/sidebarRight/SidebarRightContent.qml
+++ b/dots/.config/quickshell/ii/modules/sidebarRight/SidebarRightContent.qml
@@ -9,21 +9,24 @@ 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.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: 12
+ 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 {
@@ -62,7 +65,8 @@ Item {
SystemButtonRow {
Layout.fillHeight: false
- Layout.margins: 10
+ Layout.fillWidth: true
+ // Layout.margins: 10
Layout.topMargin: 5
Layout.bottomMargin: 0
}
@@ -108,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: {
@@ -129,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();
}
}
@@ -185,45 +189,72 @@ 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;
+ }
}
}
- 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"
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
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..535aded59
--- /dev/null
+++ b/dots/.config/quickshell/ii/modules/sidebarRight/nightLight/NightLightDialog.qml
@@ -0,0 +1,157 @@
+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
+
+ 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("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")
+ }
+
+ 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 5aa95ad35..a7d743585 100644
--- a/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/AndroidQuickPanel.qml
+++ b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/AndroidQuickPanel.qml
@@ -6,30 +6,20 @@ import QtQuick.Layouts
import Quickshell
import Quickshell.Bluetooth
-import "./androidStyle/"
+import qs.modules.sidebarRight.quickToggles.androidStyle
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 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 toggleRows: toggleRowsForList(toggles)
- readonly property list unusedToggles: {
- const types = availableToggleTypes.filter(type => !toggles.some(toggle => (toggle && toggle.type === type)))
- 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)
@@ -38,6 +28,17 @@ AbstractQuickPanel {
}
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 : []
+ readonly property list toggleRows: toggleRowsForList(toggles)
+ readonly property list unusedToggles: {
+ const types = availableToggleTypes.filter(type => !toggles.some(toggle => (toggle && toggle.type === type)))
+ return types.map(type => { return { type: type, size: 1 } })
+ }
+ readonly property list unusedToggleRows: toggleRowsForList(unusedToggles)
+
function toggleRowsForList(togglesList) {
var rows = [];
var row = [];
@@ -73,14 +74,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 +92,8 @@ AbstractQuickPanel {
Repeater {
model: ScriptModel {
- values: toggleRow.modelData
+ values: toggleRow?.modelData ?? []
+ objectProp: "type"
}
delegate: AndroidToggleDelegateChooser {
startingIndex: toggleRow.startingIndex
@@ -99,10 +101,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()
}
}
}
@@ -131,16 +134,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/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/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/AndroidQuickToggleButton.qml b/dots/.config/quickshell/ii/modules/sidebarRight/quickToggles/androidStyle/AndroidQuickToggleButton.qml
index ee9d58f45..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,6 +23,21 @@ GroupButton {
baseHeight: root.baseCellHeight
property bool editMode: false
+ enableImplicitWidthAnimation: !editMode && root.mouseArea.containsMouse
+ enableImplicitHeightAnimation: !editMode && root.mouseArea.containsMouse
+ 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()
@@ -65,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
}
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")
}
}
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/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/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
diff --git a/dots/.config/quickshell/ii/modules/verticalBar/BatteryIndicator.qml b/dots/.config/quickshell/ii/modules/verticalBar/BatteryIndicator.qml
index 27d704884..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 "./../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 e4bba0187..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 "../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 e594a0ae4..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 "../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 1c19f2828..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 "../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 8ff9386c3..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 "../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 24eed8b4f..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 "../bar" as Bar
+import qs.modules.bar as Bar
MouseArea {
id: root
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")
}
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/Brightness.qml b/dots/.config/quickshell/ii/services/Brightness.qml
index 3f9d93767..a71f8f487 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
@@ -14,6 +16,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()
@@ -84,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: {
@@ -119,17 +124,23 @@ 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();
}
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();
}
+ function setBrightnessMultiplier(value: real): void {
+ monitor.brightnessMultiplier = value;
+ setTimer.restart();
+ }
+
Component.onCompleted: {
initialize();
}
@@ -145,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: {
+ Quickshell.execDetached(["rm", screenScope.screenshotPath]); // Cleanup
+ const lightness = lightnessCollector.text
+ const newMultiplier = root.brightnessMultiplierForLightness(parseFloat(lightness))
+ Brightness.getMonitorForScreen(screenScope.modelData).setBrightnessMultiplier(newMultiplier)
+ }
+ }
+ }
+ }
+ }
+
+ // External trigger points
+
IpcHandler {
target: "brightness"
diff --git a/dots/.config/quickshell/ii/services/Hyprsunset.qml b/dots/.config/quickshell/ii/services/Hyprsunset.qml
index 2f7b3569f..8f0e36529 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
+ 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}`]);
+ }
+ }
}
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/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")) {
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
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
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
}
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..eaea5017a 100644
--- a/sdata/step/3.install-files.sh
+++ b/sdata/step/3.install-files.sh
@@ -53,21 +53,27 @@ 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
}
#####################################################################################
# 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 ;;
@@ -84,11 +90,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 +104,13 @@ case $SKIP_MISCCONF in
;;
esac
+case $SKIP_QUICKSHELL in
+ true) sleep 0;;
+ *)
+ warning_rsync; v rsync -av --delete dots/.config/quickshell/ii/ "$XDG_CONFIG_HOME"/quickshell/ii/
+ ;;
+esac
+
case $SKIP_FISH in
true) sleep 0;;
*)
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
diff --git a/sdist/gentoo/illogical-impulse-widgets/quickshell-9999.ebuild b/sdist/gentoo/illogical-impulse-quickshell-git/illogical-impulse-quickshell-git-9999-r1.ebuild
similarity index 90%
rename from sdist/gentoo/illogical-impulse-widgets/quickshell-9999.ebuild
rename to sdist/gentoo/illogical-impulse-quickshell-git/illogical-impulse-quickshell-git-9999-r1.ebuild
index 89a092516..d87712f46 100644
--- a/sdist/gentoo/illogical-impulse-widgets/quickshell-9999.ebuild
+++ b/sdist/gentoo/illogical-impulse-quickshell-git/illogical-impulse-quickshell-git-9999-r1.ebuild
@@ -3,21 +3,17 @@
EAPI=8
-inherit cmake
+inherit cmake git-r3
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
+EGIT_REPO_URI="https://github.com/quickshell-mirror/quickshell.git"
+EGIT_COMMIT="3e2ce40b18af943f9ba370ed73565e9f487663ef"
+KEYWORDS="~amd64 ~arm64 ~x86"
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"
@@ -38,21 +34,27 @@ RDEPEND="
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
)
+ dev-cpp/cli11
+ dev-build/ninja
+ dev-build/cmake
+ dev-vcs/git
+ virtual/pkgconfig
+ breakpad? ( dev-util/breakpad )
+
"
src_configure(){
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/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..a10d1111e 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
+app-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
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