Merge branch 'main' into all-tooltips

This commit is contained in:
end-4
2025-08-11 22:06:56 +07:00
committed by GitHub
54 changed files with 1362 additions and 302 deletions
+2 -2
View File
@@ -1,5 +1,5 @@
# $lock_cmd = hyprctl dispatch global quickshell:lock & pidof qs quickshell hyprlock || hyprlock
$lock_cmd = pidof hyprlock || hyprlock
$lock_cmd = hyprctl dispatch global quickshell:lock & pidof qs quickshell hyprlock || hyprlock
# $lock_cmd = pidof hyprlock || hyprlock
$suspend_cmd = systemctl suspend || loginctl suspend
general {
+3
View File
@@ -22,3 +22,6 @@ env = XDG_MENU_PREFIX, plasma-
# ######## Virtual envrionment #########
env = ILLOGICAL_IMPULSE_VIRTUAL_ENV, ~/.local/state/quickshell/.venv
# ######## Terminal application #########
env = TERMINAL,kitty -1
+8 -4
View File
@@ -5,9 +5,12 @@
##! Shell
# These absolutely need to be on top, or they won't work consistently
bindid = Super, Super_L, Toggle overview, global, quickshell:overviewToggleRelease # Toggle overview/launcher
bindid = Super, Super_R, Toggle overview, global, quickshell:overviewToggleRelease # [hidden] Toggle overview/launcher
bind = Super, Super_L, exec, qs -c $qsConfig ipc call TEST_ALIVE || pkill fuzzel || fuzzel # [hidden] Launcher (fallback)
bind = Super, Super_R, exec, qs -c $qsConfig ipc call TEST_ALIVE || pkill fuzzel || fuzzel # [hidden] Launcher (fallback)
binditn = Super, catchall, global, quickshell:overviewToggleReleaseInterrupt # [hidden]
bind = Ctrl, Super_L, global, quickshell:overviewToggleReleaseInterrupt # [hidden]
bind = Ctrl, Super_R, global, quickshell:overviewToggleReleaseInterrupt # [hidden]
bind = Super, mouse:272, global, quickshell:overviewToggleReleaseInterrupt # [hidden]
bind = Super, mouse:273, global, quickshell:overviewToggleReleaseInterrupt # [hidden]
bind = Super, mouse:274, global, quickshell:overviewToggleReleaseInterrupt # [hidden]
@@ -18,6 +21,7 @@ bind = Super, mouse_up, global, quickshell:overviewToggleReleaseInterrupt # [hi
bind = Super, mouse_down,global, quickshell:overviewToggleReleaseInterrupt # [hidden]
bindit = ,Super_L, global, quickshell:workspaceNumber # [hidden]
bindit = ,Super_R, global, quickshell:workspaceNumber # [hidden]
bindd = Super, V, Clipboard history >> clipboard, global, quickshell:overviewClipboardToggle # Clipboard history >> clipboard
bindd = Super, Period, Emoji >> clipboard, global, quickshell:overviewEmojiToggle # Emoji >> clipboard
bindd = Super, Tab, Toggle overview, global, quickshell:overviewToggle # [hidden] Toggle overview/launcher (alt)
@@ -201,10 +205,10 @@ bindl= ,XF86AudioPlay, exec, playerctl play-pause # [hidden]
bindl= ,XF86AudioPause, exec, playerctl play-pause # [hidden]
##! Apps
bind = Super, Return, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "kitty -1" "foot" "alacritty" "wezterm" "konsole" "kgx" "uxterm" "xterm" # Terminal
bind = Super, T, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "kitty -1" "foot" "alacritty" "wezterm" "konsole" "kgx" "uxterm" "xterm" # [hidden] Kitty (terminal) (alt)
bind = Ctrl+Alt, T, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "kitty -1" "foot" "alacritty" "wezterm" "konsole" "kgx" "uxterm" "xterm" # [hidden] Kitty (for Ubuntu people)
bind = Super, E, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "dolphin" "nautilus" "nemo" "thunar" "kitty -1 fish -c yazi" # File manager
bind = Super, Return, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "$TERMINAL" "kitty -1" "foot" "alacritty" "wezterm" "konsole" "kgx" "uxterm" "xterm" # Terminal
bind = Super, T, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "$TERMINAL" "kitty -1" "foot" "alacritty" "wezterm" "konsole" "kgx" "uxterm" "xterm" # [hidden] (terminal) (alt)
bind = Ctrl+Alt, T, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "$TERMINAL" "kitty -1" "foot" "alacritty" "wezterm" "konsole" "kgx" "uxterm" "xterm" # [hidden] (terminal) (for Ubuntu people)
bind = Super, E, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "dolphin" "nautilus" "nemo" "thunar" "$TERMINAL" "kitty -1 fish -c yazi" # File manager
bind = Super, W, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "google-chrome-stable" "zen-browser" "firefox" "brave" "chromium" "microsoft-edge-stable" "opera" "librewolf" # Browser
bind = Super, C, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "code" "codium" "cursor" "zed" "zedit" "zeditor" "kate" "gnome-text-editor" "emacs" "command -v nvim && kitty -1 nvim" # Code editor
bind = Super+Shift, W, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "wps" "onlyoffice-desktopeditors" # Office software
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
for cmd in "$@"; do
[[ -z "$cmd" ]] && continue
eval "command -v ${cmd%% *}" >/dev/null 2>&1 || continue
eval "$cmd" &
exit
done
exit 1
+7 -21
View File
@@ -17,11 +17,13 @@ Singleton {
property bool osdVolumeOpen: false
property bool oskOpen: false
property bool overviewOpen: false
property bool sessionOpen: false
property bool workspaceShowNumbers: false
property bool superReleaseMightTrigger: true
property bool screenLocked: false
property bool screenLockContainsCharacters: false
property bool screenUnlockFailed: false
property bool sessionOpen: false
property bool superDown: false
property bool superReleaseMightTrigger: true
property bool workspaceShowNumbers: false
property real screenZoom: 1
onScreenZoomChanged: {
@@ -31,31 +33,15 @@ Singleton {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
// When user is not reluctant while pressing super, they probably don't need to see workspace numbers
onSuperReleaseMightTriggerChanged: {
workspaceShowNumbersTimer.stop()
}
Timer {
id: workspaceShowNumbersTimer
interval: Config.options.bar.workspaces.showNumberDelay
// interval: 0
repeat: false
onTriggered: {
workspaceShowNumbers = true
}
}
GlobalShortcut {
name: "workspaceNumber"
description: "Hold to show workspace numbers, release to show icons"
onPressed: {
workspaceShowNumbersTimer.start()
root.superDown = true
}
onReleased: {
workspaceShowNumbersTimer.stop()
workspaceShowNumbers = false
root.superDown = false
}
}
@@ -26,13 +26,13 @@ Variants {
required property var modelData
// Hide when fullscreen
readonly property Toplevel activeWindow: ToplevelManager.activeToplevel
property bool focusingThisMonitor: HyprlandData.activeWorkspace?.monitor == monitor.name
visible: !(activeWindow?.fullscreen && activeWindow?.activated && focusingThisMonitor)
property list<HyprlandWorkspace> workspacesForMonitor: Hyprland.workspaces.values.filter(workspace=>workspace.monitor && workspace.monitor.name == monitor.name)
property var activeWorkspaceWithFullscreen: workspacesForMonitor.filter(workspace=>((workspace.toplevels.values.filter(window=>window.wayland.fullscreen)[0] != undefined) && workspace.active))[0]
visible: !(activeWorkspaceWithFullscreen != undefined)
// Workspaces
property HyprlandMonitor monitor: Hyprland.monitorFor(modelData)
property list<var> relevantWindows: HyprlandData.windowList.filter(win => win.monitor == monitor.id && win.workspace.id >= 0).sort((a, b) => a.workspace.id - b.workspace.id)
property list<var> relevantWindows: HyprlandData.windowList.filter(win => win.monitor == monitor?.id && win.workspace.id >= 0).sort((a, b) => a.workspace.id - b.workspace.id)
property int firstWorkspaceId: relevantWindows[0]?.workspace.id || 1
property int lastWorkspaceId: relevantWindows[relevantWindows.length - 1]?.workspace.id || 10
// Wallpaper
@@ -148,8 +148,13 @@ Variants {
// Wallpaper
Image {
id: wallpaper
visible: !bgRoot.wallpaperIsVideo
visible: opacity > 0
opacity: (status === Image.Ready && !bgRoot.wallpaperIsVideo) ? 1 : 0
Behavior on opacity {
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
}
property real value // 0 to 1, for offset
asynchronous: true
value: {
// Range = groups that workspaces span on
const chunkSize = Config?.options.bar.workspaces.shown ?? 10;
@@ -278,8 +283,8 @@ Variants {
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
text: "Enter password"
color: CF.ColorUtils.transparentize(bgRoot.colText, 0.3)
text: GlobalStates.screenUnlockFailed ? Translation.tr("Incorrect password") : Translation.tr("Enter password")
color: GlobalStates.screenUnlockFailed ? Appearance.colors.colError : bgRoot.colText
font {
pixelSize: Appearance.font.pixelSize.normal
}
@@ -45,7 +45,7 @@ Item {
elide: Text.ElideRight
text: root.focusingThisMonitor && root.activeWindow?.activated && root.biggestWindow ?
root.activeWindow?.title :
(root.biggestWindow?.title) ?? `${Translation.tr("Workspace")} ${monitor?.activeWorkspace?.id}`
(root.biggestWindow?.title) ?? `${Translation.tr("Workspace")} ${monitor?.activeWorkspace?.id ?? 1}`
}
}
+122 -74
View File
@@ -39,8 +39,30 @@ Scope {
property real useShortenedForm: (Appearance.sizes.barHellaShortenScreenWidthThreshold >= screen.width) ? 2 : (Appearance.sizes.barShortenScreenWidthThreshold >= screen.width) ? 1 : 0
readonly property int centerSideModuleWidth: (useShortenedForm == 2) ? Appearance.sizes.barCenterSideModuleWidthHellaShortened : (useShortenedForm == 1) ? Appearance.sizes.barCenterSideModuleWidthShortened : Appearance.sizes.barCenterSideModuleWidth
Timer {
id: showBarTimer
interval: (Config?.options.bar.autoHide.showWhenPressingSuper.delay ?? 100)
repeat: false
onTriggered: {
barRoot.superShow = true
}
}
Connections {
target: GlobalStates
function onSuperDownChanged() {
if (!Config?.options.bar.autoHide.showWhenPressingSuper.enable) return;
if (GlobalStates.superDown) showBarTimer.restart();
else {
showBarTimer.stop();
barRoot.superShow = false;
}
}
}
property bool superShow: false
property bool mustShow: hoverRegion.containsMouse || superShow
exclusionMode: ExclusionMode.Ignore
exclusiveZone: Appearance.sizes.baseBarHeight + (Config.options.bar.cornerStyle === 1 ? Appearance.sizes.hyprlandGapsOut : 0)
exclusiveZone: (Config?.options.bar.autoHide.enable && (!mustShow || !Config?.options.bar.autoHide.pushWindows)) ? 0 :
Appearance.sizes.baseBarHeight + (Config.options.bar.cornerStyle === 1 ? Appearance.sizes.hyprlandGapsOut : 0)
WlrLayershell.namespace: "quickshell:bar"
implicitHeight: Appearance.sizes.barHeight + Appearance.rounding.screenRounding
mask: Region {
@@ -55,90 +77,116 @@ Scope {
right: true
}
BarContent {
id: barContent
anchors {
right: parent.right
left: parent.left
top: parent.top
bottom: undefined
}
implicitHeight: Appearance.sizes.barHeight
MouseArea {
id: hoverRegion
hoverEnabled: true
anchors.fill: parent
states: State {
name: "bottom"
when: Config.options.bar.bottom
AnchorChanges {
target: barContent
anchors {
right: parent.right
left: parent.left
top: undefined
bottom: parent.bottom
BarContent {
id: barContent
implicitHeight: Appearance.sizes.barHeight
anchors {
right: parent.right
left: parent.left
top: parent.top
bottom: undefined
topMargin: (Config?.options.bar.autoHide.enable && !mustShow) ? -Appearance.sizes.barHeight + 1 : 0
bottomMargin: 0
}
Behavior on anchors.topMargin {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Behavior on anchors.bottomMargin {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
states: State {
name: "bottom"
when: Config.options.bar.bottom
AnchorChanges {
target: barContent
anchors {
right: parent.right
left: parent.left
top: undefined
bottom: parent.bottom
}
}
PropertyChanges {
target: barContent
anchors.topMargin: 0
anchors.bottomMargin: (Config?.options.bar.autoHide.enable && !mustShow) ? -Appearance.sizes.barHeight + 1 : 0
}
}
}
}
// Round decorators
Loader {
id: roundDecorators
anchors {
left: parent.left
right: parent.right
}
y: Appearance.sizes.barHeight
width: parent.width
height: Appearance.rounding.screenRounding
active: showBarBackground && Config.options.bar.cornerStyle === 0 // Hug
states: State {
name: "bottom"
when: Config.options.bar.bottom
PropertyChanges {
roundDecorators.y: 0
// Round decorators
Loader {
id: roundDecorators
anchors {
left: parent.left
right: parent.right
top: barContent.bottom
bottom: undefined
}
}
width: parent.width
height: Appearance.rounding.screenRounding
active: showBarBackground && Config.options.bar.cornerStyle === 0 // Hug
sourceComponent: Item {
implicitHeight: Appearance.rounding.screenRounding
RoundCorner {
id: leftCorner
anchors {
top: parent.top
bottom: parent.bottom
left: parent.left
}
implicitSize: Appearance.rounding.screenRounding
color: showBarBackground ? Appearance.colors.colLayer0 : "transparent"
corner: RoundCorner.CornerEnum.TopLeft
states: State {
name: "bottom"
when: Config.options.bar.bottom
PropertyChanges {
leftCorner.corner: RoundCorner.CornerEnum.BottomLeft
states: State {
name: "bottom"
when: Config.options.bar.bottom
AnchorChanges {
target: roundDecorators
anchors {
right: parent.right
left: parent.left
top: undefined
bottom: barContent.top
}
}
}
RoundCorner {
id: rightCorner
anchors {
right: parent.right
top: !Config.options.bar.bottom ? parent.top : undefined
bottom: Config.options.bar.bottom ? parent.bottom : undefined
}
implicitSize: Appearance.rounding.screenRounding
color: showBarBackground ? Appearance.colors.colLayer0 : "transparent"
corner: RoundCorner.CornerEnum.TopRight
states: State {
name: "bottom"
when: Config.options.bar.bottom
PropertyChanges {
rightCorner.corner: RoundCorner.CornerEnum.BottomRight
sourceComponent: Item {
implicitHeight: Appearance.rounding.screenRounding
RoundCorner {
id: leftCorner
anchors {
top: parent.top
bottom: parent.bottom
left: parent.left
}
implicitSize: Appearance.rounding.screenRounding
color: showBarBackground ? Appearance.colors.colLayer0 : "transparent"
corner: RoundCorner.CornerEnum.TopLeft
states: State {
name: "bottom"
when: Config.options.bar.bottom
PropertyChanges {
leftCorner.corner: RoundCorner.CornerEnum.BottomLeft
}
}
}
RoundCorner {
id: rightCorner
anchors {
right: parent.right
top: !Config.options.bar.bottom ? parent.top : undefined
bottom: Config.options.bar.bottom ? parent.bottom : undefined
}
implicitSize: Appearance.rounding.screenRounding
color: showBarBackground ? Appearance.colors.colLayer0 : "transparent"
corner: RoundCorner.CornerEnum.TopRight
states: State {
name: "bottom"
when: Config.options.bar.bottom
PropertyChanges {
rightCorner.corner: RoundCorner.CornerEnum.BottomRight
}
}
}
}
@@ -129,9 +129,9 @@ Item {
horizontalAlignment: Qt.AlignHCenter
fill: 0
text: switch(PowerProfiles.profile) {
case PowerProfile.PowerSaver: return "battery_saver"
case PowerProfile.Balanced: return "dynamic_form"
case PowerProfile.Performance: return "speed"
case PowerProfile.PowerSaver: return "energy_savings_leaf"
case PowerProfile.Balanced: return "settings_slow_motion"
case PowerProfile.Performance: return "local_fire_department"
}
iconSize: Appearance.font.pixelSize.large
color: Appearance.colors.colOnLayer2
@@ -28,6 +28,30 @@ Item {
property real workspaceIconMarginShrinked: -4
property int workspaceIndexInGroup: (monitor?.activeWorkspace?.id - 1) % Config.options.bar.workspaces.shown
property bool showNumbers: false
Timer {
id: showNumbersTimer
interval: (Config?.options.bar.autoHide.showWhenPressingSuper.delay ?? 100)
repeat: false
onTriggered: {
root.showNumbers = true
}
}
Connections {
target: GlobalStates
function onSuperDownChanged() {
if (!Config?.options.bar.autoHide.showWhenPressingSuper.enable) return;
if (GlobalStates.superDown) showNumbersTimer.restart();
else {
showNumbersTimer.stop();
root.showNumbers = false;
}
}
function onSuperReleaseMightTriggerChanged() {
showNumbersTimer.stop()
}
}
// Function to update workspaceOccupied
function updateWorkspaceOccupied() {
workspaceOccupied = Array.from({ length: Config.options.bar.workspaces.shown }, (_, i) => {
@@ -176,9 +200,9 @@ Item {
property var mainAppIconSource: Quickshell.iconPath(AppSearch.guessIcon(biggestWindow?.class), "image-missing")
StyledText { // Workspace number text
opacity: GlobalStates.workspaceShowNumbers
|| ((Config.options?.bar.workspaces.alwaysShowNumbers && (!Config.options?.bar.workspaces.showAppIcons || !workspaceButtonBackground.biggestWindow || GlobalStates.workspaceShowNumbers))
|| (GlobalStates.workspaceShowNumbers && !Config.options?.bar.workspaces.showAppIcons)
opacity: root.showNumbers
|| ((Config.options?.bar.workspaces.alwaysShowNumbers && (!Config.options?.bar.workspaces.showAppIcons || !workspaceButtonBackground.biggestWindow || root.showNumbers))
|| (root.showNumbers && !Config.options?.bar.workspaces.showAppIcons)
) ? 1 : 0
z: 3
@@ -200,7 +224,7 @@ Item {
Rectangle { // Dot instead of ws number
id: wsDot
opacity: (Config.options?.bar.workspaces.alwaysShowNumbers
|| GlobalStates.workspaceShowNumbers
|| root.showNumbers
|| (Config.options?.bar.workspaces.showAppIcons && workspaceButtonBackground.biggestWindow)
) ? 0 : 1
visible: opacity > 0
@@ -222,20 +246,20 @@ Item {
width: workspaceButtonWidth
height: workspaceButtonWidth
opacity: !Config.options?.bar.workspaces.showAppIcons ? 0 :
(workspaceButtonBackground.biggestWindow && !GlobalStates.workspaceShowNumbers && Config.options?.bar.workspaces.showAppIcons) ?
(workspaceButtonBackground.biggestWindow && !root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ?
1 : workspaceButtonBackground.biggestWindow ? workspaceIconOpacityShrinked : 0
visible: opacity > 0
IconImage {
id: mainAppIcon
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.bottomMargin: (!GlobalStates.workspaceShowNumbers && Config.options?.bar.workspaces.showAppIcons) ?
anchors.bottomMargin: (!root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ?
(workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked
anchors.rightMargin: (!GlobalStates.workspaceShowNumbers && Config.options?.bar.workspaces.showAppIcons) ?
anchors.rightMargin: (!root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ?
(workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked
source: workspaceButtonBackground.mainAppIconSource
implicitSize: (!GlobalStates.workspaceShowNumbers && Config.options?.bar.workspaces.showAppIcons) ? workspaceIconSize : workspaceIconSizeShrinked
implicitSize: (!root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ? workspaceIconSize : workspaceIconSizeShrinked
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
@@ -31,7 +31,7 @@ MouseArea {
visible: true
font.pixelSize: Appearance.font.pixelSize.small
color: Appearance.colors.colOnLayer1
text: Weather.data.temp
text: Weather.data?.temp ?? "--°"
Layout.alignment: Qt.AlignVCenter
}
}
@@ -15,9 +15,26 @@ Singleton {
property QtObject sizes
property string syntaxHighlightingTheme
// Extremely conservative transparency values for consistency and readability
property real transparency: Config.options?.appearance.transparency ? (m3colors.darkmode ? 0.1 : 0.07) : 0
property real contentTransparency: Config.options?.appearance.transparency ? (m3colors.darkmode ? 0.55 : 0.55) : 0
// Transparency. The quadratic functions were derived from analysis of hand-picked transparency values.
ColorQuantizer {
id: wallColorQuant
source: Qt.resolvedUrl(Config.options.background.wallpaperPath)
depth: 0 // 2^0 = 1 color
rescaleSize: 10
}
property real wallpaperVibrancy: (wallColorQuant.colors[0]?.hslSaturation + wallColorQuant.colors[0]?.hslLightness) / 2
property real autoBackgroundTransparency: { // y = 0.5768x^2 - 0.759x + 0.2896
let x = wallpaperVibrancy
let y = 0.5768 * (x * x) - 0.759 * (x) + 0.2896
return Math.max(0, Math.min(0.22, y))
}
property real autoContentTransparency: { // y = -10.1734x^2 + 3.4457x + 0.1872
let x = autoBackgroundTransparency
let y = -10.1734 * (x * x) + 3.4457 * (x) + 0.1872
return Math.max(0, Math.min(0.6, y))
}
property real backgroundTransparency: Config?.options.appearance.transparency.enable ? Config?.options.appearance.transparency.automatic ? autoBackgroundTransparency : Config?.options.appearance.transparency.backgroundTransparency : 0
property real contentTransparency: Config?.options.appearance.transparency.enable ? Config?.options.appearance.transparency.automatic ? autoContentTransparency : Config?.options.appearance.transparency.contentTransparency : 0
m3colors: QtObject {
property bool darkmode: false
@@ -100,26 +117,30 @@ Singleton {
colors: QtObject {
property color colSubtext: m3colors.m3outline
property color colLayer0: ColorUtils.mix(ColorUtils.transparentize(m3colors.m3background, root.transparency), m3colors.m3primary, Config.options.appearance.extraBackgroundTint ? 0.99 : 1)
property color colLayer0: ColorUtils.mix(ColorUtils.transparentize(m3colors.m3background, root.backgroundTransparency), m3colors.m3primary, Config.options.appearance.extraBackgroundTint ? 0.99 : 1)
property color colOnLayer0: m3colors.m3onBackground
property color colLayer0Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer0, colOnLayer0, 0.9, root.contentTransparency))
property color colLayer0Active: ColorUtils.transparentize(ColorUtils.mix(colLayer0, colOnLayer0, 0.8, root.contentTransparency))
property color colLayer0Border: ColorUtils.mix(root.m3colors.m3outlineVariant, colLayer0, 0.4)
property color colLayer1: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3surfaceContainerLow, m3colors.m3background, 0.8), root.contentTransparency);
property color colLayer1: ColorUtils.transparentize(m3colors.m3surfaceContainerLow, root.contentTransparency);
property color colOnLayer1: m3colors.m3onSurfaceVariant;
property color colOnLayer1Inactive: ColorUtils.mix(colOnLayer1, colLayer1, 0.45);
property color colLayer2: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3surfaceContainer, m3colors.m3surfaceContainerHigh, 0.1), root.contentTransparency)
property color colLayer2: ColorUtils.transparentize(m3colors.m3surfaceContainer, root.contentTransparency)
property color colOnLayer2: m3colors.m3onSurface;
property color colOnLayer2Disabled: ColorUtils.mix(colOnLayer2, m3colors.m3background, 0.4);
property color colLayer3: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3surfaceContainerHigh, m3colors.m3onSurface, 0.96), root.contentTransparency)
property color colOnLayer3: m3colors.m3onSurface;
property color colLayer1Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer1, colOnLayer1, 0.92), root.contentTransparency)
property color colLayer1Active: ColorUtils.transparentize(ColorUtils.mix(colLayer1, colOnLayer1, 0.85), root.contentTransparency);
property color colLayer2Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer2, colOnLayer2, 0.90), root.contentTransparency)
property color colLayer2Active: ColorUtils.transparentize(ColorUtils.mix(colLayer2, colOnLayer2, 0.80), root.contentTransparency);
property color colLayer2Disabled: ColorUtils.transparentize(ColorUtils.mix(colLayer2, m3colors.m3background, 0.8), root.contentTransparency);
property color colLayer3: ColorUtils.transparentize(m3colors.m3surfaceContainerHigh, root.contentTransparency)
property color colOnLayer3: m3colors.m3onSurface;
property color colLayer3Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer3, colOnLayer3, 0.90), root.contentTransparency)
property color colLayer3Active: ColorUtils.transparentize(ColorUtils.mix(colLayer3, colOnLayer3, 0.80), root.contentTransparency);
property color colLayer4: ColorUtils.transparentize(m3colors.m3surfaceContainerHighest, root.contentTransparency)
property color colOnLayer4: m3colors.m3onSurface;
property color colLayer4Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer4, colOnLayer4, 0.90), root.contentTransparency)
property color colLayer4Active: ColorUtils.transparentize(ColorUtils.mix(colLayer4, colOnLayer4, 0.80), root.contentTransparency);
property color colPrimary: m3colors.m3primary
property color colOnPrimary: m3colors.m3onPrimary
property color colPrimaryHover: ColorUtils.mix(colors.colPrimary, colLayer1Hover, 0.87)
@@ -148,6 +169,14 @@ Singleton {
property color colScrim: ColorUtils.transparentize(m3colors.m3scrim, 0.5)
property color colShadow: ColorUtils.transparentize(m3colors.m3shadow, 0.7)
property color colOutlineVariant: m3colors.m3outlineVariant
property color colError: m3colors.m3error
property color colErrorHover: ColorUtils.mix(m3colors.m3error, colLayer1Hover, 0.85)
property color colErrorActive: ColorUtils.mix(m3colors.m3error, colLayer1Active, 0.7)
property color colOnError: m3colors.m3onError
property color colErrorContainer: m3colors.m3errorContainer
property color colErrorContainerHover: ColorUtils.mix(m3colors.m3errorContainer, m3colors.m3onErrorContainer, 0.90)
property color colErrorContainerActive: ColorUtils.mix(m3colors.m3errorContainer, m3colors.m3onErrorContainer, 0.70)
property color colOnErrorContainer: m3colors.m3onErrorContainer
}
rounding: QtObject {
@@ -268,7 +297,6 @@ Singleton {
easing.bezierCurve: root.animation.elementMoveFast.bezierCurve
}}
}
property QtObject clickBounce: QtObject {
property int duration: 200
property int type: Easing.BezierSpline
@@ -281,7 +309,7 @@ Singleton {
}}
}
property QtObject scroll: QtObject {
property int duration: 400
property int duration: 200
property int type: Easing.BezierSpline
property list<real> bezierCurve: animationCurves.standardDecel
}
@@ -81,7 +81,12 @@ Singleton {
property JsonObject appearance: JsonObject {
property bool extraBackgroundTint: true
property int fakeScreenRounding: 2 // 0: None | 1: Always | 2: When not fullscreen
property bool transparency: false
property JsonObject transparency: JsonObject {
property bool enable: true
property bool automatic: true
property real backgroundTransparency: 0.11
property real contentTransparency: 0.57
}
property JsonObject wallpaperTheming: JsonObject {
property bool enableAppsAndShell: true
property bool enableQtApps: true
@@ -124,6 +129,14 @@ Singleton {
}
property JsonObject bar: JsonObject {
property JsonObject autoHide: JsonObject {
property bool enable: false
property bool pushWindows: false
property JsonObject showWhenPressingSuper: JsonObject {
property bool enable: true
property int delay: 140
}
}
property bool bottom: false // Instead of top
property int cornerStyle: 0 // 0: Hug | 1: Float | 2: Plain rectangle
property bool borderless: false // true for no grouping of items
@@ -181,6 +194,15 @@ Singleton {
property list<string> ignoredAppRegexes: []
}
property JsonObject interactions: JsonObject {
property JsonObject scrolling: JsonObject {
property bool fasterTouchpadScroll: true // Enable faster scrolling with touchpad
property int mouseScrollDeltaThreshold: 120 // delta >= this then it gets detected as mouse scroll rather than touchpad
property int mouseScrollFactor: 120
property int touchpadScrollFactor: 450
}
}
property JsonObject language: JsonObject {
property JsonObject translator: JsonObject {
property string engine: "auto" // Run `trans -list-engines` for available engines. auto should use google
@@ -198,6 +220,11 @@ Singleton {
}
}
property JsonObject media: JsonObject {
// Attempt to remove dupes (the aggregator playerctl one and browsers' native ones when there's plasma browser integration)
property bool filterDuplicatePlayers: true
}
property JsonObject networking: JsonObject {
property string userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
}
@@ -253,6 +280,13 @@ Singleton {
// https://doc.qt.io/qt-6/qtime.html#toString
property string format: "hh:mm"
property string dateFormat: "ddd, dd/MM"
property JsonObject pomodoro: JsonObject {
property string alertSound: ""
property int breakTime: 300
property int cyclesBeforeLongBreak: 4
property int focus: 1500
property int longBreak: 900
}
}
property JsonObject windows: JsonObject {
@@ -11,18 +11,35 @@ Singleton {
property string fileName: "states.json"
property string filePath: `${root.fileDir}/${root.fileName}`
Timer {
id: fileReloadTimer
interval: 100
repeat: false
onTriggered: {
persistentStatesFileView.reload()
}
}
Timer {
id: fileWriteTimer
interval: 100
repeat: false
onTriggered: {
persistentStatesFileView.writeAdapter()
}
}
FileView {
id: persistentStatesFileView
path: root.filePath
watchChanges: true
onFileChanged: reload()
onAdapterUpdated: {
writeAdapter()
}
onFileChanged: fileReloadTimer.restart()
onAdapterUpdated: fileWriteTimer.restart()
onLoadFailed: error => {
console.log("Failed to load persistent states file:", error);
if (error == FileViewError.FileNotFound) {
writeAdapter();
fileWriteTimer.restart();
}
}
@@ -44,6 +61,20 @@ Singleton {
property bool allowNsfw: false
property string provider: "yandere"
}
property JsonObject timer: JsonObject {
property JsonObject pomodoro: JsonObject {
property bool running: false
property int start: 0
property bool isBreak: false
property int cycle: 0
}
property JsonObject stopwatch: JsonObject {
property bool running: false
property int start: 0
property list<var> laps: []
}
}
}
}
}
@@ -10,6 +10,7 @@ import QtQuick.Layouts
Rectangle {
id: root
default property alias data: rowLayout.data
property alias uniformCellSizes: rowLayout.uniformCellSizes
property real spacing: 5
property real padding: 0
property int clickIndex: rowLayout.clickIndex
@@ -12,9 +12,9 @@ RippleButton {
leftPadding: 15
rightPadding: 15
buttonRadius: Appearance.rounding.small
colBackground: (urgency == NotificationUrgency.Critical) ? Appearance.colors.colSecondaryContainer : Appearance.colors.colSurfaceContainerHighest
colBackgroundHover: (urgency == NotificationUrgency.Critical) ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colSurfaceContainerHighestHover
colRipple: (urgency == NotificationUrgency.Critical) ? Appearance.colors.colSecondaryContainerActive : Appearance.colors.colSurfaceContainerHighestActive
colBackground: (urgency == NotificationUrgency.Critical) ? Appearance.colors.colSecondaryContainer : Appearance.colors.colLayer4
colBackgroundHover: (urgency == NotificationUrgency.Critical) ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colLayer4Hover
colRipple: (urgency == NotificationUrgency.Critical) ? Appearance.colors.colSecondaryContainerActive : Appearance.colors.colLayer4Active
contentItem: StyledText {
horizontalAlignment: Text.AlignHCenter
@@ -13,9 +13,9 @@ Rectangle { // App icon
property var urgency: NotificationUrgency.Normal
property var image: ""
property real scale: 1
property real size: 45 * scale
property real size: 38 * scale
property real materialIconScale: 0.57
property real appIconScale: 0.7
property real appIconScale: 0.8
property real smallAppIconScale: 0.49
property real materialIconSize: size * materialIconScale
property real appIconSize: size * appIconScale
@@ -105,7 +105,7 @@ Item { // Notification group area
id: background
anchors.left: parent.left
width: parent.width
color: Appearance.colors.colSurfaceContainer
color: Appearance.colors.colLayer2
radius: Appearance.rounding.normal
anchors.leftMargin: root.xOffset
@@ -140,8 +140,8 @@ Item { // Notification item area
color: (expanded && !onlyNotification) ?
(notificationObject.urgency == NotificationUrgency.Critical) ?
ColorUtils.mix(Appearance.colors.colSecondaryContainer, Appearance.colors.colLayer2, 0.35) :
(Appearance.colors.colSurfaceContainerHigh) :
ColorUtils.transparentize(Appearance.colors.colSurfaceContainerHighest)
(Appearance.colors.colLayer3) :
ColorUtils.transparentize(Appearance.colors.colLayer3)
implicitHeight: expanded ? (contentColumn.implicitHeight + padding * 2) : summaryRow.implicitHeight
Behavior on implicitHeight {
@@ -168,7 +168,7 @@ Item { // Notification item area
id: summaryText
visible: !root.onlyNotification
font.pixelSize: root.fontSize
color: Appearance.colors.colOnLayer2
color: Appearance.colors.colOnLayer3
elide: Text.ElideRight
text: root.notificationObject.summary || ""
}
@@ -53,6 +53,7 @@ TabButton {
RippleAnim {
id: rippleFadeAnim
duration: rippleDuration * 2
target: ripple
property: "opacity"
to: 0
@@ -29,6 +29,7 @@ Button {
property color colRipple: Appearance?.colors.colLayer1Active ?? "#D6CEE2"
property color colRippleToggled: Appearance?.colors.colPrimaryActive ?? "#D6CEE2"
opacity: root.enabled ? 1 : 0.4
property color buttonColor: root.enabled ? (root.toggled ?
(root.hovered ? colBackgroundToggledHover :
colBackgroundToggled) :
@@ -91,6 +92,7 @@ Button {
RippleAnim {
id: rippleFadeAnim
duration: rippleDuration * 2
target: ripple
property: "opacity"
to: 0
@@ -51,6 +51,7 @@ TabButton {
RippleAnim {
id: rippleFadeAnim
duration: rippleDuration * 2
target: ripple
property: "opacity"
to: 0
@@ -106,13 +107,29 @@ TabButton {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
Rectangle {
Item {
id: ripple
radius: Appearance.rounding.full
color: root.colRipple
width: ripple.implicitWidth
height: ripple.implicitHeight
opacity: 0
property real implicitWidth: 0
property real implicitHeight: 0
visible: width > 0 && height > 0
Behavior on opacity {
animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this)
}
RadialGradient {
anchors.fill: parent
gradient: Gradient {
GradientStop { position: 0.0; color: root.colRipple }
GradientStop { position: 0.3; color: root.colRipple }
GradientStop { position: 0.5 ; color: Qt.rgba(root.colRipple.r, root.colRipple.g, root.colRipple.b, 0) }
}
}
transform: Translate {
x: -ripple.width / 2
y: -ripple.height / 2
@@ -63,7 +63,7 @@ Item {
Layout.rightMargin: dialogPadding
}
ListView {
StyledListView {
id: choiceListView
Layout.fillWidth: true
Layout.fillHeight: true
@@ -71,9 +71,6 @@ Item {
currentIndex: root.defaultChoice !== undefined ? root.items.indexOf(root.defaultChoice) : -1
spacing: 6
maximumFlickVelocity: 3500
boundsBehavior: Flickable.DragOverBounds
model: ScriptModel {
id: choiceModel
}
@@ -1,6 +1,50 @@
import QtQuick
import qs.modules.common
Flickable {
id: root
maximumFlickVelocity: 3500
boundsBehavior: Flickable.DragOverBounds
property real touchpadScrollFactor: Config?.options.interactions.scrolling.touchpadScrollFactor ?? 100
property real mouseScrollFactor: Config?.options.interactions.scrolling.mouseScrollFactor ?? 50
property real mouseScrollDeltaThreshold: Config?.options.interactions.scrolling.mouseScrollDeltaThreshold ?? 120
// Accumulated scroll destination so wheel deltas stack while animating
property real scrollTargetY: 0
MouseArea {
visible: Config?.options.interactions.scrolling.fasterTouchpadScroll
anchors.fill: parent
acceptedButtons: Qt.NoButton
onWheel: function(wheelEvent) {
const delta = wheelEvent.angleDelta.y / root.mouseScrollDeltaThreshold;
// The angleDelta.y of a touchpad is usually small and continuous,
// while that of a mouse wheel is typically in multiples of ±120.
var scrollFactor = Math.abs(wheelEvent.angleDelta.y) >= root.mouseScrollDeltaThreshold ? root.mouseScrollFactor : root.touchpadScrollFactor;
const maxY = Math.max(0, root.contentHeight - root.height);
const base = scrollAnim.running ? root.scrollTargetY : root.contentY;
var targetY = Math.max(0, Math.min(base - delta * scrollFactor, maxY));
root.scrollTargetY = targetY;
root.contentY = targetY;
wheelEvent.accepted = true;
}
}
Behavior on contentY {
NumberAnimation {
id: scrollAnim
duration: Appearance.animation.scroll.duration
easing.type: Appearance.animation.scroll.type
easing.bezierCurve: Appearance.animation.scroll.bezierCurve
}
}
// Keep target synced when not animating (e.g., drag/flick or programmatic changes)
onContentYChanged: {
if (!scrollAnim.running) {
root.scrollTargetY = root.contentY;
}
}
}
@@ -14,6 +14,12 @@ ListView {
property int dragIndex: -1
property real dragDistance: 0
property bool popin: true
// Accumulated scroll destination so wheel deltas stack while animating
property real scrollTargetY: 0
property real touchpadScrollFactor: Config?.options.interactions.scrolling.touchpadScrollFactor ?? 100
property real mouseScrollFactor: Config?.options.interactions.scrolling.mouseScrollFactor ?? 50
property real mouseScrollDeltaThreshold: Config?.options.interactions.scrolling.mouseScrollDeltaThreshold ?? 120
function resetDrag() {
root.dragIndex = -1
@@ -23,6 +29,42 @@ ListView {
maximumFlickVelocity: 3500
boundsBehavior: Flickable.DragOverBounds
MouseArea {
visible: Config?.options.interactions.scrolling.fasterTouchpadScroll
anchors.fill: parent
acceptedButtons: Qt.NoButton
onWheel: function(wheelEvent) {
const delta = wheelEvent.angleDelta.y / root.mouseScrollDeltaThreshold;
// The angleDelta.y of a touchpad is usually small and continuous,
// while that of a mouse wheel is typically in multiples of ±120.
var scrollFactor = Math.abs(wheelEvent.angleDelta.y) >= root.mouseScrollDeltaThreshold ? root.mouseScrollFactor : root.touchpadScrollFactor;
const maxY = Math.max(0, root.contentHeight - root.height);
const base = scrollAnim.running ? root.scrollTargetY : root.contentY;
var targetY = Math.max(0, Math.min(base - delta * scrollFactor, maxY));
root.scrollTargetY = targetY;
root.contentY = targetY;
wheelEvent.accepted = true;
}
}
Behavior on contentY {
NumberAnimation {
id: scrollAnim
duration: Appearance.animation.scroll.duration
easing.type: Appearance.animation.scroll.type
easing.bezierCurve: Appearance.animation.scroll.bezierCurve
}
}
// Keep target synced when not animating (e.g., drag/flick or programmatic changes)
onContentYChanged: {
if (!scrollAnim.running) {
root.scrollTargetY = root.contentY;
}
}
add: Transition {
animations: [
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
@@ -19,6 +19,9 @@ Scope {
// 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"`])
}
}
@@ -24,7 +24,10 @@ Scope {
}
onCurrentTextChanged: {
showFailure = false; // Clear the failure text once the user starts typing.
if (currentText.length > 0) {
showFailure = false;
GlobalStates.screenUnlockFailed = false;
}
GlobalStates.screenLockContainsCharacters = currentText.length > 0;
passwordClearTimer.restart();
}
@@ -58,6 +61,7 @@ Scope {
root.unlocked();
} else {
root.showFailure = true;
GlobalStates.screenUnlockFailed = true;
}
root.currentText = "";
@@ -75,17 +75,10 @@ MouseArea {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
radius: Appearance.rounding.full
color: Appearance.colors.colLayer2
color: Appearance.m3colors.m3surfaceContainer
implicitWidth: 160
implicitHeight: 44
StyledText {
visible: root.context.showFailure && passwordBox.text.length == 0
anchors.centerIn: parent
text: "Incorrect"
color: Appearance.m3colors.m3error
}
StyledTextInput {
id: passwordBox
@@ -25,7 +25,7 @@ Scope {
property real artRounding: Appearance.rounding.verysmall
property list<real> visualizerPoints: []
property bool hasPlasmaIntegration: true
property bool hasPlasmaIntegration: false
Process {
id: plasmaIntegrationAvailabilityCheckProc
running: true
@@ -35,7 +35,9 @@ Scope {
}
}
function isRealPlayer(player) {
// return true
if (!Config.options.media.filterDuplicatePlayers) {
return true;
}
return (
// Remove unecessary native buses from browsers if there's plasma integration
!(hasPlasmaIntegration && player.dbusName.startsWith('org.mpris.MediaPlayer2.firefox')) &&
@@ -21,7 +21,7 @@ Scope {
required property var modelData
property string searchingText: ""
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.screen)
property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor.id)
property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor?.id)
screen: modelData
visible: GlobalStates.overviewOpen
@@ -20,7 +20,7 @@ Item {
property var windows: HyprlandData.windowList
property var windowByAddress: HyprlandData.windowByAddress
property var windowAddresses: HyprlandData.addresses
property var monitorData: HyprlandData.monitors.find(m => m.id === root.monitor.id)
property var monitorData: HyprlandData.monitors.find(m => m.id === root.monitor?.id)
property real scale: Config.options.overview.scale
property color activeBorderColor: Appearance.colors.colSecondary
@@ -149,14 +149,15 @@ Item {
const address = `0x${toplevel.HyprlandToplevel.address}`
var win = windowByAddress[address]
const inWorkspaceGroup = (root.workspaceGroup * root.workspacesShown < win?.workspace?.id && win?.workspace?.id <= (root.workspaceGroup + 1) * root.workspacesShown)
const inMonitor = root.monitor.id === win.monitor
return inWorkspaceGroup && inMonitor;
return inWorkspaceGroup;
})
}
}
delegate: OverviewWindow {
id: window
required property var modelData
property int monitorId: windowData?.monitor
property var monitor: HyprlandData.monitors[monitorId]
property var address: `0x${modelData.HyprlandToplevel.address}`
windowData: windowByAddress[address]
toplevel: modelData
@@ -164,9 +165,7 @@ Item {
scale: root.scale
availableWorkspaceWidth: root.workspaceImplicitWidth
availableWorkspaceHeight: root.workspaceImplicitHeight
property int monitorId: windowData?.monitor
property var monitor: HyprlandData.monitors[monitorId]
widgetMonitorId: root.monitor.id
property bool atInitPosition: (initX == x && initY == y)
@@ -1,7 +1,6 @@
import qs
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import Qt5Compat.GraphicalEffects
import QtQuick
@@ -22,6 +21,7 @@ Item { // Window
property real initY: Math.max((windowData?.at[1] - (monitorData?.y ?? 0) - monitorData?.reserved[1]) * root.scale, 0) + yOffset
property real xOffset: 0
property real yOffset: 0
property int widgetMonitorId: 0
property var targetWindowWidth: windowData?.size[0] * scale
property var targetWindowHeight: windowData?.size[1] * scale
@@ -40,6 +40,7 @@ Item { // Window
y: initY
width: windowData?.size[0] * root.scale
height: windowData?.size[1] * root.scale
opacity: windowData.monitor == widgetMonitorId ? 1 : 0.4
layer.enabled: true
layer.effect: OpacityMask {
@@ -69,6 +70,7 @@ Item { // Window
captureSource: GlobalStates.overviewOpen ? root.toplevel : null
live: true
// Color overlay for interactions
Rectangle {
anchors.fill: parent
radius: Appearance.rounding.windowRounding * root.scale
@@ -13,7 +13,8 @@ Scope {
component CornerPanelWindow: PanelWindow {
id: cornerPanelWindow
visible: (Config.options.appearance.fakeScreenRounding === 1 || (Config.options.appearance.fakeScreenRounding === 2 && !activeWindow?.fullscreen))
property bool fullscreen
visible: (Config.options.appearance.fakeScreenRounding === 1 || (Config.options.appearance.fakeScreenRounding === 2 && !fullscreen))
property var corner
exclusionMode: ExclusionMode.Ignore
@@ -44,22 +45,34 @@ Scope {
model: Quickshell.screens
Scope {
id: monitorScope
required property var modelData
property HyprlandMonitor monitor: Hyprland.monitorFor(modelData)
// Hide when fullscreen
property list<HyprlandWorkspace> workspacesForMonitor: Hyprland.workspaces.values.filter(workspace=>workspace.monitor && workspace.monitor.name == monitor.name)
property var activeWorkspaceWithFullscreen: workspacesForMonitor.filter(workspace=>((workspace.toplevels.values.filter(window=>window.wayland.fullscreen)[0] != undefined) && workspace.active))[0]
property bool fullscreen: activeWorkspaceWithFullscreen != undefined
CornerPanelWindow {
screen: modelData
corner: RoundCorner.CornerEnum.TopLeft
fullscreen: monitorScope.fullscreen
}
CornerPanelWindow {
screen: modelData
corner: RoundCorner.CornerEnum.TopRight
fullscreen: monitorScope.fullscreen
}
CornerPanelWindow {
screen: modelData
corner: RoundCorner.CornerEnum.BottomLeft
fullscreen: monitorScope.fullscreen
}
CornerPanelWindow {
screen: modelData
corner: RoundCorner.CornerEnum.BottomRight
fullscreen: monitorScope.fullscreen
}
}
}
@@ -96,6 +96,23 @@ ContentPage {
ContentSubsection {
title: Translation.tr("Overall appearance")
ConfigRow {
uniform: true
ConfigSwitch {
text: Translation.tr("Automatically hide")
checked: Config.options.bar.autoHide.enable
onCheckedChanged: {
Config.options.bar.autoHide.enable = checked;
}
}
ConfigSwitch {
text: Translation.tr("Place at the bottom")
checked: Config.options.bar.bottom
onCheckedChanged: {
Config.options.bar.bottom = checked;
}
}
}
ConfigRow {
uniform: true
ConfigSwitch {
@@ -140,9 +140,9 @@ ContentPage {
ConfigRow {
ConfigSwitch {
text: Translation.tr("Enable")
checked: Config.options.appearance.transparency
checked: Config.options.appearance.transparency.enable
onCheckedChanged: {
Config.options.appearance.transparency = checked;
Config.options.appearance.transparency.enable = checked;
}
StyledToolTip {
content: Translation.tr("Might look ass. Unsupported.")
@@ -282,6 +282,9 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
spacing: 10
popin: false
touchpadScrollFactor: Config.options.interactions.scrolling.touchpadScrollFactor * 1.4
mouseScrollFactor: Config.options.interactions.scrolling.mouseScrollFactor * 1.4
property int lastResponseLength: 0
clip: true
@@ -296,15 +299,6 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
add: null // Prevent function calls from being janky
Behavior on contentY {
NumberAnimation {
id: scrollAnim
duration: Appearance.animation.scroll.duration
easing.type: Appearance.animation.scroll.type
easing.bezierCurve: Appearance.animation.scroll.bezierCurve
}
}
model: ScriptModel {
values: Ai.messageIDs.filter(id => {
const message = Ai.messageByID[id];
@@ -137,6 +137,9 @@ Item {
anchors.fill: parent
spacing: 10
touchpadScrollFactor: Config.options.interactions.scrolling.touchpadScrollFactor * 1.4
mouseScrollFactor: Config.options.interactions.scrolling.mouseScrollFactor * 1.4
property int lastResponseLength: 0
clip: true
@@ -149,15 +152,6 @@ Item {
}
}
Behavior on contentY {
NumberAnimation {
id: scrollAnim
duration: Appearance.animation.scroll.duration
easing.type: Appearance.animation.scroll.type
easing.bezierCurve: Appearance.animation.scroll.bezierCurve
}
}
model: ScriptModel {
values: {
if(root.responses.length > booruResponseListView.lastResponseLength) {
@@ -15,7 +15,7 @@ Button {
id: root
property var imageData
property var rowHeight
property bool manualDownload: true
property bool manualDownload: false
property string previewDownloadPath
property string downloadPath
property string nsfwPath
@@ -63,13 +63,17 @@ Button {
anchors.fill: parent
width: root.rowHeight * modelData.aspect_ratio
height: root.rowHeight
visible: opacity > 0
opacity: status === Image.Ready ? 1 : 0
fillMode: Image.PreserveAspectFit
source: modelData.preview_url
sourceSize.width: root.rowHeight * modelData.aspect_ratio
sourceSize.height: root.rowHeight
visible: opacity > 0
opacity: status === Image.Ready ? 1 : 0
Behavior on opacity {
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
}
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
@@ -78,10 +82,6 @@ Button {
radius: imageRadius
}
}
Behavior on opacity {
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
}
}
RippleButton {
@@ -237,7 +237,7 @@ Rectangle {
rowHeight: imageRow.rowHeight
imageRadius: imageRow.modelData.images.length == 1 ? 50 : Appearance.rounding.normal
// Download manually to reduce redundant requests or make sure downloading works
// manualDownload: ["danbooru", "waifu.im", "t.alcy.cc"].includes(root.responseData.provider)
manualDownload: ["danbooru", "waifu.im", "t.alcy.cc"].includes(root.responseData.provider)
previewDownloadPath: root.previewDownloadPath
downloadPath: root.downloadPath
nsfwPath: root.nsfwPath
@@ -4,6 +4,7 @@ import qs
import qs.services
import "./calendar"
import "./todo"
import "./pomodoro"
import QtQuick
import QtQuick.Layouts
@@ -17,7 +18,8 @@ Rectangle {
property bool collapsed: Persistent.states.sidebar.bottomGroup.collapsed
property var tabs: [
{"type": "calendar", "name": Translation.tr("Calendar"), "icon": "calendar_month", "widget": calendarWidget},
{"type": "todo", "name": Translation.tr("To Do"), "icon": "done_outline", "widget": todoWidget}
{"type": "todo", "name": Translation.tr("To Do"), "icon": "done_outline", "widget": todoWidget},
{"type": "timer", "name": Translation.tr("Timer"), "icon": "schedule", "widget": pomodoroWidget},
]
Behavior on implicitHeight {
@@ -238,4 +240,13 @@ Rectangle {
anchors.margins: 5
}
}
// Pomodoro component
Component {
id: pomodoroWidget
PomodoroWidget {
anchors.fill: parent
anchors.margins: 5
}
}
}
@@ -15,7 +15,7 @@ Rectangle {
color: Appearance.colors.colLayer1
property int selectedTab: 0
property var tabButtonList: [{"icon": "notifications", "name": Translation.tr("Notifications")}, {"icon": "volume_up", "name": Translation.tr("Volume mixer")}]
property var tabButtonList: [{"icon": "notifications", "name": Translation.tr("Notifications")}, {"icon": "volume_up", "name": Translation.tr("Audio")}]
Keys.onPressed: (event) => {
if (event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp) {
@@ -0,0 +1,115 @@
import qs
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import Qt5Compat.GraphicalEffects
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
Item {
id: root
implicitHeight: contentColumn.implicitHeight
implicitWidth: contentColumn.implicitWidth
ColumnLayout {
id: contentColumn
anchors.fill: parent
spacing: 0
// The Pomodoro timer circle
CircularProgress {
Layout.alignment: Qt.AlignHCenter
lineWidth: 8
value: {
return TimerService.pomodoroSecondsLeft / TimerService.pomodoroLapDuration;
}
implicitSize: 200
enableAnimation: true
ColumnLayout {
anchors.centerIn: parent
spacing: 0
StyledText {
Layout.alignment: Qt.AlignHCenter
text: {
let minutes = Math.floor(TimerService.pomodoroSecondsLeft / 60).toString().padStart(2, '0');
let seconds = Math.floor(TimerService.pomodoroSecondsLeft % 60).toString().padStart(2, '0');
return `${minutes}:${seconds}`;
}
font.pixelSize: 40
color: Appearance.m3colors.m3onSurface
}
StyledText {
Layout.alignment: Qt.AlignHCenter
text: TimerService.pomodoroLongBreak ? Translation.tr("Long break") : TimerService.pomodoroBreak ? Translation.tr("Break") : Translation.tr("Focus")
font.pixelSize: Appearance.font.pixelSize.normal
color: Appearance.colors.colSubtext
}
}
Rectangle {
radius: Appearance.rounding.full
color: Appearance.colors.colLayer2
anchors {
right: parent.right
bottom: parent.bottom
}
implicitWidth: 36
implicitHeight: implicitWidth
StyledText {
id: cycleText
anchors.centerIn: parent
color: Appearance.colors.colOnLayer2
text: TimerService.pomodoroCycle + 1
}
}
}
// The Start/Stop and Reset buttons
RowLayout {
Layout.alignment: Qt.AlignHCenter
spacing: 10
RippleButton {
contentItem: StyledText {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
text: TimerService.pomodoroRunning ? Translation.tr("Pause") : (TimerService.pomodoroSecondsLeft === TimerService.focusTime) ? Translation.tr("Start") : Translation.tr("Resume")
color: TimerService.pomodoroRunning ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary
}
implicitHeight: 35
implicitWidth: 90
font.pixelSize: Appearance.font.pixelSize.larger
onClicked: TimerService.togglePomodoro()
colBackground: TimerService.pomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary
colBackgroundHover: TimerService.pomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary
}
RippleButton {
implicitHeight: 35
implicitWidth: 90
onClicked: TimerService.resetPomodoro()
enabled: (TimerService.pomodoroSecondsLeft < TimerService.pomodoroLapDuration) || TimerService.pomodoroCycle > 0 || TimerService.pomodoroBreak
font.pixelSize: Appearance.font.pixelSize.larger
colBackground: Appearance.colors.colErrorContainer
colBackgroundHover: Appearance.colors.colErrorContainerHover
colRipple: Appearance.colors.colErrorContainerActive
contentItem: StyledText {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
text: Translation.tr("Reset")
color: Appearance.colors.colOnErrorContainer
}
}
}
}
}
@@ -0,0 +1,144 @@
import qs
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Item {
id: root
property int currentTab: 0
property var tabButtonList: [
{"name": Translation.tr("Pomodoro"), "icon": "search_activity"},
{"name": Translation.tr("Stopwatch"), "icon": "timer"}
]
// These are keybinds for stopwatch and pomodoro
Keys.onPressed: (event) => {
if ((event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp) && event.modifiers === Qt.NoModifier) { // Switch tabs
if (event.key === Qt.Key_PageDown) {
currentTab = Math.min(currentTab + 1, root.tabButtonList.length - 1)
} else if (event.key === Qt.Key_PageUp) {
currentTab = Math.max(currentTab - 1, 0)
}
event.accepted = true
} else if (event.key === Qt.Key_Space || event.key === Qt.Key_S) { // Pause/resume with Space or S
if (currentTab === 0) {
TimerService.togglePomodoro()
} else {
TimerService.toggleStopwatch()
}
event.accepted = true
} else if (event.key === Qt.Key_R) { // Reset with R
if (currentTab === 0) {
TimerService.resetPomodoro()
} else {
TimerService.stopwatchReset()
}
event.accepted = true
} else if (event.key === Qt.Key_L) { // Record lap with L
TimerService.stopwatchRecordLap()
event.accepted = true
}
}
ColumnLayout {
anchors.fill: parent
spacing: 0
TabBar {
id: tabBar
Layout.fillWidth: true
currentIndex: currentTab
onCurrentIndexChanged: currentTab = currentIndex
background: Item {
WheelHandler {
onWheel: (event) => {
if (event.angleDelta.y < 0)
tabBar.currentIndex = Math.min(tabBar.currentIndex + 1, root.tabButtonList.length - 1)
else if (event.angleDelta.y > 0)
tabBar.currentIndex = Math.max(tabBar.currentIndex - 1, 0)
}
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
}
}
Repeater {
model: root.tabButtonList
delegate: SecondaryTabButton {
selected: (index == currentTab)
buttonText: modelData.name
buttonIcon: modelData.icon
}
}
}
Item { // Tab indicator
id: tabIndicator
Layout.fillWidth: true
height: 3
property bool enableIndicatorAnimation: false
Connections {
target: root
function onCurrentTabChanged() {
tabIndicator.enableIndicatorAnimation = true
}
}
Rectangle {
id: indicator
property int tabCount: root.tabButtonList.length
property real fullTabSize: root.width / tabCount;
property real targetWidth: tabBar.contentItem.children[0].children[tabBar.currentIndex].tabContentWidth
implicitWidth: targetWidth
anchors {
top: parent.top
bottom: parent.bottom
}
x: tabBar.currentIndex * fullTabSize + (fullTabSize - targetWidth) / 2
color: Appearance.colors.colPrimary
radius: Appearance.rounding.full
Behavior on x {
enabled: tabIndicator.enableIndicatorAnimation
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
Behavior on implicitWidth {
enabled: tabIndicator.enableIndicatorAnimation
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
}
}
Rectangle { // Tabbar bottom border
id: tabBarBottomBorder
Layout.fillWidth: true
height: 1
color: Appearance.colors.colOutlineVariant
}
SwipeView {
id: swipeView
Layout.topMargin: 10
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 10
clip: true
currentIndex: currentTab
onCurrentIndexChanged: {
tabIndicator.enableIndicatorAnimation = true
currentTab = currentIndex
}
// Tabs
PomodoroTimer {}
Stopwatch {}
}
}
}
@@ -0,0 +1,208 @@
import qs
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import Qt5Compat.GraphicalEffects
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
Item {
id: stopwatchTab
Layout.fillWidth: true
Layout.fillHeight: true
Item {
anchors {
fill: parent
topMargin: 8
leftMargin: 16
rightMargin: 16
}
RowLayout { // Elapsed
id: elapsedIndicator
anchors {
top: undefined
verticalCenter: parent.verticalCenter
left: controlButtons.left
leftMargin: 6
}
states: State {
name: "hasLaps"
when: TimerService.stopwatchLaps.length > 0
AnchorChanges {
target: elapsedIndicator
anchors.top: parent.top
anchors.verticalCenter: undefined
anchors.left: controlButtons.left
}
}
transitions: Transition {
AnchorAnimation {
duration: Appearance.animation.elementMoveFast.duration
easing.type: Appearance.animation.elementMoveFast.type
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
}
}
spacing: 0
StyledText {
// Layout.preferredWidth: elapsedIndicator.width * 0.6 // Prevent shakiness
font.pixelSize: 40
color: Appearance.m3colors.m3onSurface
text: {
let totalSeconds = Math.floor(TimerService.stopwatchTime) / 100
let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0')
let seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0')
return `${minutes}:${seconds}`
}
}
StyledText {
Layout.fillWidth: true
font.pixelSize: 40
color: Appearance.colors.colSubtext
text: {
return `:<sub>${(Math.floor(TimerService.stopwatchTime) % 100).toString().padStart(2, '0')}</sub>`
}
}
}
// Laps
StyledListView {
id: lapsList
anchors {
top: elapsedIndicator.bottom
bottom: controlButtons.top
left: parent.left
right: parent.right
topMargin: 16
bottomMargin: 16
}
spacing: 4
clip: true
popin: true
model: ScriptModel {
values: TimerService.stopwatchLaps.map((v, i, arr) => arr[arr.length - 1 - i])
}
delegate: Rectangle {
id: lapItem
required property int index
required property var modelData
property var horizontalPadding: 10
property var verticalPadding: 6
width: lapsList.width
implicitHeight: lapRow.implicitHeight + verticalPadding * 2
implicitWidth: lapRow.implicitWidth + horizontalPadding * 2
color: Appearance.colors.colLayer2
radius: Appearance.rounding.small
RowLayout {
id: lapRow
anchors {
fill: parent
leftMargin: lapItem.horizontalPadding
rightMargin: lapItem.horizontalPadding
topMargin: lapItem.verticalPadding
bottomMargin: lapItem.verticalPadding
}
StyledText {
font.pixelSize: Appearance.font.pixelSize.small
color: Appearance.colors.colSubtext
text: `${TimerService.stopwatchLaps.length - lapItem.index}.`
}
StyledText {
font.pixelSize: Appearance.font.pixelSize.small
text: {
const lapTime = lapItem.modelData
const _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0')
const totalSeconds = Math.floor(lapTime) / 100
const minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0')
const seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0')
return `${minutes}:${seconds}.${_10ms}`
}
}
Item { Layout.fillWidth: true }
StyledText {
font.pixelSize: Appearance.font.pixelSize.smaller
color: Appearance.colors.colPrimary
text: {
const originalIndex = TimerService.stopwatchLaps.length - lapItem.index - 1
const lastTime = originalIndex > 0 ? TimerService.stopwatchLaps[originalIndex - 1] : 0
const lapTime = lapItem.modelData - lastTime
const _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0')
const totalSeconds = Math.floor(lapTime) / 100
const minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0')
const seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0')
return `+${minutes == "00" ? "" : minutes + ":"}${seconds}.${_10ms}`
}
}
}
}
}
RowLayout {
id: controlButtons
anchors {
horizontalCenter: parent.horizontalCenter
bottom: parent.bottom
bottomMargin: 6
}
spacing: 4
RippleButton {
Layout.preferredHeight: 35
Layout.preferredWidth: 90
font.pixelSize: Appearance.font.pixelSize.larger
onClicked: {
TimerService.toggleStopwatch()
}
colBackground: TimerService.stopwatchRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary
colBackgroundHover: TimerService.stopwatchRunning ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colPrimaryHover
colRipple: TimerService.stopwatchRunning ? Appearance.colors.colSecondaryContainerActive : Appearance.colors.colPrimaryActive
contentItem: StyledText {
horizontalAlignment: Text.AlignHCenter
color: TimerService.stopwatchRunning ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary
text: TimerService.stopwatchRunning ? Translation.tr("Pause") : TimerService.stopwatchTime === 0 ? Translation.tr("Start") : Translation.tr("Resume")
}
}
RippleButton {
implicitHeight: 35
implicitWidth: 90
font.pixelSize: Appearance.font.pixelSize.larger
onClicked: {
if (TimerService.stopwatchRunning)
TimerService.stopwatchRecordLap()
else
TimerService.stopwatchReset()
}
enabled: TimerService.stopwatchTime > 0 || Persistent.states.timer.stopwatch.laps.length > 0
colBackground: TimerService.stopwatchRunning ? Appearance.colors.colLayer2 : Appearance.colors.colErrorContainer
colBackgroundHover: TimerService.stopwatchRunning ? Appearance.colors.colLayer2Hover : Appearance.colors.colErrorContainerHover
colRipple: TimerService.stopwatchRunning ? Appearance.colors.colLayer2Active : Appearance.colors.colErrorContainerActive
contentItem: StyledText {
horizontalAlignment: Text.AlignHCenter
text: TimerService.stopwatchRunning ? Translation.tr("Lap") : Translation.tr("Reset")
color: TimerService.stopwatchRunning ? Appearance.colors.colOnLayer2 : Appearance.colors.colOnErrorContainer
}
}
}
}
}
@@ -1,23 +1,23 @@
import qs.modules.common.widgets
import qs
import qs.services
import QtQuick
import Quickshell.Io
import Quickshell
import Quickshell.Hyprland
QuickToggleButton {
id: root
toggled: false
visible: false
toggled: EasyEffects.active
visible: EasyEffects.available
buttonIcon: "instant_mix"
Component.onCompleted: {
EasyEffects.fetchActiveState()
}
onClicked: {
if (toggled) {
root.toggled = false
Quickshell.execDetached(["pkill", "easyeffects"])
} else {
root.toggled = true
Quickshell.execDetached(["easyeffects", "--gapplication-service"])
}
EasyEffects.toggle()
}
altAction: () => {
@@ -25,24 +25,6 @@ QuickToggleButton {
GlobalStates.sidebarRightOpen = false
}
Process {
id: fetchAvailability
running: true
command: ["bash", "-c", "command -v easyeffects"]
onExited: (exitCode, exitStatus) => {
root.visible = exitCode === 0
}
}
Process {
id: fetchActiveState
running: true
command: ["pidof", "easyeffects"]
onExited: (exitCode, exitStatus) => {
root.toggled = exitCode === 0
}
}
StyledToolTip {
content: Translation.tr("EasyEffects | Right-click to configure")
}
@@ -6,15 +6,17 @@ import QtQuick
import QtQuick.Layouts
import Quickshell.Services.Pipewire
GroupButton {
RippleButton {
id: button
required property bool input
buttonRadius: Appearance.rounding.small
colBackground: Appearance.colors.colLayer2
colBackgroundHover: Appearance.colors.colLayer2Hover
colBackgroundActive: Appearance.colors.colLayer2Active
clickedWidth: baseWidth + 30
colRipple: Appearance.colors.colLayer2Active
implicitHeight: contentItem.implicitHeight + 6 * 2
implicitWidth: contentItem.implicitWidth + 6 * 2
contentItem: RowLayout {
anchors.fill: parent
@@ -99,19 +99,13 @@ Item {
}
}
// Separator
Rectangle {
color: Appearance.m3colors.m3outlineVariant
implicitHeight: 1
Layout.fillWidth: true
}
// Device selector
ButtonGroup {
RowLayout {
id: deviceSelectorRowLayout
Layout.fillWidth: true
Layout.fillHeight: false
uniformCellSizes: true
AudioDeviceSelectorButton {
Layout.fillWidth: true
input: false
@@ -0,0 +1,61 @@
import qs.modules.common
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Services.Pipewire
pragma Singleton
pragma ComponentBehavior: Bound
/**
* Handles EasyEffects active state and presets.
*/
Singleton {
id: root
property bool available: false
property bool active: false
function fetchAvailability() {
fetchAvailabilityProc.running = true
}
function fetchActiveState() {
fetchActiveStateProc.running = true
}
function disable() {
root.active = false
Quickshell.execDetached(["pkill", "easyeffects"])
}
function enable() {
root.active = true
Quickshell.execDetached(["easyeffects", "--gapplication-service"])
}
function toggle() {
if (root.active) {
root.disable()
} else {
root.enable()
}
}
Process {
id: fetchAvailabilityProc
running: true
command: ["bash", "-c", "command -v easyeffects"]
onExited: (exitCode, exitStatus) => {
root.available = exitCode === 0
}
}
Process {
id: fetchActiveStateProc
running: true
command: ["pidof", "easyeffects"]
onExited: (exitCode, exitStatus) => {
root.active = exitCode === 0
}
}
}
@@ -69,7 +69,7 @@ Singleton {
Process {
id: getClients
command: ["bash", "-c", "hyprctl clients -j"]
command: ["hyprctl", "clients", "-j"]
stdout: StdioCollector {
id: clientsCollector
onStreamFinished: {
@@ -87,7 +87,7 @@ Singleton {
Process {
id: getMonitors
command: ["bash", "-c", "hyprctl monitors -j"]
command: ["hyprctl", "monitors", "-j"]
stdout: StdioCollector {
id: monitorsCollector
onStreamFinished: {
@@ -98,7 +98,7 @@ Singleton {
Process {
id: getLayers
command: ["bash", "-c", "hyprctl layers -j"]
command: ["hyprctl", "layers", "-j"]
stdout: StdioCollector {
id: layersCollector
onStreamFinished: {
@@ -109,7 +109,7 @@ Singleton {
Process {
id: getWorkspaces
command: ["bash", "-c", "hyprctl workspaces -j"]
command: ["hyprctl", "workspaces", "-j"]
stdout: StdioCollector {
id: workspacesCollector
onStreamFinished: {
@@ -127,7 +127,7 @@ Singleton {
Process {
id: getActiveWorkspace
command: ["bash", "-c", "hyprctl activeworkspace -j"]
command: ["hyprctl", "activeworkspace", "-j"]
stdout: StdioCollector {
id: activeWorkspaceCollector
onStreamFinished: {
@@ -0,0 +1,142 @@
pragma Singleton
pragma ComponentBehavior: Bound
import qs
import qs.modules.common
import Quickshell
import Quickshell.Io
import QtQuick
/**
* Simple Pomodoro time manager.
*/
Singleton {
id: root
property int focusTime: Config.options.time.pomodoro.focus
property int breakTime: Config.options.time.pomodoro.breakTime
property int longBreakTime: Config.options.time.pomodoro.longBreak
property int cyclesBeforeLongBreak: Config.options.time.pomodoro.cyclesBeforeLongBreak
property string alertSound: Config.options.time.pomodoro.alertSound
property bool pomodoroRunning: Persistent.states.timer.pomodoro.running
property bool pomodoroBreak: Persistent.states.timer.pomodoro.isBreak
property bool pomodoroLongBreak: Persistent.states.timer.pomodoro.isBreak && (pomodoroCycle + 1 == cyclesBeforeLongBreak);
property int pomodoroLapDuration: pomodoroLongBreak ? longBreakTime : pomodoroBreak ? breakTime : focusTime // This is a binding that's to be kept
property int pomodoroSecondsLeft: pomodoroLapDuration // Reasonable init value, to be changed
property int pomodoroCycle: Persistent.states.timer.pomodoro.cycle
property bool stopwatchRunning: Persistent.states.timer.stopwatch.running
property int stopwatchTime: 0
property int stopwatchStart: Persistent.states.timer.stopwatch.start
property var stopwatchLaps: Persistent.states.timer.stopwatch.laps
// General
Component.onCompleted: {
if (!stopwatchRunning)
stopwatchReset();
}
function getCurrentTimeInSeconds() { // Pomodoro uses Seconds
return Math.floor(Date.now() / 1000);
}
function getCurrentTimeIn10ms() { // Stopwatch uses 10ms
return Math.floor(Date.now() / 10);
}
// Pomodoro
function refreshPomodoro() {
// Work <-> break ?
if (getCurrentTimeInSeconds() >= Persistent.states.timer.pomodoro.start + pomodoroLapDuration) {
// Reset counts
Persistent.states.timer.pomodoro.isBreak = !Persistent.states.timer.pomodoro.isBreak;
Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds();
// Send notification
let notificationMessage;
if (Persistent.states.timer.pomodoro.isBreak && (pomodoroCycle + 1 == cyclesBeforeLongBreak)) {
notificationMessage = Translation.tr(`🌿 Long break: %1 minutes`).arg(Math.floor(longBreakTime / 60));
} else if (Persistent.states.timer.pomodoro.isBreak) {
notificationMessage = Translation.tr(` Break: %1 minutes`).arg(Math.floor(breakTime / 60));
} else {
notificationMessage = Translation.tr(`🔴 Focus: %1 minutes`).arg(Math.floor(focusTime / 60));
}
Quickshell.execDetached(["notify-send", "Pomodoro", notificationMessage, "-a", "Shell"]);
if (alertSound)
Quickshell.execDetached(["ffplay", "-nodisp", "-autoexit", alertSound]);
if (!pomodoroBreak) {
Persistent.states.timer.pomodoro.cycle = (Persistent.states.timer.pomodoro.cycle + 1) % root.cyclesBeforeLongBreak;
}
}
pomodoroSecondsLeft = pomodoroLapDuration - (getCurrentTimeInSeconds() - Persistent.states.timer.pomodoro.start);
}
Timer {
id: pomodoroTimer
interval: 200
running: root.pomodoroRunning
repeat: true
onTriggered: refreshPomodoro()
}
function togglePomodoro() {
Persistent.states.timer.pomodoro.running = !pomodoroRunning;
if (Persistent.states.timer.pomodoro.running) {
// Start/Resume
Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds() + pomodoroSecondsLeft - pomodoroLapDuration;
}
}
function resetPomodoro() {
Persistent.states.timer.pomodoro.running = false;
Persistent.states.timer.pomodoro.isBreak = false;
Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds();
Persistent.states.timer.pomodoro.cycle = 0;
refreshPomodoro();
}
// Stopwatch
function refreshStopwatch() { // Stopwatch stores time in 10ms
stopwatchTime = getCurrentTimeIn10ms() - stopwatchStart;
}
Timer {
id: stopwatchTimer
interval: 10
running: root.stopwatchRunning
repeat: true
onTriggered: refreshStopwatch()
}
function toggleStopwatch() {
if (root.stopwatchRunning)
stopwatchPause();
else
stopwatchResume();
}
function stopwatchPause() {
Persistent.states.timer.stopwatch.running = false;
}
function stopwatchResume() {
if (stopwatchTime === 0) Persistent.states.timer.stopwatch.laps = [];
Persistent.states.timer.stopwatch.running = true;
Persistent.states.timer.stopwatch.start = getCurrentTimeIn10ms() - stopwatchTime;
}
function stopwatchReset() {
stopwatchTime = 0;
Persistent.states.timer.stopwatch.laps = [];
Persistent.states.timer.stopwatch.running = false;
}
function stopwatchRecordLap() {
Persistent.states.timer.stopwatch.laps.push(stopwatchTime);
}
}
@@ -100,6 +100,17 @@ ApiStrategy {
message.content += newContent;
message.rawContent += newContent;
// Usage metadata
if (dataJson.usage) {
return {
tokenUsage: {
input: dataJson.usage.prompt_tokens ?? -1,
output: dataJson.usage.completion_tokens ?? -1,
total: dataJson.usage.total_tokens ?? -1
}
};
}
if (`dataJson`.done) {
return { finished: true };
}
@@ -72,6 +72,17 @@ ApiStrategy {
message.content += newContent;
message.rawContent += newContent;
// Usage metadata
if (dataJson.usage) {
return {
tokenUsage: {
input: dataJson.usage.prompt_tokens ?? -1,
output: dataJson.usage.completion_tokens ?? -1,
total: dataJson.usage.total_tokens ?? -1
}
};
}
if (dataJson.done) {
return { finished: true };
}
+100 -43
View File
@@ -26,16 +26,24 @@ ApplicationWindow {
property real contentPadding: 8
property bool showNextTime: false
visible: true
onClosing: Qt.quit()
onClosing: {
Quickshell.execDetached([
"notify-send",
Translation.tr("Welcome app"),
Translation.tr("Enjoy! You can reopen the welcome app any time with <tt>Super+Shift+Alt+/</tt>. To open the settings app, hit <tt>Super+I</tt>"),
"-a", "Shell"
])
Qt.quit()
}
title: Translation.tr("illogical-impulse Welcome")
Component.onCompleted: {
MaterialThemeLoader.reapplyTheme()
MaterialThemeLoader.reapplyTheme();
}
minimumWidth: 600
minimumHeight: 400
width: 800
width: 900
height: 650
color: Appearance.m3colors.m3background
@@ -57,7 +65,8 @@ ApplicationWindow {
margins: contentPadding
}
Item { // Titlebar
Item {
// Titlebar
visible: Config.options?.windows.showTitlebar
Layout.fillWidth: true
implicitHeight: Math.max(welcomeText.implicitHeight, windowControlsRow.implicitHeight)
@@ -70,7 +79,7 @@ ApplicationWindow {
leftMargin: 12
}
color: Appearance.colors.colOnLayer0
text: Translation.tr("Yooooo hi there")
text: Translation.tr("Hi there! First things first...")
font.pixelSize: Appearance.font.pixelSize.title
font.family: Appearance.font.family.title
}
@@ -89,9 +98,9 @@ ApplicationWindow {
Layout.alignment: Qt.AlignVCenter
onCheckedChanged: {
if (checked) {
Quickshell.execDetached(["rm", root.firstRunFilePath])
Quickshell.execDetached(["rm", root.firstRunFilePath]);
} else {
Quickshell.execDetached(["bash", "-c", `echo '${StringUtils.shellSingleQuoteEscape(root.firstRunFileContent)}' > '${StringUtils.shellSingleQuoteEscape(root.firstRunFilePath)}'`])
Quickshell.execDetached(["bash", "-c", `echo '${StringUtils.shellSingleQuoteEscape(root.firstRunFileContent)}' > '${StringUtils.shellSingleQuoteEscape(root.firstRunFilePath)}'`]);
}
}
}
@@ -109,33 +118,63 @@ ApplicationWindow {
}
}
}
Rectangle { // Content container
Rectangle {
// Content container
color: Appearance.m3colors.m3surfaceContainerLow
radius: Appearance.rounding.windowRounding - root.contentPadding
implicitHeight: contentColumn.implicitHeight
implicitWidth: contentColumn.implicitWidth
Layout.fillWidth: true
Layout.fillHeight: true
ContentPage {
id: contentColumn
anchors.fill: parent
ContentSection {
title: Translation.tr("Bar style")
title: Translation.tr("Bar")
ConfigSelectionArray {
currentValue: Config.options.bar.cornerStyle
configOptionName: "bar.cornerStyle"
onSelected: (newValue) => {
Config.options.bar.cornerStyle = newValue; // Update local copy
ContentSubsection {
title: "Corner style"
ConfigSelectionArray {
currentValue: Config.options.bar.cornerStyle
configOptionName: "bar.cornerStyle"
onSelected: newValue => {
Config.options.bar.cornerStyle = newValue; // Update local copy
}
options: [
{
displayName: Translation.tr("Hug"),
value: 0
},
{
displayName: Translation.tr("Float"),
value: 1
},
{
displayName: Translation.tr("Plain rectangle"),
value: 2
}
]
}
}
ConfigRow {
ConfigSwitch {
text: Translation.tr("Automatically hide")
checked: Config.options.bar.autoHide.enable
onCheckedChanged: {
Config.options.bar.autoHide.enable = checked;
}
}
ConfigSwitch {
text: Translation.tr("Place at the bottom")
checked: Config.options.bar.bottom
onCheckedChanged: {
Config.options.bar.bottom = checked;
}
}
options: [
{ displayName: Translation.tr("Hug"), value: 0 },
{ displayName: Translation.tr("Float"), value: 1 },
{ displayName: Translation.tr("Plain rectangle"), value: 2 }
]
}
}
@@ -161,7 +200,7 @@ ApplicationWindow {
materialIcon: "wallpaper"
mainText: konachanWallProc.running ? Translation.tr("Be patient...") : Translation.tr("Random: Konachan")
onClicked: {
console.log(konachanWallProc.command.join(" "))
console.log(konachanWallProc.command.join(" "));
konachanWallProc.running = true;
}
StyledToolTip {
@@ -174,7 +213,7 @@ ApplicationWindow {
content: Translation.tr("Pick wallpaper image on your system")
}
onClicked: {
Quickshell.execDetached([`${Directories.wallpaperSwitchScriptPath}`])
Quickshell.execDetached([`${Directories.wallpaperSwitchScriptPath}`]);
}
mainContentComponent: Component {
RowLayout {
@@ -217,38 +256,56 @@ ApplicationWindow {
title: Translation.tr("Policies")
ConfigRow {
ColumnLayout { // Weeb policy
ContentSubsectionLabel {
text: Translation.tr("Weeb")
}
Layout.fillWidth: true
ContentSubsection {
title: "Weeb"
ConfigSelectionArray {
currentValue: Config.options.policies.weeb
configOptionName: "policies.weeb"
onSelected: (newValue) => {
onSelected: newValue => {
Config.options.policies.weeb = newValue;
}
options: [
{ displayName: Translation.tr("No"), value: 0 },
{ displayName: Translation.tr("Yes"), value: 1 },
{ displayName: Translation.tr("Closet"), value: 2 }
{
displayName: Translation.tr("No"),
value: 0
},
{
displayName: Translation.tr("Yes"),
value: 1
},
{
displayName: Translation.tr("Closet"),
value: 2
}
]
}
}
ColumnLayout { // AI policy
ContentSubsectionLabel {
text: Translation.tr("AI")
}
ContentSubsection {
title: "AI"
ConfigSelectionArray {
currentValue: Config.options.policies.ai
configOptionName: "policies.ai"
onSelected: (newValue) => {
onSelected: newValue => {
Config.options.policies.ai = newValue;
}
options: [
{ displayName: Translation.tr("No"), value: 0 },
{ displayName: Translation.tr("Yes"), value: 1 },
{ displayName: Translation.tr("Local only"), value: 2 }
{
displayName: Translation.tr("No"),
value: 0
},
{
displayName: Translation.tr("Yes"),
value: 1
},
{
displayName: Translation.tr("Local only"),
value: 2
}
]
}
}
@@ -265,7 +322,7 @@ ApplicationWindow {
RippleButtonWithIcon {
materialIcon: "keyboard_alt"
onClicked: {
Quickshell.execDetached(["qs", "-p", Quickshell.shellPath(""), "ipc", "call", "cheatsheet", "toggle"])
Quickshell.execDetached(["qs", "-p", Quickshell.shellPath(""), "ipc", "call", "cheatsheet", "toggle"]);
}
mainContentComponent: Component {
RowLayout {
@@ -296,14 +353,14 @@ ApplicationWindow {
materialIcon: "help"
mainText: Translation.tr("Usage")
onClicked: {
Qt.openUrlExternally("https://end-4.github.io/dots-hyprland-wiki/en/ii-qs/02usage/")
Qt.openUrlExternally("https://end-4.github.io/dots-hyprland-wiki/en/ii-qs/02usage/");
}
}
RippleButtonWithIcon {
materialIcon: "construction"
mainText: Translation.tr("Configuration")
onClicked: {
Qt.openUrlExternally("https://end-4.github.io/dots-hyprland-wiki/en/ii-qs/03config/")
Qt.openUrlExternally("https://end-4.github.io/dots-hyprland-wiki/en/ii-qs/03config/");
}
}
}
@@ -320,14 +377,14 @@ ApplicationWindow {
nerdIcon: "󰊤"
mainText: Translation.tr("GitHub")
onClicked: {
Qt.openUrlExternally("https://github.com/end-4/dots-hyprland")
Qt.openUrlExternally("https://github.com/end-4/dots-hyprland");
}
}
RippleButtonWithIcon {
materialIcon: "favorite"
mainText: "Funny number"
onClicked: {
Qt.openUrlExternally("https://github.com/sponsors/end-4")
Qt.openUrlExternally("https://github.com/sponsors/end-4");
}
}
}
+44 -10
View File
@@ -26,7 +26,6 @@
"Bluetooth": "蓝牙",
"Brightness": "亮度",
"Cancel": "取消",
"Chain of Thought": "思维链",
"Cheat sheet": "快捷键表",
"Choose model": "选择模型",
"Clean stuff | Excellent quality, no NSFW": "清洁内容 | 优秀质量,无 NSFW",
@@ -65,7 +64,6 @@
"Logout": "注销",
"Markdown test": "Markdown 测试",
"Math result": "数学结果",
"Night Light": "护眼模式",
"No API key set for %1": "未为 %1 设置 API 密钥",
"No audio source": "无音频源",
"No media": "无媒体",
@@ -112,7 +110,6 @@
"Unknown Artist": "未知艺术家",
"Unknown Title": "未知标题",
"Unknown function call: %1": "未知函数调用:%1",
"Uptime: %1": "运行时间:%1",
"View Markdown source": "查看 Markdown 源码",
"Volume": "音量",
"Volume mixer": "音量混合器",
@@ -123,20 +120,15 @@
"%1 does not require an API key": "%1 不需要 API 密钥",
"%1 queries pending": "%1 个查询等待中",
"%1 | Right-click to configure": "%1 | 右键点击进行配置",
"Set with /mode PROVIDER": "使用 /mode PROVIDER 设置",
"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 将侧边栏分离为窗口",
"The current API used. Endpoint:": "当前使用的 API。端点:",
"Provider set to": "提供商设置为",
"Invalid model. Supported: \n```": "无效模型。支持的:\n```",
"Switched to search mode. Continue with the user's request.": "已切换到搜索模式。继续处理用户请求。",
"Experimental | Online | Google's model\nCan do a little more but doesn't search quickly": "实验性 | 在线 | Google 模型\n功能更多但搜索速度较慢",
"To set an API key, pass it with the command\n\nTo view the key, pass \"get\" with the command<br/>\n\n### For %1:\n\n**Link**: %2\n\n%3": "要设置 API 密钥,请将其与命令一起传递\n\n要查看密钥,请将 \"get\" 与命令一起传递<br/>\n\n### 对于 %1\n\n**链接**%2\n\n%3",
"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- 如果没有想到标签,请输入页码",
"Online | Google's model\nGives up-to-date information with search.": "在线 | Google 模型\n通过搜索提供最新信息。",
"Settings": "设置",
"Save chat": "保存对话",
"Load chat": "加载对话",
@@ -236,7 +228,6 @@
"Weather": "天气",
"Pinned on startup": "启动时固定",
"Tip: Hide icons and always show numbers for\nthe classic illogical-impulse experience": "提示:隐藏图标并始终显示数字以获得经典体验",
"Appearance": "外观",
"Always show numbers": "总是显示数字",
"Buttons": "按钮",
"Keyboard toggle": "键盘切换",
@@ -334,5 +325,48 @@
"High": "高",
"Medium": "中",
"Low": "低",
"System Resource": "系统资源"
"System Resource": "系统资源",
"Tint icons": "图标着色",
"Performance Profile toggle": "性能配置文件切换",
"**Instructions**: Log into Mistral account, go to Keys on the sidebar, click Create new key": "**说明**:登录 Mistral 账户,在侧边栏中选择 Keys,点击创建新密钥",
"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": "您的包管理器正在运行",
"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": "取决于工作区",
"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如果需要,会额外执行一次切换到搜索模式",
"Overall appearance": "整体外观",
"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 最先进的多用途模型,在编程和复杂推理任务方面表现卓越。",
"When enabled keeps the content of the right sidebar loaded to reduce the delay when opening,\nat the cost of around 15MB of consistent RAM usage. Delay significance depends on your system's performance.\nUsing a different kernel might help with this delay": "启用时会保持右侧边栏内容加载以减少打开延迟,\n代价是持续使用约 15MB 内存。延迟程度取决于您的系统性能。\n使用不同的内核可能有助于减少延迟",
"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<br/>\n\n### For %1:\n\n**Link**: %2\n\n%3": "要设置 API 密钥,请使用 %4 命令传递\n\n要查看密钥,请在命令中传递 \"get\"<br/>\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",
"Disable tools": "禁用工具",
"API key is set\nChange with /key YOUR_API_KEY": "API 密钥已设置\n使用 /key YOUR_API_KEY 更改",
"Usage: %1load CHAT_NAME": "用法:%1load 聊天名称",
"Sidebars": "侧边栏",
"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 | 右键配置",
"Command rejected by user": "用户拒绝了命令",
"Invalid tool. Supported tools:\n- %1": "无效工具。支持的工具:\n- %1",
"Keep right sidebar loaded": "保持右侧边栏加载",
"Reject": "拒绝"
}