diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/arrow-right-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/arrow-right-filled.svg new file mode 100644 index 000000000..0e4e5daaa --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/arrow-right-filled.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/arrow-right.svg b/dots/.config/quickshell/ii/assets/icons/fluent/arrow-right.svg new file mode 100644 index 000000000..db97a6e38 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/arrow-right.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/arrow-up-left-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/arrow-up-left-filled.svg new file mode 100644 index 000000000..dd5011358 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/arrow-up-left-filled.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/arrow-up-left.svg b/dots/.config/quickshell/ii/assets/icons/fluent/arrow-up-left.svg new file mode 100644 index 000000000..8b6683c69 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/arrow-up-left.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/eye-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/eye-filled.svg new file mode 100644 index 000000000..fe959b74c --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/eye-filled.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/eye-off-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/eye-off-filled.svg new file mode 100644 index 000000000..d3c53b85e --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/eye-off-filled.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/eye-off.svg b/dots/.config/quickshell/ii/assets/icons/fluent/eye-off.svg new file mode 100644 index 000000000..b409256f5 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/eye-off.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/eye.svg b/dots/.config/quickshell/ii/assets/icons/fluent/eye.svg new file mode 100644 index 000000000..55be66814 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/eye.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/shield-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/shield-filled.svg new file mode 100644 index 000000000..e639be9bf --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/shield-filled.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/shield-lock-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/shield-lock-filled.svg new file mode 100644 index 000000000..b916cb4aa --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/shield-lock-filled.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/shield-lock.svg b/dots/.config/quickshell/ii/assets/icons/fluent/shield-lock.svg new file mode 100644 index 000000000..af0ed6eaa --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/shield-lock.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/shield.svg b/dots/.config/quickshell/ii/assets/icons/fluent/shield.svg new file mode 100644 index 000000000..cc53bbc8c --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/shield.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/window-shield-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/window-shield-filled.svg new file mode 100644 index 000000000..4709d3d1e --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/window-shield-filled.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/window-shield.svg b/dots/.config/quickshell/ii/assets/icons/fluent/window-shield.svg new file mode 100644 index 000000000..023ae50bd --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/window-shield.svg @@ -0,0 +1,4 @@ + + + + \ 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 121f78741..aebb91b66 100644 --- a/dots/.config/quickshell/ii/modules/common/Config.qml +++ b/dots/.config/quickshell/ii/modules/common/Config.qml @@ -138,6 +138,7 @@ Singleton { } property JsonObject palette: JsonObject { property string type: "auto" // Allowed: auto, scheme-content, scheme-expressive, scheme-fidelity, scheme-fruit-salad, scheme-monochrome, scheme-neutral, scheme-rainbow, scheme-tonal-spot + property string accentColor: "" } } @@ -153,6 +154,7 @@ Singleton { property JsonObject apps: JsonObject { property string bluetooth: "kcmshell6 kcm_bluetooth" + property string changePassword: "kitty -1 --hold=yes fish -i -c 'passwd'" property string network: "kcmshell6 kcm_networkmanagement" property string manageUser: "kcmshell6 kcm_users" property string networkEthernet: "kcmshell6 kcm_networkmanagement" @@ -346,6 +348,10 @@ Singleton { } } + property JsonObject launcher: JsonObject { + property list pinnedApps: [ "org.kde.dolphin", "kitty", "cmake-gui"] + } + property JsonObject light: JsonObject { property JsonObject night: JsonObject { property bool automatic: true @@ -432,6 +438,9 @@ Singleton { property int strokeWidth: 6 property int padding: 10 } + property JsonObject annotation: JsonObject { + property bool useSatty: false + } } property JsonObject resources: JsonObject { diff --git a/dots/.config/quickshell/ii/modules/common/functions/Session.qml b/dots/.config/quickshell/ii/modules/common/functions/Session.qml index bbb9932c3..184bd34c3 100644 --- a/dots/.config/quickshell/ii/modules/common/functions/Session.qml +++ b/dots/.config/quickshell/ii/modules/common/functions/Session.qml @@ -12,6 +12,10 @@ Singleton { }); } + function changePassword() { + Quickshell.execDetached(["bash", "-c", `${Config.options.apps.changePassword}`]); + } + function lock() { Quickshell.execDetached(["loginctl", "lock-session"]); } diff --git a/dots/.config/quickshell/ii/modules/ii/lock/LockContext.qml b/dots/.config/quickshell/ii/modules/common/panels/lock/LockContext.qml similarity index 100% rename from dots/.config/quickshell/ii/modules/ii/lock/LockContext.qml rename to dots/.config/quickshell/ii/modules/common/panels/lock/LockContext.qml diff --git a/dots/.config/quickshell/ii/modules/common/panels/lock/LockScreen.qml b/dots/.config/quickshell/ii/modules/common/panels/lock/LockScreen.qml new file mode 100644 index 000000000..9e4b9bd94 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/panels/lock/LockScreen.qml @@ -0,0 +1,157 @@ +pragma ComponentBehavior: Bound +import qs +import qs.services +import qs.modules.common +import qs.modules.common.functions +import QtQuick +import Quickshell +import Quickshell.Io +import Quickshell.Wayland +import Quickshell.Hyprland + +Scope { + id: root + + required property Component lockSurface + property alias context: lockContext + property Component sessionLockSurface: WlSessionLockSurface { + id: sessionLockSurface + color: "transparent" + Loader { + active: GlobalStates.screenLocked + anchors.fill: parent + opacity: active ? 1 : 0 + Behavior on opacity { + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) + } + sourceComponent: root.lockSurface + } + } + + Process { + id: unlockKeyringProc + onExited: (exitCode, exitStatus) => { + KeyringStorage.fetchKeyringData(); + } + } + function unlockKeyring() { + unlockKeyringProc.exec({ + environment: ({ + "UNLOCK_PASSWORD": lockContext.currentText + }), + command: ["bash", "-c", Quickshell.shellPath("scripts/keyring/unlock.sh")] + }) + } + + // This stores all the information shared between the lock surfaces on each screen. + // https://github.com/quickshell-mirror/quickshell-examples/tree/master/lockscreen + LockContext { + id: lockContext + + Connections { + target: GlobalStates + function onScreenLockedChanged() { + if (GlobalStates.screenLocked) { + lockContext.reset(); + lockContext.tryFingerUnlock(); + } + } + } + + onUnlocked: (targetAction) => { + // Perform the target action if it's not just unlocking + if (targetAction == LockContext.ActionEnum.Poweroff) { + Session.poweroff(); + return; + } else if (targetAction == LockContext.ActionEnum.Reboot) { + Session.reboot(); + return; + } + + // Unlock the keyring if configured to do so + if (Config.options.lock.security.unlockKeyring) root.unlockKeyring(); // Async + + // Unlock the screen before exiting, or the compositor will display a + // fallback lock you can't interact with. + GlobalStates.screenLocked = false; + + // Refocus last focused window on unlock (hack) + Quickshell.execDetached(["bash", "-c", `sleep 0.2; hyprctl --batch "dispatch togglespecialworkspace; dispatch togglespecialworkspace"`]) + + // Reset + lockContext.reset(); + + // Post-unlock actions + if (lockContext.alsoInhibitIdle) { + lockContext.alsoInhibitIdle = false; + Idle.toggleInhibit(true); + } + } + } + + WlSessionLock { + id: lock + locked: GlobalStates.screenLocked + + surface: root.sessionLockSurface + } + + function lock() { + if (Config.options.lock.useHyprlock) { + Quickshell.execDetached(["bash", "-c", "pidof hyprlock || hyprlock"]); + return; + } + GlobalStates.screenLocked = true; + } + + IpcHandler { + target: "lock" + + function activate(): void { + root.lock(); + } + function focus(): void { + lockContext.shouldReFocus(); + } + } + + GlobalShortcut { + name: "lock" + description: "Locks the screen" + + onPressed: { + root.lock() + } + } + + GlobalShortcut { + name: "lockFocus" + description: "Re-focuses the lock screen. This is because Hyprland after waking up for whatever reason" + + "decides to keyboard-unfocus the lock screen" + + onPressed: { + lockContext.shouldReFocus(); + } + } + + function initIfReady() { + if (!Config.ready || !Persistent.ready) return; + if (Config.options.lock.launchOnStartup && Persistent.isNewHyprlandInstance) { + root.lock(); + } else { + KeyringStorage.fetchKeyringData(); + } + } + Connections { + target: Config + function onReadyChanged() { + root.initIfReady(); + } + } + Connections { + target: Persistent + function onReadyChanged() { + root.initIfReady(); + } + } +} diff --git a/dots/.config/quickshell/ii/modules/ii/lock/pam/fprintd.conf b/dots/.config/quickshell/ii/modules/common/panels/lock/pam/fprintd.conf similarity index 100% rename from dots/.config/quickshell/ii/modules/ii/lock/pam/fprintd.conf rename to dots/.config/quickshell/ii/modules/common/panels/lock/pam/fprintd.conf diff --git a/dots/.config/quickshell/ii/modules/common/widgets/FullscreenPolkitWindow.qml b/dots/.config/quickshell/ii/modules/common/widgets/FullscreenPolkitWindow.qml new file mode 100644 index 000000000..e4c6ef725 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/widgets/FullscreenPolkitWindow.qml @@ -0,0 +1,44 @@ +pragma ComponentBehavior: Bound +import qs +import qs.services +import qs.modules.common +import qs.modules.common.widgets +import qs.modules.common.functions +import QtQuick +import Quickshell +import Quickshell.Wayland + +Scope { + id: root + required property Component contentComponent + + Loader { + active: PolkitService.active + sourceComponent: Variants { + model: Quickshell.screens + delegate: PanelWindow { + id: panelWindow + required property var modelData + screen: modelData + + anchors { + top: true + left: true + right: true + bottom: true + } + + color: "transparent" + WlrLayershell.namespace: "quickshell:polkit" + WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand + WlrLayershell.layer: WlrLayer.Overlay + exclusionMode: ExclusionMode.Ignore + + Loader { + anchors.fill: parent + sourceComponent: root.contentComponent + } + } + } + } +} diff --git a/dots/.config/quickshell/ii/modules/ii/bar/BatteryIndicator.qml b/dots/.config/quickshell/ii/modules/ii/bar/BatteryIndicator.qml index 36799fefd..176f3a065 100644 --- a/dots/.config/quickshell/ii/modules/ii/bar/BatteryIndicator.qml +++ b/dots/.config/quickshell/ii/modules/ii/bar/BatteryIndicator.qml @@ -30,7 +30,11 @@ MouseArea { height: batteryProgress.valueBarHeight RowLayout { - anchors.centerIn: parent + anchors { + horizontalCenter: parent.horizontalCenter + bottom: parent.bottom + bottomMargin: (parent.height - height) / 2 + } spacing: 0 MaterialSymbol { diff --git a/dots/.config/quickshell/ii/modules/ii/lock/Lock.qml b/dots/.config/quickshell/ii/modules/ii/lock/Lock.qml index 3d6506c54..3aae6ef7b 100644 --- a/dots/.config/quickshell/ii/modules/ii/lock/Lock.qml +++ b/dots/.config/quickshell/ii/modules/ii/lock/Lock.qml @@ -3,116 +3,39 @@ import qs import qs.services import qs.modules.common import qs.modules.common.functions +import qs.modules.common.panels.lock import QtQuick import Quickshell import Quickshell.Io import Quickshell.Wayland import Quickshell.Hyprland -Scope { +LockScreen { id: root - Process { - id: unlockKeyringProc - onExited: (exitCode, exitStatus) => { - KeyringStorage.fetchKeyringData(); - } - } - function unlockKeyring() { - unlockKeyringProc.exec({ - environment: ({ - "UNLOCK_PASSWORD": lockContext.currentText - }), - command: ["bash", "-c", Quickshell.shellPath("scripts/keyring/unlock.sh")] - }) + lockSurface: LockSurface { + context: root.context } + // Push everything down property var windowData: [] function saveWindowPositionAndTile() { - Quickshell.execDetached(["hyprctl", "keyword", "dwindle:pseudotile", "true"]) - root.windowData = HyprlandData.windowList.filter(w => (w.floating && w.workspace.id === HyprlandData.activeWorkspace.id)) + Quickshell.execDetached(["hyprctl", "keyword", "dwindle:pseudotile", "true"]); + root.windowData = HyprlandData.windowList.filter(w => (w.floating && w.workspace.id === HyprlandData.activeWorkspace.id)); root.windowData.forEach(w => { - Hyprland.dispatch(`pseudo address:${w.address}`) - Hyprland.dispatch(`settiled address:${w.address}`) - Hyprland.dispatch(`movetoworkspacesilent ${w.workspace.id},address:${w.address}`) - }) + Hyprland.dispatch(`pseudo address:${w.address}`); + Hyprland.dispatch(`settiled address:${w.address}`); + Hyprland.dispatch(`movetoworkspacesilent ${w.workspace.id},address:${w.address}`); + }); } function restoreWindowPositionAndTile() { root.windowData.forEach(w => { - Hyprland.dispatch(`setfloating address:${w.address}`) - Hyprland.dispatch(`movewindowpixel exact ${w.at[0]} ${w.at[1]}, address:${w.address}`) - Hyprland.dispatch(`pseudo address:${w.address}`) - }) - Quickshell.execDetached(["hyprctl", "keyword", "dwindle:pseudotile", "false"]) + Hyprland.dispatch(`setfloating address:${w.address}`); + Hyprland.dispatch(`movewindowpixel exact ${w.at[0]} ${w.at[1]}, address:${w.address}`); + Hyprland.dispatch(`pseudo address:${w.address}`); + }); + Quickshell.execDetached(["hyprctl", "keyword", "dwindle:pseudotile", "false"]); } - - // This stores all the information shared between the lock surfaces on each screen. - // https://github.com/quickshell-mirror/quickshell-examples/tree/master/lockscreen - LockContext { - id: lockContext - - Connections { - target: GlobalStates - function onScreenLockedChanged() { - if (GlobalStates.screenLocked) { - lockContext.reset(); - lockContext.tryFingerUnlock(); - } - } - } - - onUnlocked: (targetAction) => { - // Perform the target action if it's not just unlocking - if (targetAction == LockContext.ActionEnum.Poweroff) { - Session.poweroff(); - return; - } else if (targetAction == LockContext.ActionEnum.Reboot) { - Session.reboot(); - return; - } - - // Unlock the keyring if configured to do so - if (Config.options.lock.security.unlockKeyring) root.unlockKeyring(); // Async - - // Unlock the screen before exiting, or the compositor will display a - // fallback lock you can't interact with. - GlobalStates.screenLocked = false; - - // Refocus last focused window on unlock (hack) - Quickshell.execDetached(["bash", "-c", `sleep 0.2; hyprctl --batch "dispatch togglespecialworkspace; dispatch togglespecialworkspace"`]) - - // Reset - lockContext.reset(); - - // Post-unlock actions - if (lockContext.alsoInhibitIdle) { - lockContext.alsoInhibitIdle = false; - Idle.toggleInhibit(true); - } - } - } - - WlSessionLock { - id: lock - locked: GlobalStates.screenLocked - - WlSessionLockSurface { - color: "transparent" - Loader { - active: GlobalStates.screenLocked - anchors.fill: parent - opacity: active ? 1 : 0 - Behavior on opacity { - animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) - } - sourceComponent: LockSurface { - context: lockContext - } - } - } - } - - // Blur layer hack Variants { model: Quickshell.screens delegate: Scope { @@ -124,71 +47,12 @@ Scope { onShouldPushChanged: { if (shouldPush) { root.saveWindowPositionAndTile(); - Quickshell.execDetached(["bash", "-c", `hyprctl keyword monitor ${targetMonitorName}, addreserved, ${verticalMovementDistance}, ${-verticalMovementDistance}, ${horizontalSqueeze}, ${horizontalSqueeze}`]) + Quickshell.execDetached(["bash", "-c", `hyprctl keyword monitor ${targetMonitorName}, addreserved, ${verticalMovementDistance}, ${-verticalMovementDistance}, ${horizontalSqueeze}, ${horizontalSqueeze}`]); } else { - Quickshell.execDetached(["bash", "-c", `hyprctl keyword monitor ${targetMonitorName}, addreserved, 0, 0, 0, 0`]) + Quickshell.execDetached(["bash", "-c", `hyprctl keyword monitor ${targetMonitorName}, addreserved, 0, 0, 0, 0`]); root.restoreWindowPositionAndTile(); } } } } - - function lock() { - if (Config.options.lock.useHyprlock) { - Quickshell.execDetached(["bash", "-c", "pidof hyprlock || hyprlock"]); - return; - } - GlobalStates.screenLocked = true; - } - - IpcHandler { - target: "lock" - - function activate(): void { - root.lock(); - } - function focus(): void { - lockContext.shouldReFocus(); - } - } - - GlobalShortcut { - name: "lock" - description: "Locks the screen" - - onPressed: { - root.lock() - } - } - - GlobalShortcut { - name: "lockFocus" - description: "Re-focuses the lock screen. This is because Hyprland after waking up for whatever reason" - + "decides to keyboard-unfocus the lock screen" - - onPressed: { - lockContext.shouldReFocus(); - } - } - - function initIfReady() { - if (!Config.ready || !Persistent.ready) return; - if (Config.options.lock.launchOnStartup && Persistent.isNewHyprlandInstance) { - root.lock(); - } else { - KeyringStorage.fetchKeyringData(); - } - } - Connections { - target: Config - function onReadyChanged() { - root.initIfReady(); - } - } - Connections { - target: Persistent - function onReadyChanged() { - root.initIfReady(); - } - } } diff --git a/dots/.config/quickshell/ii/modules/ii/lock/LockSurface.qml b/dots/.config/quickshell/ii/modules/ii/lock/LockSurface.qml index e44eecacb..b2482132d 100644 --- a/dots/.config/quickshell/ii/modules/ii/lock/LockSurface.qml +++ b/dots/.config/quickshell/ii/modules/ii/lock/LockSurface.qml @@ -7,6 +7,7 @@ import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions +import qs.modules.common.panels.lock import qs.modules.ii.bar as Bar import Quickshell import Quickshell.Services.SystemTray diff --git a/dots/.config/quickshell/ii/modules/ii/polkit/Polkit.qml b/dots/.config/quickshell/ii/modules/ii/polkit/Polkit.qml index e1c54a42f..50f04f675 100644 --- a/dots/.config/quickshell/ii/modules/ii/polkit/Polkit.qml +++ b/dots/.config/quickshell/ii/modules/ii/polkit/Polkit.qml @@ -6,37 +6,10 @@ import qs.modules.common.functions import QtQuick import Quickshell import Quickshell.Wayland -import Quickshell.Hyprland -Scope { +FullscreenPolkitWindow { id: root - - Loader { - active: PolkitService.active - sourceComponent: Variants { - model: Quickshell.screens - delegate: PanelWindow { - id: panelWindow - required property var modelData - screen: modelData - - anchors { - top: true - left: true - right: true - bottom: true - } - - color: "transparent" - WlrLayershell.namespace: "quickshell:polkit" - WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand - WlrLayershell.layer: WlrLayer.Overlay - exclusionMode: ExclusionMode.Ignore - - PolkitContent { - anchors.fill: parent - } - } - } + contentComponent: Component { + PolkitContent {} } } diff --git a/dots/.config/quickshell/ii/modules/ii/polkit/PolkitContent.qml b/dots/.config/quickshell/ii/modules/ii/polkit/PolkitContent.qml index baef7f0b5..7dfd045db 100644 --- a/dots/.config/quickshell/ii/modules/ii/polkit/PolkitContent.qml +++ b/dots/.config/quickshell/ii/modules/ii/polkit/PolkitContent.qml @@ -66,12 +66,7 @@ Item { WindowDialogParagraph { Layout.fillWidth: true horizontalAlignment: Text.AlignLeft - text: { - if (!PolkitService.flow) return; - return PolkitService.flow.message.endsWith(".") - ? PolkitService.flow.message.slice(0, -1) - : PolkitService.flow.message - } + text: PolkitService.cleanMessage } MaterialTextField { @@ -79,11 +74,7 @@ Item { Layout.fillWidth: true focus: true enabled: PolkitService.interactionAvailable - placeholderText: { - const inputPrompt = PolkitService.flow?.inputPrompt.trim() ?? ""; - const cleanedInputPrompt = inputPrompt.endsWith(":") ? inputPrompt.slice(0, -1) : inputPrompt; - return cleanedInputPrompt || (root.usePasswordChars ? Translation.tr("Password") : Translation.tr("Input")) - } + placeholderText: PolkitService.cleanPrompt echoMode: root.usePasswordChars ? TextInput.Password : TextInput.Normal onAccepted: root.submit(); @@ -95,7 +86,7 @@ Item { } WindowDialogButtonRow { - + Layout.bottomMargin: 10 // I honestly don't know why this is necessary Item { Layout.fillWidth: true } diff --git a/dots/.config/quickshell/ii/modules/ii/regionSelector/RegionSelection.qml b/dots/.config/quickshell/ii/modules/ii/regionSelector/RegionSelection.qml index 8f484591b..59dc186e7 100644 --- a/dots/.config/quickshell/ii/modules/ii/regionSelector/RegionSelection.qml +++ b/dots/.config/quickshell/ii/modules/ii/regionSelector/RegionSelection.qml @@ -261,6 +261,7 @@ PanelWindow { const uploadAndGetUrl = (filePath) => { return `curl -sF files[]=@'${StringUtils.shellSingleQuoteEscape(filePath)}' ${root.fileUploadApiEndpoint} | jq -r '.files[0].url'` } + const annotationCommand = `${Config.options.regionSelector.annotation.useSatty ? "satty" : "swappy"} -f -`; switch (root.action) { case RegionSelection.SnipAction.Copy: if (saveScreenshotDir === "") { @@ -282,7 +283,7 @@ PanelWindow { break; case RegionSelection.SnipAction.Edit: - snipProc.command = ["bash", "-c", `${cropToStdout} | swappy -f - && ${cleanup}`] + snipProc.command = ["bash", "-c", `${cropToStdout} | ${annotationCommand} && ${cleanup}`] break; case RegionSelection.SnipAction.Search: snipProc.command = ["bash", "-c", `${cropInPlace} && xdg-open "${root.imageSearchEngineBaseUrl}$(${uploadAndGetUrl(root.screenshotPath)})" && ${cleanup}`] diff --git a/dots/.config/quickshell/ii/modules/ii/sessionScreen/SessionScreen.qml b/dots/.config/quickshell/ii/modules/ii/sessionScreen/SessionScreen.qml index 8899c3e75..6350d18a9 100644 --- a/dots/.config/quickshell/ii/modules/ii/sessionScreen/SessionScreen.qml +++ b/dots/.config/quickshell/ii/modules/ii/sessionScreen/SessionScreen.qml @@ -14,61 +14,13 @@ import Quickshell.Hyprland Scope { id: root property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name) - property bool packageManagerRunning: false - property bool downloadRunning: false - - component DescriptionLabel: Rectangle { - id: descriptionLabel - property string text - property color textColor: Appearance.colors.colOnTooltip - color: Appearance.colors.colTooltip - clip: true - radius: Appearance.rounding.normal - implicitHeight: descriptionLabelText.implicitHeight + 10 * 2 - implicitWidth: descriptionLabelText.implicitWidth + 15 * 2 - - Behavior on implicitWidth { - animation: Appearance.animation.elementMove.numberAnimation.createObject(this) - } - - StyledText { - id: descriptionLabelText - anchors.centerIn: parent - color: descriptionLabel.textColor - text: descriptionLabel.text - } - } - - function detectRunningStuff() { - packageManagerRunning = false; - downloadRunning = false; - detectPackageManagerProc.running = false; - detectPackageManagerProc.running = true; - detectDownloadProc.running = false; - detectDownloadProc.running = true; - } - - Process { - id: detectPackageManagerProc - command: ["bash", "-c", "pidof pacman yay paru dnf zypper apt apx xbps flatpak snap apk yum epsi pikman"] - onExited: (exitCode, exitStatus) => { - root.packageManagerRunning = (exitCode === 0); - } - } - - Process { - id: detectDownloadProc - command: ["bash", "-c", "pidof curl wget aria2c yt-dlp || ls ~/Downloads | grep -E '\.crdownload$|\.part$'"] - onExited: (exitCode, exitStatus) => { - root.downloadRunning = (exitCode === 0); - } - } Loader { id: sessionLoader active: GlobalStates.sessionOpen onActiveChanged: { - if (sessionLoader.active) root.detectRunningStuff(); + if (sessionLoader.active) + SessionWarnings.refresh(); } Connections { @@ -84,7 +36,7 @@ Scope { id: sessionRoot visible: sessionLoader.active property string subtitle - + function hide() { GlobalStates.sessionOpen = false; } @@ -110,7 +62,7 @@ Scope { id: sessionMouseArea anchors.fill: parent onClicked: { - sessionRoot.hide() + sessionRoot.hide(); } } @@ -119,7 +71,7 @@ Scope { anchors.centerIn: parent spacing: 15 - Keys.onPressed: (event) => { + Keys.onPressed: event => { if (event.key === Qt.Key_Escape) { sessionRoot.hide(); } @@ -128,7 +80,8 @@ Scope { ColumnLayout { Layout.alignment: Qt.AlignHCenter spacing: 0 - StyledText { // Title + StyledText { + // Title Layout.alignment: Qt.AlignHCenter horizontalAlignment: Text.AlignHCenter font { @@ -139,7 +92,8 @@ Scope { text: Translation.tr("Session") } - StyledText { // Small instruction + StyledText { + // Small instruction Layout.alignment: Qt.AlignHCenter horizontalAlignment: Text.AlignHCenter font.pixelSize: Appearance.font.pixelSize.normal @@ -157,8 +111,14 @@ Scope { focus: sessionRoot.visible buttonIcon: "lock" buttonText: Translation.tr("Lock") - onClicked: { Session.lock(); sessionRoot.hide() } - onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } + onClicked: { + Session.lock(); + sessionRoot.hide(); + } + onFocusChanged: { + if (focus) + sessionRoot.subtitle = buttonText; + } KeyNavigation.right: sessionSleep KeyNavigation.down: sessionHibernate } @@ -166,8 +126,14 @@ Scope { id: sessionSleep buttonIcon: "dark_mode" buttonText: Translation.tr("Sleep") - onClicked: { Session.suspend(); sessionRoot.hide() } - onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } + onClicked: { + Session.suspend(); + sessionRoot.hide(); + } + onFocusChanged: { + if (focus) + sessionRoot.subtitle = buttonText; + } KeyNavigation.left: sessionLock KeyNavigation.right: sessionLogout KeyNavigation.down: sessionShutdown @@ -176,8 +142,14 @@ Scope { id: sessionLogout buttonIcon: "logout" buttonText: Translation.tr("Logout") - onClicked: { Session.logout(); sessionRoot.hide() } - onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } + onClicked: { + Session.logout(); + sessionRoot.hide(); + } + onFocusChanged: { + if (focus) + sessionRoot.subtitle = buttonText; + } KeyNavigation.left: sessionSleep KeyNavigation.right: sessionTaskManager KeyNavigation.down: sessionReboot @@ -186,8 +158,14 @@ Scope { id: sessionTaskManager buttonIcon: "browse_activity" buttonText: Translation.tr("Task Manager") - onClicked: { Session.launchTaskManager(); sessionRoot.hide() } - onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } + onClicked: { + Session.launchTaskManager(); + sessionRoot.hide(); + } + onFocusChanged: { + if (focus) + sessionRoot.subtitle = buttonText; + } KeyNavigation.left: sessionLogout KeyNavigation.down: sessionFirmwareReboot } @@ -196,8 +174,14 @@ Scope { id: sessionHibernate buttonIcon: "downloading" buttonText: Translation.tr("Hibernate") - onClicked: { Session.hibernate(); sessionRoot.hide() } - onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } + onClicked: { + Session.hibernate(); + sessionRoot.hide(); + } + onFocusChanged: { + if (focus) + sessionRoot.subtitle = buttonText; + } KeyNavigation.up: sessionLock KeyNavigation.right: sessionShutdown } @@ -205,8 +189,14 @@ Scope { id: sessionShutdown buttonIcon: "power_settings_new" buttonText: Translation.tr("Shutdown") - onClicked: { Session.poweroff(); sessionRoot.hide() } - onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } + onClicked: { + Session.poweroff(); + sessionRoot.hide(); + } + onFocusChanged: { + if (focus) + sessionRoot.subtitle = buttonText; + } KeyNavigation.left: sessionHibernate KeyNavigation.right: sessionReboot KeyNavigation.up: sessionSleep @@ -215,8 +205,14 @@ Scope { id: sessionReboot buttonIcon: "restart_alt" buttonText: Translation.tr("Reboot") - onClicked: { Session.reboot(); sessionRoot.hide() } - onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } + onClicked: { + Session.reboot(); + sessionRoot.hide(); + } + onFocusChanged: { + if (focus) + sessionRoot.subtitle = buttonText; + } KeyNavigation.left: sessionShutdown KeyNavigation.right: sessionFirmwareReboot KeyNavigation.up: sessionLogout @@ -225,8 +221,14 @@ Scope { id: sessionFirmwareReboot buttonIcon: "settings_applications" buttonText: Translation.tr("Reboot to firmware settings") - onClicked: { Session.rebootToFirmware(); sessionRoot.hide() } - onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } + onClicked: { + Session.rebootToFirmware(); + sessionRoot.hide(); + } + onFocusChanged: { + if (focus) + sessionRoot.subtitle = buttonText; + } KeyNavigation.up: sessionTaskManager KeyNavigation.left: sessionReboot } @@ -247,7 +249,7 @@ Scope { spacing: 10 Loader { - active: root.packageManagerRunning + active: SessionWarnings.packageManagerRunning visible: active sourceComponent: DescriptionLabel { text: Translation.tr("Your package manager is running") @@ -256,7 +258,7 @@ Scope { } } Loader { - active: root.downloadRunning + active: SessionWarnings.downloadRunning visible: active sourceComponent: DescriptionLabel { text: Translation.tr("There might be a download in progress") @@ -268,6 +270,28 @@ Scope { } } + component DescriptionLabel: Rectangle { + id: descriptionLabel + property string text + property color textColor: Appearance.colors.colOnTooltip + color: Appearance.colors.colTooltip + clip: true + radius: Appearance.rounding.normal + implicitHeight: descriptionLabelText.implicitHeight + 10 * 2 + implicitWidth: descriptionLabelText.implicitWidth + 15 * 2 + + Behavior on implicitWidth { + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) + } + + StyledText { + id: descriptionLabelText + anchors.centerIn: parent + color: descriptionLabel.textColor + text: descriptionLabel.text + } + } + IpcHandler { target: "session" @@ -276,11 +300,11 @@ Scope { } function close(): void { - GlobalStates.sessionOpen = false + GlobalStates.sessionOpen = false; } function open(): void { - GlobalStates.sessionOpen = true + GlobalStates.sessionOpen = true; } } @@ -298,7 +322,7 @@ Scope { description: "Opens session screen on press" onPressed: { - GlobalStates.sessionOpen = true + GlobalStates.sessionOpen = true; } } @@ -307,8 +331,7 @@ Scope { description: "Closes session screen on press" onPressed: { - GlobalStates.sessionOpen = false + GlobalStates.sessionOpen = false; } } - } diff --git a/dots/.config/quickshell/ii/modules/waffle/actionCenter/bluetooth/BluetoothControl.qml b/dots/.config/quickshell/ii/modules/waffle/actionCenter/bluetooth/BluetoothControl.qml index 72f442b87..05e89dafe 100644 --- a/dots/.config/quickshell/ii/modules/waffle/actionCenter/bluetooth/BluetoothControl.qml +++ b/dots/.config/quickshell/ii/modules/waffle/actionCenter/bluetooth/BluetoothControl.qml @@ -99,7 +99,7 @@ Item { WPanelSeparator {} FooterRectangle { - FooterMoreButton { + WTextButton { anchors { verticalCenter: parent.verticalCenter left: parent.left diff --git a/dots/.config/quickshell/ii/modules/waffle/actionCenter/mainPage/MainPageBodyToggles.qml b/dots/.config/quickshell/ii/modules/waffle/actionCenter/mainPage/MainPageBodyToggles.qml index 88dbc9e03..64298b712 100644 --- a/dots/.config/quickshell/ii/modules/waffle/actionCenter/mainPage/MainPageBodyToggles.qml +++ b/dots/.config/quickshell/ii/modules/waffle/actionCenter/mainPage/MainPageBodyToggles.qml @@ -87,38 +87,16 @@ Item { } } - Column { + VerticalPageIndicator { anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right anchors.rightMargin: 6 - spacing: 6 - - NavigationArrow { - down: false - } - - Repeater { - model: root.pages - delegate: MouseArea { - id: pageIndicator - required property int index - hoverEnabled: true - onClicked: root.currentPage = index - anchors.horizontalCenter: parent.horizontalCenter - implicitWidth: 6 - implicitHeight: 6 - - Circle { - anchors.centerIn: parent - diameter: (index === root.currentPage || pageIndicator.containsMouse) && !pageIndicator.pressed ? 6 : 4 - color: pageIndicator.containsMouse ? Looks.colors.controlBgHover : Looks.colors.controlBg - } - } - } - - NavigationArrow { - down: true - } + + currentIndex: root.currentPage + count: root.pages + onClicked: (index) => root.currentPage = index + onIncreasePage: root.increasePage(); + onDecreasePage: root.decreasePage(); } FocusedScrollMouseArea { @@ -126,25 +104,7 @@ Item { anchors.fill: parent acceptedButtons: Qt.NoButton hoverEnabled: false - onScrollUp: decreasePage(); - onScrollDown: increasePage(); - } - - component NavigationArrow: FluentIcon { - id: navArrow - required property bool down - anchors.horizontalCenter: parent.horizontalCenter - implicitHeight: 12 - implicitWidth: 12 - (2 * upArea.containsPress) - icon: down ? "caret-down" : "caret-up" - color: upArea.containsMouse ? Looks.colors.controlBgHover : Looks.colors.controlBg - filled: true - opacity: ((down && root.currentPage < root.pages - 1) || (!down && root.currentPage > 0)) ? 1 : 0 - MouseArea { - id: upArea - anchors.fill: parent - hoverEnabled: true - onClicked: navArrow.down ? root.increasePage() : root.decreasePage(); - } + onScrollUp: root.decreasePage(); + onScrollDown: root.increasePage(); } } diff --git a/dots/.config/quickshell/ii/modules/waffle/actionCenter/wifi/WifiControl.qml b/dots/.config/quickshell/ii/modules/waffle/actionCenter/wifi/WifiControl.qml index c90d06fc0..061c74154 100644 --- a/dots/.config/quickshell/ii/modules/waffle/actionCenter/wifi/WifiControl.qml +++ b/dots/.config/quickshell/ii/modules/waffle/actionCenter/wifi/WifiControl.qml @@ -89,7 +89,7 @@ Item { WPanelSeparator {} FooterRectangle { - FooterMoreButton { + WTextButton { anchors { verticalCenter: parent.verticalCenter left: parent.left diff --git a/dots/.config/quickshell/ii/modules/waffle/bar/SearchButton.qml b/dots/.config/quickshell/ii/modules/waffle/bar/SearchButton.qml index 3e8dd6282..6bb48f8fc 100644 --- a/dots/.config/quickshell/ii/modules/waffle/bar/SearchButton.qml +++ b/dots/.config/quickshell/ii/modules/waffle/bar/SearchButton.qml @@ -12,9 +12,9 @@ AppButton { iconName: checked ? "system-search-checked" : "system-search" separateLightDark: true - checked: GlobalStates.overviewOpen + checked: GlobalStates.searchOpen && LauncherSearch.query !== "" onClicked: { - GlobalStates.overviewOpen = !GlobalStates.overviewOpen; // For now... + GlobalStates.searchOpen = !GlobalStates.searchOpen; // For now... } BarToolTip { diff --git a/dots/.config/quickshell/ii/modules/waffle/bar/StartButton.qml b/dots/.config/quickshell/ii/modules/waffle/bar/StartButton.qml index a92a85578..7fa716b07 100644 --- a/dots/.config/quickshell/ii/modules/waffle/bar/StartButton.qml +++ b/dots/.config/quickshell/ii/modules/waffle/bar/StartButton.qml @@ -14,7 +14,7 @@ AppButton { leftInset: Config.options.waffles.bar.leftAlignApps ? 12 : 0 iconName: down ? "start-here-pressed" : "start-here" - checked: GlobalStates.searchOpen + checked: GlobalStates.searchOpen && LauncherSearch.query === "" onClicked: { GlobalStates.searchOpen = !GlobalStates.searchOpen; } diff --git a/dots/.config/quickshell/ii/modules/waffle/bar/tasks/WindowPreview.qml b/dots/.config/quickshell/ii/modules/waffle/bar/tasks/WindowPreview.qml index 9f114609f..764d91ca7 100644 --- a/dots/.config/quickshell/ii/modules/waffle/bar/tasks/WindowPreview.qml +++ b/dots/.config/quickshell/ii/modules/waffle/bar/tasks/WindowPreview.qml @@ -67,7 +67,7 @@ Button { } } - CloseButton { + WindowCloseButton { id: closeButton } } @@ -91,46 +91,14 @@ Button { } } - component CloseButton: Button { - id: reusableCloseButton + component WindowCloseButton: CloseButton { visible: root.hovered Layout.leftMargin: 4 implicitHeight: 30 implicitWidth: 30 + radius: Looks.radius.large - root.padding onClicked: { root.toplevel.close(); } - - Rectangle { - z: 0 - color: "transparent" - anchors.fill: closeButtonBg - anchors.margins: -1 - opacity: closeButtonBg.opacity - border.width: 1 - radius: closeButtonBg.radius + 1 - border.color: Looks.colors.bg2Border - } - - background: Rectangle { - id: closeButtonBg - z: 1 - opacity: reusableCloseButton.hovered ? 1 : 0 - radius: Looks.radius.large - root.padding - color: reusableCloseButton.pressed ? Looks.colors.dangerActive : Looks.colors.danger - Behavior on opacity { - animation: Looks.transition.opacity.createObject(this) - } - Behavior on color { - animation: Looks.transition.color.createObject(this) - } - } - - contentItem: FluentIcon { - z: 2 - anchors.centerIn: parent - icon: "dismiss" - implicitSize: 10 - } } } diff --git a/dots/.config/quickshell/ii/modules/waffle/lock/WaffleLock.qml b/dots/.config/quickshell/ii/modules/waffle/lock/WaffleLock.qml new file mode 100644 index 000000000..581ce9152 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/lock/WaffleLock.qml @@ -0,0 +1,344 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Qt5Compat.GraphicalEffects +import Quickshell +import Quickshell.Io +import Quickshell.Wayland +import Quickshell.Hyprland +import qs +import qs.services +import qs.modules.common +import qs.modules.common.functions +import qs.modules.common.widgets +import qs.modules.common.panels.lock +import qs.modules.waffle.looks +import qs.modules.waffle.sessionScreen as SessionScreen + +LockScreen { + id: root + + property bool passwordView: false + + lockSurface: Item { + id: lockSurfaceItem + + Component.onCompleted: { + root.passwordView = false; + lockSurfaceItem.forceActiveFocus(); + } + + Keys.onPressed: { + interactables.switchToFocusedView(); + } + + Image { + id: bg + z: 0 + anchors.fill: parent + sourceSize: Qt.size(lockSurfaceItem.width, lockSurfaceItem.height) + source: Config.options.background.wallpaperPath + fillMode: Image.PreserveAspectCrop + } + + GaussianBlur { + z: 1 + anchors.fill: parent + source: bg + radius: 100 + samples: radius * 2 + 1 + scale: root.passwordView ? 1.1 : 1 + opacity: root.passwordView ? 1 : 0 + + Behavior on opacity { + animation: Looks.transition.opacity.createObject(this) + } + + Behavior on scale { + NumberAnimation { + duration: 400 + easing.type: Easing.BezierSpline + easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn + } + } + } + + Interactables { + id: interactables + z: 2 + anchors.fill: parent + } + } + + component Interactables: Rectangle { + id: interactablesComponent + color: ColorUtils.transparentize("#000000", 0.8) + // Button { + // onClicked: { + // root.context.unlocked(LockContext.ActionEnum.Unlock); + // GlobalStates.screenLocked = false; + // } + // text: "woah it doesnt work let me out pls uwu colon three" + // } + + function switchToFocusedView() { + root.passwordView = true; + } + + Item { + id: unfocusedContent + anchors.fill: parent + visible: !root.passwordView + ClockTextGroup { + anchors { + horizontalCenter: parent.horizontalCenter + top: parent.top + topMargin: interactablesComponent.height * 0.1 + } + } + RowLayout { + anchors { + bottom: parent.bottom + right: parent.right + bottomMargin: 21 + rightMargin: 31 + } + IconIndicator { + baseIcon: "wifi-1" + icon: WIcons.internetIcon + } + IconIndicator { + baseIcon: WIcons.batteryIcon + icon: WIcons.batteryLevelIcon + } + } + } + + Item { + id: focusedContent + anchors.fill: parent + visible: root.passwordView + + PasswordGroup { + visible: root.passwordView + anchors { + horizontalCenter: parent.horizontalCenter + verticalCenter: parent.verticalCenter + } + } + + RowLayout { + visible: root.passwordView + anchors { + bottom: parent.bottom + right: parent.right + bottomMargin: 21 + rightMargin: 31 + } + SessionScreen.PowerButton { + id: powerButton + } + } + } + } + + component IconIndicator: Item { + id: iconIndicator + required property string baseIcon + required property string icon + default property alias data: iconWidget.data + implicitWidth: 40 + implicitHeight: 40 + FluentIcon { + id: iconWidget + anchors.centerIn: parent + icon: iconIndicator.baseIcon + color: Looks.darkColors.inactiveIcon + implicitSize: 20 + FluentIcon { + anchors.fill: parent + icon: iconIndicator.icon + } + } + } + + component ClockTextGroup: Column { + id: clockTextGroup + spacing: -3 + + WText { + anchors.horizontalCenter: parent.horizontalCenter + color: Looks.darkColors.fg + font.pixelSize: 133 + font.weight: Looks.font.weight.strong + text: { + // Don't take am/pm + // Match groups of digits separated by non-digit chars (e.g., "12:34", "12.34", "12-34") + let match = DateTime.time.match(/(\d{1,2})\D+(\d{2})/); + return match ? `${match[1]}${DateTime.time.match(/\D+/)[0]}${match[2]}` : DateTime.time; + } + } + + WText { + id: dateLabel + color: Looks.darkColors.fg + anchors.horizontalCenter: parent.horizontalCenter + font.pixelSize: 28 + font.weight: Looks.font.weight.strong + text: DateTime.collapsedCalendarFormat + } + } + + component PasswordGroup: ColumnLayout { + id: passwordGroup + spacing: 15 + + WUserAvatar { + Layout.alignment: Qt.AlignHCenter + sourceSize: Qt.size(192, 192) + } + + WText { + Layout.alignment: Qt.AlignHCenter + text: SystemInfo.username + color: Looks.darkColors.fg + font.pixelSize: 26 + font.weight: Looks.font.weight.strong + } + + Rectangle { + id: passwordInputWrapper + Layout.topMargin: 10 + Layout.alignment: Qt.AlignHCenter + Layout.bottomMargin: 132 + color: "transparent" + implicitWidth: 296 + implicitHeight: 36 + border.width: 2 + border.color: Looks.applyContentTransparency(Looks.darkColors.bg1Border) + radius: Looks.radius.medium + + Rectangle { + id: passwordInputBackground + anchors.fill: parent + anchors.margins: 2 + radius: Looks.radius.small + 1 + color: passwordInput.focus ? Looks.applyBackgroundTransparency(Looks.darkColors.bg1Base) : Looks.applyContentTransparency(Looks.darkColors.bg1) + + RowLayout { + anchors.fill: parent + anchors.margins: 6 + spacing: 3 + + WTextInput { + id: passwordInput + Layout.fillHeight: true + Layout.fillWidth: true + verticalAlignment: TextInput.AlignVCenter + inputMethodHints: Qt.ImhSensitiveData + echoMode: passwordVisibilityButton.pressed ? TextInput.Normal : TextInput.Password + color: Looks.darkColors.fg + + font.pixelSize: 12 + WText { + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + visible: passwordInput.text.length === 0 + text: Translation.tr("Password") + font.pixelSize: Looks.font.pixelSize.large + color: Looks.darkColors.fg + opacity: 0.8 + } + + onTextChanged: root.context.currentText = this.text + onAccepted: { + root.context.tryUnlock(); + } + Connections { + target: root.context + function onCurrentTextChanged() { + passwordInput.text = root.context.currentText; + } + } + Connections { + target: root + function onPasswordViewChanged() { + passwordInput.forceActiveFocus(); + } + } + + Keys.onPressed: event => { + root.context.resetClearTimer(); + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.NoButton + cursorShape: Qt.IBeamCursor + } + } + + PasswordBoxButton { + id: passwordVisibilityButton + property bool passwordVisible: false + visible: passwordInput.text.length > 0 + onPressed: passwordVisible = true + onReleased: passwordVisible = false + icon.name: passwordVisible ? "eye-off" : "eye" + } + + PasswordBoxButton { + onClicked: { + root.context.tryUnlock(); + } + icon.name: "arrow-right" + } + } + } + Rectangle { + id: activeIndicatorLine + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + implicitHeight: 2 + color: passwordInput.focus ? Looks.colors.accent : Looks.applyContentTransparency(Looks.darkColors.bg2Border) + } + + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: passwordInputWrapper.width + height: passwordInputWrapper.height + radius: passwordInputWrapper.radius + } + } + } + + Item {} + } + + component PasswordBoxButton: WButton { + id: pwBoxBtn + implicitWidth: 28 + implicitHeight: 22 + + property color colBackground: ColorUtils.transparentize(Looks.darkColors.bg1) + property color colBackgroundHover: ColorUtils.transparentize(Looks.darkColors.bg2Hover) + property color colBackgroundActive: ColorUtils.transparentize(Looks.darkColors.bg2Active) + fgColor: checked ? Looks.colors.accentFg : Looks.darkColors.fg + + checked: hovered + + contentItem: Item { + FluentIcon { + color: pwBoxBtn.fgColor + anchors.centerIn: parent + icon: pwBoxBtn.icon.name + implicitSize: 16 + } + } + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/CloseButton.qml b/dots/.config/quickshell/ii/modules/waffle/looks/CloseButton.qml new file mode 100644 index 000000000..3345d0cc8 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/looks/CloseButton.qml @@ -0,0 +1,48 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Qt5Compat.GraphicalEffects +import qs.services +import qs.modules.common +import qs.modules.common.functions +import qs.modules.waffle.looks +import qs.modules.waffle.bar +import Quickshell + +Button { + id: reusableCloseButton + implicitHeight: 30 + implicitWidth: 30 + property alias radius: closeButtonBg.radius + + Rectangle { + z: 0 + color: "transparent" + anchors.fill: closeButtonBg + anchors.margins: -1 + opacity: closeButtonBg.opacity + border.width: 1 + radius: closeButtonBg.radius + 1 + border.color: Looks.colors.bg2Border + } + + background: Rectangle { + id: closeButtonBg + z: 1 + opacity: reusableCloseButton.hovered ? 1 : 0 + color: reusableCloseButton.pressed ? Looks.colors.dangerActive : Looks.colors.danger + Behavior on opacity { + animation: Looks.transition.opacity.createObject(this) + } + Behavior on color { + animation: Looks.transition.color.createObject(this) + } + } + + contentItem: FluentIcon { + z: 2 + anchors.centerIn: parent + icon: "dismiss" + implicitSize: 10 + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml b/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml index 802add786..51e7fe40c 100644 --- a/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml +++ b/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml @@ -96,12 +96,12 @@ Singleton { property color bg0Opaque: root.dark ? root.darkColors.bg0 : root.lightColors.bg0 property color bg0: ColorUtils.transparentize(bg0Opaque, root.backgroundTransparency) property color bg0Border: ColorUtils.transparentize(root.dark ? root.darkColors.bg0Border : root.lightColors.bg0Border, root.backgroundTransparency) - property color bg1Base: ColorUtils.transparentize(root.dark ? root.darkColors.bg1Base : root.lightColors.bg1Base, root.backgroundTransparency) + property color bg1Base: root.dark ? root.darkColors.bg1Base : root.lightColors.bg1Base property color bg1: ColorUtils.transparentize(root.dark ? root.darkColors.bg1 : root.lightColors.bg1, root.contentTransparency) property color bg1Hover: ColorUtils.transparentize(root.dark ? root.darkColors.bg1Hover : root.lightColors.bg1Hover, root.contentTransparency) property color bg1Active: ColorUtils.transparentize(root.dark ? root.darkColors.bg1Active : root.lightColors.bg1Active, root.contentTransparency) property color bg1Border: ColorUtils.transparentize(root.dark ? root.darkColors.bg1Border : root.lightColors.bg1Border, root.contentTransparency) - property color bg2Base: ColorUtils.transparentize(root.dark ? root.darkColors.bg2Base : root.lightColors.bg2Base, root.backgroundTransparency) + property color bg2Base: root.dark ? root.darkColors.bg2Base : root.lightColors.bg2Base property color bg2: ColorUtils.transparentize(root.dark ? root.darkColors.bg2 : root.lightColors.bg2, root.contentTransparency) property color bg2Hover: ColorUtils.transparentize(root.dark ? root.darkColors.bg2Hover : root.lightColors.bg2Hover, root.contentTransparency) property color bg2Active: ColorUtils.transparentize(root.dark ? root.darkColors.bg2Active : root.lightColors.bg2Active, root.contentTransparency) @@ -146,7 +146,8 @@ Singleton { property int thin: Font.Normal property int regular: Font.Medium property int strong: Font.DemiBold - property int stronger: Font.Bold + property int stronger: (Font.DemiBold + 2*Font.Bold) / 3 + property int strongest: Font.Bold } property QtObject pixelSize: QtObject { property real normal: 11 @@ -154,6 +155,11 @@ Singleton { property real larger: 15 property real xlarger: 17 } + property QtObject variableAxes: QtObject { + property var ui: ({ + "wdth": 25 + }) + } } transition: QtObject { diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/VerticalPageIndicator.qml b/dots/.config/quickshell/ii/modules/waffle/looks/VerticalPageIndicator.qml new file mode 100644 index 000000000..bb46bdc1c --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/looks/VerticalPageIndicator.qml @@ -0,0 +1,73 @@ +pragma ComponentBehavior: Bound +import Qt.labs.synchronizer +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import Quickshell +import qs +import qs.services +import qs.modules.common +import qs.modules.common.widgets +import qs.modules.common.functions +import qs.modules.waffle.looks + +Column { + id: root + + property bool showArrows: true + property int currentIndex: 0 + property int count: 1 + signal clicked(int index) + signal increasePage() + signal decreasePage() + + visible: count > 1 + spacing: 6 + + NavigationArrow { + visible: root.showArrows + down: false + } + + Repeater { + model: root.count + delegate: MouseArea { + id: pageIndicator + required property int index + hoverEnabled: true + onClicked: root.clicked(index); + anchors.horizontalCenter: parent.horizontalCenter + implicitWidth: 6 + implicitHeight: 6 + + Circle { + anchors.centerIn: parent + diameter: (index === root.currentIndex || pageIndicator.containsMouse) && !pageIndicator.pressed ? 6 : 4 + color: pageIndicator.containsMouse ? Looks.colors.controlBgHover : Looks.colors.controlBg + } + } + } + + NavigationArrow { + visible: root.showArrows + down: true + } + + component NavigationArrow: FluentIcon { + id: navArrow + required property bool down + anchors.horizontalCenter: parent.horizontalCenter + implicitHeight: 12 + implicitWidth: 12 - (2 * upArea.containsPress) + icon: down ? "caret-down" : "caret-up" + color: upArea.containsMouse ? Looks.colors.controlBgHover : Looks.colors.controlBg + filled: true + opacity: ((down && root.currentIndex < root.count - 1) || (!down && root.currentIndex > 0)) ? 1 : 0 + MouseArea { + id: upArea + anchors.fill: parent + hoverEnabled: true + onClicked: navArrow.down ? root.increasePage() : root.decreasePage(); + } + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WAppIcon.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WAppIcon.qml index 6f71c65bb..bd0f2fce4 100644 --- a/dots/.config/quickshell/ii/modules/waffle/looks/WAppIcon.qml +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WAppIcon.qml @@ -17,4 +17,6 @@ Kirigami.Icon { roundToIconSize: false fallback: root.iconName source: tryCustomIcon ? `${Looks.iconsPath}/${root.iconName}${!root.separateLightDark ? "" : Looks.dark ? "-dark" : "-light"}.svg` : fallback + + color: Looks.colors.fg } diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WMenu.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WMenu.qml index a18c59ec9..9f4f7f340 100644 --- a/dots/.config/quickshell/ii/modules/waffle/looks/WMenu.qml +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WMenu.qml @@ -14,6 +14,9 @@ Menu { property bool downDirection: false property bool hasIcons: false // TODO: implement + property color color: Looks.colors.bg1Base + property alias backgroundPane: bgPane + implicitWidth: background.implicitWidth + margins * 2 implicitHeight: background.implicitHeight + margins * 2 margins: 10 @@ -58,7 +61,7 @@ Menu { bottomMargin: root.downDirection ? root.margins : root.sourceEdgeMargin } contentItem: Rectangle { - color: Looks.colors.bg1Base + color: root.color implicitWidth: menuListView.implicitWidth + root.padding * 2 implicitHeight: root.contentItem.implicitHeight + root.padding * 2 } @@ -66,6 +69,10 @@ Menu { } } + Component.onCompleted: { + menuListView.itemAtIndex(0)?.forceActiveFocus(); + } + contentItem: Item { implicitWidth: menuListView.implicitWidth implicitHeight: menuListView.implicitHeight @@ -91,6 +98,5 @@ Menu { delegate: WMenuItem { id: menuItemDelegate - width: ListView.view?.width } } diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WMenuItem.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WMenuItem.qml index 3030bc122..731b0d704 100644 --- a/dots/.config/quickshell/ii/modules/waffle/looks/WMenuItem.qml +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WMenuItem.qml @@ -55,6 +55,9 @@ MenuItem { rightInset: inset horizontalPadding: 11 + width: ListView.view?.width + height: visible ? implicitHeight : 0 + background: Rectangle { id: backgroundRect radius: Looks.radius.medium diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WRectangularShadowThis.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WRectangularShadowThis.qml new file mode 100644 index 000000000..5ad46de29 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WRectangularShadowThis.qml @@ -0,0 +1,15 @@ +import QtQuick +import QtQuick.Effects +import qs.modules.common +import qs.modules.common.widgets + +Item { + default property Item contentItem + property Item shadow: WRectangularShadow { + target: contentItem + } + implicitWidth: contentItem.implicitWidth + implicitHeight: contentItem.implicitHeight + + children: [shadow, contentItem] +} diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WText.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WText.qml index 0da156893..ab6dd60b7 100644 --- a/dots/.config/quickshell/ii/modules/waffle/looks/WText.qml +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WText.qml @@ -8,10 +8,11 @@ Text { color: Looks.colors.fg font { - hintingPreference: Font.PreferFullHinting + hintingPreference: Font.PreferDefaultHinting family: Looks.font.family.ui pixelSize: Looks.font.pixelSize.normal weight: Looks.font.weight.regular + variableAxes: Looks.font.variableAxes.ui } linkColor: Looks.colors.link diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/FooterMoreButton.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WTextButton.qml similarity index 100% rename from dots/.config/quickshell/ii/modules/waffle/looks/FooterMoreButton.qml rename to dots/.config/quickshell/ii/modules/waffle/looks/WTextButton.qml diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WTextField.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WTextField.qml new file mode 100644 index 000000000..a3cc9dbc0 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WTextField.qml @@ -0,0 +1,31 @@ +import qs.modules.common +import QtQuick +import QtQuick.Controls.FluentWinUI3 +import QtQuick.Controls + +TextField { + id: root + + clip: true + renderType: Text.NativeRendering + verticalAlignment: Text.AlignVCenter + color: Looks.colors.fg + + palette { + active: Looks.colors.accent + } + + font { + hintingPreference: Font.PreferDefaultHinting + family: Looks.font.family.ui + pixelSize: Looks.font.pixelSize.normal + weight: Looks.font.weight.regular + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.NoButton + hoverEnabled: true + cursorShape: Qt.IBeamCursor + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/polkit/WPolkitContent.qml b/dots/.config/quickshell/ii/modules/waffle/polkit/WPolkitContent.qml new file mode 100644 index 000000000..1218efb69 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/polkit/WPolkitContent.qml @@ -0,0 +1,208 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import qs.services +import qs.modules.common +import qs.modules.common.functions +import qs.modules.common.widgets +import qs.modules.waffle.looks + +Rectangle { + id: root + + color: "#000000" + readonly property bool usePasswordChars: !PolkitService.flow?.responseVisible ?? true + + Keys.onPressed: event => { // Esc to close + if (event.key === Qt.Key_Escape) { + PolkitService.cancel(); + } + } + + StyledImage { + anchors.fill: parent + source: Config.options.background.wallpaperPath + fillMode: Image.PreserveAspectCrop + + Rectangle { + anchors.fill: parent + color: ColorUtils.transparentize("#000000", 0.31) + + PolkitDialog { + id: dialog + DragHandler { + target: null + property real startX: dialog.x + property real startY: dialog.y + onActiveChanged: { + if (!active) return; + startX = dialog.x; + startY = dialog.y; + } + xAxis.onActiveValueChanged: { + dialog.x = Math.round(startX + xAxis.activeValue); + } + yAxis.onActiveValueChanged: { + dialog.y = Math.round(startY + yAxis.activeValue); + } + } + x: Math.round((parent.width - width) / 2) + y: Math.round((parent.height - height) / 2) + } + } + } + + component PolkitDialog: WPane { + borderColor: Looks.colors.ambientShadow + + contentItem: WPanelPageColumn { + PolkitDialogHeader { + Layout.fillWidth: true + } + BodyRectangle { + id: dialogBody + implicitHeight: bodyContent.implicitHeight + 48 + implicitWidth: 434 + color: Looks.colors.bg1Base + + ColumnLayout { + id: bodyContent + anchors.fill: parent + anchors.margins: 24 + spacing: 20 + + RowLayout { + Layout.fillWidth: true + spacing: 15 + + WAppIcon { + iconName: PolkitService.flow?.iconName ?? "window-shield" + fallback: PolkitService.flow?.iconName == "" ? `${Looks.iconsPath}/window-shield` : PolkitService.flow.iconName + isMask: PolkitService.flow?.iconName === "" + tryCustomIcon: false + } + WText { + Layout.fillWidth: true + horizontalAlignment: Text.AlignLeft + font.pixelSize: Looks.font.pixelSize.larger + font.weight: Looks.font.weight.strongest + text: { + const iconName = PolkitService.flow?.iconName ?? ""; + if (iconName === "") + return Translation.tr("Command-line-invoked Action"); + const desktopEntry = DesktopEntries.applications.values.find(entry => { + return entry.icon == iconName; + }); + return desktopEntry ? desktopEntry.name : Translation.tr("Unknown Application"); + } + } + } + + WText { + Layout.fillWidth: true + wrapMode: Text.Wrap + horizontalAlignment: Text.AlignLeft + text: PolkitService.cleanMessage + } + + WTextField { + id: inputField + Layout.fillWidth: true + focus: true + enabled: PolkitService.interactionAvailable + placeholderText: PolkitService.cleanPrompt + echoMode: root.usePasswordChars ? TextInput.Password : TextInput.Normal + onAccepted: PolkitService.submit(inputField.text) + + Keys.onPressed: event => { // Esc to close + if (event.key === Qt.Key_Escape) { + PolkitService.cancel(); + } + } + + Component.onCompleted: forceActiveFocus() + Connections { + target: PolkitService + function onInteractionAvailableChanged() { + if (!PolkitService.interactionAvailable) + return; + inputField.text = ""; + inputField.forceActiveFocus(); + } + } + } + } + } + BodyRectangle { + implicitHeight: 80 + color: Looks.colors.bgPanelFooterBase + RowLayout { + anchors.fill: parent + anchors.margins: 24 + spacing: 8 + uniformCellSizes: true + + WButton { + Layout.fillWidth: true + implicitHeight: 32 + colBackground: Looks.colors.bg1 + horizontalAlignment: Text.AlignHCenter + text: Translation.tr("Yes") + onClicked: PolkitService.submit(inputField.text) + } + WButton { + Layout.fillWidth: true + implicitHeight: 32 + horizontalAlignment: Text.AlignHCenter + checked: true + text: Translation.tr("No") + onClicked: PolkitService.cancel() + } + } + } + } + } + + component PolkitDialogHeader: BodyRectangle { + implicitHeight: headerContent.implicitHeight + color: Looks.colors.bg2Base + + CloseButton { + anchors { + top: parent.top + right: parent.right + } + radius: 0 + implicitWidth: 32 + implicitHeight: 32 + + onClicked: { + PolkitService.cancel(); + } + } + + ColumnLayout { + id: headerContent + anchors.fill: parent + anchors.leftMargin: 24 + anchors.rightMargin: 24 + spacing: 18 + + WText { + Layout.topMargin: 20 + Layout.fillWidth: true + horizontalAlignment: Text.AlignLeft + text: Translation.tr("Polkit") + } + WText { + Layout.fillWidth: true + Layout.bottomMargin: 12 + horizontalAlignment: Text.AlignLeft + wrapMode: Text.Wrap + text: Translation.tr("Do you want to allow this app to make changes to your device?") + font.pixelSize: Looks.font.pixelSize.xlarger + font.weight: Looks.font.weight.strongest + } + } + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/polkit/WafflePolkit.qml b/dots/.config/quickshell/ii/modules/waffle/polkit/WafflePolkit.qml new file mode 100644 index 000000000..8aa7d5672 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/polkit/WafflePolkit.qml @@ -0,0 +1,15 @@ +import qs +import qs.services +import qs.modules.common +import qs.modules.common.widgets +import qs.modules.common.functions +import QtQuick +import Quickshell +import Quickshell.Wayland + +FullscreenPolkitWindow { + id: root + contentComponent: Component { + WPolkitContent {} + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/sessionScreen/PowerButton.qml b/dots/.config/quickshell/ii/modules/waffle/sessionScreen/PowerButton.qml new file mode 100644 index 000000000..d3bd07ad3 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/sessionScreen/PowerButton.qml @@ -0,0 +1,76 @@ +import qs +import qs.services +import qs.modules.common +import qs.modules.common.widgets +import qs.modules.common.functions +import qs.modules.waffle.looks +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io + +WSessionScreenTextButton { + id: root + implicitWidth: 40 + implicitHeight: 40 + focusRingRadius: Looks.radius.large + colBackground: ColorUtils.transparentize(Looks.darkColors.bg2) + colBackgroundHover: Looks.applyContentTransparency(Looks.darkColors.bg2Hover) + colBackgroundActive: Looks.applyContentTransparency(Looks.darkColors.bg2Active) + property color color: { + if (root.down) { + return root.colBackgroundActive; + } else if (root.hovered) { + return root.colBackgroundHover; + } else { + return root.colBackground; + } + } + background: Rectangle { + id: background + radius: Looks.radius.medium + color: root.color + } + contentItem: Item { + FluentIcon { + anchors.centerIn: parent + implicitSize: 20 + icon: "power" + color: root.fgColor + } + } + + onClicked: { + powerMenu.visible = !powerMenu.visible; + } + + WMenu { + id: powerMenu + x: -powerMenu.implicitWidth / 2 + root.implicitWidth / 2 + y: -powerMenu.implicitHeight + + color: Looks.darkColors.bg1Base + Component.onCompleted: { + powerMenu.backgroundPane.borderColor = Looks.applyContentTransparency(Looks.darkColors.bg2Border); + } + delegate: WMenuItem { + id: menuItemDelegate + colBackground: ColorUtils.transparentize(Looks.darkColors.bg1Base) + colBackgroundHover: Looks.applyContentTransparency(Looks.darkColors.bg2Hover) + colBackgroundActive: Looks.applyContentTransparency(Looks.darkColors.bg2Active) + colForeground: Looks.darkColors.fg + } + + Action { + icon.name: "power" + text: Translation.tr("Shut down") + onTriggered: Session.poweroff() + } + Action { + icon.name: "arrow-counterclockwise" + text: Translation.tr("Restart") + onTriggered: Session.reboot() + } + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/sessionScreen/SessionScreenContent.qml b/dots/.config/quickshell/ii/modules/waffle/sessionScreen/SessionScreenContent.qml new file mode 100644 index 000000000..6a9d0ef31 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/sessionScreen/SessionScreenContent.qml @@ -0,0 +1,139 @@ +import qs +import qs.services +import qs.modules.common +import qs.modules.common.widgets +import qs.modules.common.functions +import qs.modules.waffle.looks +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell + +Item { + id: root + + Component.onCompleted: { + lockButton.forceActiveFocus(); + } + + ColumnLayout { + anchors.centerIn: parent + spacing: 4 + + WSessionScreenTextButton { + id: lockButton + focus: true + text: Translation.tr("Lock") + onClicked: { + GlobalStates.sessionOpen = false; + Session.lock(); + } + KeyNavigation.up: powerButton + KeyNavigation.down: signOutButton + } + WSessionScreenTextButton { + id: signOutButton + focus: true + text: Translation.tr("Sign out") + onClicked: { + GlobalStates.sessionOpen = false; + Session.logout(); + } + KeyNavigation.up: lockButton + KeyNavigation.down: changePasswordButton + } + + WSessionScreenTextButton { + id: changePasswordButton + focus: true + text: Translation.tr("Change password") + onClicked: { + GlobalStates.sessionOpen = false; + Session.changePassword(); + } + KeyNavigation.up: signOutButton + KeyNavigation.down: taskManagerButton + } + + WSessionScreenTextButton { + id: taskManagerButton + focus: true + text: Translation.tr("Task Manager") + onClicked: { + GlobalStates.sessionOpen = false; + Session.launchTaskManager(); + } + KeyNavigation.up: signOutButton + KeyNavigation.down: cancelButton + } + + CancelButton { + id: cancelButton + Layout.fillWidth: true + Layout.leftMargin: 5 + Layout.rightMargin: 5 + Layout.topMargin: 38 + onClicked: GlobalStates.sessionOpen = false + KeyNavigation.up: taskManagerButton + KeyNavigation.down: powerButton + } + } + + RowLayout { + anchors { + bottom: parent.bottom + right: parent.right + bottomMargin: 21 + rightMargin: 31 + } + PowerButton { + id: powerButton + KeyNavigation.up: cancelButton + KeyNavigation.down: lockButton + } + } + + component CancelButton: WBorderlessButton { + id: root + implicitHeight: 32 + colBackground: Looks.darkColors.bg1Base + colBackgroundHover: Qt.lighter(Looks.darkColors.bg1Base, 1.2) + colBackgroundActive: Qt.lighter(Looks.darkColors.bg1Base, 1.1) + colForeground: Looks.darkColors.fg + + property bool keyboardDown: false + + Keys.onPressed: event => { + if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { + keyboardDown = true; + event.accepted = true; + } + } + Keys.onReleased: event => { + if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { + keyboardDown = false; + root.clicked(); + event.accepted = true; + } + } + + contentItem: WText { + text: Translation.tr("Cancel") + horizontalAlignment: Text.AlignHCenter + font.pixelSize: Looks.font.pixelSize.large + color: root.colForeground + } + + Rectangle { + visible: cancelButton.focus + anchors { + fill: parent + margins: -3 + } + radius: cancelButton.background.radius + 4 + color: "transparent" + border.width: 2 + border.color: "#ffffff" + } + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/sessionScreen/WSessionScreenTextButton.qml b/dots/.config/quickshell/ii/modules/waffle/sessionScreen/WSessionScreenTextButton.qml new file mode 100644 index 000000000..875ca03b4 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/sessionScreen/WSessionScreenTextButton.qml @@ -0,0 +1,55 @@ +pragma ComponentBehavior: Bound +import QtQuick +import qs +import qs.modules.waffle.looks + +WTextButton { + id: root + + implicitWidth: 135 + implicitHeight: 40 + horizontalPadding: 5 + + property bool keyboardDown: false + property alias focusRingRadius: focusRing.radius + fgColor: (root.pressed || root.keyboardDown) ? Looks.darkColors.fg1 : Looks.darkColors.fg + + Keys.onPressed: event => { + if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { + keyboardDown = true; + event.accepted = true; + } + } + Keys.onReleased: event => { + if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { + keyboardDown = false; + root.clicked(); + event.accepted = true; + } + } + + contentItem: Item { + id: contentItem + implicitWidth: buttonText.implicitWidth + + WText { + id: buttonText + anchors.fill: parent + color: root.fgColor + text: root.text + font.pixelSize: Looks.font.pixelSize.large + } + } + + Rectangle { + id: focusRing + visible: root.focus + anchors { + fill: parent + margins: -4 + } + color: "transparent" + border.width: 2 + border.color: "#ffffff" + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/sessionScreen/WaffleSessionScreen.qml b/dots/.config/quickshell/ii/modules/waffle/sessionScreen/WaffleSessionScreen.qml new file mode 100644 index 000000000..7f0d1a5d7 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/sessionScreen/WaffleSessionScreen.qml @@ -0,0 +1,116 @@ +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 +import Quickshell.Io +import Quickshell.Wayland +import Quickshell.Hyprland + +Scope { + id: root + property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name) + + Loader { + id: sessionLoader + active: GlobalStates.sessionOpen + onActiveChanged: { + if (sessionLoader.active) SessionWarnings.refresh(); + } + + Connections { + target: GlobalStates + function onScreenLockedChanged() { + if (GlobalStates.screenLocked) { + GlobalStates.sessionOpen = false; + } + } + } + + sourceComponent: PanelWindow { // Session menu + id: sessionRoot + visible: sessionLoader.active + property string subtitle + + function hide() { + GlobalStates.sessionOpen = false; + } + + exclusionMode: ExclusionMode.Ignore + WlrLayershell.namespace: "quickshell:session" + WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive + // This is a big surface so we needa carefully choose the transparency, + // or we'll get a large scary rgb blob + color: "#000000" + + anchors { + top: true + left: true + right: true + bottom: true + } + + Item { + anchors.fill: parent + Keys.onPressed: (event) => { + if (event.key === Qt.Key_Escape) { + sessionRoot.hide(); + } + } + + SessionScreenContent { + anchors.fill: parent + } + } + } + } + + IpcHandler { + target: "session" + + function toggle(): void { + GlobalStates.sessionOpen = !GlobalStates.sessionOpen; + } + + function close(): void { + GlobalStates.sessionOpen = false + } + + function open(): void { + GlobalStates.sessionOpen = true + } + } + + GlobalShortcut { + name: "sessionToggle" + description: "Toggles session screen on press" + + onPressed: { + GlobalStates.sessionOpen = !GlobalStates.sessionOpen; + } + } + + GlobalShortcut { + name: "sessionOpen" + description: "Opens session screen on press" + + onPressed: { + GlobalStates.sessionOpen = true + } + } + + GlobalShortcut { + name: "sessionClose" + description: "Closes session screen on press" + + onPressed: { + GlobalStates.sessionOpen = false + } + } + +} diff --git a/dots/.config/quickshell/ii/modules/waffle/startMenu/StartMenuContent.qml b/dots/.config/quickshell/ii/modules/waffle/startMenu/StartMenuContent.qml index 2f76ca0ca..fb506dd49 100644 --- a/dots/.config/quickshell/ii/modules/waffle/startMenu/StartMenuContent.qml +++ b/dots/.config/quickshell/ii/modules/waffle/startMenu/StartMenuContent.qml @@ -9,6 +9,8 @@ import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks +import qs.modules.waffle.startMenu.startPage +import qs.modules.waffle.startMenu.searchPage WBarAttachedPanelContent { id: root @@ -99,7 +101,7 @@ WBarAttachedPanelContent { } } Item { - implicitHeight: root.searching ? 736 : 736 // TODO: Make sizes naturally inferred + implicitHeight: root.searching ? 800 : 800 // TODO: Make sizes naturally inferred Layout.fillWidth: true Loader { id: pageContentLoader diff --git a/dots/.config/quickshell/ii/modules/waffle/startMenu/StartPageContent.qml b/dots/.config/quickshell/ii/modules/waffle/startMenu/StartPageContent.qml deleted file mode 100644 index f2f863cd2..000000000 --- a/dots/.config/quickshell/ii/modules/waffle/startMenu/StartPageContent.qml +++ /dev/null @@ -1,227 +0,0 @@ -pragma ComponentBehavior: Bound -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import Qt5Compat.GraphicalEffects -import Quickshell -import qs -import qs.services -import qs.modules.common -import qs.modules.common.functions -import qs.modules.common.widgets -import qs.modules.waffle.looks - -WPanelPageColumn { - id: root - - WPanelSeparator {} - - BodyRectangle { - Layout.fillHeight: true - } - - WPanelSeparator {} - - StartFooter { - Layout.fillWidth: true - } - - component StartFooter: FooterRectangle { - implicitHeight: 63 - - UserButton { - anchors { - left: parent.left - leftMargin: 52 - bottom: parent.bottom - bottomMargin: 12 - } - } - - PowerButton { - anchors { - right: parent.right - rightMargin: 52 - bottom: parent.bottom - bottomMargin: 12 - } - } - } - - component UserButton: WBorderlessButton { - id: userButton - implicitWidth: userButtonRow.implicitWidth + 12 * 2 - implicitHeight: 40 - - contentItem: Item { - RowLayout { - id: userButtonRow - anchors.centerIn: parent - spacing: 12 - - WUserAvatar { - sourceSize: Qt.size(32, 32) - } - WText { - Layout.alignment: Qt.AlignVCenter - text: SystemInfo.username - } - } - } - - onClicked: { - userMenu.open(); - } - - WToolTip { - text: SystemInfo.username - } - - Popup { - id: userMenu - x: -51 - y: -userMenu.implicitHeight + userButton.implicitHeight / 2 - 10 - - background: null - - WToolTipContent { - id: popupContent - horizontalPadding: 10 - verticalPadding: 7 - radius: Looks.radius.large - realContentItem: Item { - implicitWidth: userMenuContentLayout.implicitWidth - implicitHeight: userMenuContentLayout.implicitHeight - - ColumnLayout { - id: userMenuContentLayout - anchors { - fill: parent - leftMargin: popupContent.horizontalPadding - rightMargin: popupContent.horizontalPadding - topMargin: popupContent.verticalPadding - bottomMargin: popupContent.verticalPadding - } - spacing: 5 - - RowLayout { - Layout.fillWidth: true - Layout.leftMargin: 6 - FluentIcon { - Layout.alignment: Qt.AlignVCenter - implicitSize: 22 - icon: "corporation" - monochrome: false - } - WText { - Layout.alignment: Qt.AlignVCenter - text: "Megahard" - font.pixelSize: Looks.font.pixelSize.large - font.weight: Looks.font.weight.strong - } - Item { Layout.fillWidth: true } - WBorderlessButton { - Layout.alignment: Qt.AlignVCenter - implicitHeight: 36 - implicitWidth: textItem.implicitWidth + 10 * 2 - contentItem: WText { - id: textItem - text: Translation.tr("Sign out") - font.pixelSize: Looks.font.pixelSize.large - } - onClicked: Session.logout() - } - } - Item { // Force min width 360 (using min on the item somehow doesn't work) - implicitWidth: 334 - } - RowLayout { - Layout.fillWidth: true - Layout.bottomMargin: 7 - Layout.leftMargin: 6 - spacing: 12 - WUserAvatar { - sourceSize: Qt.size(58, 58) - } - ColumnLayout { - Layout.fillWidth: true - Layout.alignment: Qt.AlignVCenter - spacing: 2 - WText { - text: SystemInfo.username - font.pixelSize: Looks.font.pixelSize.larger - font.weight: Looks.font.weight.strong - } - WText { - color: Looks.colors.fg1 - text: Translation.tr("Local account") - } - WText { - color: Looks.colors.accent - text: Translation.tr("Manage my account") - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: { - Quickshell.execDetached(["bash", "-c", Config.options.apps.manageUser]) - GlobalStates.searchOpen = false; - } - } - } - } - } - } - } - } - } - } - - component PowerButton: WBorderlessButton { - id: powerButton - implicitWidth: 40 - implicitHeight: 40 - - contentItem: Item { - FluentIcon { - anchors.centerIn: parent - icon: "power" - implicitSize: 20 - } - } - - WToolTip { - extraVisibleCondition: !powerMenu.visible - text: qsTr("Power") - } - - onClicked: { - powerMenu.open() - } - - WMenu { - id: powerMenu - x: -powerMenu.implicitWidth / 2 + powerButton.implicitWidth / 2 - y: -powerMenu.implicitHeight - 4 - Action { - icon.name: "lock-closed" - text: Translation.tr("Lock") - onTriggered: Session.lock() - } - Action { - icon.name: "weather-moon" - text: Translation.tr("Sleep") - onTriggered: Session.suspend() - } - Action { - icon.name: "power" - text: Translation.tr("Shut down") - onTriggered: Session.poweroff() - } - Action { - icon.name: "arrow-counterclockwise" - text: Translation.tr("Restart") - onTriggered: Session.reboot() - } - } - } -} diff --git a/dots/.config/quickshell/ii/modules/waffle/startMenu/WaffleStartMenu.qml b/dots/.config/quickshell/ii/modules/waffle/startMenu/WaffleStartMenu.qml index 15ffc6f43..62bdc65bf 100644 --- a/dots/.config/quickshell/ii/modules/waffle/startMenu/WaffleStartMenu.qml +++ b/dots/.config/quickshell/ii/modules/waffle/startMenu/WaffleStartMenu.qml @@ -70,6 +70,19 @@ Scope { } } + function toggleClipboard() { + if (LauncherSearch.query.startsWith(Config.options.search.prefix.clipboard) || !GlobalStates.searchOpen) { + GlobalStates.searchOpen = !GlobalStates.searchOpen; + } + LauncherSearch.ensurePrefix(Config.options.search.prefix.clipboard); + } + function toggleEmojis() { + if (LauncherSearch.query.startsWith(Config.options.search.prefix.emojis) || !GlobalStates.searchOpen) { + GlobalStates.searchOpen = !GlobalStates.searchOpen; + } + LauncherSearch.ensurePrefix(Config.options.search.prefix.emojis); + } + IpcHandler { target: "search" @@ -119,4 +132,22 @@ Scope { GlobalStates.superReleaseMightTrigger = false; } } + + GlobalShortcut { + name: "overviewClipboardToggle" + description: "Toggle clipboard query on overview widget" + + onPressed: { + root.toggleClipboard(); + } + } + + GlobalShortcut { + name: "overviewEmojiToggle" + description: "Toggle emoji query on overview widget" + + onPressed: { + root.toggleEmojis(); + } + } } diff --git a/dots/.config/quickshell/ii/modules/waffle/startMenu/SearchEntryIcon.qml b/dots/.config/quickshell/ii/modules/waffle/startMenu/searchPage/SearchEntryIcon.qml similarity index 100% rename from dots/.config/quickshell/ii/modules/waffle/startMenu/SearchEntryIcon.qml rename to dots/.config/quickshell/ii/modules/waffle/startMenu/searchPage/SearchEntryIcon.qml diff --git a/dots/.config/quickshell/ii/modules/waffle/startMenu/SearchPageContent.qml b/dots/.config/quickshell/ii/modules/waffle/startMenu/searchPage/SearchPageContent.qml similarity index 100% rename from dots/.config/quickshell/ii/modules/waffle/startMenu/SearchPageContent.qml rename to dots/.config/quickshell/ii/modules/waffle/startMenu/searchPage/SearchPageContent.qml diff --git a/dots/.config/quickshell/ii/modules/waffle/startMenu/WSearchResultButton.qml b/dots/.config/quickshell/ii/modules/waffle/startMenu/searchPage/SearchResultButton.qml similarity index 100% rename from dots/.config/quickshell/ii/modules/waffle/startMenu/WSearchResultButton.qml rename to dots/.config/quickshell/ii/modules/waffle/startMenu/searchPage/SearchResultButton.qml diff --git a/dots/.config/quickshell/ii/modules/waffle/startMenu/SearchResults.qml b/dots/.config/quickshell/ii/modules/waffle/startMenu/searchPage/SearchResults.qml similarity index 91% rename from dots/.config/quickshell/ii/modules/waffle/startMenu/SearchResults.qml rename to dots/.config/quickshell/ii/modules/waffle/startMenu/searchPage/SearchResults.qml index a9e83929e..aeaa4748c 100644 --- a/dots/.config/quickshell/ii/modules/waffle/startMenu/SearchResults.qml +++ b/dots/.config/quickshell/ii/modules/waffle/startMenu/searchPage/SearchResults.qml @@ -5,6 +5,7 @@ import qs.modules.common import qs.modules.waffle.looks import qs.modules.common.functions import qs.modules.common.models +import qs.modules.waffle.startMenu import Quickshell import QtQuick.Layouts import QtQuick.Controls @@ -119,7 +120,7 @@ RowLayout { onModelChanged: { root.focusFirstItem(); } - delegate: WSearchResultButton { + delegate: SearchResultButton { required property int index required property var modelData entry: modelData @@ -189,6 +190,7 @@ RowLayout { const isAppEntry = resultPreview.entry.type === Translation.tr("App"); const appId = isAppEntry ? resultPreview.entry.id : ""; const pinned = isAppEntry ? (Config.options.dock.pinnedApps.includes(appId)) : false; + const startPinned = isAppEntry ? (Config.options.launcher.pinnedApps.includes(appId)) : false; var result = [ searchResultComp.createObject(null, { name: resultPreview.entry.verb, @@ -198,6 +200,16 @@ RowLayout { resultPreview.entry.execute(); } }), + ...(isAppEntry ? [ + searchResultComp.createObject(null, { + name: startPinned ? Translation.tr("Unpin from Start") : Translation.tr("Pin to Start"), + iconName: startPinned ? "keep_off" : "keep", + iconType: LauncherSearchResult.IconType.Material, + execute: () => { + LauncherApps.togglePin(appId); + } + }) + ] : []), ...(isAppEntry ? [ searchResultComp.createObject(null, { name: pinned ? Translation.tr("Unpin from taskbar") : Translation.tr("Pin to taskbar"), @@ -207,7 +219,7 @@ RowLayout { TaskbarApps.togglePin(appId); } }) - ] : []) + ] : []), ]; result = result.concat(resultPreview.entry.actions); return result; diff --git a/dots/.config/quickshell/ii/modules/waffle/startMenu/TagStrip.qml b/dots/.config/quickshell/ii/modules/waffle/startMenu/searchPage/TagStrip.qml similarity index 96% rename from dots/.config/quickshell/ii/modules/waffle/startMenu/TagStrip.qml rename to dots/.config/quickshell/ii/modules/waffle/startMenu/searchPage/TagStrip.qml index a1deb0027..33aef1906 100644 --- a/dots/.config/quickshell/ii/modules/waffle/startMenu/TagStrip.qml +++ b/dots/.config/quickshell/ii/modules/waffle/startMenu/searchPage/TagStrip.qml @@ -8,6 +8,7 @@ import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks +import qs.modules.waffle.startMenu RowLayout { id: root @@ -65,8 +66,8 @@ RowLayout { WMenu { id: accountsMenu - x: -accountsMenu.implicitWidth + optionsButton.implicitWidth - y: optionsButton.height + 10 + x: -accountsMenu.implicitWidth + optionsButton.implicitWidth + 10 + y: optionsButton.height downDirection: true Action { icon.name: "people-settings" diff --git a/dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/AggregatedAppCategoryModel.qml b/dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/AggregatedAppCategoryModel.qml new file mode 100644 index 000000000..e3886235c --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/AggregatedAppCategoryModel.qml @@ -0,0 +1,7 @@ +import QtQuick +import qs.services + +QtObject { + property string name + property list categories +} diff --git a/dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/AllAppsGrid.qml b/dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/AllAppsGrid.qml new file mode 100644 index 000000000..aa1321eb8 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/AllAppsGrid.qml @@ -0,0 +1,84 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Layouts +import Quickshell +import qs +import qs.services +import qs.modules.common +import qs.modules.common.functions +import qs.modules.common.widgets +import qs.modules.waffle.looks + +GridLayout { + id: root + + columns: 4 + + Component { + id: aggAppCatComp + AggregatedAppCategoryModel {} + } + property list aggregatedCategories: [ + aggAppCatComp.createObject(null, { + name: Translation.tr("Productivity"), + categories: ["Development", "Education", "Network", "Office"] + }), aggAppCatComp.createObject(null, { + name: Translation.tr("Utilities & Tools"), + categories: ["Utility", "Science"] + }), aggAppCatComp.createObject(null, { + name: Translation.tr("Creativity"), + categories: ["AudioVideo", "Graphics"] + }), aggAppCatComp.createObject(null, { + name: Translation.tr("Other"), + categories: ["Game"] + }), aggAppCatComp.createObject(null, { + name: Translation.tr("System"), + categories: ["Settings", "System"] + }) + ] + + Repeater { + model: root.aggregatedCategories + delegate: AppCategory { + required property var modelData + aggregatedCategory: modelData + } + } + + columnSpacing: 27 + rowSpacing: 12 + component AppCategory: Item { + id: categoryItem + property AggregatedAppCategoryModel aggregatedCategory + implicitWidth: categoryLayout.implicitWidth + implicitHeight: categoryLayout.implicitHeight + ColumnLayout { + id: categoryLayout + anchors.fill: parent + spacing: 4 + + AppCategoryGrid { + id: categoryGrid + Layout.fillWidth: true + aggregatedCategory: categoryItem.aggregatedCategory + } + + WButton { + id: categoryButton + Layout.fillWidth: true + implicitHeight: 32 + + contentItem: WText { + id: categoryButtonText + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + elide: Text.ElideRight + text: categoryItem.aggregatedCategory.name + } + onClicked: { + categoryGrid.openCategoryFolder(); + } + } + } + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/AppCategoryGrid.qml b/dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/AppCategoryGrid.qml new file mode 100644 index 000000000..fa38e53f3 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/AppCategoryGrid.qml @@ -0,0 +1,341 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs +import qs.services +import qs.modules.common +import qs.modules.common.functions +import qs.modules.common.widgets +import qs.modules.waffle.looks + +Rectangle { + id: root + property AggregatedAppCategoryModel aggregatedCategory + property list desktopEntries: [...DesktopEntries.applications.values.filter(app => { + const appCategories = app.categories; + const gridCategories = root.aggregatedCategory.categories; + return appCategories.some(cat => gridCategories.indexOf(cat) !== -1); + })].sort((a, b) => a.name.localeCompare(b.name)); + + property Item windowRootItem: { + var item = root; + // print("FINDING ROOT") + while (item.parent != null) { + if (item.parent.toString().includes("ProxyWindow")) + break; + item = item.parent; + } + // print(item.width, item.height) + return item; + } + function openCategoryFolder() { + categoryFolderPopup.open(); + } + + radius: Looks.radius.large + color: Looks.colors.bg1 + border.width: 1 + border.color: ColorUtils.transparentize(Looks.colors.ambientShadow, 0.7) + implicitWidth: 156 + implicitHeight: 156 + + GridLayout { + id: categoryAppsGrid + anchors.fill: parent + anchors.margins: 10 + columns: 2 + rows: 2 + columnSpacing: 0 + rowSpacing: 0 + uniformCellHeights: true + uniformCellWidths: true + + Repeater { + model: ScriptModel { + values: root.desktopEntries.slice(0, 3) + } + delegate: SmallGridAppButton { + required property DesktopEntry modelData + desktopEntry: modelData + } + } + Loader { + id: categoryOpenButtonLoader + // It's like this on the real thing - you get an invisible button if there's not enough items + opacity: root.desktopEntries.length > 3 ? 1 : 0 + active: true + sourceComponent: CategoryOpenButton { + aggregatedCategory: root.aggregatedCategory + } + } + } + + Popup { + id: categoryFolderPopup + // I don't even know what the fuck is going on at this point + // I hate point mapping + property point originPoint: categoryOpenButtonLoader.mapToItem(root, categoryOpenButtonLoader.width / 2, categoryOpenButtonLoader.height / 2) + property point windowCenterPoint: { + const rootContentItem = root.windowRootItem; + const canvasPosInRoot = root.mapFromItem(rootContentItem, rootContentItem.width / 2, rootContentItem.height / 2); + const sectionItem = root.parent.parent.parent; + const positionInSection = sectionItem.mapFromItem(categoryOpenButtonLoader, categoryOpenButtonLoader.x, categoryOpenButtonLoader.y); + const targetY = Math.max(-positionInSection.y + 212, canvasPosInRoot.y); + return Qt.point(canvasPosInRoot.x, targetY); + } + + enter: Transition { + NumberAnimation { + target: categoryFolderPopup + property: "x" + from: categoryFolderPopup.originPoint.x - categoryOpenButtonLoader.width * 5 / 2 + to: categoryFolderPopup.windowCenterPoint.x - categoryFolderPopup.width / 2 + duration: 300 + easing.type: Easing.BezierSpline + easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn + } + NumberAnimation { + target: categoryFolderPopup + property: "y" + from: categoryFolderPopup.originPoint.y - categoryOpenButtonLoader.height * 3 / 2 + to: categoryFolderPopup.windowCenterPoint.y - categoryFolderPopup.height / 2 + duration: 300 + easing.type: Easing.BezierSpline + easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn + } + NumberAnimation { + target: categoryFolderPopup + property: "scale" + from: 0 + to: 1 + duration: 300 + easing.type: Easing.BezierSpline + easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn + } + } + + exit: Transition { + NumberAnimation { + target: categoryFolderPopup + property: "x" + to: categoryFolderPopup.originPoint.x - categoryOpenButtonLoader.width * 5 / 2 + duration: 200 + easing.type: Easing.BezierSpline + easing.bezierCurve: Looks.transition.easing.bezierCurve.easeOut + } + NumberAnimation { + target: categoryFolderPopup + property: "y" + to: categoryFolderPopup.originPoint.y - categoryOpenButtonLoader.height * 3 / 2 + duration: 200 + easing.type: Easing.BezierSpline + easing.bezierCurve: Looks.transition.easing.bezierCurve.easeOut + } + NumberAnimation { + target: categoryFolderPopup + property: "scale" + from: 1 + to: 0 + duration: 200 + easing.type: Easing.BezierSpline + easing.bezierCurve: Looks.transition.easing.bezierCurve.easeOut + } + } + + background: null + + Loader { + id: folderContentLoader + active: categoryFolderPopup.visible + sourceComponent: WRectangularShadowThis { + CategoryFolderContent { + title: root.aggregatedCategory.name + desktopEntries: root.desktopEntries + } + } + } + } + + component CategoryFolderContent: WToolTipContent { + id: categoryFolderContent + property string title + property list desktopEntries: root.desktopEntries + horizontalPadding: 0 + verticalPadding: 0 + radius: Looks.radius.large + realContentItem: Item { + implicitWidth: 448 + implicitHeight: 376 + ColumnLayout { + anchors { + fill: parent + leftMargin: 32 + rightMargin: 32 + topMargin: 40 + bottomMargin: 32 + } + spacing: 28 + WText { + Layout.fillWidth: true + text: categoryFolderContent.title + font.pixelSize: Looks.font.pixelSize.xlarger + font.weight: Looks.font.weight.stronger + elide: Text.ElideRight + horizontalAlignment: Text.AlignHCenter + } + Item { + Layout.fillWidth: true + Layout.fillHeight: true + + SwipeView { + id: categoryFolderSwipeView + anchors.fill: parent + orientation: Qt.Vertical + clip: true + + Repeater { + model: Math.ceil(root.desktopEntries.length / 12) + delegate: Item { + id: folderPage + required property int index + width: SwipeView.view.width + height: SwipeView.view.height + BigAppGrid { + anchors { + top: parent.top + left: parent.left + } + columns: 4 + rows: 3 + desktopEntries: root.desktopEntries.slice(folderPage.index * 12, (folderPage.index + 1) * 12) + } + } + } + } + VerticalPageIndicator { + anchors.verticalCenter: parent.verticalCenter + anchors.right: categoryFolderSwipeView.right + anchors.rightMargin: -19 + + showArrows: false + currentIndex: categoryFolderSwipeView.currentIndex + count: Math.ceil(root.desktopEntries.length / 12) + onClicked: index => categoryFolderSwipeView.currentIndex = index + } + } + } + FocusedScrollMouseArea { + z: 999 + anchors.fill: parent + acceptedButtons: Qt.NoButton + hoverEnabled: false + onScrollUp: categoryFolderSwipeView.decrementCurrentIndex() + onScrollDown: categoryFolderSwipeView.incrementCurrentIndex() + } + } + } + + component CategoryOpenButton: SmallGridButton { + id: categoryOpenButton + property AggregatedAppCategoryModel aggregatedCategory + + onClicked: root.openCategoryFolder() + contentItem: Item { + Behavior on scale { + NumberAnimation { + id: scaleAnim + easing.type: Easing.BezierSpline + easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn + } + } + GridLayout { + anchors.centerIn: parent + rows: 2 + columns: 2 + rowSpacing: 2 + columnSpacing: 2 + + Repeater { + model: root.desktopEntries.slice(3, 7) + delegate: WAppIcon { + required property DesktopEntry modelData + tryCustomIcon: false + iconName: modelData.icon + implicitSize: 16 + } + } + } + } + } + + component SmallGridAppButton: SmallGridButton { + id: smallGridAppButton + property DesktopEntry desktopEntry + + property bool pinnedStart: LauncherApps.isPinned(smallGridAppButton.desktopEntry.id); + property bool pinnedTaskbar: TaskbarApps.isPinned(smallGridAppButton.desktopEntry.id); + + onClicked: { + GlobalStates.searchOpen = false; + desktopEntry.execute(); + } + + contentItem: Item { + Behavior on scale { + NumberAnimation { + id: scaleAnim + easing.type: Easing.BezierSpline + easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn + } + } + WAppIcon { + anchors.centerIn: parent + tryCustomIcon: false + iconName: smallGridAppButton.desktopEntry.icon + implicitSize: 34 + } + } + + WToolTip { + text: smallGridAppButton.desktopEntry.name + } + + altAction: () => { + appMenu.popup(); + } + + WMenu { + id: appMenu + downDirection: true + + WMenuItem { + icon.name: smallGridAppButton.pinnedStart ? "pin-off" : "pin" + text: smallGridAppButton.pinnedStart ? Translation.tr("Unpin from Start") : Translation.tr("Pin to Start") + onTriggered: { + LauncherApps.togglePin(smallGridAppButton.desktopEntry.id); + } + } + WMenuItem { + icon.name: smallGridAppButton.pinnedTaskbar ? "pin-off" : "pin" + text: smallGridAppButton.pinnedTaskbar ? Translation.tr("Unpin from taskbar") : Translation.tr("Pin to taskbar") + onTriggered: { + TaskbarApps.togglePin(smallGridAppButton.desktopEntry.id); + } + } + } + } + + component SmallGridButton: WButton { + id: root + implicitWidth: 68 + implicitHeight: 68 + + property real pressedScale: 5 / 6 + + onDownChanged: { + contentItem.scale = root.down ? root.pressedScale : 1; // If/When we do dragging, the scale is 1.25 + } + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/BigAppGrid.qml b/dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/BigAppGrid.qml new file mode 100644 index 000000000..dbc44adf8 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/BigAppGrid.qml @@ -0,0 +1,37 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Qt5Compat.GraphicalEffects +import Quickshell +import qs +import qs.services +import qs.modules.common +import qs.modules.common.functions +import qs.modules.common.widgets +import qs.modules.waffle.looks + +GridLayout { + id: root + + property list desktopEntries: [] + + columnSpacing: 0 + rowSpacing: 0 + + uniformCellHeights: true + uniformCellWidths: true + + Repeater { + model: root.desktopEntries + delegate: StartAppButton { + id: pinnedAppButton + required property var modelData + desktopEntry: modelData + onClicked: { + GlobalStates.searchOpen = false; + desktopEntry.execute(); + } + } + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/StartAppButton.qml b/dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/StartAppButton.qml new file mode 100644 index 000000000..8df30bd85 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/StartAppButton.qml @@ -0,0 +1,98 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Qt5Compat.GraphicalEffects +import Quickshell +import qs +import qs.services +import qs.modules.common +import qs.modules.common.functions +import qs.modules.common.widgets +import qs.modules.waffle.looks + +WButton { + id: root + required property DesktopEntry desktopEntry + + property bool pinnedStart: LauncherApps.isPinned(root.desktopEntry.id); + property bool pinnedTaskbar: TaskbarApps.isPinned(root.desktopEntry.id); + + implicitWidth: 96 + implicitHeight: 84 + horizontalPadding: 0 + verticalPadding: 0 + contentItem: ColumnLayout { + spacing: 3 + WAppIcon { + Layout.topMargin: 12 + Layout.alignment: Qt.AlignHCenter + iconName: root.desktopEntry.icon + implicitSize: 34 + tryCustomIcon: false + } + WText { + Layout.fillHeight: true + Layout.fillWidth: true + Layout.leftMargin: 8 + Layout.rightMargin: 8 + text: root.desktopEntry.name + wrapMode: Text.Wrap + elide: Text.ElideRight + maximumLineCount: 2 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignTop + } + } + WToolTip { + text: root.desktopEntry.name + } + + altAction: () => { + appMenu.popup() + } + + WMenu { + id: appMenu + downDirection: true + + WMenuItem { + visible: root.pinnedStart + icon.name: "arrow-up-left" + text: Translation.tr("Move to front") + onTriggered: { + LauncherApps.moveToFront(root.desktopEntry.id); + } + } + WMenuItem { + visible: root.pinnedStart + icon.name: "arrow-left" + text: Translation.tr("Move left") + onTriggered: { + LauncherApps.moveLeft(root.desktopEntry.id); + } + } + WMenuItem { + visible: root.pinnedStart + icon.name: "arrow-right" + text: Translation.tr("Move right") + onTriggered: { + LauncherApps.moveRight(root.desktopEntry.id); + } + } + WMenuItem { + icon.name: root.pinnedStart ? "pin-off" : "pin" + text: root.pinnedStart ? Translation.tr("Unpin from Start") : Translation.tr("Pin to Start") + onTriggered: { + LauncherApps.togglePin(root.desktopEntry.id); + } + } + WMenuItem { + icon.name: root.pinnedTaskbar ? "pin-off" : "pin" + text: root.pinnedTaskbar ? Translation.tr("Unpin from taskbar") : Translation.tr("Pin to taskbar") + onTriggered: { + TaskbarApps.togglePin(root.desktopEntry.id); + } + } + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/StartPageApps.qml b/dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/StartPageApps.qml new file mode 100644 index 000000000..b4539b317 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/StartPageApps.qml @@ -0,0 +1,76 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs +import qs.services +import qs.modules.common +import qs.modules.common.functions +import qs.modules.common.widgets +import qs.modules.waffle.looks + +BodyRectangle { + id: root + + ColumnLayout { + anchors { + fill: parent + leftMargin: 32 + rightMargin: 32 + topMargin: 25 + bottomMargin: 30 + } + spacing: 26 + + PinnedApps { + Layout.fillWidth: true + } + + AllApps { + implicitHeight: 300 // for now + } + } + + component PinnedApps: PageSection { + title: Translation.tr("Pinned") + + BigAppGrid { + Layout.fillWidth: true + columns: 8 + desktopEntries: Config.options.launcher.pinnedApps.map(appId => DesktopEntries.byId(appId)) + } + } + + component AllApps: PageSection { + title: Translation.tr("All") + // TODO: Do we wanna also implement list view and grid view? + // (instead of only category view) + AllAppsGrid { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.leftMargin: 32 + Layout.rightMargin: 32 + } + } + + component PageSection: ColumnLayout { + id: pageSection + required property string title + default property alias data: pageSectionContentArea.data + + spacing: 16 + + WText { + Layout.leftMargin: 32 + text: pageSection.title + font.pixelSize: Looks.font.pixelSize.large + font.weight: Looks.font.weight.stronger + } + + ColumnLayout { + id: pageSectionContentArea + Layout.fillWidth: true + } + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/StartPageContent.qml b/dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/StartPageContent.qml new file mode 100644 index 000000000..3d0eabb8a --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/StartPageContent.qml @@ -0,0 +1,99 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Qt5Compat.GraphicalEffects +import Quickshell +import qs +import qs.services +import qs.modules.common +import qs.modules.common.functions +import qs.modules.common.widgets +import qs.modules.waffle.looks + +WPanelPageColumn { + id: root + + WPanelSeparator {} + + StartPageApps { + Layout.fillHeight: true + } + + WPanelSeparator {} + + StartFooter { + Layout.fillWidth: true + } + + component StartFooter: FooterRectangle { + implicitHeight: 63 + + StartUserButton { + anchors { + left: parent.left + leftMargin: 52 + bottom: parent.bottom + bottomMargin: 12 + } + } + + PowerButton { + anchors { + right: parent.right + rightMargin: 52 + bottom: parent.bottom + bottomMargin: 12 + } + } + } + + component PowerButton: WBorderlessButton { + id: powerButton + implicitWidth: 40 + implicitHeight: 40 + + contentItem: Item { + FluentIcon { + anchors.centerIn: parent + icon: "power" + implicitSize: 20 + } + } + + WToolTip { + extraVisibleCondition: !powerMenu.visible + text: qsTr("Power") + } + + onClicked: { + powerMenu.open() + } + + WMenu { + id: powerMenu + x: -powerMenu.implicitWidth / 2 + powerButton.implicitWidth / 2 + y: -powerMenu.implicitHeight - 4 + Action { + icon.name: "lock-closed" + text: Translation.tr("Lock") + onTriggered: Session.lock() + } + Action { + icon.name: "weather-moon" + text: Translation.tr("Sleep") + onTriggered: Session.suspend() + } + Action { + icon.name: "power" + text: Translation.tr("Shut down") + onTriggered: Session.poweroff() + } + Action { + icon.name: "arrow-counterclockwise" + text: Translation.tr("Restart") + onTriggered: Session.reboot() + } + } + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/StartUserButton.qml b/dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/StartUserButton.qml new file mode 100644 index 000000000..274883c72 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/StartUserButton.qml @@ -0,0 +1,140 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Qt5Compat.GraphicalEffects +import Quickshell +import qs +import qs.services +import qs.modules.common +import qs.modules.common.functions +import qs.modules.common.widgets +import qs.modules.waffle.looks + +WBorderlessButton { + id: userButton + implicitWidth: userButtonRow.implicitWidth + 12 * 2 + implicitHeight: 40 + + contentItem: Item { + RowLayout { + id: userButtonRow + anchors.centerIn: parent + spacing: 12 + + WUserAvatar { + sourceSize: Qt.size(32, 32) + } + WText { + Layout.alignment: Qt.AlignVCenter + text: SystemInfo.username + } + } + } + + onClicked: { + userMenu.open(); + } + + WToolTip { + text: SystemInfo.username + } + + Popup { + id: userMenu + x: -51 + y: -userMenu.implicitHeight + userButton.implicitHeight / 2 - 10 + + background: null + + WToolTipContent { + id: popupContent + horizontalPadding: 10 + verticalPadding: 7 + radius: Looks.radius.large + realContentItem: Item { + implicitWidth: userMenuContentLayout.implicitWidth + implicitHeight: userMenuContentLayout.implicitHeight + + ColumnLayout { + id: userMenuContentLayout + anchors { + fill: parent + leftMargin: popupContent.horizontalPadding + rightMargin: popupContent.horizontalPadding + topMargin: popupContent.verticalPadding + bottomMargin: popupContent.verticalPadding + } + spacing: 5 + + RowLayout { + Layout.fillWidth: true + Layout.leftMargin: 6 + FluentIcon { + Layout.alignment: Qt.AlignVCenter + implicitSize: 22 + icon: "corporation" + monochrome: false + } + WText { + Layout.alignment: Qt.AlignVCenter + text: "Megahard" + font.pixelSize: Looks.font.pixelSize.large + font.weight: Looks.font.weight.strong + } + Item { Layout.fillWidth: true } + WBorderlessButton { + Layout.alignment: Qt.AlignVCenter + implicitHeight: 36 + implicitWidth: textItem.implicitWidth + 10 * 2 + contentItem: WText { + id: textItem + text: Translation.tr("Sign out") + font.pixelSize: Looks.font.pixelSize.large + } + onClicked: Session.logout() + } + } + Item { // Force min width 360 (using min on the item somehow doesn't work) + implicitWidth: 334 + } + RowLayout { + Layout.fillWidth: true + Layout.bottomMargin: 7 + Layout.leftMargin: 6 + spacing: 12 + WUserAvatar { + sourceSize: Qt.size(58, 58) + } + ColumnLayout { + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + spacing: 2 + WText { + text: SystemInfo.username + font.pixelSize: Looks.font.pixelSize.larger + font.weight: Looks.font.weight.strong + } + WText { + color: Looks.colors.fg1 + text: Translation.tr("Local account") + } + WText { + color: Looks.colors.accent + text: Translation.tr("Manage my account") + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { + Quickshell.execDetached(["bash", "-c", Config.options.apps.manageUser]) + GlobalStates.searchOpen = false; + } + } + } + } + } + } + } + } + } +} diff --git a/dots/.config/quickshell/ii/scripts/colors/switchwall.sh b/dots/.config/quickshell/ii/scripts/colors/switchwall.sh index 31f260760..430114d5b 100755 --- a/dots/.config/quickshell/ii/scripts/colors/switchwall.sh +++ b/dots/.config/quickshell/ii/scripts/colors/switchwall.sh @@ -319,6 +319,13 @@ main() { get_type_from_config() { jq -r '.appearance.palette.type' "$SHELL_CONFIG_FILE" 2>/dev/null || echo "auto" } + get_accent_color_from_config() { + jq -r '.appearance.palette.accentColor' "$SHELL_CONFIG_FILE" 2>/dev/null || echo "" + } + set_accent_color() { + local color="$1" + jq --arg color "$color" '.appearance.palette.accentColor = $color' "$SHELL_CONFIG_FILE" > "$SHELL_CONFIG_FILE.tmp" && mv "$SHELL_CONFIG_FILE.tmp" "$SHELL_CONFIG_FILE" + } detect_scheme_type_from_image() { local img="$1" @@ -338,12 +345,14 @@ main() { shift 2 ;; --color) - color_flag="1" if [[ "$2" =~ ^#?[A-Fa-f0-9]{6}$ ]]; then - color="$2" + set_accent_color "$2" + shift 2 + elif [[ "$2" == "clear" ]]; then + set_accent_color "" shift 2 else - color=$(hyprpicker --no-fancy) + set_accent_color $(hyprpicker --no-fancy) shift fi ;; @@ -365,6 +374,13 @@ main() { esac done + # If accentColor is set in config, use it + config_color="$(get_accent_color_from_config)" + if [[ "$config_color" =~ ^#?[A-Fa-f0-9]{6}$ ]]; then + color_flag="1" + color="$config_color" + fi + # If type_flag is not set, get it from config if [[ -z "$type_flag" ]]; then type_flag="$(get_type_from_config)" diff --git a/dots/.config/quickshell/ii/services/Audio.qml b/dots/.config/quickshell/ii/services/Audio.qml index 29b0a2f68..68ebc3ae0 100644 --- a/dots/.config/quickshell/ii/services/Audio.qml +++ b/dots/.config/quickshell/ii/services/Audio.qml @@ -106,9 +106,9 @@ Singleton { if (newVolume - lastVolume > maxAllowedIncrease) { sink.audio.volume = lastVolume; root.sinkProtectionTriggered(Translation.tr("Illegal increment")); - } else if (Math.round(newVolume * 100) / 100 > maxAllowed || newVolume > root.hardMaxValue) { + } else if (newVolume > maxAllowed || newVolume > root.hardMaxValue) { root.sinkProtectionTriggered(Translation.tr("Exceeded max allowed")); - sink.audio.volume = maxAllowed; + sink.audio.volume = Math.min(lastVolume, maxAllowed); } lastVolume = sink.audio.volume; } diff --git a/dots/.config/quickshell/ii/services/LauncherApps.qml b/dots/.config/quickshell/ii/services/LauncherApps.qml new file mode 100644 index 000000000..0017f3812 --- /dev/null +++ b/dots/.config/quickshell/ii/services/LauncherApps.qml @@ -0,0 +1,41 @@ +pragma Singleton + +import qs.modules.common +import QtQuick +import Quickshell + +Singleton { + id: root + + function isPinned(appId) { + return Config.options.launcher.pinnedApps.indexOf(appId) !== -1; + } + + function togglePin(appId) { + if (root.isPinned(appId)) { + Config.options.launcher.pinnedApps = Config.options.launcher.pinnedApps.filter(id => id !== appId) + } else { + Config.options.launcher.pinnedApps = Config.options.launcher.pinnedApps.concat([appId]) + } + } + + function moveToFront(appId) { + if (!root.isPinned(appId)) return; + const pinnedApps = Config.options.launcher.pinnedApps; + Config.options.launcher.pinnedApps = [appId].concat(pinnedApps.filter(id => id !== appId)); + } + + function moveLeft(appId) { + const pinnedApps = Config.options.launcher.pinnedApps; + const index = pinnedApps.indexOf(appId); + if (index === -1 || index === 0) return; + Config.options.launcher.pinnedApps = pinnedApps.slice(0, index - 1).concat([appId]).concat(pinnedApps[index - 1]).concat(pinnedApps.slice(index + 1)); + } + + function moveRight(appId) { + const pinnedApps = Config.options.launcher.pinnedApps; + const index = pinnedApps.indexOf(appId); + if (index === -1 || index === pinnedApps.length - 1) return; + Config.options.launcher.pinnedApps = pinnedApps.slice(0, index).concat(pinnedApps[index + 1]).concat([appId]).concat(pinnedApps.slice(index + 2)); + } +} diff --git a/dots/.config/quickshell/ii/services/LauncherSearch.qml b/dots/.config/quickshell/ii/services/LauncherSearch.qml index 49de2158e..f5a18bec9 100644 --- a/dots/.config/quickshell/ii/services/LauncherSearch.qml +++ b/dots/.config/quickshell/ii/services/LauncherSearch.qml @@ -20,6 +20,17 @@ Singleton { } } + // https://specifications.freedesktop.org/menu/latest/category-registry.html + property list mainRegisteredCategories: ["AudioVideo", "Development", "Education", "Game", "Graphics", "Network", "Office", "Science", "Settings", "System", "Utility"] + property list appCategories: DesktopEntries.applications.values.reduce((acc, entry) => { + for (const category of entry.categories) { + if (!acc.includes(category) && mainRegisteredCategories.includes(category)) { + acc.push(category); + } + } + return acc; + }, []).sort() + property var searchActions: [ { action: "accentcolor", diff --git a/dots/.config/quickshell/ii/services/PolkitService.qml b/dots/.config/quickshell/ii/services/PolkitService.qml index 56576f4f4..758f0b0b8 100644 --- a/dots/.config/quickshell/ii/services/PolkitService.qml +++ b/dots/.config/quickshell/ii/services/PolkitService.qml @@ -11,6 +11,18 @@ Singleton { property alias active: polkitAgent.isActive property alias flow: polkitAgent.flow property bool interactionAvailable: false + property string cleanMessage: { + if (!root.flow) return ""; + return root.flow.message.endsWith(".") + ? root.flow.message.slice(0, -1) + : root.flow.message + } + property string cleanPrompt: { + const inputPrompt = PolkitService.flow?.inputPrompt.trim() ?? ""; + const cleanedInputPrompt = inputPrompt.endsWith(":") ? inputPrompt.slice(0, -1) : inputPrompt; + const usePasswordChars = !PolkitService.flow?.responseVisible ?? true + return cleanedInputPrompt || (usePasswordChars ? Translation.tr("Password") : Translation.tr("Input")) + } function cancel() { root.flow.cancelAuthenticationRequest() diff --git a/dots/.config/quickshell/ii/services/SessionWarnings.qml b/dots/.config/quickshell/ii/services/SessionWarnings.qml new file mode 100644 index 000000000..c698d28c7 --- /dev/null +++ b/dots/.config/quickshell/ii/services/SessionWarnings.qml @@ -0,0 +1,39 @@ +pragma Singleton + +import qs.modules.common +import qs.modules.common.functions +import QtQuick +import Quickshell +import Quickshell.Io + +Singleton { + id: root + + property bool packageManagerRunning: false + property bool downloadRunning: false + + function refresh() { + packageManagerRunning = false; + downloadRunning = false; + detectPackageManagerProc.running = false; + detectPackageManagerProc.running = true; + detectDownloadProc.running = false; + detectDownloadProc.running = true; + } + + Process { + id: detectPackageManagerProc + command: ["bash", "-c", "pidof pacman yay paru dnf zypper apt apx xbps snap apk yum epsi pikman"] + onExited: (exitCode, exitStatus) => { + root.packageManagerRunning = (exitCode === 0); + } + } + + Process { + id: detectDownloadProc + command: ["bash", "-c", "pidof curl wget aria2c yt-dlp || ls ~/Downloads | grep -E '\.crdownload$|\.part$'"] + onExited: (exitCode, exitStatus) => { + root.downloadRunning = (exitCode === 0); + } + } +} diff --git a/dots/.config/quickshell/ii/services/TaskbarApps.qml b/dots/.config/quickshell/ii/services/TaskbarApps.qml index 052abcaec..6351896b8 100644 --- a/dots/.config/quickshell/ii/services/TaskbarApps.qml +++ b/dots/.config/quickshell/ii/services/TaskbarApps.qml @@ -8,8 +8,12 @@ import Quickshell.Wayland Singleton { id: root + function isPinned(appId) { + return Config.options.dock.pinnedApps.indexOf(appId) !== -1; + } + function togglePin(appId) { - if (Config.options.dock.pinnedApps.indexOf(appId) !== -1) { + if (root.isPinned(appId)) { Config.options.dock.pinnedApps = Config.options.dock.pinnedApps.filter(id => id !== appId) } else { Config.options.dock.pinnedApps = Config.options.dock.pinnedApps.concat([appId]) diff --git a/dots/.config/quickshell/ii/services/Updates.qml b/dots/.config/quickshell/ii/services/Updates.qml index 58b8be892..48549ac3c 100644 --- a/dots/.config/quickshell/ii/services/Updates.qml +++ b/dots/.config/quickshell/ii/services/Updates.qml @@ -13,6 +13,7 @@ Singleton { id: root property bool available: false + property alias checking: checkUpdatesProc.running property int count: 0 readonly property bool updateAdvised: available && count > Config.options.updates.adviseUpdateThreshold diff --git a/dots/.config/quickshell/ii/shell.qml b/dots/.config/quickshell/ii/shell.qml index 7bb565d11..b8fa41c3f 100644 --- a/dots/.config/quickshell/ii/shell.qml +++ b/dots/.config/quickshell/ii/shell.qml @@ -6,7 +6,6 @@ // Adjust this to make the shell smaller or larger //@ pragma Env QT_SCALE_FACTOR=1 - import qs.modules.common import qs.modules.ii.background import qs.modules.ii.bar @@ -31,9 +30,12 @@ import qs.modules.ii.wallpaperSelector import qs.modules.waffle.actionCenter import qs.modules.waffle.background import qs.modules.waffle.bar +import qs.modules.waffle.lock import qs.modules.waffle.notificationCenter import qs.modules.waffle.onScreenDisplay +import qs.modules.waffle.polkit import qs.modules.waffle.startMenu +import qs.modules.waffle.sessionScreen import QtQuick import QtQuick.Window @@ -82,9 +84,12 @@ ShellRoot { PanelLoader { identifier: "wActionCenter"; component: WaffleActionCenter {} } PanelLoader { identifier: "wBar"; component: WaffleBar {} } PanelLoader { identifier: "wBackground"; component: WaffleBackground {} } + PanelLoader { identifier: "wLock"; component: WaffleLock {} } PanelLoader { identifier: "wNotificationCenter"; component: WaffleNotificationCenter {} } PanelLoader { identifier: "wOnScreenDisplay"; component: WaffleOSD {} } + PanelLoader { identifier: "wPolkit"; component: WafflePolkit {} } PanelLoader { identifier: "wStartMenu"; component: WaffleStartMenu {} } + PanelLoader { identifier: "wSessionScreen"; component: WaffleSessionScreen {} } ReloadPopup {} component PanelLoader: LazyLoader { @@ -97,7 +102,7 @@ ShellRoot { property list families: ["ii", "waffle"] property var panelFamilies: ({ "ii": ["iiBar", "iiBackground", "iiCheatsheet", "iiDock", "iiLock", "iiMediaControls", "iiNotificationPopup", "iiOnScreenDisplay", "iiOnScreenKeyboard", "iiOverlay", "iiOverview", "iiPolkit", "iiRegionSelector", "iiScreenCorners", "iiSessionScreen", "iiSidebarLeft", "iiSidebarRight", "iiVerticalBar", "iiWallpaperSelector"], - "waffle": ["wActionCenter", "wBar", "wBackground", "wNotificationCenter", "wOnScreenDisplay", "wStartMenu", "iiCheatsheet", "iiLock", "iiNotificationPopup", "iiOnScreenKeyboard", "iiOverlay", "iiPolkit", "iiRegionSelector", "iiSessionScreen", "iiWallpaperSelector"], + "waffle": ["wActionCenter", "wBar", "wBackground", "wLock", "wNotificationCenter", "wOnScreenDisplay", "wPolkit", "wSessionScreen", "wStartMenu", "iiCheatsheet", "iiNotificationPopup", "iiOnScreenKeyboard", "iiOverlay", "iiRegionSelector", "iiWallpaperSelector"], }) function cyclePanelFamily() { const currentIndex = families.indexOf(Config.options.panelFamily) diff --git a/dots/.config/quickshell/ii/translations/zh_CN.json b/dots/.config/quickshell/ii/translations/zh_CN.json index 432fdd38d..3b4c17d69 100644 --- a/dots/.config/quickshell/ii/translations/zh_CN.json +++ b/dots/.config/quickshell/ii/translations/zh_CN.json @@ -8,8 +8,8 @@ "Su": "日/*keep*/", "%1 characters": "%1 个字符", "**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key": "**价格**:免费。数据用于训练。\n\n**说明**:登录 Google 账户,允许 AI Studio 创建 Google Cloud 项目或其他要求,然后返回并点击获取 API 密钥", - "**Pricing**: free. Data use policy varies depending on your OpenRouter account settings.\n\n**Instructions**: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key": "**价格**:免费。数据使用政策取决于您的 OpenRouter 账户设置。\n\n**说明**:登录 OpenRouter 账户,在右上角菜单中选择 Keys,点击创建 API 密钥", - ". Notes for Zerochan:\n- You must enter a color\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!": ". Notes for Zerochan:\n- You must enter a color\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!", + "**Pricing**: free. Data use policy varies depending on your OpenRouter account settings.\n\n**Instructions**: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key": "**价格**:免费。数据使用政策取决于你的 OpenRouter 账户设置。\n\n**说明**:登录 OpenRouter 账户,在右上角菜单中选择 Keys,点击创建 API 密钥", + ". Notes for Zerochan:\n- You must enter a color\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!": "。Zerochan 注意事项:\n- 你需要指定一个颜色\n- 请在 `sidebar.booru.zerochan.username` 配置项内填写你的 Zerochan 用户名。如果不这样做[将可能会被封禁](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!", "No further instruction provided": "未提供进一步说明", "API key set for %1": "已为 %1 设置 API 密钥", "API key:\n\n```txt\n%1\n```": "API 密钥:\n\n```txt\n%1\n```", @@ -26,10 +26,9 @@ "Bluetooth": "蓝牙", "Brightness": "亮度", "Cancel": "取消", - "Cheat sheet": "快捷键表", + "Cheat sheet": "快捷键指南", "Choose model": "选择模型", "Clean stuff | Excellent quality, no NSFW": "清洁内容 | 优秀质量,无 NSFW", - "Clear": "清除", "Clear chat history": "清除聊天记录", "Clear the current list of images": "清除当前图片列表", "Close": "关闭", @@ -51,23 +50,20 @@ "Go to source (%1)": "转到源 (%1)", "Hibernate": "休眠", "Input": "输入", - "Intelligence": "智能体", + "Intelligence": "智能", "Interface": "界面", "Invalid arguments. Must provide `key` and `value`.": "参数无效。必须提供 `key` 和 `value`。", "Jump to current month": "跳转到当前月份", "Keep system awake": "保持系统唤醒", "Large images | God tier quality, no NSFW.": "大尺寸图片 | 顶级质量,无 NSFW", "Large language models": "大语言模型", - "Launch": "启动", "Local Ollama model | %1": "本地 Ollama 模型 | %1", "Lock": "锁定", "Logout": "注销", "Markdown test": "Markdown 测试", "Math result": "数学结果", "No API key set for %1": "未为 %1 设置 API 密钥", - "No audio source": "无音频源", "No media": "无媒体", - "No notifications": "无通知", "Not visible to model": "对模型不可见", "Nothing here!": "这里什么都没有!", "Notifications": "通知", @@ -77,13 +73,11 @@ "Page %1": "第 %1 页", "Reboot": "重启", "Reboot to firmware settings": "重启到固件设置", - "Reload Hyprland & Quickshell": "重新加载 Hyprland 和 Quickshell", + "Reload Hyprland & Quickshell": "重新加载 Hyprland 与 Quickshell", "Run": "运行", - "Run command": "运行命令", "Save": "保存", "Save to Downloads": "保存到下载文件夹", "Search": "搜索", - "Search the web": "在网络上搜索", "Search, calculate or run": "搜索、计算或运行", "Select Language": "选择语言", "Session": "会话", @@ -97,13 +91,13 @@ "Task Manager": "任务管理器", "Task description": "任务描述", "Temperature must be between 0 and 2": "温度必须在 0 到 2 之间", - "Temperature set to %1": "温度设置为 %1", + "Temperature set to %1": "温度已设置为 %1", "Temperature: %1": "温度:%1", "The hentai one | Great quantity, a lot of NSFW, quality varies wildly": "成人向 | 数量巨大,大量 NSFW,质量参差不齐", "The popular one | Best quantity, but quality can vary wildly": "最受欢迎 | 数量最多,但质量参差不齐", "Thinking": "思考中", "Translation goes here...": "翻译结果会显示在这里...", - "Translator": "翻译器", + "Translator": "翻译", "Unfinished": "未完成", "Unknown": "未知", "Unknown Album": "未知专辑", @@ -114,26 +108,23 @@ "Volume": "音量", "Volume mixer": "音量混合器", "Waifus only | Excellent quality, limited quantity": "仅限角色 | 优秀质量,数量有限", - "Waiting for response...": "等待响应...", "Workspace": "工作区", "%1 Safe Storage": "%1 安全存储", "%1 does not require an API key": "%1 不需要 API 密钥", - "%1 queries pending": "%1 个查询等待中", - "%1 | Right-click to configure": "%1 | 右键点击进行配置", - "Invalid API provider. Supported: \n-": "无效的 API 提供商。支持的:\n-", - "Unknown command:": "未知命令:", - "Type /key to get started with online models\nCtrl+O to expand the sidebar\nCtrl+P to detach sidebar into a window": "输入 /key 开始使用在线模型\nCtrl+O 展开侧边栏\nCtrl+P 将侧边栏分离为窗口", - "Provider set to": "提供商设置为", - "Invalid model. Supported: \n```": "无效模型。支持的:\n```", + "%1 | Right-click to configure": "%1 | 右键以配置", + "Invalid API provider. Supported: \n- ": "无效的 API 提供商。支持的有:\n- ", + "Unknown command: ": "未知命令:", + "Provider set to ": "提供商已设置为 ", + "Invalid model. Supported: \n```\n": "无效模型。支持的有:\n```\n", "Switched to search mode. Continue with the user's request.": "已切换到搜索模式。继续处理用户请求。", - "Enter tags, or \"%1\" for commands": "输入标签,或 \"%1\" 查看命令", - "Online via %1 | %2's model": "通过 %1 在线 | %2 的模型", - "That didn't work. Tips:\n- Check your tags and NSFW settings\n- If you don't have a tag in mind, type a page number": "没有找到结果。提示:\n- 检查您的标签和 NSFW 设置\n- 如果没有想到标签,请输入页码", + "Enter tags, or \"%1\" for commands": "输入标签,或 “%1” 以查看命令", + "Online via %1 | %2's model": "在线 | 通过 %1 | %2 的模型", + "That didn't work. Tips:\n- Check your tags and NSFW settings\n- If you don't have a tag in mind, type a page number": "没有找到结果。提示:\n- 检查你的标签和 NSFW 设置\n- 如果还没想到标签,可以直接输入页码", "Settings": "设置", - "Save chat": "保存对话", - "Load chat": "加载对话", + "Save chat": "保存聊天记录", + "Load chat": "加载聊天记录", "or": "或", - "Set the system prompt for the model.": "为模型设置系统提示。", + "Set the system prompt for the model.": "为模型设置系统提示词。", "To Do": "待办", "Calendar": "日历", "Advanced": "高级", @@ -141,11 +132,11 @@ "Services": "服务", "Light": "浅色", "Dark": "深色", - "Fidelity": "保真度", + "Fidelity": "保真", "Fruit Salad": "水果沙拉", "When not fullscreen": "非全屏时", "Choose file": "选择文件", - "Random SFW Anime wallpaper from Konachan\nImage is saved to ~/Pictures/Wallpapers": "随机 Konachan SFW 动漫壁纸\n图片保存到 ~/图片/Wallpapers", + "Random SFW Anime wallpaper from Konachan\nImage is saved to ~/Pictures/Wallpapers": "随机 Konachan SFW 动漫壁纸\n图片会保存到 ~/Pictures/Wallpapers", "Be patient...": "请耐心等待...", "Tonal Spot": "色调点", "Auto": "自动", @@ -162,7 +153,7 @@ "Pick wallpaper image on your system": "在系统中选择壁纸图片", "No": "否", "AI": "AI", - "Local only": "仅本地", + "Local only": "仅限本地", "Policies": "策略", "Weeb": "二次元", "Closet": "隐藏", @@ -173,14 +164,14 @@ "Style & wallpaper": "样式与壁纸", "Configuration": "配置", "Keybinds": "快捷键", - "Float": "浮动", + "Float": "悬浮", "Hug": "贴合", "illogical-impulse Welcome": "illogical-impulse 欢迎页", "Info": "信息", "Volume limit": "音量限制", "Prevents abrupt increments and restricts volume limit": "防止骤增并限制音量", "Resources": "资源", - "12h am/pm": "12小时 上午/下午", + "12h am/pm": "12小时制 am/pm", "Base URL": "基础 URL", "Audio": "声音", "Networking": "网络", @@ -200,24 +191,22 @@ "24h": "24小时制", "Use Levenshtein distance-based algorithm instead of fuzzy": "使用 Levenshtein 距离算法替代模糊匹配", "System prompt": "系统提示词", - "12h AM/PM": "12小时 AM/PM", - "Could be better if you make a ton of typos,\nbut results can be weird and might not work with acronyms\n(e.g. \"GIMP\" might not give you the paint program)": "如果你经常打错字可能更好用,但结果可能很奇怪,并且可能无法匹配缩写(如 \"GIMP\" 可能搜不到绘图程序)", + "12h AM/PM": "12小时制 AM/PM", + "Could be better if you make a ton of typos,\nbut results can be weird and might not work with acronyms\n(e.g. \"GIMP\" might not give you the paint program)": "如果你经常打错字可能更好用,但结果可能会奇怪,并且可能无法匹配缩写(如 “GIMP” 可能搜不到绘图程序)", "Critical warning": "临界警告", "User agent (for services that require it)": "用户代理(部分服务需要)", - "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.": "这些区域可能是图片或屏幕中具有一定包容性的部分。\n可能并不总是准确。\n这是通过本地运行的图像处理算法实现的,没有使用 AI。", "Workspaces shown": "显示的工作区数", "Dark/Light toggle": "深浅色切换", "Dock": "停靠栏", "Weather": "天气", "Pinned on startup": "启动时固定", - "Always show numbers": "总是显示数字", + "Always show numbers": "始终显示数字", "Keyboard toggle": "键盘切换", - "Scale (%)": "缩放比例(%)", + "Scale (%)": "缩放比例(%)", "Overview": "概览", "Rows": "行数", - "Screenshot tool": "截图工具", - "Number show delay when pressing Super (ms)": "按下 Super 时数字显示延迟(ms)", - "Timeout (ms)": "超时时间(ms)", + "Number show delay when pressing Super (ms)": "按下 Super 时的数字显示延迟(毫秒)", + "Timeout (ms)": "显示时间(毫秒)", "Show app icons": "显示应用图标", "Workspaces": "工作区", "Columns": "列数", @@ -226,7 +215,6 @@ "Mic toggle": "麦克风切换", "Hover to reveal": "悬停显示", "Bar": "条栏", - "Show regions of potential interest": "显示可能感兴趣的区域", "Color picker": "取色器", "Help & Support": "帮助与支持", "Discussions": "讨论区", @@ -242,12 +230,11 @@ "Terminal": "终端", "Shell & utilities": "Shell 与工具", "Qt apps": "Qt 应用", - "Force dark mode in terminal": "终端强制使用深色模式", + "Force dark mode in terminal": "强制终端使用深色模式", "Report a Bug": "报告问题", "Issues": "问题追踪", - "Drag or click a region • LMB: Copy • RMB: Edit": "拖动或点击一个区域 • 鼠标左键:复制 • 鼠标右键:编辑", "Current model: %1\nSet it with %2model MODEL": "当前模型:%1\n使用 %2model MODEL 设置", - "Message the model... \"%1\" for commands": "与模型对话... \"%1\" 查看命令", + "Message the model... \"%1\" for commands": "向模型发送消息... “%1” 以查看命令", "The current system prompt is\n\n---\n\n%1": "当前系统提示词为\n\n---\n\n%1", "Model set to %1": "模型已设置为 %1", "Loaded the following system prompt\n\n---\n\n%1": "已加载以下系统提示词\n\n---\n\n%1", @@ -255,16 +242,13 @@ "Save chat to %1": "保存聊天记录到 %1", "Load chat from %1": "从 %1 加载聊天记录", "Load prompt from %1": "从 %1 加载提示词", - "Select output device": "选择输出设备", - "%1 • %2 tasks": "%1 • %2 个任务", - "Online models disallowed\n\nControlled by `policies.ai` config option": "禁止在线模型\n\n由 `policies.ai` 配置项控制", - "Select input device": "选择输入设备", + "%1 • %2 tasks": "%1 • %2 项任务", + "Online models disallowed\n\nControlled by `policies.ai` config option": "已禁止在线模型\n\n由 `policies.ai` 配置项控制", "Low battery": "电量低", "Registration failed. Please inspect manually with the warp-cli command": "注册失败。请使用 warp-cli 命令手动检查", "Code saved to file": "代码已保存到文件", - "Consider plugging in your device": "请考虑连接您的设备", + "Consider plugging in your device": "请考虑为你的设备充电", "Weather Service": "天气服务", - "Please charge!\nAutomatic suspend triggers at %1": "请充电!\n自动挂起将在 %1 时触发", "Cloudflare WARP (1.1.1.1)": "Cloudflare WARP (1.1.1.1)", "Cloudflare WARP": "Cloudflare WARP", "Download complete": "下载完成", @@ -288,43 +272,40 @@ "Fully charged": "已充满电", "Charging:": "充电功率:", "Discharging:": "放电功率:", - "No pending tasks": "没有待办任务", + "No pending tasks": "没有要做的任务", "... and %1 more": "... 还有 %1 个", "Used:": "已用:", "Free:": "可用:", "Total:": "总计:", "Load:": "负载:", - "High": "高", - "Medium": "中", - "Low": "低", + "Medium": "适中", "Tint icons": "图标着色", - "Performance Profile toggle": "性能配置文件切换", - "**Instructions**: Log into Mistral account, go to Keys on the sidebar, click Create new key": "**说明**:登录 Mistral 账户,在侧边栏中选择 Keys,点击创建新密钥", + "Performance Profile toggle": "性能配置切换", + "**Instructions**: Log into Mistral account, go to Keys on the sidebar, click Create new key": "**说明**:登录 Mistral 账户,在侧边栏中选择 Keys,点击 Create new key", "Invalid arguments. Must provide `command`.": "参数无效。必须提供 `command`。", "Thought": "思考", - "Online | Google's model\nA Gemini 2.5 Flash model optimized for cost-efficiency and high throughput.": "在线 | Google 模型\n针对成本效益和高吞吐量优化的 Gemini 2.5 Flash 模型。", - "Online | Google's model\nFast, can perform searches for up-to-date information": "在线 | Google 模型\n速度快,可搜索最新信息", - "Your package manager is running": "您的包管理器正在运行", + "Online | Google's model\nA Gemini 2.5 Flash model optimized for cost-efficiency and high throughput.": "在线 | Google 的模型\n针对成本效益和高吞吐量优化的 Gemini 2.5 Flash 模型。", + "Online | Google's model\nFast, can perform searches for up-to-date information": "在线 | Google 的模型\n速度快,可搜索最新信息", + "Your package manager is running": "你的包管理器正在运行", "Gives the model search capabilities (immediately)": "为模型提供搜索功能(即时)", "Set the tool to use for the model.": "设置模型使用的工具。", "Night Light | Right-click to toggle Auto mode": "夜间模式 | 右键切换自动模式", "Online | %1's model | Delivers fast, responsive and well-formatted answers. Disadvantages: not very eager to do stuff; might make up unknown function calls": "在线 | %1 的模型 | 提供快速、响应迅速且格式良好的答案。缺点:不太积极主动;可能编造未知的函数调用", - "Depends on workspace": "取决于工作区", + "Depends on workspace": "随工作区移动", "Usage: %1tool TOOL_NAME": "用法:%1tool 工具名称", "Tray": "托盘", "Usage: %1save CHAT_NAME": "用法:%1save 聊天名称", "Approve": "批准", - "Depends on sidebars": "取决于侧边栏", - "Commands, edit configs, search.\nTakes an extra turn to switch to search mode if that's needed": "命令、编辑配置、搜索。\n如果需要,会额外执行一次切换到搜索模式", + "Depends on sidebars": "随侧边栏移动", + "Commands, edit configs, search.\nTakes an extra turn to switch to search mode if that's needed": "执行命令、编辑配置、搜索。\n如果需要,会额外执行一次切换到搜索模式", "Up %1": "运行 %1", - "Tool set to: %1": "工具设置为:%1", - "Wallpaper parallax": "壁纸视差", - "Online | Google's model\nGoogle's state-of-the-art multipurpose model that excels at coding and complex reasoning tasks.": "在线 | Google 模型\nGoogle 最先进的多用途模型,在编程和复杂推理任务方面表现卓越。", + "Tool set to: %1": "工具已设置为 %1", + "Online | Google's model\nGoogle's state-of-the-art multipurpose model that excels at coding and complex reasoning tasks.": "在线 | Google 的模型\nGoogle 最先进的多用途模型,在编程和复杂推理任务方面表现卓越。", "Tint app icons": "应用图标着色", - "Preferred wallpaper zoom (%)": "首选壁纸缩放比例 (%)", - "To set an API key, pass it with the %4 command\n\nTo view the key, pass \"get\" with the command
\n\n### For %1:\n\n**Link**: %2\n\n%3": "要设置 API 密钥,请使用 %4 命令传递\n\n要查看密钥,请在命令中传递 \"get\"
\n\n### 对于 %1:\n\n**链接**:%2\n\n%3", + "Preferred wallpaper zoom (%)": "首选壁纸缩放比例(%)", + "To set an API key, pass it with the %4 command\n\nTo view the key, pass \"get\" with the command
\n\n### For %1:\n\n**Link**: %2\n\n%3": "要设置 API 密钥,请使用 %4 命令传递\n\n要查看密钥,请在命令中传递 “get”
\n\n### 对于 %1:\n\n**链接**:%2\n\n%3", "No API key\nSet it with /key YOUR_API_KEY": "无 API 密钥\n使用 /key YOUR_API_KEY 设置", - "Total token count\nInput: %1\nOutput: %2": "总令牌数\n输入:%1\n输出:%2", + "Total token count\nInput: %1\nOutput: %2": "总词元数\n输入:%1\n输出:%2", "Disable tools": "禁用工具", "API key is set\nChange with /key YOUR_API_KEY": "API 密钥已设置\n使用 /key YOUR_API_KEY 更改", "Usage: %1load CHAT_NAME": "用法:%1load 聊天名称", @@ -332,17 +313,17 @@ "Temperature\nChange with /temp VALUE": "温度\n使用 /temp VALUE 更改", "Current tool: %1\nSet it with %2tool TOOL": "当前工具:%1\n使用 %2tool TOOL 设置", "There might be a download in progress": "可能有下载正在进行", - "Online | Google's model\nNewer model that's slower than its predecessor but should deliver higher quality answers": "在线 | Google 模型\n比前代模型更慢但应该提供更高质量答案的新模型", - "EasyEffects | Right-click to configure": "EasyEffects | 右键配置", + "Online | Google's model\nNewer model that's slower than its predecessor but should deliver higher quality answers": "在线 | Google 的模型\n比前代模型更慢但应该提供更高质量答案的新模型", + "EasyEffects | Right-click to configure": "EasyEffects | 右键以配置", "Command rejected by user": "用户拒绝了命令", "Invalid tool. Supported tools:\n- %1": "无效工具。支持的工具:\n- %1", "Keep right sidebar loaded": "保持右侧边栏加载", "Reject": "拒绝", "Enter password": "输入密码", "Automatically hide": "自动隐藏", - "**Pricing**: Free tier available with limited rates. See https://docs.github.com/en/billing/concepts/product-billing/github-models\n\n**Instructions**: Generate a GitHub personal access token with Models permission, then set as API key here\n\n**Note**: To use this you will have to set the temperature parameter to 1": "**定价**:提供免费层,速率有限。详情见 https://docs.github.com/en/billing/concepts/product-billing/github-models\n\n**说明**:生成一个具有 Models 权限的 GitHub 个人访问令牌,并在此处将其设置为 API 密钥\n\n**注意**:使用此提供商时需要将温度参数设置为 1", + "**Pricing**: Free tier available with limited rates. See https://docs.github.com/en/billing/concepts/product-billing/github-models\n\n**Instructions**: Generate a GitHub personal access token with Models permission, then set as API key here\n\n**Note**: To use this you will have to set the temperature parameter to 1": "**定价**:提供免费层级,速率有限。详情见 https://docs.github.com/en/billing/concepts/product-billing/github-models\n\n**说明**:生成一个具有 Models 权限的 GitHub 个人访问令牌,并在此处将其设置为 API 密钥\n\n**注意**:使用此提供商时需要将温度参数设置为 1", "Conflicts with the shell's system tray implementation": "与 Shell 的系统托盘实现冲突", - "Enjoy! You can reopen the welcome app any time with Super+Shift+Alt+/. To open the settings app, hit Super+I": "祝您愉快!随时可以按 Super+Shift+Alt+/ 重新打开欢迎应用。要打开设置应用,请按 Super+I", + "Enjoy! You can reopen the welcome app any time with Super+Shift+Alt+/. To open the settings app, hit Super+I": "祝你愉快!可以随时按 Super+Shift+Alt+/ 重新打开欢迎应用。要打开设置应用,请按 Super+I", "Reset": "重置", "☕ Break: %1 minutes": "☕ 休息:%1 分钟", "Pomodoro": "番茄钟", @@ -356,12 +337,11 @@ "Config file": "配置文件", "Stopwatch": "秒表", "Break": "休息", - "Shell conflicts killer": "终止冲突程序", + "Shell conflicts killer": "冲突终止程序", "Vertical": "垂直", "🔴 Focus: %1 minutes": "🔴 专注:%1 分钟", "System uptime:": "系统运行时间:", "Focus": "专注", - "Open the shell config file.\nIf the button doesn't work or doesn't open in your favorite editor,\nyou can manually open ~/.config/illogical-impulse/config.json": "打开 Shell 配置文件。\n如果按钮无法工作或没有在您喜欢的编辑器中打开,\n可以手动打开 ~/.config/illogical-impulse/config.json", "Attach a file. Only works with Gemini.": "附加文件。仅适用于 Gemini。", "🌿 Long break: %1 minutes": "🌿 长休息:%1 分钟", "Always": "始终", @@ -370,32 +350,31 @@ "Timer": "计时器", "Conflicts with the shell's notification implementation": "与 Shell 的通知实现冲突", "Pause": "暂停", - "Feels like %1": "体感温度:%1", + "Feels like %1": "体感温度 %1", "Lap": "计时", "Welcome app": "欢迎应用", "Corner style": "角落样式", "Language": "语言", - "Select the language for the user interface.\n\"Auto\" will use your system's locale.": "选择用户界面的语言。\n\"自动\" 将使用系统语言环境。", + "Select the language for the user interface.\n\"Auto\" will use your system's locale.": "选择用户界面的语言。\n“自动”将使用系统区域设置。", "Auto (System)": "自动(系统)", "Interface Language": "界面语言", "Paired": "已配对", - "Hit \"/\" to search": "按 \"/\" 搜索", + "Hit \"/\" to search": "按 “/” 以搜索", "Region width": "区域宽度", "Math": "数学", - "When this is off you'll have to click": "关闭后需要点击", + "When this is off you'll have to click": "若关闭则需要点击来打开", "Edit directory": "编辑目录", "Pills": "胶囊", "No active player": "无活动播放器", "Search wallpapers": "搜索壁纸", "Rect": "矩形", - "Make sure your player has MPRIS support\nor try turning off duplicate player filtering": "请确保您的播放器支持 MPRIS\n或尝试关闭重复播放器过滤", + "Make sure your player has MPRIS support\nor try turning off duplicate player filtering": "请确保你的播放器支持 MPRIS\n或尝试关闭重复播放器过滤", "Shell command": "Shell 命令", "Screen round corner": "屏幕圆角", "Password": "密码", "Bluetooth devices": "蓝牙设备", "Wallpaper & Colors": "壁纸与配色", "Details": "详细信息", - "Show clock": "显示时钟", "Connected": "已连接", "Open network portal": "打开网络门户", "Bar style": "条栏样式", @@ -404,32 +383,32 @@ "Value scroll": "滚动调整数值", "Line-separated": "线条分隔", "Region height": "区域高度", - "Pick a wallpaper": "选择壁纸", - "Visualize region": "可视化区域", + "Pick a wallpaper": "挑选壁纸", + "Visualize region": "显示区域", "Bottom": "底部", - "Usage: %1superpaste NUM_OF_ENTRIES[i]\nSupply i when you want images\nExamples:\n%1superpaste 4i for the last 4 images\n%1superpaste 7 for the last 7 entries": "用法:%1superpaste 条目数[i]\n需要图片时加上 i\n示例:\n%1superpaste 4i 获取最近 4 张图片\n%1superpaste 7 获取最近 7 条记录", - "Terminal: Harmony (%)": "终端:协调度 (%)", + "Usage: %1superpaste NUM_OF_ENTRIES[i]\nSupply i when you want images\nExamples:\n%1superpaste 4i for the last 4 images\n%1superpaste 7 for the last 7 entries": "用法:%1superpaste 条目数[i]\n需要图片时加上 i\n示例:\n%1superpaste 4i 粘贴最近 4 张图片\n%1superpaste 7 粘贴最近 7 个条目", + "Terminal: Harmony (%)": "终端:协调度(%)", "Forget": "忘记", "Background": "背景", "Top": "顶部", - "Not all options are available in this app. You should also check the config file by hitting the \"Config file\" button on the topleft corner or opening %1 manually.": "并非所有选项都在此应用中提供。您还应点击左上角的“配置文件”按钮或手动打开 %1 查看配置文件。", - "General": "常规", + "Not all options are available in this app. You should also check the config file by hitting the \"Config file\" button on the topleft corner or opening %1 manually.": "并非所有选项都在此应用中提供。你还应点击左上角的“配置文件”按钮,或手动打开 %1 以查看配置文件。", + "General": "通用", "Right": "右侧", "Utility buttons": "工具按钮", "Quick": "快速", - "Terminal: Foreground boost (%)": "终端:前景增强 (%)", + "Terminal: Foreground boost (%)": "终端:前景增强(%)", "Left": "左侧", - "Tip: right-clicking a group\nalso expands it": "提示:右键点击一个分组\n也可展开", + "Tip: right-clicking a group\nalso expands it": "提示:右键点击一个分组\n也可将其展开", "Change any time later with /dark, /light, /wallpaper in the launcher\nIf the shell's colors aren't changing:\n 1. Open the right sidebar with Super+N\n 2. Click \"Reload Hyprland & Quickshell\" in the top-right corner": "稍后可在启动器中通过 /dark、/light、/wallpaper 随时更改\n如果 Shell 的配色没有变化:\n 1. 用 Super+N 打开右侧边栏\n 2. 点击右上角的“重新加载 Hyprland 与 Quickshell”", "Place at bottom": "放置在底部", - "Allows you to open sidebars by clicking or hovering screen corners regardless of bar position": "无论条栏位置如何,都允许通过点击或悬停屏幕角落打开侧边栏", + "Allows you to open sidebars by clicking or hovering screen corners regardless of bar position": "无论条栏位置,都允许通过点击或悬停在屏幕角落来打开侧边栏", "Positioning": "位置", "Disconnect": "断开连接", "Unknown device": "未知设备", "Make icons pinned by default": "默认固定图标", "Bar & screen": "条栏与屏幕", "Corner open": "角落打开", - "Hover to trigger": "悬停触发", + "Hover to trigger": "悬停以触发", "Terminal: Harmonize threshold": "终端:协调阈值", "Bar position": "条栏位置", "Place the corners to trigger at the bottom": "将触发角落放置在底部", @@ -439,34 +418,305 @@ "Launch on startup": "启动时锁屏", "Also unlock keyring": "同时解锁密钥环", "Tip: Close a window with Super+Q": "提示: 使用 Super+Q 关闭窗口", - "Remember that on most devices one can always hold the power button to force shutdown\nThis only makes it a tiny bit harder for accidents to happen": "请记住,大多数设备仍可以长按电源键进行强制关机\n此选项只会降低误触的可能性而已", - "This is usually safe and needed for your browser and AI sidebar anyway\nMostly useful for those who use lock on startup instead of a display manager that does it (GDM, SDDM, etc.)": "这通常是安全的,并且对浏览器和 AI 侧边栏也是必要的\n主要对那些使用“开机自动锁屏”而不是显示管理器(如 GDM、SDDM 等)执行锁屏的用户有用", - "Show \"Locked\" text": "显示“已锁定”文字", + "Remember that on most devices one can always hold the power button to force shutdown\nThis only makes it a tiny bit harder for accidents to happen": "请记住,大多数设备仍可以通过长按电源键强制关机\n此选项只会略微降低误触的可能性而已", + "This is usually safe and needed for your browser and AI sidebar anyway\nMostly useful for those who use lock on startup instead of a display manager that does it (GDM, SDDM, etc.)": "这通常是安全的,并且对浏览器和 AI 侧边栏也是必要的\n主要对使用“启动时锁屏”而非显示管理器(如 GDM、SDDM 等)执行锁屏的用户有用", + "Show \"Locked\" text": "显示“已锁定”字样", "at": "在", - "Simple digital": "简洁数字", "Style: general": "样式:通用", - "Pick random from this folder": "从此文件随机选择", + "Pick random from this folder": "从此文件夹随机选择", "Back": "返回", "Cancel wallpaper selection": "取消壁纸选择", - "Timeout duration (if not defined by notification) (ms)": "延时时间 (若通知内未定义)(毫秒)", + "Timeout duration (if not defined by notification) (ms)": "显示时间(若通知未指定)(毫秒)", "Enable blur": "启用模糊", - "Material cookie": "Material 曲奇", "Click to toggle light/dark mode\n(applied when wallpaper is chosen)": "点击来切换浅色/深色模式\n(仅在选择壁纸后生效)", - "Use the system file picker instead\nRight-click to make this the default behavior": "改为使用系统文件选择器\n右键点击可设为默认行为", + "Use the system file picker instead\nRight-click to make this the default behavior": "改为使用系统文件选择器\n右键以将此设为默认行为", "Center clock": "居中时钟", - "Press Super+G to toggle appearance": "按下 Super+G 切换准星显示", "Lock screen": "锁屏", - "Crosshair code (in Valorant's format)": "准星代码 (瓦罗兰特格式)", + "Crosshair code (in Valorant's format)": "准星代码(瓦罗兰特格式)", "Random: osu! seasonal": "随机:osu! 季节性壁纸", "Work safety": "安全模式", "Require password to power off/restart": "需要密码来关机或重启", "Random osu! seasonal background\nImage is saved to ~/Pictures/Wallpapers": "随机 osu! 季节性壁纸\n图片会保存到 ~/Pictures/Wallpapers", "Open editor": "打开编辑器", - "Extra wallpaper zoom (%)": "额外壁纸缩放 (%)", + "Extra wallpaper zoom (%)": "额外壁纸缩放(%)", "Security": "安全", "Clock style": "时钟样式", "Style: Blurred": "样式:模糊", - "Crosshair overlay": "射击准星叠加", "Locked": "已锁定", - "Wallpaper safety enforced": "已启用壁纸安全模式" + "Wallpaper safety enforced": "已启用壁纸安全模式", + "Hour hand": "时针", + "Automatic": "自动", + "Language not listed or incomplete translations?\nYou can choose to generate translations for it with Gemini.\n1. Open the left sidebar with Super+A, set model to Gemini (if it isn't already)\n2. Type /key, hit Enter and follow the instructions\n3. Type /key YOUR_API_KEY\n4. Type the locale of your language below and press Generate": "想要的语言不在列表内或翻译不完整?\n你可以选择使用 Gemini 为其生成翻译。\n1. 按 Super+A 打开左侧边栏,将模型设置为 Gemini(如果不已经是了的话)\n2. 输入 /key,按 Enter 然后跟随说明\n3. 输入 /key YOUR_API_KEY\n4. 在下方输入你的语言代码并按下生成", + "Auto styling with Gemini": "使用 Gemini 自动决定样式", + "Audio output | Right-click for volume mixer & device selector": "音频输出 | 右键打开音量合成器与设备选择器", + "Most busy": "最繁杂处", + "Title font": "标题字体", + "Nerd font icons": "Nerd Font 图标", + "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.": "可能是带有一定边界的图片或屏幕部分。结果不一定总是准确。\n这是通过本地的图片处理算法完成的,过程没有 AI 参与。", + "Hollow": "空心", + "Turn on from sunset to sunrise": "在日落到日出期间开启", + "Notes": "笔记", + "It may take a few seconds to update": "可能需要几秒钟来更新", + "Google Lens": "Google 智能镜头", + "Monospace font": "等宽字体", + "Auto, ": "自动,", + "Classic": "经典", + "Font family name (e.g., JetBrains Mono NF)": "字体名称(如 JetBrains Mono NF)", + "Clear all": "全部清除", + "Show aim lines": "显示瞄准线", + "System sound": "系统声音", + "Search for apps": "搜索应用", + "Circle to Search": "圈定即搜", + "Thin": "纤细", + "Content region": "内容区域", + "Bubble": "气泡", + "Enable opening zoom animation": "启用打开时的缩放动画", + "Secured": "安全", + "Wi-Fi": "Wi-Fi", + "Manage my account": "管理我的账户", + "Regenerate": "重新生成", + "%1\nInternet access": "%1\nInternet 访问", + "Font family name (e.g., Space Grotesk)": "字体名称(如 Space Grotesk)", + "Inactive": "未启用", + "Least busy": "最空旷处", + "Constantly rotate": "持续旋转", + "Anti-flashbang (experimental)": "防高亮保护(实验性)", + "Bold": "加粗", + "Open the shell config file\nAlternatively right-click to copy path": "打开 Shell 配置文件\n或右键以复制路径", + "Description font size": "描述字体大小", + "Dot": "圆点", + "Second hand": "秒针", + "Generate translation with Gemini": "使用 Gemini 生成翻译", + "Enable GPS based location": "启用基于 GPS 的位置", + "Microphone": "麦克风", + "Virtual Keyboard": "屏幕键盘", + "Shut down": "关机", + "Digits in the middle": "在中心显示数字", + "If you want to somehow use fingerprint unlock...": "如果你想想办法用指纹解锁的话...", + "Couldn't recognize music": "未识别到歌曲", + "Split buttons": "拆分按键", + "Number style": "数字样式", + "Write something here...\nUse '-' to create copyable bullet points, like this:\n\nSheep fricker\n- 4x Slab\n- 1x Boat\n- 4x Redstone Dust\n- 1x Sticky Piston\n- 1x End Rod\n- 4x Redstone Repeater\n- 1x Redstone Torch\n- 1x Sheep": "在这里写些什么...\n使用 '-' 来创建可复制的列表项,比如这样:\n\n羊羊快♂乐机\n- 4x 半砖\n- 1x 船\n- 4x 红石粉\n- 1x 黏性活塞\n- 1x 末地烛\n- 4x 红石中继器\n- 1x 红石火把\n- 1x 绵羊", + "Network": "网络", + "Overlay: Floating Image": "叠加面板:悬浮图像", + "Unpin from taskbar": "从任务栏取消固定", + "Sound input": "声音输入", + "Not connected": "未连接", + "Use macOS-like symbols for mods keys": "为修饰键使用 macOS 风格的图标", + "Use symbols for mouse": "使用图标表示鼠标键", + "Fonts": "字体", + "Circle": "圈选", + "Wallpaper selector": "壁纸选择器", + "(Plugged in)": "(电源已接通)", + "Sound effects": "声音效果", + "Intensity": "强度", + "Close window": "关闭窗口", + "Video Recording Path": "视频录制路径", + "Speakers (%1): %2": "扬声器 (%1): %2", + "Perhaps what you're listening to is too niche": "也许你听的音乐太小众了", + "Animate time change": "时间变化动画", + "Set FPS limit": "设置帧数限制", + "Identify Music": "识别音乐", + "Focusing": "专注", + "Sound output": "声音输出", + "Type /key to get started with online models\nCtrl+O to expand sidebar\nCtrl+P to pin sidebar\nCtrl+D to detach sidebar": "输入 /key 来开始使用在线模型\nCtrl+O 拓宽侧边栏\nCtrl+P 固定侧边栏\nCtrl+D 分离侧边栏", + "Parallax": "视差效果", + "EasyEffects": "EasyEffects", + "Show only when locked": "仅在锁屏时显示", + "Date style": "日期样式", + "Font family name (e.g., Google Sans Flex)": "字体名称(如 Google Sans Flex)", + "+%1 notifications": "+%1 个通知", + "Hide clipboard images copied from sussy sources": "隐藏剪贴板中来自奇奇怪怪来源的图片", + "Use Hyprlock (instead of Quickshell)": "使用 Hyprlock(而非 Quickshell)", + "Darken screen": "屏幕变暗", + "Generating...\nDon't close this window!": "生成中...\n请勿关闭此窗口!", + "Dial style": "表盘样式", + "Anti-flashbang": "防高亮保护", + "Please charge!\nAutomatic suspend triggers at %1%": "请充电!\n将在电量为 %1% 时自动挂起", + "Battery full": "电量已充满", + "More Bluetooth settings": "更多蓝牙设置", + "Center icons": "图标居中", + "Generate\nTypically takes 2 minutes": "生成\n通常需要 2 分钟", + "Overlay: Crosshair": "叠加面板:准星", + "You can also manually edit cheatsheet.superKey": "你也可以手动编辑 cheatsheet.superKey 配置项", + "Layers": "显示层", + "Sign out": "注销", + "Android": "安卓", + "Recognize music | Right-click to toggle source": "识别音乐 | 右键以切换源", + "Why this is cool:\nFor non-0 values, it won't trigger when you reach the\nscreen corner along the horizontal edge, but it will when\nyou do along the vertical edge": "为什么这很酷:\n对于非 0 的数值,当你沿着水平边缘抵达屏幕角时,它不会触发;\n但当你沿着垂直边缘抵达屏幕角时,它就会触发", + "Locale code, e.g. fr_FR, de_DE, zh_CN...": "语言代码,如 fr_FR、de_DE、zh_CN 等", + "of %1": "共 %1", + "Clock style (locked)": "时钟样式(锁屏时)", + "City name": "城市名", + "Make sure you have songrec installed": "请确保你已安装 songrec", + "Windows": "窗口", + "Power Profile": "电源模式", + "Used for code and terminal": "用于代码和终端", + "Select language": "选择语言", + "File Explorer": "文件资源管理器", + "Saved ": "已保存 ", + "Not secured": "不安全", + "Overlay: General": "叠加面板:通用", + "Keybind font size": "快捷键字体大小", + "Nothing": "空空如也", + "Main font": "主字体", + "End session": "结束专注", + "Listening...": "正在听取...", + "Font family name (e.g., Readex Pro)": "字体名称(如 Readex Pro)", + "Fill": "填充", + "Dark Mode": "深色模式", + "Restart": "重启", + "Dots": "圆点", + "%1 mins": "%1 分钟", + "Font used for Nerd Font icons": "用于 Nerd Font 图标的字体", + "Region selector (screen snipping/Google Lens)": "区域选择器(屏幕截图与 Google 智能镜头)", + "Battery: %1%2": "电池状态:%1%2", + "Click to cycle through power profiles": "点击以循环切换电源模式", + "When the previous option is off and this is on,\nyou can still hover the corner's end to open sidebar,\nand the remaining area can be used for volume/brightness scroll": "当上一个选项为关闭且此项开启时,你仍然\n可以通过悬停在角落的末端来打开侧边栏,\n剩余的区域将可用于滚动调节音量与亮度", + "Widgets": "小组件", + "On-screen keyboard": "屏幕键盘", + "Used for general UI text": "用于通用 UI 文本", + "Line": "线条", + "Replace 󱕐 for \"Scroll ↓\", 󱕑 \"Scroll ↑\", L󰍽 \"LMB\", R󰍽 \"RMB\", 󱕒 \"Scroll ↑/↓\" and ⇞/⇟ for \"Page_↑/↓\"": "如用 󱕐 来表示 “Scroll ↓”,󱕑 “Scroll ↑”,L󰍽 “LMB”,R󰍽 “RMB”,以及 󱕒 “Scroll ↑/↓” 和 ⇞/⇟ 来表示 “Page_↑/↓”", + "Unmuted": "已打开", + "Path copied": "路径已复制", + "Uses Gemini to categorize the wallpaper then picks a preset based on it.\nYou'll need to set Gemini API key on the left sidebar first.\nImages are downscaled for performance, but just to be safe,\ndo not select wallpapers with sensitive information.": "使用 Gemini 对壁纸进行分类,然后根据分类选择一个预设。\n你需要先在左侧边栏设置 Gemini API 密钥。\n图片会被降低分辨率以提高性能, 但为了安全起见,\n请勿选择包含敏感信息的壁纸。", + "Unread indicator: show count": "未读指示器:显示数量", + "RAM": "内存", + "Saving...": "保存中...", + "Illegal increment": "超过最大增量限制", + "\nLMB to enable/disable\nRMB to toggle size\nScroll to swap position": "\n左键以启用/禁用\n右键以切换尺寸\n滚动以交换位置", + "Health:": "电池健康:", + "Display modifiers and keys in multiple keycap (e.g., \"Ctrl + A\" instead of \"Ctrl A\" or \"󰘴 + A\" instead of \"󰘴 A\")": "使用多个“键帽”显示修饰键和按键(如显示为 “Ctrl + A” 而非 “Ctrl A”,或 “󰘴 + A” 而非 “󰘴 A”)", + "Cookie clock settings": "曲奇时钟设置", + "Eye protection": "护眼选项", + "Tooltips": "悬停提示", + "See fewer": "查看更少", + "Click to show": "点击以显示", + "Circle selection": "圈定选区", + "Enter a valid number": "请输入有效的数字", + "Music Recognition": "音乐识别", + "Sounds": "提示音", + "Input device": "输入设备", + "On": "开", + "Hide sussy/anime wallpapers": "隐藏可疑或动漫壁纸", + "Full": "完整", + "Image source": "图像来源", + "Night Light": "夜间模式", + "Digital clock settings": "数字时钟设置", + "e.g. 󰘴 for Ctrl, 󰘵 for Alt, 󰘶 for Shift, etc": "如用 󰘴 来表示 Ctrl,󰘵 来表示 Alt,󰘶 来表示 Shift 等", + "Use system file picker": "使用系统文件选择器", + "Show hidden icons": "显示隐藏的图标", + "Exceeded max allowed": "已超过最高限制", + "Please unplug the charger": "请拔掉充电器", + "Numbers": "数字", + "Example use case: eroge on one workspace, dark Discord window on another": "使用示例:在一个工作区玩小黄游,另一个工作区开着深色的 Discord 窗口", + "Enabled": "已打开", + "Recognize music": "识别音乐", + "Digital": "数字", + "Audio input | Right-click for volume mixer & device selector": "音频输入 | 右键打开音量合成器与设备选择器", + "Use old sine wave cookie implementation": "使用旧版正弦波形曲奇实现", + "Used for displaying numbers": "用于显示数字", + "Music Recognized": "识别到歌曲", + "Numbers font": "数字字体", + "Media": "媒体", + "Quick toggles": "快捷设置", + "Copy path": "复制路径", + "Screenshot Path (leave empty to just copy)": "屏幕截图路径(留空则只复制)", + "Draggable": "可移动", + "Off": "关", + "Super key symbol": "Super 键图标", + "Normal": "正常", + "Scroll to Bottom": "滚动到底部", + "Audio output": "音频输出", + "Use varying shapes for password characters": "使用多样形状显示密码字符", + "Hour marks": "时标", + "Night Light | Right-click to configure": "夜间模式 | 右键以配置", + "Edit quick toggles": "编辑快捷设置", + "Total duration timeout (s)": "总持续时长(秒)", + "Font family name": "字体名称", + "Rectangular selection": "矩形选区", + "Sides": "边数", + "Stroke width": "笔画粗细", + "Widget: Clock": "小组件:时钟", + "Audio input": "音频输入", + "Polling interval (s)": "轮询间隔(秒)", + "Used for decorative/expressive text": "用于装饰性或富有表现力的文字", + "Enable translator": "启用翻译", + "Pin to taskbar": "固定到任务栏", + "Active": "已启用", + "Used for headings and titles": "用于标题和副标题", + "More volume settings": "更多音量设置", + "Minute hand": "分针", + "Enable if you want clocks to show seconds accurately": "启用以让时钟精准显示秒数", + "Keep awake": "保持唤醒", + "Local account": "本地账户", + "Save paths": "保存路径", + "Open recordings folder": "打开录像文件夹", + "Muted": "已静音", + "Sliders": "滑块", + "CPU": "CPU", + "Second precision": "精确显秒", + "Border": "内圈", + "Reading font": "阅读字体", + "Press Super+G to open the overlay and pin the crosshair": "按 Super+G 来打开叠加面板,然后固定准星", + "Show": "显示", + "More Internet settings": "更多 Internet 设置", + "Get the latest features and security improvements with\nthe newest feature update.\n\n%1 packages": "通过安装更新获取最新的功能和\n安全改进。\n\n%1 个软件包", + "Quote": "语录", + "Widget: Weather": "小组件:天气", + "Used for reading large blocks of text": "用于阅读大段文字", + "with vertical offset": "使用垂直偏移", + "Han chars": "汉字", + "e.g. 󱊫 for F1, 󱊶 for F12": "如用 󱊫 来表示 F1,󱊶 来表示 F12 等", + "Internet": "网络", + "Show notifications": "显示通知", + "Force hover open at absolute corner": "强制在绝对角落悬停打开", + "Use symbols for function keys": "使用符号表示功能键", + "Record": "屏幕录制", + "Authentication": "身份验证", + "Hint target regions": "建议目标区域", + "Enable now": "现在启用", + "You'll need to enter your Gemini API key first.\nType /key on the sidebar for instructions.": "你需要先输入 Gemini API 密钥。\n在侧边栏输入 /key 以获取说明。", + "Output device": "输出设备", + "Swap": "虚拟内存", + "Full warning": "满电警告", + "Padding": "额外边距", + "Expressive font": "表现力字体", + "Balance brightness based on content": "根据内容更改亮度", + "Cookie": "曲奇", + "Fahrenheit unit": "华氏度单位", + "Roman": "罗马", + "Polling interval (m)": "轮询间隔(分钟)", + "Close all windows": "关闭所有窗口", + "Adjust the color temperature": "调整色温", + "Task View": "任务视图", + "More comfortable viewing at night": "夜间浏览更舒适", + "No new notifications": "没有新通知", + "All": "全部", + "Move to front": "移到前面", + "Emoji": "表情符号", + "Best match": "最佳匹配", + "Other": "其他", + "Unpin from Start": "从“开始”屏幕取消固定", + "Polkit": "Polkit", + "Productivity": "效率", + "Web": "网页", + "Apps": "应用", + "Manage accounts": "管理账户", + "Commands": "命令", + "Do you want to allow this app to make changes to your device?": "你要允许此应用对你的设备进行更改吗?", + "Actions": "操作", + "Open": "打开", + "Pinned": "已固定", + "Move right": "右移", + "Command": "命令", + "Utilities & Tools": "实用工具", + "Change password": "更改密码", + "Command-line-invoked Action": "由命令行执行的操作", + "Unknown Application": "未知应用", + "No applications": "没有应用", + "Creativity": "创意", + "Move left": "左移", + "Pin to Start": "固定到“开始”屏幕" } \ No newline at end of file