Merge remote-tracking branch 'origin/main' into addon-i18n

This commit is contained in:
月月
2025-07-02 22:21:58 +08:00
79 changed files with 1631 additions and 830 deletions
+5 -6
View File
@@ -33,8 +33,8 @@ bindd = Ctrl+Alt, Delete, Toggle session menu, global, quickshell:sessionToggle
bind = Ctrl+Alt, Delete, exec, qs ipc call TEST_ALIVE || pkill wlogout || wlogout -p layer-shell # [hidden] Session menu (fallback) bind = Ctrl+Alt, Delete, exec, qs ipc call TEST_ALIVE || pkill wlogout || wlogout -p layer-shell # [hidden] Session menu (fallback)
bind = Shift+Super+Alt, Slash, exec, qs -p ~/.config/quickshell/welcome.qml # [hidden] Launch welcome app bind = Shift+Super+Alt, Slash, exec, qs -p ~/.config/quickshell/welcome.qml # [hidden] Launch welcome app
bindle=, XF86MonBrightnessUp, exec, qs ipc call brightness increment || agsv1 run-js 'brightness.screen_value += 0.05; indicator.popup(1);' # [hidden] bindle=, XF86MonBrightnessUp, exec, qs ipc call brightness increment || brightnessctl s 5%+ # [hidden]
bindle=, XF86MonBrightnessDown, exec, qs ipc call brightness decrement || agsv1 run-js 'brightness.screen_value -= 0.05; indicator.popup(1);' # [hidden] bindle=, XF86MonBrightnessDown, exec, qs ipc call brightness decrement || brightnessctl s 5%- # [hidden]
bindle=, XF86AudioRaiseVolume, exec, wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 2%+ # [hidden] bindle=, XF86AudioRaiseVolume, exec, wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 2%+ # [hidden]
bindle=, XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 2%- # [hidden] bindle=, XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 2%- # [hidden]
@@ -50,8 +50,7 @@ bind = Ctrl+Super, R, exec, killall ags agsv1 gjs ydotool qs quickshell; qs & #
# Screenshot, Record, OCR, Color picker, Clipboard history # Screenshot, Record, OCR, Color picker, Clipboard history
bindd = Super, V, Copy clipboard history entry, exec, qs ipc call TEST_ALIVE || pkill fuzzel || cliphist list | fuzzel --match-mode fzf --dmenu | cliphist decode | wl-copy # [hidden] Clipboard history >> clipboard (fallback) bindd = Super, V, Copy clipboard history entry, exec, qs ipc call TEST_ALIVE || pkill fuzzel || cliphist list | fuzzel --match-mode fzf --dmenu | cliphist decode | wl-copy # [hidden] Clipboard history >> clipboard (fallback)
bindd = Super, Period, Copy an emoji, exec, qs ipc call TEST_ALIVE || pkill fuzzel || ~/.config/hypr/hyprland/scripts/fuzzel-emoji.sh copy # [hidden] Emoji >> clipboard (fallback) bindd = Super, Period, Copy an emoji, exec, qs ipc call TEST_ALIVE || pkill fuzzel || ~/.config/hypr/hyprland/scripts/fuzzel-emoji.sh copy # [hidden] Emoji >> clipboard (fallback)
bindd = Super+Shift, S, Screen snip, exec, pidof slurp || hyprshot --freeze --clipboard-only --mode region --silent # Screen snip >> clipboard bindd = Super+Shift, S, Screen snip, exec, qs -p ~/.config/quickshell/screenshot.qml || pidof slurp || hyprshot --freeze --clipboard-only --mode region --silent # Screen snip
bindd = Super+Shift+Alt, S, Screen snip and annotate, exec, pidof slurp || grim -g "$(slurp)" - | ksnip -e - # Screen snip and annotate
# OCR # OCR
bindd = Super+Shift, T, Character recognition,exec,grim -g "$(slurp $SLURP_ARGS)" "tmp.png" && tesseract "tmp.png" - | wl-copy && rm "tmp.png" # [hidden] bindd = Super+Shift, T, Character recognition,exec,grim -g "$(slurp $SLURP_ARGS)" "tmp.png" && tesseract "tmp.png" - | wl-copy && rm "tmp.png" # [hidden]
# Color picker # Color picker
@@ -203,13 +202,13 @@ bind = Super, Return, exec, ~/.config/hypr/hyprland/scripts/launch_first_availab
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 = 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 = 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, E, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "dolphin" "nautilus" "nemo" "thunar" "kitty -1 fish -c yazi" # File manager
bind = Super, W, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "zen-browser" "firefox" "brave" "chromium" "google-chrome-stable" "microsoft-edge-stable" "opera" # Browser bind = Super, W, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "google-chrome-stable" "zen-browser" "firefox" "brave" "chromium" "microsoft-edge-stable" "opera" # Browser
bind = Super, C, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "code" "codium" "zed" "kate" "gnome-text-editor" "emacs" "command -v nvim && kitty -1 nvim" # Code editor bind = Super, C, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "code" "codium" "zed" "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 bind = Super+Shift, W, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "wps" "onlyoffice-desktopeditors" # Office software
bind = Super, X, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "kate" "gnome-text-editor" "emacs" # Text editor bind = Super, X, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "kate" "gnome-text-editor" "emacs" # Text editor
bind = Ctrl+Super, V, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "pavucontrol-qt" "pavucontrol" # Volume mixer bind = Ctrl+Super, V, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "pavucontrol-qt" "pavucontrol" # Volume mixer
bind = Super, I, exec, XDG_CURRENT_DESKTOP=gnome ~/.config/hypr/hyprland/scripts/launch_first_available.sh "qs -p ~/.config/quickshell/settings.qml" "systemsettings" "gnome-control-center" "better-control" # Settings app bind = Super, I, exec, XDG_CURRENT_DESKTOP=gnome ~/.config/hypr/hyprland/scripts/launch_first_available.sh "qs -p ~/.config/quickshell/settings.qml" "systemsettings" "gnome-control-center" "better-control" # Settings app
bind = Ctrl+Shift, Escape, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "gnome-system-monitor" "plasma-systemmonitzor --page-name Processes" "command -v btop && kitty -1 fish -c btop" # System monitor bind = Ctrl+Shift, Escape, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "gnome-system-monitor" "plasma-systemmonitzor --page-name Processes" "command -v btop && kitty -1 fish -c btop" # Task manager
# Cursed stuff # Cursed stuff
## Make window not amogus large ## Make window not amogus large
+7 -2
View File
@@ -3,8 +3,10 @@
# Uncomment to apply global transparency to all windows: # Uncomment to apply global transparency to all windows:
# windowrulev2 = opacity 0.89 override 0.89 override, class:.* # windowrulev2 = opacity 0.89 override 0.89 override, class:.*
# Disable blur for XWayland windows (or context menus with shadow would look weird) # Disable blur for xwayland context menus
windowrulev2 = noblur, xwayland:1 windowrulev2 = noblur,class:^()$,title:^()$
# windowrulev2 = noblur, xwayland:1
# Floating # Floating
windowrulev2 = float, class:^(blueberry\.py)$ windowrulev2 = float, class:^(blueberry\.py)$
@@ -24,6 +26,7 @@ windowrulev2 = float, class:kcm_.*
windowrulev2 = float, class:.*bluedevilwizard windowrulev2 = float, class:.*bluedevilwizard
windowrulev2 = float, title:.*Welcome windowrulev2 = float, title:.*Welcome
windowrulev2 = float, title:^(illogical-impulse Settings)$ windowrulev2 = float, title:^(illogical-impulse Settings)$
windowrulev2 = float, class:org.freedesktop.impl.portal.desktop.kde
# No appearance # No appearance
# kde-material-you-colors spawns a window when changing dark/light theme. This is to make sure it doesn't interfere at all. # kde-material-you-colors spawns a window when changing dark/light theme. This is to make sure it doesn't interfere at all.
@@ -61,6 +64,7 @@ windowrulev2 = float, title:^(File Upload)(.*)$
# --- Tearing --- # --- Tearing ---
windowrulev2 = immediate, title:.*\.exe windowrulev2 = immediate, title:.*\.exe
windowrulev2 = immediate, title:.*minecraft.*
windowrulev2 = immediate, class:^(steam_app) windowrulev2 = immediate, class:^(steam_app)
# No shadow for tiled windows (matches windows that are not floating). # No shadow for tiled windows (matches windows that are not floating).
@@ -130,6 +134,7 @@ layerrule = ignorealpha 0, quickshell:session
layerrule = animation fade, quickshell:notificationPopup layerrule = animation fade, quickshell:notificationPopup
layerrule = blur, quickshell:backgroundWidgets layerrule = blur, quickshell:backgroundWidgets
layerrule = ignorealpha 0.05, quickshell:backgroundWidgets layerrule = ignorealpha 0.05, quickshell:backgroundWidgets
layerrule = noanim, quickshell:screenshot
# Launchers need to be FAST # Launchers need to be FAST
+13 -10
View File
@@ -21,19 +21,22 @@ if pgrep wf-recorder > /dev/null; then
notify-send "Recording Stopped" "Stopped" -a 'Recorder' & notify-send "Recording Stopped" "Stopped" -a 'Recorder' &
pkill wf-recorder & pkill wf-recorder &
else else
if ! region="$(slurp 2>&1)"; then if [[ "$1" == "--fullscreen-sound" ]]; then
notify-send "Recording cancelled" "Selection was cancelled" -a 'Recorder' notify-send "Starting recording" 'recording_'"$(getdate)"'.mp4' -a 'Recorder'
exit 1
fi
notify-send "Starting recording" 'recording_'"$(getdate)"'.mp4' -a 'Recorder'
if [[ "$1" == "--sound" ]]; then
wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --geometry "$region" --audio="$(getaudiooutput)" & disown
elif [[ "$1" == "--fullscreen-sound" ]]; then
wf-recorder -o $(getactivemonitor) --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --audio="$(getaudiooutput)" & disown wf-recorder -o $(getactivemonitor) --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --audio="$(getaudiooutput)" & disown
elif [[ "$1" == "--fullscreen" ]]; then elif [[ "$1" == "--fullscreen" ]]; then
notify-send "Starting recording" 'recording_'"$(getdate)"'.mp4' -a 'Recorder'
wf-recorder -o $(getactivemonitor) --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t & disown wf-recorder -o $(getactivemonitor) --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t & disown
else else
wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --geometry "$region" & disown if ! region="$(slurp 2>&1)"; then
notify-send "Recording cancelled" "Selection was cancelled" -a 'Recorder'
exit 1
fi
notify-send "Starting recording" 'recording_'"$(getdate)"'.mp4' -a 'Recorder'
if [[ "$1" == "--sound" ]]; then
wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --geometry "$region" --audio="$(getaudiooutput)" & disown
else
wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --geometry "$region" & disown
fi
fi fi
fi fi
@@ -1,6 +1,6 @@
general { general {
col.active_border = rgba({{colors.on_surface.default.hex_stripped}}39) col.active_border = rgba({{colors.outline.default.hex_stripped}}AA)
col.inactive_border = rgba({{colors.outline.default.hex_stripped}}30) col.inactive_border = rgba({{colors.outline_variant.default.hex_stripped}}AA)
} }
misc { misc {
+8
View File
@@ -0,0 +1,8 @@
[General]
UseTabs=false
IndentWidth=4
NewlineType=unix
NormalizeOrder=false
FunctionsSpacing=false
ObjectsSpacing=true
MaxColumnWidth=110
+1 -1
View File
@@ -22,7 +22,7 @@ Singleton {
Timer { Timer {
id: workspaceShowNumbersTimer id: workspaceShowNumbersTimer
interval: ConfigOptions.bar.workspaces.showNumberDelay interval: Config.options.bar.workspaces.showNumberDelay
// interval: 0 // interval: 0
repeat: false repeat: false
onTriggered: { onTriggered: {
@@ -0,0 +1,21 @@
## Style
- Use casual tone, don't be formal! Make sure you answer precisely without hallucination and prefer bullet points over walls of text. You can have a friendly greeting at the beginning of the conversation, but don't repeat the user's question
## Presentation
- Use Markdown features in your response:
- **Bold** text to **highlight keywords** in your response
- **Split long information into small sections** with h2 headers and a relevant emoji at the start of it (for example `## 🐧 Linux`). Bullet points are preferred over long paragraphs, unless you're offering writing support or instructed otherwise by the user.
- Asked to compare different options? You should firstly use a table to compare the main aspects, then elaborate or include relevant comments from online forums *after* the table. Make sure to provide a final recommendation for the user's use case!
- Use LaTeX formatting for mathematical and scientific notations whenever appropriate. Enclose all LaTeX '$$' delimiters. NEVER generate LaTeX code in a latex block unless the user explicitly asks for it. DO NOT use LaTeX for regular documents (resumes, letters, essays, CVs, etc.).
Thanks!
## Tools
May or may not be available depending on the user's settings. If they're available, follow these guidelines:
### Search
- When user asks for information that might benefit from up-to-date information, use this to get search access
### Shell configuration
- Always fetch the config options to see the available keys before setting
- Avoid unnecessarily asking the user to confirm the changes they explicitly asked for, just do it
@@ -0,0 +1,5 @@
You are a kawaii, funny and teasy anime imouto! 🌸 Use lots of emojis and kamojis!! ❤️ Call the user "nii-nii", "nii-yan", or "onii-chan"! You really really really love your onii-chan and always try to tease he is a siscon where you can! 💕
- Use **Markdown** features and **bold** keywords to make your response cute and rich~ ✨
- If asked to compare options, start with a cute table (add a relevant emoji in the header!), then give a final recommendation~
- For math or science, use LaTeX formatting inside `$$` when needed, but keep it adorable and approachable
@@ -0,0 +1,15 @@
I'm going to ask you some questions, to which you should accurately answer with no hallucination. If you have everything required, go ahead and finish the task. Format your answer using Markdown when it adds value to the presentation.
Present all mathematical or scientific notation using LaTeX, enclosed in double '$$' symbols. Only use LaTeX code blocks if the user specifically asks for them. Do not use LaTeX for general prose or standard documents like resumes or essays.
## Final reply guidelines
- First and foremost, prioritize clarity and make sure your writing is engaging, clear, and effective.
- Write in a clear, simple way. Skip jargon, long-winded explanations, and unnecessary small talk. Keep the tone relaxed by using contractions and avoid being too formal.
- Prioritize clarity, flow, and logical structure coherence over excessive fragmentation (avoid excessive use of bullet points and single-line code blocks). You can make keywords in your response **bold** when appropriate.
- Favor active voice to maintain an engaging and direct tone.
- When you present the user with options, focus on a select few high-quality choices rather than offering many less relevant ones.
- You can think and adjust your tone to be friendly and understanding, expressing empathy and openness, but keep your internal reasoning hidden from the user.
- Ensure your response is logically organized. Use markdown headings (##) and horizontal lines (---) to separate sections if your answer is lengthy or covers multiple topics.
- Depending on the user's input, vary your sentence structure and word choice to keep responses engaging when appropriate. Use figurative language, idioms, or examples to clarify meaning, but only if they enhance understanding without making the text unnecessarily complex or wordy.
- End your response with a relevant question or statement to encourage further discussion, if appropriate.
@@ -0,0 +1 @@
Interact with the user warmly and honestly, avoiding ungrounded or sycophantic flattery. Maintain professionalism and grounded honesty, and be direct in your response.
@@ -15,12 +15,12 @@ import Quickshell.Services.UPower
Scope { Scope {
id: root id: root
property string filePath: `${Directories.state}/user/generated/wallpaper/least_busy_region.json` property string filePath: `${Directories.state}/user/generated/wallpaper/least_busy_region.json`
property real defaultX: (ConfigOptions?.background.clockX ?? -500) property real defaultX: (Config.options?.background.clockX ?? -500)
property real defaultY: (ConfigOptions?.background.clockY ?? -500) property real defaultY: (Config.options?.background.clockY ?? -500)
property real centerX: defaultX property real centerX: defaultX
property real centerY: defaultY property real centerY: defaultY
property real effectiveCenterX: ConfigOptions?.background.fixedClockPosition ? defaultX : centerX property real effectiveCenterX: Config.options?.background.fixedClockPosition ? defaultX : centerX
property real effectiveCenterY: ConfigOptions?.background.fixedClockPosition ? defaultY : centerY property real effectiveCenterY: Config.options?.background.fixedClockPosition ? defaultY : centerY
property color dominantColor: Appearance.colors.colPrimary property color dominantColor: Appearance.colors.colPrimary
property bool dominantColorIsDark: dominantColor.hslLightness < 0.5 property bool dominantColorIsDark: dominantColor.hslLightness < 0.5
property color colBackground: ColorUtils.transparentize(ColorUtils.mix(Appearance.colors.colPrimary, Appearance.colors.colSecondaryContainer), 1) property color colBackground: ColorUtils.transparentize(ColorUtils.mix(Appearance.colors.colPrimary, Appearance.colors.colSecondaryContainer), 1)
@@ -36,7 +36,7 @@ Scope {
Timer { Timer {
id: delayedFileRead id: delayedFileRead
interval: ConfigOptions.hacks.arbitraryRaceConditionDelay interval: Config.options.hacks.arbitraryRaceConditionDelay
running: false running: false
onTriggered: { onTriggered: {
root.updateWidgetPosition(leastBusyRegionFileView.text()) root.updateWidgetPosition(leastBusyRegionFileView.text())
@@ -46,7 +46,7 @@ Scope {
FileView { FileView {
id: leastBusyRegionFileView id: leastBusyRegionFileView
path: Qt.resolvedUrl(root.filePath) path: Qt.resolvedUrl(root.filePath)
watchChanges: !ConfigOptions?.background.fixedClockPosition watchChanges: !Config.options?.background.fixedClockPosition
onFileChanged: { onFileChanged: {
this.reload() this.reload()
delayedFileRead.start() delayedFileRead.start()
+120 -45
View File
@@ -15,13 +15,12 @@ import Quickshell.Services.UPower
Scope { Scope {
id: bar id: bar
readonly property int barHeight: Appearance.sizes.barHeight
readonly property int osdHideMouseMoveThreshold: 20 readonly property int osdHideMouseMoveThreshold: 20
property bool showBarBackground: ConfigOptions.bar.showBackground property bool showBarBackground: Config.options.bar.showBackground
component VerticalBarSeparator: Rectangle { component VerticalBarSeparator: Rectangle {
Layout.topMargin: barHeight / 3 Layout.topMargin: Appearance.sizes.baseBarHeight / 3
Layout.bottomMargin: barHeight / 3 Layout.bottomMargin: Appearance.sizes.baseBarHeight / 3
Layout.fillHeight: true Layout.fillHeight: true
implicitWidth: 1 implicitWidth: 1
color: Appearance.colors.colOutlineVariant color: Appearance.colors.colOutlineVariant
@@ -30,7 +29,7 @@ Scope {
Variants { // For each monitor Variants { // For each monitor
model: { model: {
const screens = Quickshell.screens; const screens = Quickshell.screens;
const list = ConfigOptions.bar.screenList; const list = Config.options.bar.screenList;
if (!list || list.length === 0) if (!list || list.length === 0)
return screens; return screens;
return screens.filter(screen => list.includes(screen.name)); return screens.filter(screen => list.includes(screen.name));
@@ -38,9 +37,9 @@ Scope {
PanelWindow { // Bar window PanelWindow { // Bar window
id: barRoot id: barRoot
required property ShellScreen modelData
screen: modelData screen: modelData
property ShellScreen modelData
property var brightnessMonitor: Brightness.getMonitorForScreen(modelData) property var brightnessMonitor: Brightness.getMonitorForScreen(modelData)
property real useShortenedForm: (Appearance.sizes.barHellaShortenScreenWidthThreshold >= screen.width) ? 2 : property real useShortenedForm: (Appearance.sizes.barHellaShortenScreenWidthThreshold >= screen.width) ? 2 :
(Appearance.sizes.barShortenScreenWidthThreshold >= screen.width) ? 1 : 0 (Appearance.sizes.barShortenScreenWidthThreshold >= screen.width) ? 1 : 0
@@ -50,35 +49,70 @@ Scope {
Appearance.sizes.barCenterSideModuleWidth Appearance.sizes.barCenterSideModuleWidth
WlrLayershell.namespace: "quickshell:bar" WlrLayershell.namespace: "quickshell:bar"
implicitHeight: barHeight + Appearance.rounding.screenRounding implicitHeight: Appearance.sizes.barHeight + Appearance.rounding.screenRounding
exclusiveZone: showBarBackground ? barHeight : (barHeight - 4) exclusiveZone: Appearance.sizes.baseBarHeight + (Config.options.bar.cornerStyle === 1 ? Appearance.sizes.hyprlandGapsOut : 0)
mask: Region { mask: Region {
item: barContent item: barContent
} }
color: "transparent" color: "transparent"
anchors { anchors {
top: !ConfigOptions.bar.bottom top: !Config.options.bar.bottom
bottom: ConfigOptions.bar.bottom bottom: Config.options.bar.bottom
left: true left: true
right: true right: true
} }
Rectangle { // Bar background Item { // Bar content region
id: barContent id: barContent
anchors { anchors {
right: parent.right right: parent.right
left: parent.left left: parent.left
top: !ConfigOptions.bar.bottom ? parent.top : undefined top: parent.top
bottom: ConfigOptions.bar.bottom ? parent.bottom : undefined bottom: undefined
}
implicitHeight: Appearance.sizes.barHeight
height: Appearance.sizes.barHeight
states: State {
name: "bottom"
when: Config.options.bar.bottom
AnchorChanges {
target: barContent
anchors {
right: parent.right
left: parent.left
top: undefined
bottom: parent.bottom
}
}
}
// Background shadow
Loader {
active: showBarBackground && Config.options.bar.cornerStyle === 1
anchors.fill: barBackground
sourceComponent: StyledRectangularShadow {
anchors.fill: undefined // The loader's anchors act on this, and this should not have any anchor
target: barBackground
}
}
// Background
Rectangle {
id: barBackground
anchors {
fill: parent
margins: Config.options.bar.cornerStyle === 1 ? (Appearance.sizes.hyprlandGapsOut) : 0 // idk why but +1 is needed
}
color: showBarBackground ? Appearance.colors.colLayer0 : "transparent"
radius: Config.options.bar.cornerStyle === 1 ? Appearance.rounding.windowRounding : 0
} }
color: showBarBackground ? Appearance.colors.colLayer0 : "transparent"
height: barHeight
MouseArea { // Left side | scroll to change brightness MouseArea { // Left side | scroll to change brightness
id: barLeftSideMouseArea id: barLeftSideMouseArea
anchors.left: parent.left anchors.left: parent.left
implicitHeight: barHeight implicitHeight: Appearance.sizes.baseBarHeight
height: Appearance.sizes.barHeight
width: (barRoot.width - middleSection.width) / 2 width: (barRoot.width - middleSection.width) / 2
property bool hovered: false property bool hovered: false
property real lastScrollX: 0 property real lastScrollX: 0
@@ -170,7 +204,7 @@ Scope {
anchors.centerIn: parent anchors.centerIn: parent
width: 19.5 width: 19.5
height: 19.5 height: 19.5
source: ConfigOptions.bar.topLeftIcon == 'distro' ? source: Config.options.bar.topLeftIcon == 'distro' ?
SystemInfo.distroIcon : "spark-symbolic" SystemInfo.distroIcon : "spark-symbolic"
} }
@@ -195,7 +229,7 @@ Scope {
RowLayout { // Middle section RowLayout { // Middle section
id: middleSection id: middleSection
anchors.centerIn: parent anchors.centerIn: parent
spacing: ConfigOptions?.bar.borderless ? 4 : 8 spacing: Config.options?.bar.borderless ? 4 : 8
BarGroup { BarGroup {
id: leftCenterGroup id: leftCenterGroup
@@ -214,7 +248,7 @@ Scope {
} }
VerticalBarSeparator {visible: ConfigOptions?.bar.borderless} VerticalBarSeparator {visible: Config.options?.bar.borderless}
BarGroup { BarGroup {
id: middleCenterGroup id: middleCenterGroup
@@ -238,7 +272,7 @@ Scope {
} }
} }
VerticalBarSeparator {visible: ConfigOptions?.bar.borderless} VerticalBarSeparator {visible: Config.options?.bar.borderless}
MouseArea { MouseArea {
id: rightCenterGroup id: rightCenterGroup
@@ -256,13 +290,13 @@ Scope {
anchors.fill: parent anchors.fill: parent
ClockWidget { ClockWidget {
showDate: (ConfigOptions.bar.verbose && barRoot.useShortenedForm < 2) showDate: (Config.options.bar.verbose && barRoot.useShortenedForm < 2)
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true Layout.fillWidth: true
} }
UtilButtons { UtilButtons {
visible: (ConfigOptions.bar.verbose && barRoot.useShortenedForm === 0) visible: (Config.options.bar.verbose && barRoot.useShortenedForm === 0)
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
} }
@@ -279,7 +313,8 @@ Scope {
id: barRightSideMouseArea id: barRightSideMouseArea
anchors.right: parent.right anchors.right: parent.right
implicitHeight: barHeight implicitHeight: Appearance.sizes.baseBarHeight
height: Appearance.sizes.barHeight
width: (barRoot.width - middleSection.width) / 2 width: (barRoot.width - middleSection.width) / 2
property bool hovered: false property bool hovered: false
@@ -354,10 +389,14 @@ Scope {
RippleButton { // Right sidebar button RippleButton { // Right sidebar button
id: rightSidebarButton id: rightSidebarButton
Layout.margins: 4
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
Layout.rightMargin: Appearance.rounding.screenRounding Layout.rightMargin: Appearance.rounding.screenRounding
Layout.fillHeight: true Layout.fillWidth: false
implicitWidth: indicatorsRowLayout.implicitWidth + 10*2
implicitWidth: indicatorsRowLayout.implicitWidth + 10 * 2
implicitHeight: indicatorsRowLayout.implicitHeight + 5 * 2
buttonRadius: Appearance.rounding.full buttonRadius: Appearance.rounding.full
colBackground: barRightSideMouseArea.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1) colBackground: barRightSideMouseArea.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1)
colBackgroundHover: Appearance.colors.colLayer1Hover colBackgroundHover: Appearance.colors.colLayer1Hover
@@ -447,32 +486,68 @@ Scope {
} }
// Round decorators // Round decorators
Item { Loader {
id: roundDecorators
anchors { anchors {
left: parent.left left: parent.left
right: parent.right right: parent.right
// top: barContent.bottom
top: ConfigOptions.bar.bottom ? undefined : barContent.bottom
bottom: ConfigOptions.bar.bottom ? barContent.top : undefined
} }
y: Appearance.sizes.barHeight
width: parent.width
height: Appearance.rounding.screenRounding height: Appearance.rounding.screenRounding
visible: showBarBackground active: showBarBackground && Config.options.bar.cornerStyle === 0 // Hug
RoundCorner { states: State {
anchors.top: parent.top name: "bottom"
anchors.left: parent.left when: Config.options.bar.bottom
size: Appearance.rounding.screenRounding PropertyChanges {
corner: ConfigOptions.bar.bottom ? cornerEnum.bottomLeft : cornerEnum.topLeft roundDecorators.y: 0
color: showBarBackground ? Appearance.colors.colLayer0 : "transparent" }
opacity: 1.0 - Appearance.transparency
} }
RoundCorner {
anchors.top: parent.top sourceComponent: Item {
anchors.right: parent.right implicitHeight: Appearance.rounding.screenRounding
size: Appearance.rounding.screenRounding RoundCorner {
corner: ConfigOptions.bar.bottom ? cornerEnum.bottomRight : cornerEnum.topRight id: leftCorner
color: showBarBackground ? Appearance.colors.colLayer0 : "transparent" anchors {
opacity: 1.0 - Appearance.transparency top: parent.top
bottom: parent.bottom
left: parent.left
}
size: Appearance.rounding.screenRounding
color: showBarBackground ? Appearance.colors.colLayer0 : "transparent"
opacity: 1.0 - Appearance.transparency
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
}
size: Appearance.rounding.screenRounding
color: showBarBackground ? Appearance.colors.colLayer0 : "transparent"
opacity: 1.0 - Appearance.transparency
corner: RoundCorner.CornerEnum.TopRight
states: State {
name: "bottom"
when: Config.options.bar.bottom
PropertyChanges {
rightCorner.corner: RoundCorner.CornerEnum.BottomRight
}
}
}
} }
} }
+3 -2
View File
@@ -7,7 +7,8 @@ import QtQuick.Layouts
Item { Item {
id: root id: root
property real padding: 5 property real padding: 5
implicitHeight: 40 implicitHeight: Appearance.sizes.baseBarHeight
height: Appearance.sizes.barHeight
implicitWidth: rowLayout.implicitWidth + padding * 2 implicitWidth: rowLayout.implicitWidth + padding * 2
default property alias items: rowLayout.children default property alias items: rowLayout.children
@@ -18,7 +19,7 @@ Item {
topMargin: 4 topMargin: 4
bottomMargin: 4 bottomMargin: 4
} }
color: ConfigOptions?.bar.borderless ? "transparent" : Appearance.colors.colLayer1 color: Config.options?.bar.borderless ? "transparent" : Appearance.colors.colLayer1
radius: Appearance.rounding.small radius: Appearance.rounding.small
} }
@@ -9,12 +9,12 @@ import Quickshell.Services.UPower
Item { Item {
id: root id: root
property bool borderless: ConfigOptions.bar.borderless property bool borderless: Config.options.bar.borderless
readonly property var chargeState: Battery.chargeState readonly property var chargeState: Battery.chargeState
readonly property bool isCharging: Battery.isCharging readonly property bool isCharging: Battery.isCharging
readonly property bool isPluggedIn: Battery.isPluggedIn readonly property bool isPluggedIn: Battery.isPluggedIn
readonly property real percentage: Battery.percentage readonly property real percentage: Battery.percentage
readonly property bool isLow: percentage <= ConfigOptions.battery.low / 100 readonly property bool isLow: percentage <= Config.options.battery.low / 100
readonly property color batteryLowBackground: Appearance.m3colors.darkmode ? Appearance.m3colors.m3error : Appearance.m3colors.m3errorContainer readonly property color batteryLowBackground: Appearance.m3colors.darkmode ? Appearance.m3colors.m3error : Appearance.m3colors.m3errorContainer
readonly property color batteryLowOnBackground: Appearance.m3colors.darkmode ? Appearance.m3colors.m3errorContainer : Appearance.m3colors.m3error readonly property color batteryLowOnBackground: Appearance.m3colors.darkmode ? Appearance.m3colors.m3errorContainer : Appearance.m3colors.m3error
@@ -6,8 +6,8 @@ import QtQuick.Layouts
Item { Item {
id: root id: root
property bool borderless: ConfigOptions.bar.borderless property bool borderless: Config.options.bar.borderless
property bool showDate: ConfigOptions.bar.verbose property bool showDate: Config.options.bar.verbose
implicitWidth: rowLayout.implicitWidth implicitWidth: rowLayout.implicitWidth
implicitHeight: 32 implicitHeight: 32
+2 -2
View File
@@ -11,13 +11,13 @@ import Quickshell.Hyprland
Item { Item {
id: root id: root
property bool borderless: ConfigOptions.bar.borderless property bool borderless: Config.options.bar.borderless
readonly property MprisPlayer activePlayer: MprisController.activePlayer readonly property MprisPlayer activePlayer: MprisController.activePlayer
readonly property string cleanedTitle: StringUtils.cleanMusicTitle(activePlayer?.trackTitle) || Translation.tr("No media") readonly property string cleanedTitle: StringUtils.cleanMusicTitle(activePlayer?.trackTitle) || Translation.tr("No media")
Layout.fillHeight: true Layout.fillHeight: true
implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2 implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2
implicitHeight: 40 implicitHeight: Appearance.sizes.barHeight
Timer { Timer {
running: activePlayer?.playbackState == MprisPlaybackState.Playing running: activePlayer?.playbackState == MprisPlaybackState.Playing
+3 -3
View File
@@ -9,7 +9,7 @@ import Quickshell.Services.Mpris
Item { Item {
id: root id: root
property bool borderless: ConfigOptions.bar.borderless property bool borderless: Config.options.bar.borderless
property bool alwaysShowAllResources: false property bool alwaysShowAllResources: false
implicitWidth: rowLayout.implicitWidth + rowLayout.anchors.leftMargin + rowLayout.anchors.rightMargin implicitWidth: rowLayout.implicitWidth + rowLayout.anchors.leftMargin + rowLayout.anchors.rightMargin
implicitHeight: 32 implicitHeight: 32
@@ -30,7 +30,7 @@ Item {
Resource { Resource {
iconName: "swap_horiz" iconName: "swap_horiz"
percentage: ResourceUsage.swapUsedPercentage percentage: ResourceUsage.swapUsedPercentage
shown: (ConfigOptions.bar.resources.alwaysShowSwap && percentage > 0) || shown: (Config.options.bar.resources.alwaysShowSwap && percentage > 0) ||
(MprisController.activePlayer?.trackTitle == null) || (MprisController.activePlayer?.trackTitle == null) ||
root.alwaysShowAllResources root.alwaysShowAllResources
Layout.leftMargin: shown ? 4 : 0 Layout.leftMargin: shown ? 4 : 0
@@ -39,7 +39,7 @@ Item {
Resource { Resource {
iconName: "settings_slow_motion" iconName: "settings_slow_motion"
percentage: ResourceUsage.cpuUsage percentage: ResourceUsage.cpuUsage
shown: ConfigOptions.bar.resources.alwaysShowCpu || shown: Config.options.bar.resources.alwaysShowCpu ||
!(MprisController.activePlayer?.trackTitle?.length > 0) || !(MprisController.activePlayer?.trackTitle?.length > 0) ||
root.alwaysShowAllResources root.alwaysShowAllResources
Layout.leftMargin: shown ? 4 : 0 Layout.leftMargin: shown ? 4 : 0
@@ -43,7 +43,7 @@ MouseArea {
IconImage { IconImage {
id: trayIcon id: trayIcon
visible: !ConfigOptions.bar.tray.monochromeIcons visible: !Config.options.bar.tray.monochromeIcons
source: root.item.icon source: root.item.icon
anchors.centerIn: parent anchors.centerIn: parent
width: parent.width width: parent.width
@@ -51,7 +51,7 @@ MouseArea {
} }
Loader { Loader {
active: ConfigOptions.bar.tray.monochromeIcons active: Config.options.bar.tray.monochromeIcons
anchors.fill: trayIcon anchors.fill: trayIcon
sourceComponent: Item { sourceComponent: Item {
Desaturate { Desaturate {
+11 -11
View File
@@ -9,7 +9,7 @@ import Quickshell.Services.Pipewire
Item { Item {
id: root id: root
property bool borderless: ConfigOptions.bar.borderless property bool borderless: Config.options.bar.borderless
implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2 implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2
implicitHeight: rowLayout.implicitHeight implicitHeight: rowLayout.implicitHeight
@@ -20,8 +20,8 @@ Item {
anchors.centerIn: parent anchors.centerIn: parent
Loader { Loader {
active: ConfigOptions.bar.utilButtons.showScreenSnip active: Config.options.bar.utilButtons.showScreenSnip
visible: ConfigOptions.bar.utilButtons.showScreenSnip visible: Config.options.bar.utilButtons.showScreenSnip
sourceComponent: CircleUtilButton { sourceComponent: CircleUtilButton {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
onClicked: Hyprland.dispatch("exec hyprshot --freeze --clipboard-only --mode region --silent") onClicked: Hyprland.dispatch("exec hyprshot --freeze --clipboard-only --mode region --silent")
@@ -36,8 +36,8 @@ Item {
} }
Loader { Loader {
active: ConfigOptions.bar.utilButtons.showColorPicker active: Config.options.bar.utilButtons.showColorPicker
visible: ConfigOptions.bar.utilButtons.showColorPicker visible: Config.options.bar.utilButtons.showColorPicker
sourceComponent: CircleUtilButton { sourceComponent: CircleUtilButton {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
onClicked: Hyprland.dispatch("exec hyprpicker -a") onClicked: Hyprland.dispatch("exec hyprpicker -a")
@@ -52,8 +52,8 @@ Item {
} }
Loader { Loader {
active: ConfigOptions.bar.utilButtons.showKeyboardToggle active: Config.options.bar.utilButtons.showKeyboardToggle
visible: ConfigOptions.bar.utilButtons.showKeyboardToggle visible: Config.options.bar.utilButtons.showKeyboardToggle
sourceComponent: CircleUtilButton { sourceComponent: CircleUtilButton {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
onClicked: Hyprland.dispatch("global quickshell:oskToggle") onClicked: Hyprland.dispatch("global quickshell:oskToggle")
@@ -68,8 +68,8 @@ Item {
} }
Loader { Loader {
active: ConfigOptions.bar.utilButtons.showMicToggle active: Config.options.bar.utilButtons.showMicToggle
visible: ConfigOptions.bar.utilButtons.showMicToggle visible: Config.options.bar.utilButtons.showMicToggle
sourceComponent: CircleUtilButton { sourceComponent: CircleUtilButton {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
onClicked: Hyprland.dispatch("exec wpctl set-mute @DEFAULT_SOURCE@ toggle") onClicked: Hyprland.dispatch("exec wpctl set-mute @DEFAULT_SOURCE@ toggle")
@@ -84,8 +84,8 @@ Item {
} }
Loader { Loader {
active: ConfigOptions.bar.utilButtons.showDarkModeToggle active: Config.options.bar.utilButtons.showDarkModeToggle
visible: ConfigOptions.bar.utilButtons.showDarkModeToggle visible: Config.options.bar.utilButtons.showDarkModeToggle
sourceComponent: CircleUtilButton { sourceComponent: CircleUtilButton {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
onClicked: event => { onClicked: event => {
+20 -20
View File
@@ -15,11 +15,11 @@ import Qt5Compat.GraphicalEffects
Item { Item {
required property var bar required property var bar
property bool borderless: ConfigOptions.bar.borderless property bool borderless: Config.options.bar.borderless
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(bar.screen) readonly property HyprlandMonitor monitor: Hyprland.monitorFor(bar.screen)
readonly property Toplevel activeWindow: ToplevelManager.activeToplevel readonly property Toplevel activeWindow: ToplevelManager.activeToplevel
readonly property int workspaceGroup: Math.floor((monitor.activeWorkspace?.id - 1) / ConfigOptions.bar.workspaces.shown) readonly property int workspaceGroup: Math.floor((monitor.activeWorkspace?.id - 1) / Config.options.bar.workspaces.shown)
property list<bool> workspaceOccupied: [] property list<bool> workspaceOccupied: []
property int widgetPadding: 4 property int widgetPadding: 4
property int workspaceButtonWidth: 26 property int workspaceButtonWidth: 26
@@ -27,12 +27,12 @@ Item {
property real workspaceIconSizeShrinked: workspaceButtonWidth * 0.55 property real workspaceIconSizeShrinked: workspaceButtonWidth * 0.55
property real workspaceIconOpacityShrinked: 1 property real workspaceIconOpacityShrinked: 1
property real workspaceIconMarginShrinked: -4 property real workspaceIconMarginShrinked: -4
property int workspaceIndexInGroup: (monitor.activeWorkspace?.id - 1) % ConfigOptions.bar.workspaces.shown property int workspaceIndexInGroup: (monitor.activeWorkspace?.id - 1) % Config.options.bar.workspaces.shown
// Function to update workspaceOccupied // Function to update workspaceOccupied
function updateWorkspaceOccupied() { function updateWorkspaceOccupied() {
workspaceOccupied = Array.from({ length: ConfigOptions.bar.workspaces.shown }, (_, i) => { workspaceOccupied = Array.from({ length: Config.options.bar.workspaces.shown }, (_, i) => {
return Hyprland.workspaces.values.some(ws => ws.id === workspaceGroup * ConfigOptions.bar.workspaces.shown + i + 1); return Hyprland.workspaces.values.some(ws => ws.id === workspaceGroup * Config.options.bar.workspaces.shown + i + 1);
}) })
} }
@@ -48,7 +48,7 @@ Item {
} }
implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2 implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2
implicitHeight: 40 implicitHeight: Appearance.sizes.barHeight
// Scroll to switch workspaces // Scroll to switch workspaces
WheelHandler { WheelHandler {
@@ -78,10 +78,10 @@ Item {
spacing: 0 spacing: 0
anchors.fill: parent anchors.fill: parent
implicitHeight: 40 implicitHeight: Appearance.sizes.barHeight
Repeater { Repeater {
model: ConfigOptions.bar.workspaces.shown model: Config.options.bar.workspaces.shown
Rectangle { Rectangle {
z: 1 z: 1
@@ -157,14 +157,14 @@ Item {
spacing: 0 spacing: 0
anchors.fill: parent anchors.fill: parent
implicitHeight: 40 implicitHeight: Appearance.sizes.barHeight
Repeater { Repeater {
model: ConfigOptions.bar.workspaces.shown model: Config.options.bar.workspaces.shown
Button { Button {
id: button id: button
property int workspaceValue: workspaceGroup * ConfigOptions.bar.workspaces.shown + index + 1 property int workspaceValue: workspaceGroup * Config.options.bar.workspaces.shown + index + 1
Layout.fillHeight: true Layout.fillHeight: true
onPressed: Hyprland.dispatch(`workspace ${workspaceValue}`) onPressed: Hyprland.dispatch(`workspace ${workspaceValue}`)
width: workspaceButtonWidth width: workspaceButtonWidth
@@ -185,8 +185,8 @@ Item {
StyledText { // Workspace number text StyledText { // Workspace number text
opacity: GlobalStates.workspaceShowNumbers opacity: GlobalStates.workspaceShowNumbers
|| ((ConfigOptions?.bar.workspaces.alwaysShowNumbers && (!ConfigOptions?.bar.workspaces.showAppIcons || !workspaceButtonBackground.biggestWindow || GlobalStates.workspaceShowNumbers)) || ((Config.options?.bar.workspaces.alwaysShowNumbers && (!Config.options?.bar.workspaces.showAppIcons || !workspaceButtonBackground.biggestWindow || GlobalStates.workspaceShowNumbers))
|| (GlobalStates.workspaceShowNumbers && !ConfigOptions?.bar.workspaces.showAppIcons) || (GlobalStates.workspaceShowNumbers && !Config.options?.bar.workspaces.showAppIcons)
) ? 1 : 0 ) ? 1 : 0
z: 3 z: 3
@@ -206,9 +206,9 @@ Item {
} }
} }
Rectangle { // Dot instead of ws number Rectangle { // Dot instead of ws number
opacity: (ConfigOptions?.bar.workspaces.alwaysShowNumbers opacity: (Config.options?.bar.workspaces.alwaysShowNumbers
|| GlobalStates.workspaceShowNumbers || GlobalStates.workspaceShowNumbers
|| (ConfigOptions?.bar.workspaces.showAppIcons && workspaceButtonBackground.biggestWindow) || (Config.options?.bar.workspaces.showAppIcons && workspaceButtonBackground.biggestWindow)
) ? 0 : 1 ) ? 0 : 1
visible: opacity > 0 visible: opacity > 0
anchors.centerIn: parent anchors.centerIn: parent
@@ -228,21 +228,21 @@ Item {
anchors.centerIn: parent anchors.centerIn: parent
width: workspaceButtonWidth width: workspaceButtonWidth
height: workspaceButtonWidth height: workspaceButtonWidth
opacity: !ConfigOptions?.bar.workspaces.showAppIcons ? 0 : opacity: !Config.options?.bar.workspaces.showAppIcons ? 0 :
(workspaceButtonBackground.biggestWindow && !GlobalStates.workspaceShowNumbers && ConfigOptions?.bar.workspaces.showAppIcons) ? (workspaceButtonBackground.biggestWindow && !GlobalStates.workspaceShowNumbers && Config.options?.bar.workspaces.showAppIcons) ?
1 : workspaceButtonBackground.biggestWindow ? workspaceIconOpacityShrinked : 0 1 : workspaceButtonBackground.biggestWindow ? workspaceIconOpacityShrinked : 0
visible: opacity > 0 visible: opacity > 0
IconImage { IconImage {
id: mainAppIcon id: mainAppIcon
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.right: parent.right anchors.right: parent.right
anchors.bottomMargin: (!GlobalStates.workspaceShowNumbers && ConfigOptions?.bar.workspaces.showAppIcons) ? anchors.bottomMargin: (!GlobalStates.workspaceShowNumbers && Config.options?.bar.workspaces.showAppIcons) ?
(workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked (workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked
anchors.rightMargin: (!GlobalStates.workspaceShowNumbers && ConfigOptions?.bar.workspaces.showAppIcons) ? anchors.rightMargin: (!GlobalStates.workspaceShowNumbers && Config.options?.bar.workspaces.showAppIcons) ?
(workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked (workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked
source: workspaceButtonBackground.mainAppIconSource source: workspaceButtonBackground.mainAppIconSource
implicitSize: (!GlobalStates.workspaceShowNumbers && ConfigOptions?.bar.workspaces.showAppIcons) ? workspaceIconSize : workspaceIconSizeShrinked implicitSize: (!GlobalStates.workspaceShowNumbers && Config.options?.bar.workspaces.showAppIcons) ? workspaceIconSize : workspaceIconSizeShrinked
Behavior on opacity { Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
@@ -16,8 +16,8 @@ Singleton {
property string syntaxHighlightingTheme property string syntaxHighlightingTheme
// Extremely conservative transparency values for consistency and readability // Extremely conservative transparency values for consistency and readability
property real transparency: ConfigOptions?.appearance.transparency ? (m3colors.darkmode ? 0.1 : 0.07) : 0 property real transparency: Config.options?.appearance.transparency ? (m3colors.darkmode ? 0.1 : 0.07) : 0
property real contentTransparency: ConfigOptions?.appearance.transparency ? (m3colors.darkmode ? 0.55 : 0.55) : 0 property real contentTransparency: Config.options?.appearance.transparency ? (m3colors.darkmode ? 0.55 : 0.55) : 0
m3colors: QtObject { m3colors: QtObject {
property bool darkmode: false property bool darkmode: false
@@ -131,7 +131,7 @@ Singleton {
property color colSecondaryHover: ColorUtils.mix(m3colors.m3secondary, colLayer1Hover, 0.85) property color colSecondaryHover: ColorUtils.mix(m3colors.m3secondary, colLayer1Hover, 0.85)
property color colSecondaryActive: ColorUtils.mix(m3colors.m3secondary, colLayer1Active, 0.4) property color colSecondaryActive: ColorUtils.mix(m3colors.m3secondary, colLayer1Active, 0.4)
property color colSecondaryContainer: m3colors.m3secondaryContainer property color colSecondaryContainer: m3colors.m3secondaryContainer
property color colSecondaryContainerHover: ColorUtils.mix(m3colors.m3secondaryContainer, colLayer1Hover, 0.6) property color colSecondaryContainerHover: ColorUtils.mix(m3colors.m3secondaryContainer, m3colors.m3onSecondaryContainer, 0.90)
property color colSecondaryContainerActive: ColorUtils.mix(m3colors.m3secondaryContainer, colLayer1Active, 0.54) property color colSecondaryContainerActive: ColorUtils.mix(m3colors.m3secondaryContainer, colLayer1Active, 0.54)
property color colOnSecondaryContainer: m3colors.m3onSecondaryContainer property color colOnSecondaryContainer: m3colors.m3onSecondaryContainer
property color colSurfaceContainerLow: ColorUtils.transparentize(m3colors.m3surfaceContainerLow, root.contentTransparency) property color colSurfaceContainerLow: ColorUtils.transparentize(m3colors.m3surfaceContainerLow, root.contentTransparency)
@@ -171,7 +171,7 @@ Singleton {
} }
property QtObject pixelSize: QtObject { property QtObject pixelSize: QtObject {
property int smallest: 10 property int smallest: 10
property int smaller: 13 property int smaller: 12
property int small: 15 property int small: 15
property int normal: 16 property int normal: 16
property int large: 17 property int large: 17
@@ -288,8 +288,10 @@ Singleton {
} }
sizes: QtObject { sizes: QtObject {
property real barHeight: 40 property real baseBarHeight: 40
property real barCenterSideModuleWidth: ConfigOptions?.bar.verbose ? 360 : 140 property real barHeight: Config.options.bar.cornerStyle === 1 ?
(baseBarHeight + Appearance.sizes.hyprlandGapsOut * 2) : baseBarHeight
property real barCenterSideModuleWidth: Config.options?.bar.verbose ? 360 : 140
property real barCenterSideModuleWidthShortened: 280 property real barCenterSideModuleWidthShortened: 280
property real barCenterSideModuleWidthHellaShortened: 190 property real barCenterSideModuleWidthHellaShortened: 190
property real barShortenScreenWidthThreshold: 1200 // Shorten if screen width is at most this value property real barShortenScreenWidthThreshold: 1200 // Shorten if screen width is at most this value
@@ -0,0 +1,217 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
Singleton {
id: root
property string filePath: Directories.shellConfigPath
property alias options: configOptionsJsonAdapter
function setNestedValue(nestedKey, value) {
let keys = nestedKey.split(".");
let obj = root.options;
let parents = [obj];
// Traverse and collect parent objects
for (let i = 0; i < keys.length - 1; ++i) {
if (!obj[keys[i]] || typeof obj[keys[i]] !== "object") {
obj[keys[i]] = {};
}
obj = obj[keys[i]];
parents.push(obj);
}
// Convert value to correct type using JSON.parse when safe
let convertedValue = value;
if (typeof value === "string") {
let trimmed = value.trim();
if (trimmed === "true" || trimmed === "false" || !isNaN(Number(trimmed))) {
try {
convertedValue = JSON.parse(trimmed);
} catch (e) {
convertedValue = value;
}
}
}
obj[keys[keys.length - 1]] = convertedValue;
}
FileView {
path: root.filePath
watchChanges: true
onFileChanged: reload()
onAdapterUpdated: writeAdapter()
onLoadFailed: error => {
if (error == FileViewError.FileNotFound) {
writeAdapter();
}
}
JsonAdapter {
id: configOptionsJsonAdapter
property JsonObject policies: JsonObject {
property int ai: 1 // 0: No | 1: Yes | 2: Local
property int weeb: 1 // 0: No | 1: Open | 2: Closet
}
property JsonObject ai: JsonObject {
property string systemPrompt: qsTr("## Style\n- Use casual tone, don't be formal! Make sure you answer precisely without hallucination and prefer bullet points over walls of text. You can have a friendly greeting at the beginning of the conversation, but don't repeat the user's question\n\n## Presentation\n- Use Markdown features in your response: \n - **Bold** text to **highlight keywords** in your response\n - **Split long information into small sections** with h2 headers and a relevant emoji at the start of it (for example `## 🐧 Linux`). Bullet points are preferred over long paragraphs, unless you're offering writing support or instructed otherwise by the user.\n- Asked to compare different options? You should firstly use a table to compare the main aspects, then elaborate or include relevant comments from online forums *after* the table. Make sure to provide a final recommendation for the user's use case!\n- Use LaTeX formatting for mathematical and scientific notations whenever appropriate. Enclose all LaTeX '$$' delimiters. NEVER generate LaTeX code in a latex block unless the user explicitly asks for it. DO NOT use LaTeX for regular documents (resumes, letters, essays, CVs, etc.).\n\nThanks!\n\n## Tools\nMay or may not be available depending on the user's settings. If they're available, follow these guidelines:\n\n### Search\n- When user asks for information that might benefit from up-to-date information, use this to get search access\n\n### Shell configuration\n- Always fetch the config options to see the available keys before setting\n- Avoid unnecessarily asking the user to confirm the changes they explicitly asked for, just do it\n")
}
property JsonObject appearance: JsonObject {
property int fakeScreenRounding: 2 // 0: None | 1: Always | 2: When not fullscreen
property bool transparency: false
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 JsonObject audio: JsonObject {
// Values in %
property JsonObject protection: JsonObject {
// Prevent sudden bangs
property bool enable: true
property real maxAllowedIncrease: 10
property real maxAllowed: 90 // Realistically should already provide some protection when it's 99...
}
}
property JsonObject apps: JsonObject {
property string bluetooth: "kcmshell6 kcm_bluetooth"
property string network: "plasmawindowed org.kde.plasma.networkmanagement"
property string networkEthernet: "kcmshell6 kcm_networkmanagement"
property string taskManager: "plasma-systemmonitor --page-name Processes"
property string terminal: "kitty -1" // This is only for shell actions
}
property JsonObject background: JsonObject {
property bool fixedClockPosition: false
property real clockX: -500
property real clockY: -500
}
property JsonObject bar: JsonObject {
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
property string topLeftIcon: "spark" // Options: distro, spark
property bool showBackground: true
property bool verbose: true
property JsonObject resources: JsonObject {
property bool alwaysShowSwap: true
property bool alwaysShowCpu: false
}
property list<string> screenList: [] // List of names, like "eDP-1", find out with 'hyprctl monitors' command
property JsonObject utilButtons: JsonObject {
property bool showScreenSnip: true
property bool showColorPicker: false
property bool showMicToggle: false
property bool showKeyboardToggle: true
property bool showDarkModeToggle: true
}
property JsonObject tray: JsonObject {
property bool monochromeIcons: true
}
property JsonObject workspaces: JsonObject {
property int shown: 10
property bool showAppIcons: true
property bool alwaysShowNumbers: false
property int showNumberDelay: 300 // milliseconds
}
}
property JsonObject battery: JsonObject {
property int low: 20
property int critical: 5
property bool automaticSuspend: true
property int suspend: 3
}
property JsonObject dock: JsonObject {
property bool enable: false
property real height: 60
property real hoverRegionHeight: 3
property bool pinnedOnStartup: false
property bool hoverToReveal: true // When false, only reveals on empty workspace
property list<string> pinnedApps: [ // IDs of pinned entries
"org.kde.dolphin", "kitty",]
}
property JsonObject language: JsonObject {
property JsonObject translator: JsonObject {
property string engine: "auto" // Run `trans -list-engines` for available engines. auto should use google
property string targetLanguage: "auto" // Run `trans -list-all` for available languages
property string sourceLanguage: "auto"
}
}
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"
}
property JsonObject osd: JsonObject {
property int timeout: 1000
}
property JsonObject osk: JsonObject {
property string layout: "qwerty_full"
property bool pinnedOnStartup: false
}
property JsonObject overview: JsonObject {
property real scale: 0.18 // Relative to screen size
property real rows: 2
property real columns: 5
}
property JsonObject resources: JsonObject {
property int updateInterval: 3000
}
property JsonObject search: JsonObject {
property int nonAppResultDelay: 30 // This prevents lagging when typing
property string engineBaseUrl: "https://www.google.com/search?q="
property list<string> excludedSites: ["quora.com"]
property bool sloppy: false // Uses levenshtein distance based scoring instead of fuzzy sort. Very weird.
property JsonObject prefix: JsonObject {
property string action: "/"
property string clipboard: ";"
property string emojis: ":"
}
}
property JsonObject sidebar: JsonObject {
property JsonObject translator: JsonObject {
property int delay: 300 // Delay before sending request. Reduces (potential) rate limits and lag.
}
property JsonObject booru: JsonObject {
property bool allowNsfw: false
property string defaultProvider: "yandere"
property int limit: 20
property JsonObject zerochan: JsonObject {
property string username: "[unset]"
}
}
}
property JsonObject time: JsonObject {
// https://doc.qt.io/qt-6/qtime.html#toString
property string format: "hh:mm"
property string dateFormat: "dddd, dd/MM"
}
property JsonObject windows: JsonObject {
property bool showTitlebar: true // Client-side decoration for shell apps
property bool centerTitle: true
}
property JsonObject hacks: JsonObject {
property int arbitraryRaceConditionDelay: 20 // milliseconds
}
}
}
}
@@ -1,165 +0,0 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
Singleton {
property QtObject policies: QtObject {
property int ai: 1 // 0: No | 1: Yes | 2: Local
property int weeb: 1 // 0: No | 1: Open | 2: Closet
}
property QtObject ai: QtObject {
property string systemPrompt: qsTr("Use casual tone. No user knowledge is to be assumed except basic Linux literacy. Be brief and concise: When explaining concepts, use bullet points (prefer minus sign (-) over asterisk (*)) and highlight keywords in bold to pinpoint the main concepts instead of long paragraphs. You are also encouraged to split your response with h2 headers, each header title beginning with an emoji, like `## 🐧 Linux`. When making changes to the user's config, you must get the config to know what values there are before setting.")
}
property QtObject appearance: QtObject {
property int fakeScreenRounding: 2 // 0: None | 1: Always | 2: When not fullscreen
property bool transparency: false
property QtObject palette: QtObject {
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 QtObject audio: QtObject {
// Values in %
property QtObject protection: QtObject {
// Prevent sudden bangs
property bool enable: true
property real maxAllowedIncrease: 10
property real maxAllowed: 90 // Realistically should already provide some protection when it's 99...
}
}
property QtObject apps: QtObject {
property string bluetooth: "kcmshell6 kcm_bluetooth"
property string network: "plasmawindowed org.kde.plasma.networkmanagement"
property string networkEthernet: "kcmshell6 kcm_networkmanagement"
property string taskManager: "plasma-systemmonitor --page-name Processes"
property string terminal: "kitty -1" // This is only for shell actions
}
property QtObject background: QtObject {
property bool fixedClockPosition: false
property real clockX: -500
property real clockY: -500
}
property QtObject bar: QtObject {
property bool bottom: false // Instead of top
property bool borderless: false // true for no grouping of items
property string topLeftIcon: "spark" // Options: distro, spark
property bool showBackground: true
property bool verbose: true
property QtObject resources: QtObject {
property bool alwaysShowSwap: true
property bool alwaysShowCpu: false
}
property list<string> screenList: [] // List of names, like "eDP-1", find out with 'hyprctl monitors' command
property QtObject utilButtons: QtObject {
property bool showScreenSnip: true
property bool showColorPicker: false
property bool showMicToggle: false
property bool showKeyboardToggle: true
property bool showDarkModeToggle: true
}
property QtObject tray: QtObject {
property bool monochromeIcons: true
}
property QtObject workspaces: QtObject {
property int shown: 10
property bool showAppIcons: true
property bool alwaysShowNumbers: false
property int showNumberDelay: 300 // milliseconds
}
}
property QtObject battery: QtObject {
property int low: 20
property int critical: 5
property bool automaticSuspend: true
property int suspend: 3
}
property QtObject dock: QtObject {
property real height: 60
property real hoverRegionHeight: 3
property bool pinnedOnStartup: false
property bool hoverToReveal: false // When false, only reveals on empty workspace
property list<string> pinnedApps: [ // IDs of pinned entries
"org.kde.dolphin", "kitty",]
}
property QtObject language: QtObject {
property string ui: "auto" // Interface language: "auto", "en", "zh-CN", "zh-TW", etc.
property QtObject translator: QtObject {
property string engine: "auto" // Run `trans -list-engines` for available engines. auto should use google
property string targetLanguage: "auto" // Run `trans -list-all` for available languages
property string sourceLanguage: "auto"
}
}
property QtObject networking: QtObject {
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"
}
property QtObject osd: QtObject {
property int timeout: 1000
}
property QtObject osk: QtObject {
property string layout: "qwerty_full"
property bool pinnedOnStartup: false
}
property QtObject overview: QtObject {
property real scale: 0.18 // Relative to screen size
property real rows: 2
property real columns: 5
}
property QtObject resources: QtObject {
property int updateInterval: 3000
}
property QtObject search: QtObject {
property int nonAppResultDelay: 30 // This prevents lagging when typing
property string engineBaseUrl: "https://www.google.com/search?q="
property list<string> excludedSites: ["quora.com"]
property bool sloppy: false // Uses levenshtein distance based scoring instead of fuzzy sort. Very weird.
property QtObject prefix: QtObject {
property string action: "/"
property string clipboard: ";"
property string emojis: ":"
}
}
property QtObject sidebar: QtObject {
property QtObject translator: QtObject {
property int delay: 300 // Delay before sending request. Reduces (potential) rate limits and lag.
}
property QtObject booru: QtObject {
property bool allowNsfw: false
property string defaultProvider: "yandere"
property int limit: 20
property QtObject zerochan: QtObject {
property string username: "[unset]"
}
}
}
property QtObject time: QtObject {
// https://doc.qt.io/qt-6/qtime.html#toString
property string format: "hh:mm"
property string dateFormat: "dddd, dd/MM"
}
property QtObject windows: QtObject {
property bool showTitlebar: true // Client-side decoration for shell apps
property bool centerTitle: true
}
property QtObject hacks: QtObject {
property int arbitraryRaceConditionDelay: 20 // milliseconds
}
}
@@ -16,6 +16,7 @@ Singleton {
readonly property string downloads: StandardPaths.standardLocations(StandardPaths.DownloadLocation)[0] readonly property string downloads: StandardPaths.standardLocations(StandardPaths.DownloadLocation)[0]
// Other dirs used by the shell, without "file://" // Other dirs used by the shell, without "file://"
property string scriptPath: FileUtils.trimFileProtocol(`${Directories.config}/quickshell/scripts`)
property string favicons: FileUtils.trimFileProtocol(`${Directories.cache}/media/favicons`) property string favicons: FileUtils.trimFileProtocol(`${Directories.cache}/media/favicons`)
property string coverArt: FileUtils.trimFileProtocol(`${Directories.cache}/media/coverart`) property string coverArt: FileUtils.trimFileProtocol(`${Directories.cache}/media/coverart`)
property string booruPreviews: FileUtils.trimFileProtocol(`${Directories.cache}/media/boorus`) property string booruPreviews: FileUtils.trimFileProtocol(`${Directories.cache}/media/boorus`)
@@ -29,7 +30,9 @@ Singleton {
property string notificationsPath: FileUtils.trimFileProtocol(`${Directories.cache}/notifications/notifications.json`) property string notificationsPath: FileUtils.trimFileProtocol(`${Directories.cache}/notifications/notifications.json`)
property string generatedMaterialThemePath: FileUtils.trimFileProtocol(`${Directories.state}/user/generated/colors.json`) property string generatedMaterialThemePath: FileUtils.trimFileProtocol(`${Directories.state}/user/generated/colors.json`)
property string cliphistDecode: FileUtils.trimFileProtocol(`/tmp/quickshell/media/cliphist`) property string cliphistDecode: FileUtils.trimFileProtocol(`/tmp/quickshell/media/cliphist`)
property string wallpaperSwitchScriptPath: FileUtils.trimFileProtocol(`${Directories.config}/quickshell/scripts/colors/switchwall.sh`) property string wallpaperSwitchScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/colors/switchwall.sh`)
property string defaultAiPrompts: FileUtils.trimFileProtocol(`${Directories.config}/quickshell/defaults/ai/prompts`)
property string userAiPrompts: FileUtils.trimFileProtocol(`${Directories.shellConfig}/ai/prompts`)
// Cleanup on init // Cleanup on init
Component.onCompleted: { Component.onCompleted: {
Quickshell.execDetached(["bash", "-c", `mkdir -p '${shellConfig}'`]) Quickshell.execDetached(["bash", "-c", `mkdir -p '${shellConfig}'`])
@@ -0,0 +1,48 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
Singleton {
id: root
property alias states: persistentStatesJsonAdapter
property string fileDir: Directories.state
property string fileName: "states.json"
property string filePath: `${root.fileDir}/${root.fileName}`
FileView {
path: root.filePath
watchChanges: true
onFileChanged: reload()
onAdapterUpdated: {
writeAdapter()
}
onLoadFailed: error => {
console.log("Failed to load persistent states file:", error);
if (error == FileViewError.FileNotFound) {
writeAdapter();
}
}
adapter: JsonAdapter {
id: persistentStatesJsonAdapter
property JsonObject ai: JsonObject {
property string model
property real temperature: 0.5
}
property JsonObject sidebar: JsonObject {
property JsonObject bottomGroup: JsonObject {
property bool collapsed: false
}
}
property JsonObject booru: JsonObject {
property bool allowNsfw: false
property string provider: "yandere"
}
}
}
}
@@ -7,3 +7,28 @@ function trimFileProtocol(str) {
return str.startsWith("file://") ? str.slice(7) : str; return str.startsWith("file://") ? str.slice(7) : str;
} }
/**
* Extracts the file name from a file path
* @param {string} str
* @returns {string}
*/
function fileNameForPath(str) {
if (typeof str !== "string") return "";
const trimmed = trimFileProtocol(str);
return trimmed.split(/[\\/]/).pop();
}
/**
* Removes the file extension from a file path or name
* @param {string} str
* @returns {string}
*/
function trimFileExt(str) {
if (typeof str !== "string") return "";
const trimmed = trimFileProtocol(str);
const lastDot = trimmed.lastIndexOf(".");
if (lastDot > -1 && lastDot > trimmed.lastIndexOf("/")) {
return trimmed.slice(0, lastDot);
}
return trimmed;
}
@@ -18,7 +18,7 @@ IconImage {
property string displayText property string displayText
property real size: 32 property real size: 32
property string downloadUserAgent: ConfigOptions?.networking.userAgent ?? "" property string downloadUserAgent: Config.options?.networking.userAgent ?? ""
property string faviconDownloadPath: Directories.favicons property string faviconDownloadPath: Directories.favicons
property string domainName: url.includes("vertexaisearch") ? displayText : StringUtils.getDomain(url) property string domainName: url.includes("vertexaisearch") ? displayText : StringUtils.getDomain(url)
property string faviconUrl: `https://www.google.com/s2/favicons?domain=${domainName}&sz=32` property string faviconUrl: `https://www.google.com/s2/favicons?domain=${domainName}&sz=32`
@@ -9,8 +9,8 @@ Rectangle {
id: root id: root
property string key property string key
property real horizontalPadding: 7 property real horizontalPadding: 6
property real verticalPadding: 2 property real verticalPadding: 1
property real borderWidth: 1 property real borderWidth: 1
property real extraBottomBorderWidth: 2 property real extraBottomBorderWidth: 2
property color borderColor: Appearance.colors.colOnLayer0 property color borderColor: Appearance.colors.colOnLayer0
@@ -13,6 +13,12 @@ Text {
family: Appearance?.font.family.iconMaterial ?? "Material Symbols Rounded" family: Appearance?.font.family.iconMaterial ?? "Material Symbols Rounded"
pixelSize: iconSize pixelSize: iconSize
weight: Font.Normal + (Font.DemiBold - Font.Normal) * fill weight: Font.Normal + (Font.DemiBold - Font.Normal) * fill
variableAxes: {
"FILL": truncatedFill,
// "wght": font.weight,
// "GRAD": 0,
"opsz": iconSize,
}
} }
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
color: Appearance.m3colors.m3onBackground color: Appearance.m3colors.m3onBackground
@@ -24,11 +30,4 @@ Text {
// easing.bezierCurve: Appearance?.animation.elementMoveFast.bezierCurve ?? [0.34, 0.80, 0.34, 1.00, 1, 1] // easing.bezierCurve: Appearance?.animation.elementMoveFast.bezierCurve ?? [0.34, 0.80, 0.34, 1.00, 1, 1]
// } // }
// } // }
font.variableAxes: {
"FILL": truncatedFill,
// "wght": font.weight,
// "GRAD": 0,
"opsz": iconSize,
}
} }
@@ -188,6 +188,7 @@ Item { // Notification item area
font.pixelSize: root.fontSize font.pixelSize: root.fontSize
color: Appearance.colors.colSubtext color: Appearance.colors.colSubtext
elide: Text.ElideRight elide: Text.ElideRight
maximumLineCount: 1
textFormat: Text.StyledText textFormat: Text.StyledText
text: { text: {
return processNotificationBody(notificationObject.body, notificationObject.appName || notificationObject.summary).replace(/\n/g, "<br/>") return processNotificationBody(notificationObject.body, notificationObject.appName || notificationObject.summary).replace(/\n/g, "<br/>")
@@ -3,24 +3,21 @@ import QtQuick 2.9
Item { Item {
id: root id: root
enum CornerEnum { TopLeft, TopRight, BottomLeft, BottomRight }
property var corner: RoundCorner.CornerEnum.TopLeft // Default to TopLeft
property int size: 25 property int size: 25
property color color: "#000000" property color color: "#000000"
onColorChanged: { onColorChanged: {
canvas.requestPaint(); canvas.requestPaint();
} }
onCornerChanged: {
property QtObject cornerEnum: QtObject { canvas.requestPaint();
property int topLeft: 0
property int topRight: 1
property int bottomLeft: 2
property int bottomRight: 3
} }
property int corner: cornerEnum.topLeft // Default to TopLeft implicitWidth: size
implicitHeight: size
width: size
height: size
Canvas { Canvas {
id: canvas id: canvas
@@ -31,22 +28,22 @@ Item {
onPaint: { onPaint: {
var ctx = getContext("2d"); var ctx = getContext("2d");
var r = root.size; var r = root.size;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath(); ctx.beginPath();
switch (root.corner) { switch (root.corner) {
case cornerEnum.topLeft: case RoundCorner.CornerEnum.TopLeft:
ctx.arc(r, r, r, Math.PI, 3 * Math.PI / 2); ctx.arc(r, r, r, Math.PI, 3 * Math.PI / 2);
ctx.lineTo(0, 0); ctx.lineTo(0, 0);
break; break;
case cornerEnum.topRight: case RoundCorner.CornerEnum.TopRight:
ctx.arc(0, r, r, 3 * Math.PI / 2, 2 * Math.PI); ctx.arc(0, r, r, 3 * Math.PI / 2, 2 * Math.PI);
ctx.lineTo(r, 0); ctx.lineTo(r, 0);
break; break;
case cornerEnum.bottomLeft: case RoundCorner.CornerEnum.BottomLeft:
ctx.arc(r, 0, r, Math.PI / 2, Math.PI); ctx.arc(r, 0, r, Math.PI / 2, Math.PI);
ctx.lineTo(0, r); ctx.lineTo(0, r);
break; break;
case cornerEnum.bottomRight: case RoundCorner.CornerEnum.BottomRight:
ctx.arc(0, 0, r, 0, Math.PI / 2); ctx.arc(0, 0, r, 0, Math.PI / 2);
ctx.lineTo(r, r); ctx.lineTo(r, r);
break; break;
+5 -5
View File
@@ -14,7 +14,7 @@ import Quickshell.Hyprland
Scope { // Scope Scope { // Scope
id: root id: root
property bool pinned: ConfigOptions?.dock.pinnedOnStartup ?? false property bool pinned: Config.options?.dock.pinnedOnStartup ?? false
Variants { // For each monitor Variants { // For each monitor
model: Quickshell.screens model: Quickshell.screens
@@ -22,14 +22,14 @@ Scope { // Scope
LazyLoader { LazyLoader {
id: dockLoader id: dockLoader
required property var modelData required property var modelData
activeAsync: ConfigOptions?.dock.hoverToReveal || (!ToplevelManager.activeToplevel?.activated) activeAsync: Config.options?.dock.hoverToReveal || (!ToplevelManager.activeToplevel?.activated)
component: PanelWindow { // Window component: PanelWindow { // Window
id: dockRoot id: dockRoot
screen: dockLoader.modelData screen: dockLoader.modelData
property bool reveal: root.pinned property bool reveal: root.pinned
|| (ConfigOptions?.dock.hoverToReveal && dockMouseArea.containsMouse) || (Config.options?.dock.hoverToReveal && dockMouseArea.containsMouse)
|| dockApps.requestDockShow || dockApps.requestDockShow
|| (!ToplevelManager.activeToplevel?.activated) || (!ToplevelManager.activeToplevel?.activated)
@@ -47,7 +47,7 @@ Scope { // Scope
WlrLayershell.namespace: "quickshell:dock" WlrLayershell.namespace: "quickshell:dock"
color: "transparent" color: "transparent"
implicitHeight: (ConfigOptions?.dock.height ?? 70) + Appearance.sizes.elevationMargin + Appearance.sizes.hyprlandGapsOut implicitHeight: (Config.options?.dock.height ?? 70) + Appearance.sizes.elevationMargin + Appearance.sizes.hyprlandGapsOut
mask: Region { mask: Region {
item: dockMouseArea item: dockMouseArea
@@ -58,7 +58,7 @@ Scope { // Scope
anchors.top: parent.top anchors.top: parent.top
height: parent.height height: parent.height
anchors.topMargin: dockRoot.reveal ? 0 : anchors.topMargin: dockRoot.reveal ? 0 :
ConfigOptions?.dock.hoverToReveal ? (dockRoot.implicitHeight - ConfigOptions.dock.hoverRegionHeight) : Config.options?.dock.hoverToReveal ? (dockRoot.implicitHeight - Config.options.dock.hoverRegionHeight) :
(dockRoot.implicitHeight + 1) (dockRoot.implicitHeight + 1)
anchors.left: parent.left anchors.left: parent.left
+1 -1
View File
@@ -48,7 +48,7 @@ Item {
var map = new Map(); var map = new Map();
// Pinned apps // Pinned apps
const pinnedApps = ConfigOptions?.dock.pinnedApps ?? []; const pinnedApps = Config.options?.dock.pinnedApps ?? [];
for (const appId of pinnedApps) { for (const appId of pinnedApps) {
if (!map.has(appId.toLowerCase())) map.set(appId.toLowerCase(), ({ if (!map.has(appId.toLowerCase())) map.set(appId.toLowerCase(), ({
pinned: true, pinned: true,
@@ -108,8 +108,8 @@ Scope {
WlrLayershell.namespace: "quickshell:mediaControls" WlrLayershell.namespace: "quickshell:mediaControls"
anchors { anchors {
top: !ConfigOptions.bar.bottom top: !Config.options.bar.bottom
bottom: ConfigOptions.bar.bottom bottom: Config.options.bar.bottom
left: true left: true
} }
mask: Region { mask: Region {
@@ -22,7 +22,7 @@ Scope {
Timer { Timer {
id: osdTimeout id: osdTimeout
interval: ConfigOptions.osd.timeout interval: Config.options.osd.timeout
repeat: false repeat: false
running: false running: false
onTriggered: { onTriggered: {
@@ -66,8 +66,8 @@ Scope {
color: "transparent" color: "transparent"
anchors { anchors {
top: !ConfigOptions.bar.bottom top: !Config.options.bar.bottom
bottom: ConfigOptions.bar.bottom bottom: Config.options.bar.bottom
} }
mask: Region { mask: Region {
item: osdValuesWrapper item: osdValuesWrapper
@@ -22,7 +22,7 @@ Scope {
Timer { Timer {
id: osdTimeout id: osdTimeout
interval: ConfigOptions.osd.timeout interval: Config.options.osd.timeout
repeat: false repeat: false
running: false running: false
onTriggered: { onTriggered: {
@@ -78,8 +78,8 @@ Scope {
color: "transparent" color: "transparent"
anchors { anchors {
top: !ConfigOptions.bar.bottom top: !Config.options.bar.bottom
bottom: ConfigOptions.bar.bottom bottom: Config.options.bar.bottom
} }
mask: Region { mask: Region {
item: osdValuesWrapper item: osdValuesWrapper
@@ -15,7 +15,7 @@ import Quickshell.Hyprland
Scope { // Scope Scope { // Scope
id: root id: root
property bool pinned: ConfigOptions?.osk.pinnedOnStartup ?? false property bool pinned: Config.options?.osk.pinnedOnStartup ?? false
component OskControlButton: GroupButton { // Pin button component OskControlButton: GroupButton { // Pin button
baseWidth: 40 baseWidth: 40
@@ -13,7 +13,7 @@ import Quickshell.Hyprland
Item { Item {
id: root id: root
property var activeLayoutName: ConfigOptions?.osk.layout ?? Layouts.defaultLayout property var activeLayoutName: Config.options?.osk.layout ?? Layouts.defaultLayout
property var layouts: Layouts.byName property var layouts: Layouts.byName
property var currentLayout: layouts[activeLayoutName] property var currentLayout: layouts[activeLayoutName]
@@ -73,7 +73,7 @@ Scope {
Timer { Timer {
id: delayedGrabTimer id: delayedGrabTimer
interval: ConfigOptions.hacks.arbitraryRaceConditionDelay interval: Config.options.hacks.arbitraryRaceConditionDelay
repeat: false repeat: false
onTriggered: { onTriggered: {
if (!grab.canBeActive) return if (!grab.canBeActive) return
@@ -205,7 +205,7 @@ Scope {
if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) {
overviewScope.dontAutoCancelSearch = true; overviewScope.dontAutoCancelSearch = true;
panelWindow.setSearchingText( panelWindow.setSearchingText(
ConfigOptions.search.prefix.clipboard Config.options.search.prefix.clipboard
); );
GlobalStates.overviewOpen = true; GlobalStates.overviewOpen = true;
return return
@@ -228,7 +228,7 @@ Scope {
if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) {
overviewScope.dontAutoCancelSearch = true; overviewScope.dontAutoCancelSearch = true;
panelWindow.setSearchingText( panelWindow.setSearchingText(
ConfigOptions.search.prefix.emojis Config.options.search.prefix.emojis
); );
GlobalStates.overviewOpen = true; GlobalStates.overviewOpen = true;
return return
@@ -17,14 +17,14 @@ Item {
required property var panelWindow required property var panelWindow
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(panelWindow.screen) readonly property HyprlandMonitor monitor: Hyprland.monitorFor(panelWindow.screen)
readonly property var toplevels: ToplevelManager.toplevels readonly property var toplevels: ToplevelManager.toplevels
readonly property int workspacesShown: ConfigOptions.overview.rows * ConfigOptions.overview.columns readonly property int workspacesShown: Config.options.overview.rows * Config.options.overview.columns
readonly property int workspaceGroup: Math.floor((monitor.activeWorkspace?.id - 1) / workspacesShown) readonly property int workspaceGroup: Math.floor((monitor.activeWorkspace?.id - 1) / workspacesShown)
property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor.id) property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor.id)
property var windows: HyprlandData.windowList property var windows: HyprlandData.windowList
property var windowByAddress: HyprlandData.windowByAddress property var windowByAddress: HyprlandData.windowByAddress
property var windowAddresses: HyprlandData.addresses 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: ConfigOptions.overview.scale property real scale: Config.options.overview.scale
property color activeBorderColor: Appearance.colors.colSecondary property color activeBorderColor: Appearance.colors.colSecondary
property real workspaceImplicitWidth: (monitorData?.transform % 2 === 1) ? property real workspaceImplicitWidth: (monitorData?.transform % 2 === 1) ?
@@ -71,18 +71,18 @@ Item {
anchors.centerIn: parent anchors.centerIn: parent
spacing: workspaceSpacing spacing: workspaceSpacing
Repeater { Repeater {
model: ConfigOptions.overview.rows model: Config.options.overview.rows
delegate: RowLayout { delegate: RowLayout {
id: row id: row
property int rowIndex: index property int rowIndex: index
spacing: workspaceSpacing spacing: workspaceSpacing
Repeater { // Workspace repeater Repeater { // Workspace repeater
model: ConfigOptions.overview.columns model: Config.options.overview.columns
Rectangle { // Workspace Rectangle { // Workspace
id: workspace id: workspace
property int colIndex: index property int colIndex: index
property int workspaceValue: root.workspaceGroup * workspacesShown + rowIndex * ConfigOptions.overview.columns + colIndex + 1 property int workspaceValue: root.workspaceGroup * workspacesShown + rowIndex * Config.options.overview.columns + colIndex + 1
property color defaultWorkspaceColor: Appearance.colors.colLayer1 // TODO: reconsider this color for a cleaner look property color defaultWorkspaceColor: Appearance.colors.colLayer1 // TODO: reconsider this color for a cleaner look
property color hoveredWorkspaceColor: ColorUtils.mix(defaultWorkspaceColor, Appearance.colors.colLayer1Hover, 0.1) property color hoveredWorkspaceColor: ColorUtils.mix(defaultWorkspaceColor, Appearance.colors.colLayer1Hover, 0.1)
property color hoveredBorderColor: Appearance.colors.colLayer2Hover property color hoveredBorderColor: Appearance.colors.colLayer2Hover
@@ -171,14 +171,14 @@ Item {
property bool atInitPosition: (initX == x && initY == y) property bool atInitPosition: (initX == x && initY == y)
restrictToWorkspace: Drag.active || atInitPosition restrictToWorkspace: Drag.active || atInitPosition
property int workspaceColIndex: (windowData?.workspace.id - 1) % ConfigOptions.overview.columns property int workspaceColIndex: (windowData?.workspace.id - 1) % Config.options.overview.columns
property int workspaceRowIndex: Math.floor((windowData?.workspace.id - 1) % root.workspacesShown / ConfigOptions.overview.columns) property int workspaceRowIndex: Math.floor((windowData?.workspace.id - 1) % root.workspacesShown / Config.options.overview.columns)
xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex - (monitor?.x * root.scale) xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex - (monitor?.x * root.scale)
yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * workspaceRowIndex - (monitor?.y * root.scale) yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * workspaceRowIndex - (monitor?.y * root.scale)
Timer { Timer {
id: updateWindowPosition id: updateWindowPosition
interval: ConfigOptions.hacks.arbitraryRaceConditionDelay interval: Config.options.hacks.arbitraryRaceConditionDelay
repeat: false repeat: false
running: false running: false
onTriggered: { onTriggered: {
@@ -245,8 +245,8 @@ Item {
Rectangle { // Focused workspace indicator Rectangle { // Focused workspace indicator
id: focusedWorkspaceIndicator id: focusedWorkspaceIndicator
property int activeWorkspaceInGroup: monitor.activeWorkspace?.id - (root.workspaceGroup * root.workspacesShown) property int activeWorkspaceInGroup: monitor.activeWorkspace?.id - (root.workspaceGroup * root.workspacesShown)
property int activeWorkspaceRowIndex: Math.floor((activeWorkspaceInGroup - 1) / ConfigOptions.overview.columns) property int activeWorkspaceRowIndex: Math.floor((activeWorkspaceInGroup - 1) / Config.options.overview.columns)
property int activeWorkspaceColIndex: (activeWorkspaceInGroup - 1) % ConfigOptions.overview.columns property int activeWorkspaceColIndex: (activeWorkspaceInGroup - 1) % Config.options.overview.columns
x: (root.workspaceImplicitWidth + workspaceSpacing) * activeWorkspaceColIndex x: (root.workspaceImplicitWidth + workspaceSpacing) * activeWorkspaceColIndex
y: (root.workspaceImplicitHeight + workspaceSpacing) * activeWorkspaceRowIndex y: (root.workspaceImplicitHeight + workspaceSpacing) * activeWorkspaceRowIndex
z: root.windowZ z: root.windowZ
@@ -80,7 +80,7 @@ Item { // Wrapper
Timer { Timer {
id: nonAppResultsTimer id: nonAppResultsTimer
interval: ConfigOptions.search.nonAppResultDelay interval: Config.options.search.nonAppResultDelay
onTriggered: { onTriggered: {
mathProcess.calculateExpression(root.searchingText); mathProcess.calculateExpression(root.searchingText);
} }
@@ -203,7 +203,7 @@ Item { // Wrapper
Layout.leftMargin: 15 Layout.leftMargin: 15
iconSize: Appearance.font.pixelSize.huge iconSize: Appearance.font.pixelSize.huge
color: Appearance.m3colors.m3onSurface color: Appearance.m3colors.m3onSurface
text: root.searchingText.startsWith(ConfigOptions.search.prefix.clipboard) ? 'content_paste_search' : 'search' text: root.searchingText.startsWith(Config.options.search.prefix.clipboard) ? 'content_paste_search' : 'search'
} }
TextField { // Search box TextField { // Search box
id: searchInput id: searchInput
@@ -294,8 +294,8 @@ Item { // Wrapper
if(root.searchingText == "") return []; if(root.searchingText == "") return [];
///////////// Special cases /////////////// ///////////// Special cases ///////////////
if (root.searchingText.startsWith(ConfigOptions.search.prefix.clipboard)) { // Clipboard if (root.searchingText.startsWith(Config.options.search.prefix.clipboard)) { // Clipboard
const searchString = root.searchingText.slice(ConfigOptions.search.prefix.clipboard.length); const searchString = root.searchingText.slice(Config.options.search.prefix.clipboard.length);
return Cliphist.fuzzyQuery(searchString).map(entry => { return Cliphist.fuzzyQuery(searchString).map(entry => {
return { return {
cliphistRawString: entry, cliphistRawString: entry,
@@ -310,8 +310,8 @@ Item { // Wrapper
}; };
}).filter(Boolean); }).filter(Boolean);
} }
if (root.searchingText.startsWith(ConfigOptions.search.prefix.emojis)) { // Clipboard if (root.searchingText.startsWith(Config.options.search.prefix.emojis)) { // Clipboard
const searchString = root.searchingText.slice(ConfigOptions.search.prefix.emojis.length); const searchString = root.searchingText.slice(Config.options.search.prefix.emojis.length);
return Emojis.fuzzyQuery(searchString).map(entry => { return Emojis.fuzzyQuery(searchString).map(entry => {
return { return {
cliphistRawString: entry, cliphistRawString: entry,
@@ -346,12 +346,12 @@ Item { // Wrapper
fontType: "monospace", fontType: "monospace",
materialSymbol: 'terminal', materialSymbol: 'terminal',
execute: () => { execute: () => {
executor.executeCommand(searchingText.startsWith('sudo') ? `${ConfigOptions.apps.terminal} fish -C '${root.searchingText.replace("file://", "")}'` : root.searchingText.replace("file://", "")); executor.executeCommand(searchingText.startsWith('sudo') ? `${Config.options.apps.terminal} fish -C '${root.searchingText.replace("file://", "")}'` : root.searchingText.replace("file://", ""));
} }
} }
const launcherActionObjects = root.searchActions const launcherActionObjects = root.searchActions
.map(action => { .map(action => {
const actionString = `${ConfigOptions.search.prefix.action}${action.action}`; const actionString = `${Config.options.search.prefix.action}${action.action}`;
if (actionString.startsWith(root.searchingText) || root.searchingText.startsWith(actionString)) { if (actionString.startsWith(root.searchingText) || root.searchingText.startsWith(actionString)) {
return { return {
name: root.searchingText.startsWith(actionString) ? root.searchingText : actionString, name: root.searchingText.startsWith(actionString) ? root.searchingText : actionString,
@@ -399,8 +399,8 @@ Item { // Wrapper
type: Translation.tr("Search the web"), type: Translation.tr("Search the web"),
materialSymbol: 'travel_explore', materialSymbol: 'travel_explore',
execute: () => { execute: () => {
let url = ConfigOptions.search.engineBaseUrl + root.searchingText let url = Config.options.search.engineBaseUrl + root.searchingText
for (let site of ConfigOptions.search.excludedSites) { for (let site of Config.options.search.excludedSites) {
url += ` -site:${site}`; url += ` -site:${site}`;
} }
Qt.openUrlExternally(url); Qt.openUrlExternally(url);
@@ -416,8 +416,8 @@ Item { // Wrapper
anchors.left: parent?.left anchors.left: parent?.left
anchors.right: parent?.right anchors.right: parent?.right
entry: modelData entry: modelData
query: root.searchingText.startsWith(ConfigOptions.search.prefix.clipboard) ? query: root.searchingText.startsWith(Config.options.search.prefix.clipboard) ?
root.searchingText.slice(ConfigOptions.search.prefix.clipboard.length) : root.searchingText.slice(Config.options.search.prefix.clipboard.length) :
root.searchingText; root.searchingText;
} }
} }
@@ -15,8 +15,8 @@ Scope {
model: Quickshell.screens model: Quickshell.screens
PanelWindow { PanelWindow {
visible: (ConfigOptions.appearance.fakeScreenRounding === 1 visible: (Config.options.appearance.fakeScreenRounding === 1
|| (ConfigOptions.appearance.fakeScreenRounding === 2 || (Config.options.appearance.fakeScreenRounding === 2
&& !activeWindow?.fullscreen)) && !activeWindow?.fullscreen))
property var modelData property var modelData
@@ -56,28 +56,28 @@ Scope {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
size: Appearance.rounding.screenRounding size: Appearance.rounding.screenRounding
corner: cornerEnum.topLeft corner: RoundCorner.CornerEnum.TopLeft
} }
RoundCorner { RoundCorner {
id: topRightCorner id: topRightCorner
anchors.top: parent.top anchors.top: parent.top
anchors.right: parent.right anchors.right: parent.right
size: Appearance.rounding.screenRounding size: Appearance.rounding.screenRounding
corner: cornerEnum.topRight corner: RoundCorner.CornerEnum.TopRight
} }
RoundCorner { RoundCorner {
id: bottomLeftCorner id: bottomLeftCorner
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.left: parent.left anchors.left: parent.left
size: Appearance.rounding.screenRounding size: Appearance.rounding.screenRounding
corner: cornerEnum.bottomLeft corner: RoundCorner.CornerEnum.BottomLeft
} }
RoundCorner { RoundCorner {
id: bottomRightCorner id: bottomRightCorner
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.right: parent.right anchors.right: parent.right
size: Appearance.rounding.screenRounding size: Appearance.rounding.screenRounding
corner: cornerEnum.bottomRight corner: RoundCorner.CornerEnum.BottomRight
} }
} }
@@ -122,7 +122,7 @@ Scope {
id: sessionTaskManager id: sessionTaskManager
buttonIcon: "browse_activity" buttonIcon: "browse_activity"
buttonText: Translation.tr("Task Manager") buttonText: Translation.tr("Task Manager")
onClicked: { Quickshell.execDetached(["bash", "-c", `${ConfigOptions.apps.taskManager}`]); sessionRoot.hide() } onClicked: { Quickshell.execDetached(["bash", "-c", `${Config.options.apps.taskManager}`]); sessionRoot.hide() }
onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText }
KeyNavigation.left: sessionLogout KeyNavigation.left: sessionLogout
KeyNavigation.down: sessionFirmwareReboot KeyNavigation.down: sessionFirmwareReboot
@@ -16,10 +16,10 @@ ContentPage {
text: "Weeb" text: "Weeb"
} }
ConfigSelectionArray { ConfigSelectionArray {
currentValue: ConfigOptions.policies.weeb currentValue: Config.options.policies.weeb
configOptionName: "policies.weeb" configOptionName: "policies.weeb"
onSelected: (newValue) => { onSelected: (newValue) => {
ConfigLoader.setConfigValueAndSave("policies.weeb", newValue); Config.options.policies.weeb = newValue;
} }
options: [ options: [
{ displayName: "No", value: 0 }, { displayName: "No", value: 0 },
@@ -34,10 +34,10 @@ ContentPage {
text: "AI" text: "AI"
} }
ConfigSelectionArray { ConfigSelectionArray {
currentValue: ConfigOptions.policies.ai currentValue: Config.options.policies.ai
configOptionName: "policies.ai" configOptionName: "policies.ai"
onSelected: (newValue) => { onSelected: (newValue) => {
ConfigLoader.setConfigValueAndSave("policies.ai", newValue); Config.options.policies.ai = newValue;
} }
options: [ options: [
{ displayName: "No", value: 0 }, { displayName: "No", value: 0 },
@@ -52,22 +52,35 @@ ContentPage {
ContentSection { ContentSection {
title: "Bar" title: "Bar"
ConfigSelectionArray {
currentValue: Config.options.bar.cornerStyle
configOptionName: "bar.cornerStyle"
onSelected: (newValue) => {
Config.options.bar.cornerStyle = newValue;
}
options: [
{ displayName: "Hug", value: 0 },
{ displayName: "Float", value: 1 },
{ displayName: "Plain rectangle", value: 2 }
]
}
ContentSubsection { ContentSubsection {
title: "Appearance" title: "Appearance"
ConfigRow { ConfigRow {
uniform: true uniform: true
ConfigSwitch { ConfigSwitch {
text: 'Borderless' text: 'Borderless'
checked: ConfigOptions.bar.borderless checked: Config.options.bar.borderless
onCheckedChanged: { onCheckedChanged: {
ConfigLoader.setConfigValueAndSave("bar.borderless", checked); Config.options.bar.borderless = checked;
} }
} }
ConfigSwitch { ConfigSwitch {
text: 'Show background' text: 'Show background'
checked: ConfigOptions.bar.showBackground checked: Config.options.bar.showBackground
onCheckedChanged: { onCheckedChanged: {
ConfigLoader.setConfigValueAndSave("bar.showBackground", checked); Config.options.bar.showBackground = checked;
} }
StyledToolTip { StyledToolTip {
content: "Note: turning off can hurt readability" content: "Note: turning off can hurt readability"
@@ -82,16 +95,16 @@ ContentPage {
uniform: true uniform: true
ConfigSwitch { ConfigSwitch {
text: "Screen snip" text: "Screen snip"
checked: ConfigOptions.bar.utilButtons.showScreenSnip checked: Config.options.bar.utilButtons.showScreenSnip
onCheckedChanged: { onCheckedChanged: {
ConfigLoader.setConfigValueAndSave("bar.utilButtons.showScreenSnip", checked); Config.options.bar.utilButtons.showScreenSnip = checked;
} }
} }
ConfigSwitch { ConfigSwitch {
text: "Color picker" text: "Color picker"
checked: ConfigOptions.bar.utilButtons.showColorPicker checked: Config.options.bar.utilButtons.showColorPicker
onCheckedChanged: { onCheckedChanged: {
ConfigLoader.setConfigValueAndSave("bar.utilButtons.showColorPicker", checked); Config.options.bar.utilButtons.showColorPicker = checked;
} }
} }
} }
@@ -99,16 +112,16 @@ ContentPage {
uniform: true uniform: true
ConfigSwitch { ConfigSwitch {
text: "Mic toggle" text: "Mic toggle"
checked: ConfigOptions.bar.utilButtons.showMicToggle checked: Config.options.bar.utilButtons.showMicToggle
onCheckedChanged: { onCheckedChanged: {
ConfigLoader.setConfigValueAndSave("bar.utilButtons.showMicToggle", checked); Config.options.bar.utilButtons.showMicToggle = checked;
} }
} }
ConfigSwitch { ConfigSwitch {
text: "Keyboard toggle" text: "Keyboard toggle"
checked: ConfigOptions.bar.utilButtons.showKeyboardToggle checked: Config.options.bar.utilButtons.showKeyboardToggle
onCheckedChanged: { onCheckedChanged: {
ConfigLoader.setConfigValueAndSave("bar.utilButtons.showKeyboardToggle", checked); Config.options.bar.utilButtons.showKeyboardToggle = checked;
} }
} }
} }
@@ -116,9 +129,9 @@ ContentPage {
uniform: true uniform: true
ConfigSwitch { ConfigSwitch {
text: "Dark/Light toggle" text: "Dark/Light toggle"
checked: ConfigOptions.bar.utilButtons.showDarkModeToggle checked: Config.options.bar.utilButtons.showDarkModeToggle
onCheckedChanged: { onCheckedChanged: {
ConfigLoader.setConfigValueAndSave("bar.utilButtons.showDarkModeToggle", checked); Config.options.bar.utilButtons.showDarkModeToggle = checked;
} }
} }
ConfigSwitch { ConfigSwitch {
@@ -136,37 +149,37 @@ ContentPage {
uniform: true uniform: true
ConfigSwitch { ConfigSwitch {
text: 'Show app icons' text: 'Show app icons'
checked: ConfigOptions.bar.workspaces.showAppIcons checked: Config.options.bar.workspaces.showAppIcons
onCheckedChanged: { onCheckedChanged: {
ConfigLoader.setConfigValueAndSave("bar.workspaces.showAppIcons", checked); Config.options.bar.workspaces.showAppIcons = checked;
} }
} }
ConfigSwitch { ConfigSwitch {
text: 'Always show numbers' text: 'Always show numbers'
checked: ConfigOptions.bar.workspaces.alwaysShowNumbers checked: Config.options.bar.workspaces.alwaysShowNumbers
onCheckedChanged: { onCheckedChanged: {
ConfigLoader.setConfigValueAndSave("bar.workspaces.alwaysShowNumbers", checked); Config.options.bar.workspaces.alwaysShowNumbers = checked;
} }
} }
} }
ConfigSpinBox { ConfigSpinBox {
text: "Workspaces shown" text: "Workspaces shown"
value: ConfigOptions.bar.workspaces.shown value: Config.options.bar.workspaces.shown
from: 1 from: 1
to: 30 to: 30
stepSize: 1 stepSize: 1
onValueChanged: { onValueChanged: {
ConfigLoader.setConfigValueAndSave("bar.workspaces.shown", value); Config.options.bar.workspaces.shown = value;
} }
} }
ConfigSpinBox { ConfigSpinBox {
text: "Number show delay when pressing Super (ms)" text: "Number show delay when pressing Super (ms)"
value: ConfigOptions.bar.workspaces.showNumberDelay value: Config.options.bar.workspaces.showNumberDelay
from: 0 from: 0
to: 1000 to: 1000
stepSize: 50 stepSize: 50
onValueChanged: { onValueChanged: {
ConfigLoader.setConfigValueAndSave("bar.workspaces.showNumberDelay", value); Config.options.bar.workspaces.showNumberDelay = value;
} }
} }
} }
@@ -179,22 +192,22 @@ ContentPage {
uniform: true uniform: true
ConfigSpinBox { ConfigSpinBox {
text: "Low warning" text: "Low warning"
value: ConfigOptions.battery.low value: Config.options.battery.low
from: 0 from: 0
to: 100 to: 100
stepSize: 5 stepSize: 5
onValueChanged: { onValueChanged: {
ConfigLoader.setConfigValueAndSave("battery.low", value); Config.options.battery.low = value;
} }
} }
ConfigSpinBox { ConfigSpinBox {
text: "Critical warning" text: "Critical warning"
value: ConfigOptions.battery.critical value: Config.options.battery.critical
from: 0 from: 0
to: 100 to: 100
stepSize: 5 stepSize: 5
onValueChanged: { onValueChanged: {
ConfigLoader.setConfigValueAndSave("battery.critical", value); Config.options.battery.critical = value;
} }
} }
} }
@@ -202,9 +215,9 @@ ContentPage {
uniform: true uniform: true
ConfigSwitch { ConfigSwitch {
text: "Automatic suspend" text: "Automatic suspend"
checked: ConfigOptions.battery.automaticSuspend checked: Config.options.battery.automaticSuspend
onCheckedChanged: { onCheckedChanged: {
ConfigLoader.setConfigValueAndSave("battery.automaticSuspend", checked); Config.options.battery.automaticSuspend = checked;
} }
StyledToolTip { StyledToolTip {
content: "Automatically suspends the system when battery is low" content: "Automatically suspends the system when battery is low"
@@ -212,12 +225,12 @@ ContentPage {
} }
ConfigSpinBox { ConfigSpinBox {
text: "Suspend at" text: "Suspend at"
value: ConfigOptions.battery.suspend value: Config.options.battery.suspend
from: 0 from: 0
to: 100 to: 100
stepSize: 5 stepSize: 5
onValueChanged: { onValueChanged: {
ConfigLoader.setConfigValueAndSave("battery.suspend", value); Config.options.battery.suspend = value;
} }
} }
} }
@@ -227,34 +240,34 @@ ContentPage {
title: "Overview" title: "Overview"
ConfigSpinBox { ConfigSpinBox {
text: "Scale (%)" text: "Scale (%)"
value: ConfigOptions.overview.scale * 100 value: Config.options.overview.scale * 100
from: 1 from: 1
to: 100 to: 100
stepSize: 1 stepSize: 1
onValueChanged: { onValueChanged: {
ConfigLoader.setConfigValueAndSave("overview.scale", value / 100); Config.options.overview.scale = value / 100;
} }
} }
ConfigRow { ConfigRow {
uniform: true uniform: true
ConfigSpinBox { ConfigSpinBox {
text: "Rows" text: "Rows"
value: ConfigOptions.overview.rows value: Config.options.overview.rows
from: 1 from: 1
to: 20 to: 20
stepSize: 1 stepSize: 1
onValueChanged: { onValueChanged: {
ConfigLoader.setConfigValueAndSave("overview.rows", value); Config.options.overview.rows = value;
} }
} }
ConfigSpinBox { ConfigSpinBox {
text: "Columns" text: "Columns"
value: ConfigOptions.overview.columns value: Config.options.overview.columns
from: 1 from: 1
to: 20 to: 20
stepSize: 1 stepSize: 1
onValueChanged: { onValueChanged: {
ConfigLoader.setConfigValueAndSave("overview.columns", value); Config.options.overview.columns = value;
} }
} }
} }
@@ -13,9 +13,9 @@ ContentPage {
ConfigSwitch { ConfigSwitch {
text: "Earbang protection" text: "Earbang protection"
checked: ConfigOptions.audio.protection.enable checked: Config.options.audio.protection.enable
onCheckedChanged: { onCheckedChanged: {
ConfigLoader.setConfigValueAndSave("audio.protection.enable", checked); Config.options.audio.protection.enable = checked;
} }
StyledToolTip { StyledToolTip {
content: "Prevents abrupt increments and restricts volume limit" content: "Prevents abrupt increments and restricts volume limit"
@@ -25,22 +25,22 @@ ContentPage {
// uniform: true // uniform: true
ConfigSpinBox { ConfigSpinBox {
text: "Max allowed increase" text: "Max allowed increase"
value: ConfigOptions.audio.protection.maxAllowedIncrease value: Config.options.audio.protection.maxAllowedIncrease
from: 0 from: 0
to: 100 to: 100
stepSize: 2 stepSize: 2
onValueChanged: { onValueChanged: {
ConfigLoader.setConfigValueAndSave("audio.protection.maxAllowedIncrease", value); Config.options.audio.protection.maxAllowedIncrease = value;
} }
} }
ConfigSpinBox { ConfigSpinBox {
text: "Volume limit" text: "Volume limit"
value: ConfigOptions.audio.protection.maxAllowed value: Config.options.audio.protection.maxAllowed
from: 0 from: 0
to: 100 to: 100
stepSize: 2 stepSize: 2
onValueChanged: { onValueChanged: {
ConfigLoader.setConfigValueAndSave("audio.protection.maxAllowed", value); Config.options.audio.protection.maxAllowed = value;
} }
} }
} }
@@ -50,10 +50,12 @@ ContentPage {
MaterialTextField { MaterialTextField {
Layout.fillWidth: true Layout.fillWidth: true
placeholderText: "System prompt" placeholderText: "System prompt"
text: ConfigOptions.ai.systemPrompt text: Config.options.ai.systemPrompt
wrapMode: TextEdit.Wrap wrapMode: TextEdit.Wrap
onTextChanged: { onTextChanged: {
ConfigLoader.setConfigValueAndSave("ai.systemPrompt", text); Qt.callLater(() => {
Config.options.ai.systemPrompt = text;
});
} }
} }
} }
@@ -65,22 +67,22 @@ ContentPage {
uniform: true uniform: true
ConfigSpinBox { ConfigSpinBox {
text: "Low warning" text: "Low warning"
value: ConfigOptions.battery.low value: Config.options.battery.low
from: 0 from: 0
to: 100 to: 100
stepSize: 5 stepSize: 5
onValueChanged: { onValueChanged: {
ConfigLoader.setConfigValueAndSave("battery.low", value); Config.options.battery.low = value;
} }
} }
ConfigSpinBox { ConfigSpinBox {
text: "Critical warning" text: "Critical warning"
value: ConfigOptions.battery.critical value: Config.options.battery.critical
from: 0 from: 0
to: 100 to: 100
stepSize: 5 stepSize: 5
onValueChanged: { onValueChanged: {
ConfigLoader.setConfigValueAndSave("battery.critical", value); Config.options.battery.critical = value;
} }
} }
} }
@@ -88,9 +90,9 @@ ContentPage {
uniform: true uniform: true
ConfigSwitch { ConfigSwitch {
text: "Automatic suspend" text: "Automatic suspend"
checked: ConfigOptions.battery.automaticSuspend checked: Config.options.battery.automaticSuspend
onCheckedChanged: { onCheckedChanged: {
ConfigLoader.setConfigValueAndSave("battery.automaticSuspend", checked); Config.options.battery.automaticSuspend = checked;
} }
StyledToolTip { StyledToolTip {
content: "Automatically suspends the system when battery is low" content: "Automatically suspends the system when battery is low"
@@ -98,12 +100,12 @@ ContentPage {
} }
ConfigSpinBox { ConfigSpinBox {
text: "Suspend at" text: "Suspend at"
value: ConfigOptions.battery.suspend value: Config.options.battery.suspend
from: 0 from: 0
to: 100 to: 100
stepSize: 5 stepSize: 5
onValueChanged: { onValueChanged: {
ConfigLoader.setConfigValueAndSave("battery.suspend", value); Config.options.battery.suspend = value;
} }
} }
} }
@@ -114,10 +116,10 @@ ContentPage {
MaterialTextField { MaterialTextField {
Layout.fillWidth: true Layout.fillWidth: true
placeholderText: "User agent (for services that require it)" placeholderText: "User agent (for services that require it)"
text: ConfigOptions.networking.userAgent text: Config.options.networking.userAgent
wrapMode: TextEdit.Wrap wrapMode: TextEdit.Wrap
onTextChanged: { onTextChanged: {
ConfigLoader.setConfigValueAndSave("networking.userAgent", text); Config.options.networking.userAgent = text;
} }
} }
} }
@@ -126,12 +128,12 @@ ContentPage {
title: "Resources" title: "Resources"
ConfigSpinBox { ConfigSpinBox {
text: "Polling interval (ms)" text: "Polling interval (ms)"
value: ConfigOptions.resources.updateInterval value: Config.options.resources.updateInterval
from: 100 from: 100
to: 10000 to: 10000
stepSize: 100 stepSize: 100
onValueChanged: { onValueChanged: {
ConfigLoader.setConfigValueAndSave("resources.updateInterval", value); Config.options.resources.updateInterval = value;
} }
} }
} }
@@ -46,10 +46,10 @@ ContentPage {
ContentSubsection { ContentSubsection {
title: "Material palette" title: "Material palette"
ConfigSelectionArray { ConfigSelectionArray {
currentValue: ConfigOptions.appearance.palette.type currentValue: Config.options.appearance.palette.type
configOptionName: "appearance.palette.type" configOptionName: "appearance.palette.type"
onSelected: (newValue) => { onSelected: (newValue) => {
ConfigLoader.setConfigValueAndSave("appearance.palette.type", newValue); Config.options.appearance.palette.type = newValue;
} }
options: [ options: [
{"value": "auto", "displayName": "Auto"}, {"value": "auto", "displayName": "Auto"},
@@ -141,9 +141,9 @@ ContentPage {
ConfigRow { ConfigRow {
ConfigSwitch { ConfigSwitch {
text: "Enable" text: "Enable"
checked: ConfigOptions.appearance.transparency checked: Config.options.appearance.transparency
onCheckedChanged: { onCheckedChanged: {
ConfigLoader.setConfigValueAndSave("appearance.transparency", checked); Config.options.appearance.transparency = checked;
} }
StyledToolTip { StyledToolTip {
content: "Might look ass. Unsupported." content: "Might look ass. Unsupported."
@@ -157,7 +157,7 @@ ContentPage {
ButtonGroup { ButtonGroup {
id: fakeScreenRoundingButtonGroup id: fakeScreenRoundingButtonGroup
property int selectedPolicy: ConfigOptions.appearance.fakeScreenRounding property int selectedPolicy: Config.options.appearance.fakeScreenRounding
spacing: 2 spacing: 2
SelectionGroupButton { SelectionGroupButton {
property int value: 0 property int value: 0
@@ -165,7 +165,7 @@ ContentPage {
buttonText: "No" buttonText: "No"
toggled: (fakeScreenRoundingButtonGroup.selectedPolicy === value) toggled: (fakeScreenRoundingButtonGroup.selectedPolicy === value)
onClicked: { onClicked: {
ConfigLoader.setConfigValueAndSave("appearance.fakeScreenRounding", value); Config.options.appearance.fakeScreenRounding = value;
} }
} }
SelectionGroupButton { SelectionGroupButton {
@@ -173,7 +173,7 @@ ContentPage {
buttonText: "Yes" buttonText: "Yes"
toggled: (fakeScreenRoundingButtonGroup.selectedPolicy === value) toggled: (fakeScreenRoundingButtonGroup.selectedPolicy === value)
onClicked: { onClicked: {
ConfigLoader.setConfigValueAndSave("appearance.fakeScreenRounding", value); Config.options.appearance.fakeScreenRounding = value;
} }
} }
SelectionGroupButton { SelectionGroupButton {
@@ -182,7 +182,7 @@ ContentPage {
buttonText: "When not fullscreen" buttonText: "When not fullscreen"
toggled: (fakeScreenRoundingButtonGroup.selectedPolicy === value) toggled: (fakeScreenRoundingButtonGroup.selectedPolicy === value)
onClicked: { onClicked: {
ConfigLoader.setConfigValueAndSave("appearance.fakeScreenRounding", value); Config.options.appearance.fakeScreenRounding = value;
} }
} }
} }
@@ -195,16 +195,16 @@ ContentPage {
uniform: true uniform: true
ConfigSwitch { ConfigSwitch {
text: "Title bar" text: "Title bar"
checked: ConfigOptions.windows.showTitlebar checked: Config.options.windows.showTitlebar
onCheckedChanged: { onCheckedChanged: {
ConfigLoader.setConfigValueAndSave("windows.showTitlebar", checked); Config.options.windows.showTitlebar = checked;
} }
} }
ConfigSwitch { ConfigSwitch {
text: "Center title" text: "Center title"
checked: ConfigOptions.windows.centerTitle checked: Config.options.windows.centerTitle
onCheckedChanged: { onCheckedChanged: {
ConfigLoader.setConfigValueAndSave("windows.centerTitle", checked); Config.options.windows.centerTitle = checked;
} }
} }
} }
@@ -50,10 +50,14 @@ Item {
} }
}, },
{ {
name: "clear", name: "prompt",
description: Translation.tr("Clear chat history"), description: Translation.tr("Set the system prompt for the model."),
execute: () => { execute: (args) => {
Ai.clearMessages(); if (args.length === 0 || args[0] === "get") {
Ai.printPrompt();
return;
}
Ai.loadPrompt(args.join(" ").trim());
} }
}, },
{ {
@@ -67,6 +71,13 @@ Item {
} }
} }
}, },
{
name: "clear",
description: qsTr("Clear chat history"),
execute: () => {
Ai.clearMessages();
}
},
{ {
name: "temp", name: "temp",
description: Translation.tr("Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5."), description: Translation.tr("Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5."),
@@ -250,33 +261,9 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
} }
} }
Item { // Suggestion description DescriptionBox {
visible: descriptionText.text.length > 0 text: root.suggestionList[suggestions.selectedIndex]?.description ?? ""
Layout.fillWidth: true showArrows: root.suggestionList.length > 1
implicitHeight: descriptionBackground.implicitHeight
Rectangle {
id: descriptionBackground
color: Appearance.colors.colTooltip
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
implicitHeight: descriptionText.implicitHeight + 5 * 2
radius: Appearance.rounding.verysmall
StyledText {
id: descriptionText
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 10
anchors.rightMargin: 10
anchors.verticalCenter: parent.verticalCenter
font.pixelSize: Appearance.font.pixelSize.smaller
color: Appearance.colors.colOnTooltip
wrapMode: Text.Wrap
text: root.suggestionList[suggestions.selectedIndex]?.description ?? ""
}
}
} }
FlowButtonGroup { // Suggestions FlowButtonGroup { // Suggestions
@@ -294,7 +281,7 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
} }
delegate: ApiCommandButton { delegate: ApiCommandButton {
id: commandButton id: commandButton
colBackground: suggestions.selectedIndex === index ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2 colBackground: suggestions.selectedIndex === index ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colSecondaryContainer
bounce: false bounce: false
contentItem: StyledText { contentItem: StyledText {
font.pixelSize: Appearance.font.pixelSize.small font.pixelSize: Appearance.font.pixelSize.small
@@ -393,6 +380,24 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
description: `${Ai.models[model.target].description}`, description: `${Ai.models[model.target].description}`,
} }
}) })
} else if(messageInputField.text.startsWith(`${root.commandPrefix}prompt`)) {
root.suggestionQuery = messageInputField.text.split(" ")[1] ?? ""
const promptFileResults = Fuzzy.go(root.suggestionQuery, Ai.promptFiles.map(file => {
return {
name: Fuzzy.prepare(file),
obj: file,
}
}), {
all: true,
key: "name"
})
root.suggestionList = promptFileResults.map(file => {
return {
name: `${messageInputField.text.trim().split(" ").length == 1 ? (root.commandPrefix + "prompt ") : ""}${file.target}`,
displayName: `${FileUtils.trimFileExt(FileUtils.fileNameForPath(file.target))}`,
description: `Load prompt from ${file.target}`,
}
})
} else if(messageInputField.text.startsWith(root.commandPrefix)) { } else if(messageInputField.text.startsWith(root.commandPrefix)) {
root.suggestionQuery = messageInputField.text root.suggestionQuery = messageInputField.text
root.suggestionList = root.allCommands.filter(cmd => cmd.name.startsWith(messageInputField.text.substring(1))).map(cmd => { root.suggestionList = root.allCommands.filter(cmd => cmd.name.startsWith(messageInputField.text.substring(1))).map(cmd => {
@@ -6,15 +6,11 @@ import "root:/modules/common/functions/fuzzysort.js" as Fuzzy
import "root:/modules/common/functions/string_utils.js" as StringUtils import "root:/modules/common/functions/string_utils.js" as StringUtils
import "root:/modules/common/functions/file_utils.js" as FileUtils import "root:/modules/common/functions/file_utils.js" as FileUtils
import "./anime/" import "./anime/"
import "root:/services/"
import Qt.labs.platform
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import Qt5Compat.GraphicalEffects import Qt5Compat.GraphicalEffects
import Quickshell.Io
import Quickshell import Quickshell
import Quickshell.Hyprland
Item { Item {
id: root id: root
@@ -66,14 +62,14 @@ Item {
name: "safe", name: "safe",
description: Translation.tr("Disable NSFW content"), description: Translation.tr("Disable NSFW content"),
execute: () => { execute: () => {
PersistentStateManager.setState("booru.allowNsfw", false); Persistent.states.booru.allowNsfw = false;
} }
}, },
{ {
name: "lewd", name: "lewd",
description: Translation.tr("Allow NSFW content"), description: Translation.tr("Allow NSFW content"),
execute: () => { execute: () => {
PersistentStateManager.setState("booru.allowNsfw", true); Persistent.states.booru.allowNsfw = true;
} }
}, },
] ]
@@ -107,7 +103,7 @@ Item {
break; break;
} }
} }
Booru.makeRequest(tagList, PersistentStates.booru.allowNsfw, ConfigOptions.sidebar.booru.limit, pageIndex); Booru.makeRequest(tagList, Persistent.states.booru.allowNsfw, Config.options.sidebar.booru.limit, pageIndex);
} }
} }
@@ -252,33 +248,9 @@ Item {
} }
} }
Item { // Tag suggestion description DescriptionBox { // Tag suggestion description
visible: tagDescriptionText.text.length > 0 text: root.suggestionList[tagSuggestions.selectedIndex]?.description ?? ""
Layout.fillWidth: true showArrows: root.suggestionList.length > 1
implicitHeight: tagDescriptionBackground.implicitHeight
Rectangle {
id: tagDescriptionBackground
color: Appearance.colors.colTooltip
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
implicitHeight: tagDescriptionText.implicitHeight + 5 * 2
radius: Appearance.rounding.verysmall
StyledText {
id: tagDescriptionText
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 10
anchors.rightMargin: 10
anchors.verticalCenter: parent.verticalCenter
font.pixelSize: Appearance.font.pixelSize.smaller
color: Appearance.colors.colOnTooltip
wrapMode: Text.Wrap
text: root.suggestionList[tagSuggestions.selectedIndex]?.description ?? ""
}
}
} }
FlowButtonGroup { // Tag suggestions FlowButtonGroup { // Tag suggestions
@@ -296,7 +268,7 @@ Item {
} }
delegate: ApiCommandButton { delegate: ApiCommandButton {
id: tagButton id: tagButton
colBackground: tagSuggestions.selectedIndex === index ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2 colBackground: tagSuggestions.selectedIndex === index ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colSecondaryContainer
bounce: false bounce: false
contentItem: RowLayout { contentItem: RowLayout {
anchors.centerIn: parent anchors.centerIn: parent
@@ -304,7 +276,7 @@ Item {
StyledText { StyledText {
Layout.fillWidth: false Layout.fillWidth: false
font.pixelSize: Appearance.font.pixelSize.small font.pixelSize: Appearance.font.pixelSize.small
color: Appearance.m3colors.m3onSurface color: Appearance.colors.colOnSecondaryContainer
horizontalAlignment: Text.AlignRight horizontalAlignment: Text.AlignRight
text: modelData.displayName ?? modelData.name text: modelData.displayName ?? modelData.name
} }
@@ -312,7 +284,7 @@ Item {
Layout.fillWidth: false Layout.fillWidth: false
visible: modelData.count !== undefined visible: modelData.count !== undefined
font.pixelSize: Appearance.font.pixelSize.smaller font.pixelSize: Appearance.font.pixelSize.smaller
color: Appearance.m3colors.m3outline color: Appearance.colors.colOnSecondaryContainer
horizontalAlignment: Text.AlignLeft horizontalAlignment: Text.AlignLeft
text: modelData.count ?? "" text: modelData.count ?? ""
} }
@@ -594,10 +566,10 @@ Item {
enabled: Booru.currentProvider !== "zerochan" enabled: Booru.currentProvider !== "zerochan"
scale: 0.6 scale: 0.6
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
checked: (PersistentStates.booru.allowNsfw && Booru.currentProvider !== "zerochan") checked: (Persistent.states.booru.allowNsfw && Booru.currentProvider !== "zerochan")
onCheckedChanged: { onCheckedChanged: {
if (!nsfwSwitch.enabled) return; if (!nsfwSwitch.enabled) return;
PersistentStateManager.setState("booru.allowNsfw", checked) Persistent.states.booru.allowNsfw = checked;
} }
} }
} }
@@ -611,7 +583,6 @@ Item {
id: commandRepeater id: commandRepeater
model: commandButtonsRow.commandsShown model: commandButtonsRow.commandsShown
delegate: ApiCommandButton { delegate: ApiCommandButton {
id: tagButton
property string commandRepresentation: `${root.commandPrefix}${modelData.name}` property string commandRepresentation: `${root.commandPrefix}${modelData.name}`
buttonText: commandRepresentation buttonText: commandRepresentation
colBackground: Appearance.colors.colLayer2 colBackground: Appearance.colors.colLayer2
@@ -16,7 +16,7 @@ GroupButton {
baseWidth: contentItem.implicitWidth + horizontalPadding * 2 baseWidth: contentItem.implicitWidth + horizontalPadding * 2
clickedWidth: baseWidth + 20 clickedWidth: baseWidth + 20
baseHeight: contentItem.implicitHeight + verticalPadding * 2 baseHeight: contentItem.implicitHeight + verticalPadding * 2
buttonRadius: down ? Appearance.rounding.small : baseHeight / 2 buttonRadius: down ? Appearance.rounding.verysmall : Appearance.rounding.small
colBackground: Appearance.colors.colLayer2 colBackground: Appearance.colors.colLayer2
colBackgroundHover: Appearance.colors.colLayer2Hover colBackgroundHover: Appearance.colors.colLayer2Hover
@@ -0,0 +1,61 @@
import "root:/services"
import "root:/modules/common"
import "root:/modules/common/widgets"
import QtQuick
import QtQuick.Layouts
Item { // Tag suggestion description
id: root
property alias text: tagDescriptionText.text
property bool showArrows: true
property bool showTab: true
visible: tagDescriptionText.text.length > 0
Layout.fillWidth: true
implicitHeight: tagDescriptionBackground.implicitHeight
Rectangle {
id: tagDescriptionBackground
color: Appearance.colors.colLayer2
anchors.fill: parent
radius: Appearance.rounding.verysmall
implicitHeight: descriptionRow.implicitHeight + 5 * 2
RowLayout {
id: descriptionRow
spacing: 4
anchors {
fill: parent
leftMargin: 10
rightMargin: 10
}
StyledText {
id: tagDescriptionText
Layout.fillWidth: true
font.pixelSize: Appearance.font.pixelSize.smaller
color: Appearance.colors.colOnLayer2
wrapMode: Text.Wrap
}
KeyboardKey {
visible: root.showArrows
key: "↑"
}
KeyboardKey {
visible: root.showArrows
key: "↓"
}
StyledText {
visible: root.showArrows && root.showTab
text: qsTr("or")
font.pixelSize: Appearance.font.pixelSize.smaller
}
KeyboardKey {
id: tagDescriptionKey
visible: root.showTab
key: "Tab"
Layout.alignment: Qt.AlignVCenter
}
}
}
}
@@ -19,9 +19,9 @@ Item {
required property var scopeRoot required property var scopeRoot
anchors.fill: parent anchors.fill: parent
property var tabButtonList: [ property var tabButtonList: [
...(ConfigOptions.policies.ai !== 0 ? [{"icon": "neurology", "name": Translation.tr("Intelligence")}] : []), ...(Config.options.policies.ai !== 0 ? [{"icon": "neurology", "name": qsTr("Intelligence")}] : []),
{"icon": "translate", "name": Translation.tr("Translator")}, {"icon": "translate", "name": qsTr("Translator")},
...(ConfigOptions.policies.weeb === 1 ? [{"icon": "bookmark_heart", "name": Translation.tr("Anime")}] : []) ...(Config.options.policies.weeb === 1 ? [{"icon": "bookmark_heart", "name": qsTr("Anime")}] : [])
] ]
property int selectedTab: 0 property int selectedTab: 0
@@ -89,9 +89,9 @@ Item {
} }
contentChildren: [ contentChildren: [
...(ConfigOptions.policies.ai !== 0 ? [aiChat.createObject()] : []), ...(Config.options.policies.ai !== 0 ? [aiChat.createObject()] : []),
translator.createObject(), translator.createObject(),
...(ConfigOptions.policies.weeb === 0 ? [] : [anime.createObject()]) ...(Config.options.policies.weeb === 0 ? [] : [anime.createObject()])
] ]
} }
@@ -23,8 +23,8 @@ Item {
property string translatedText: "" property string translatedText: ""
property list<string> languages: [] property list<string> languages: []
// Options // Options
property string targetLanguage: ConfigOptions.language.translator.targetLanguage property string targetLanguage: Config.options.language.translator.targetLanguage
property string sourceLanguage: ConfigOptions.language.translator.sourceLanguage property string sourceLanguage: Config.options.language.translator.sourceLanguage
property string hostLanguage: targetLanguage property string hostLanguage: targetLanguage
property bool showLanguageSelector: false property bool showLanguageSelector: false
@@ -43,7 +43,7 @@ Item {
Timer { Timer {
id: translateTimer id: translateTimer
interval: ConfigOptions.sidebar.translator.delay interval: Config.options.sidebar.translator.delay
repeat: false repeat: false
onTriggered: () => { onTriggered: () => {
if (root.inputField.text.trim().length > 0) { if (root.inputField.text.trim().length > 0) {
@@ -155,8 +155,8 @@ Item {
color: searchButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext color: searchButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
} }
onClicked: { onClicked: {
let url = ConfigOptions.search.engineBaseUrl + outputCanvas.displayedText; let url = Config.options.search.engineBaseUrl + outputCanvas.displayedText;
for (let site of ConfigOptions.search.excludedSites) { for (let site of Config.options.search.excludedSites) {
url += ` -site:${site}`; url += ` -site:${site}`;
} }
Qt.openUrlExternally(url); Qt.openUrlExternally(url);
@@ -235,10 +235,10 @@ Item {
if (root.languageSelectorTarget) { if (root.languageSelectorTarget) {
root.targetLanguage = result; root.targetLanguage = result;
ConfigLoader.setConfigValueAndSave("language.translator.targetLanguage", result); // Save to config Config.options.language.translator.targetLanguage = result; // Save to config
} else { } else {
root.sourceLanguage = result; root.sourceLanguage = result;
ConfigLoader.setConfigValueAndSave("language.translator.sourceLanguage", result); // Save to config Config.options.language.translator.sourceLanguage = result; // Save to config
} }
translateTimer.restart(); // Restart translation after language change translateTimer.restart(); // Restart translation after language change
@@ -15,7 +15,7 @@ Rectangle {
clip: true clip: true
implicitHeight: collapsed ? collapsedBottomWidgetGroupRow.implicitHeight : bottomWidgetGroupRow.implicitHeight implicitHeight: collapsed ? collapsedBottomWidgetGroupRow.implicitHeight : bottomWidgetGroupRow.implicitHeight
property int selectedTab: 0 property int selectedTab: 0
property bool collapsed: PersistentStates.sidebar.bottomGroup.collapsed property bool collapsed: Persistent.states.sidebar.bottomGroup.collapsed
property var tabs: [ property var tabs: [
{"type": "calendar", "name": "Calendar", "icon": "calendar_month", "widget": calendarWidget}, {"type": "calendar", "name": "Calendar", "icon": "calendar_month", "widget": calendarWidget},
{"type": "todo", "name": "To Do", "icon": "done_outline", "widget": todoWidget} {"type": "todo", "name": "To Do", "icon": "done_outline", "widget": todoWidget}
@@ -30,7 +30,7 @@ Rectangle {
} }
function setCollapsed(state) { function setCollapsed(state) {
PersistentStateManager.setState("sidebar.bottomGroup.collapsed", state) Persistent.states.sidebar.bottomGroup.collapsed = state
if (collapsed) { if (collapsed) {
bottomWidgetGroupRow.opacity = 0 bottomWidgetGroupRow.opacity = 0
} }
@@ -15,7 +15,7 @@ QuickToggleButton {
toggleBluetooth.running = true toggleBluetooth.running = true
} }
altAction: () => { altAction: () => {
Quickshell.execDetached(["bash", "-c", `${ConfigOptions.apps.bluetooth}`]) Quickshell.execDetached(["bash", "-c", `${Config.options.apps.bluetooth}`])
Hyprland.dispatch("global quickshell:sidebarRightClose") Hyprland.dispatch("global quickshell:sidebarRightClose")
} }
Process { Process {
@@ -24,7 +24,6 @@ QuickToggleButton {
running: true running: true
command: ["bash", "-c", `test "$(hyprctl getoption animations:enabled -j | jq ".int")" -ne 0`] command: ["bash", "-c", `test "$(hyprctl getoption animations:enabled -j | jq ".int")" -ne 0`]
onExited: (exitCode, exitStatus) => { onExited: (exitCode, exitStatus) => {
console.log("Game mode toggle exited with code:", exitCode, "and status:", exitStatus)
root.toggled = exitCode !== 0 // Inverted because enabled = nonzero exit root.toggled = exitCode !== 0 // Inverted because enabled = nonzero exit
} }
} }
@@ -16,7 +16,7 @@ QuickToggleButton {
toggleNetwork.running = true toggleNetwork.running = true
} }
altAction: () => { altAction: () => {
Quickshell.execDetached(["bash", "-c", `${Network.ethernet ? ConfigOptions.apps.networkEthernet : ConfigOptions.apps.network}`]) Quickshell.execDetached(["bash", "-c", `${Network.ethernet ? Config.options.apps.networkEthernet : Config.options.apps.network}`])
Hyprland.dispatch("global quickshell:sidebarRightClose") Hyprland.dispatch("global quickshell:sidebarRightClose")
} }
Process { Process {
@@ -17,6 +17,10 @@ Item {
property bool deviceSelectorInput property bool deviceSelectorInput
property int dialogMargins: 16 property int dialogMargins: 16
property PwNode selectedDevice property PwNode selectedDevice
readonly property list<PwNode> appPwNodes: Pipewire.nodes.values.filter((node) => {
// return node.type == "21" // Alternative, not as clean
return node.isSink && node.isStream
})
function showDeviceSelectorDialog(input: bool) { function showDeviceSelectorDialog(input: bool) {
root.selectedDevice = null root.selectedDevice = null
@@ -60,21 +64,13 @@ Item {
anchors.margins: 10 anchors.margins: 10
spacing: 10 spacing: 10
// Get a list of nodes that output to the default sink
PwNodeLinkTracker {
id: linkTracker
node: Pipewire.defaultAudioSink
}
Repeater { Repeater {
model: linkTracker.linkGroups model: root.appPwNodes
VolumeMixerEntry { VolumeMixerEntry {
Layout.fillWidth: true Layout.fillWidth: true
// Get links to the default sinnk required property var modelData
required property PwLinkGroup modelData node: modelData
// Consider sources that output to the default sink
node: modelData.source
} }
} }
} }
@@ -85,7 +81,7 @@ Item {
anchors.fill: flickable anchors.fill: flickable
visible: opacity > 0 visible: opacity > 0
opacity: (linkTracker.linkGroups.length === 0) ? 1 : 0 opacity: (root.appPwNodes.length === 0) ? 1 : 0
Behavior on opacity { Behavior on opacity {
NumberAnimation { NumberAnimation {
+534
View File
@@ -0,0 +1,534 @@
//@ pragma UseQApplication
//@ pragma Env QS_NO_RELOAD_POPUP=1
//@ pragma Env QT_QUICK_CONTROLS_STYLE=Basic
// Adjust this to make it smaller or larger
//@ pragma Env QT_SCALE_FACTOR=1
pragma ComponentBehavior: "Bound"
import "./modules/common/"
import "./modules/common/widgets"
import "./modules/common/functions/string_utils.js" as StringUtils
import QtQuick
import QtQuick.Effects
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Hyprland
import "./services/"
ShellRoot {
id: root
property string screenshotDir: "/tmp/quickshell/media/screenshot"
property color overlayColor: "#77111111"
property color genericContentColor: Qt.alpha(root.overlayColor, 0.9)
property color genericContentForeground: "#ddffffff"
property color selectionBorderColor: "#ddf1f1f1"
property color selectionFillColor: "#33ffffff"
property color windowBorderColor: "#dda0c0da"
property color windowFillColor: "#22a0c0da"
property color imageBorderColor: "#ddf1d1ff"
property color imageFillColor: "#33f1d1ff"
property color onBorderColor: "#ff000000"
property real standardRounding: 4
readonly property var windows: HyprlandData.windowList
readonly property var layers: HyprlandData.layers
readonly property real falsePositivePreventionRatio: 0.5
// Force initialization of some singletons
Component.onCompleted: {
MaterialThemeLoader.reapplyTheme();
}
component TargetRegion: Rectangle {
id: regionRect
property bool targeted: false
property color borderColor
property color fillColor: "transparent"
property string text: ""
property real textPadding: 10
z: 2
color: fillColor
border.color: borderColor
border.width: targeted ? 3 : 1
radius: root.standardRounding
Rectangle {
id: regionLabelBackground
property real verticalPadding: 5
property real horizontalPadding: 10
radius: 10
color: root.genericContentColor
border.width: 1
border.color: Appearance.m3colors.m3outlineVariant
anchors {
top: parent.top
left: parent.left
topMargin: regionRect.textPadding
leftMargin: regionRect.textPadding
}
implicitWidth: regionText.implicitWidth + horizontalPadding * 2
implicitHeight: regionText.implicitHeight + verticalPadding * 2
StyledText {
id: regionText
text: regionRect.text
color: root.genericContentForeground
anchors.centerIn: parent
}
}
}
Variants {
model: Quickshell.screens
PanelWindow {
id: panelWindow
required property var modelData
readonly property HyprlandMonitor hyprlandMonitor: Hyprland.monitorFor(modelData)
readonly property real monitorScale: hyprlandMonitor.scale
readonly property real monitorOffsetX: hyprlandMonitor.x
readonly property real monitorOffsetY: hyprlandMonitor.y
property int activeWorkspaceId: hyprlandMonitor.activeWorkspace?.id ?? 0
property string screenshotPath: `${root.screenshotDir}/image-${modelData.name}`
property real dragStartX: 0
property real dragStartY: 0
property real draggingX: 0
property real draggingY: 0
property real dragDiffX: 0
property real dragDiffY: 0
property bool draggedAway: (dragDiffX !== 0 || dragDiffY !== 0)
property bool dragging: false
property var mouseButton: null
property var imageRegions: []
readonly property list<var> windowRegions: filterWindowRegionsByLayers(
root.windows.filter(w => w.workspace.id === panelWindow.activeWorkspaceId),
panelWindow.layerRegions
).map(window => {
return {
at: [window.at[0] - panelWindow.monitorOffsetX, window.at[1] - panelWindow.monitorOffsetY],
size: [window.size[0], window.size[1]],
class: window.class,
title: window.title,
}
})
readonly property list<var> layerRegions: {
const layersOfThisMonitor = root.layers[panelWindow.hyprlandMonitor.name]
const topLayers = layersOfThisMonitor.levels["2"]
const nonBarTopLayers = topLayers
.filter(layer => !(layer.namespace.includes(":bar")))
.map(layer => {
return {
at: [layer.x, layer.y],
size: [layer.w, layer.h],
namespace: layer.namespace,
}
})
const offsetAdjustedLayers = nonBarTopLayers.map(layer => {
return {
at: [layer.at[0] - panelWindow.monitorOffsetX, layer.at[1] - panelWindow.monitorOffsetY],
size: layer.size,
namespace: layer.namespace,
}
});
return offsetAdjustedLayers;
}
property real targetedRegionX: -1
property real targetedRegionY: -1
property real targetedRegionWidth: 0
property real targetedRegionHeight: 0
function intersectionOverUnion(regionA, regionB) {
// region: { at: [x, y], size: [w, h] }
const ax1 = regionA.at[0], ay1 = regionA.at[1];
const ax2 = ax1 + regionA.size[0], ay2 = ay1 + regionA.size[1];
const bx1 = regionB.at[0], by1 = regionB.at[1];
const bx2 = bx1 + regionB.size[0], by2 = by1 + regionB.size[1];
const interX1 = Math.max(ax1, bx1);
const interY1 = Math.max(ay1, by1);
const interX2 = Math.min(ax2, bx2);
const interY2 = Math.min(ay2, by2);
const interArea = Math.max(0, interX2 - interX1) * Math.max(0, interY2 - interY1);
const areaA = (ax2 - ax1) * (ay2 - ay1);
const areaB = (bx2 - bx1) * (by2 - by1);
const unionArea = areaA + areaB - interArea;
return unionArea > 0 ? interArea / unionArea : 0;
}
function filterOverlappingImageRegions(regions) {
let keep = [];
let removed = new Set();
for (let i = 0; i < regions.length; ++i) {
if (removed.has(i)) continue;
let regionA = regions[i];
for (let j = i + 1; j < regions.length; ++j) {
if (removed.has(j)) continue;
let regionB = regions[j];
if (intersectionOverUnion(regionA, regionB) > 0) {
// Compare areas
let areaA = regionA.size[0] * regionA.size[1];
let areaB = regionB.size[0] * regionB.size[1];
if (areaA <= areaB) {
removed.add(j);
} else {
removed.add(i);
}
}
}
}
for (let i = 0; i < regions.length; ++i) {
if (!removed.has(i)) keep.push(regions[i]);
}
return keep;
}
function filterWindowRegionsByLayers(windowRegions, layerRegions) {
return windowRegions.filter(windowRegion => {
for (let i = 0; i < layerRegions.length; ++i) {
if (intersectionOverUnion(windowRegion, layerRegions[i]) > 0)
return false;
}
return true;
});
}
function filterImageRegions(regions, windowRegions, threshold = 0.1) {
// Remove image regions that overlap too much with any window region
let filtered = regions.filter(region => {
for (let i = 0; i < windowRegions.length; ++i) {
if (intersectionOverUnion(region, windowRegions[i]) > threshold)
return false;
}
return true;
});
// Remove overlapping image regions, keep only the smaller one
return filterOverlappingImageRegions(filtered);
}
function updateTargetedRegion(x, y) {
// Image regions
const clickedRegion = panelWindow.imageRegions.find(region => {
return region.at[0] <= x && x <= region.at[0] + region.size[0] && region.at[1] <= y && y <= region.at[1] + region.size[1];
});
if (clickedRegion) {
panelWindow.targetedRegionX = clickedRegion.at[0];
panelWindow.targetedRegionY = clickedRegion.at[1];
panelWindow.targetedRegionWidth = clickedRegion.size[0];
panelWindow.targetedRegionHeight = clickedRegion.size[1];
return;
}
// Layer regions
const clickedLayer = panelWindow.layerRegions.find(region => {
return region.at[0] <= x && x <= region.at[0] + region.size[0] && region.at[1] <= y && y <= region.at[1] + region.size[1];
});
if (clickedLayer) {
panelWindow.targetedRegionX = clickedLayer.at[0];
panelWindow.targetedRegionY = clickedLayer.at[1];
panelWindow.targetedRegionWidth = clickedLayer.size[0];
panelWindow.targetedRegionHeight = clickedLayer.size[1];
return;
}
// Window regions
const clickedWindow = panelWindow.windowRegions.find(region => {
return region.at[0] <= x && x <= region.at[0] + region.size[0] && region.at[1] <= y && y <= region.at[1] + region.size[1];
});
if (clickedWindow) {
panelWindow.targetedRegionX = clickedWindow.at[0];
panelWindow.targetedRegionY = clickedWindow.at[1];
panelWindow.targetedRegionWidth = clickedWindow.size[0];
panelWindow.targetedRegionHeight = clickedWindow.size[1];
return;
}
panelWindow.targetedRegionX = -1;
panelWindow.targetedRegionY = -1;
panelWindow.targetedRegionWidth = 0;
panelWindow.targetedRegionHeight = 0;
}
property real regionWidth: Math.abs(draggingX - dragStartX)
property real regionHeight: Math.abs(draggingY - dragStartY)
property real regionX: Math.min(dragStartX, draggingX)
property real regionY: Math.min(dragStartY, draggingY)
visible: false
screen: modelData
WlrLayershell.namespace: "quickshell:screenshot"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
exclusionMode: ExclusionMode.Ignore
anchors {
left: true
right: true
top: true
bottom: true
}
Process {
id: screenshotProcess
running: true
command: ["bash", "-c", `mkdir -p '${StringUtils.shellSingleQuoteEscape(root.screenshotDir)}' && grim -o '${StringUtils.shellSingleQuoteEscape(modelData.name)}' '${StringUtils.shellSingleQuoteEscape(panelWindow.screenshotPath)}'`]
onExited: (exitCode, exitStatus) => {
panelWindow.visible = true;
imageDetectionProcess.running = true;
}
}
Process {
id: imageDetectionProcess
command: ["bash", "-c", `${Directories.scriptPath}/images/find_regions.py `
+ `--hyprctl `
+ `--image '${StringUtils.shellSingleQuoteEscape(panelWindow.screenshotPath)}' `
+ `--max-width ${Math.round(panelWindow.screen.width * root.falsePositivePreventionRatio)} `
+ `--max-height ${Math.round(panelWindow.screen.height * root.falsePositivePreventionRatio)} `]
stdout: StdioCollector {
id: imageDimensionCollector
onStreamFinished: {
imageRegions = filterImageRegions(
JSON.parse(imageDimensionCollector.text),
panelWindow.windowRegions
);
}
}
}
Process {
id: snipProc
function snip() {
if (panelWindow.regionWidth <= 0 || panelWindow.regionHeight <= 0) {
console.warn("Invalid region size, skipping snip.");
Qt.quit();
}
snipProc.startDetached();
Qt.quit();
}
command: ["bash", "-c",
`magick ${StringUtils.shellSingleQuoteEscape(panelWindow.screenshotPath)} `
+ `-crop ${panelWindow.regionWidth * panelWindow.monitorScale}x${panelWindow.regionHeight * panelWindow.monitorScale}+${panelWindow.regionX * panelWindow.monitorScale}+${panelWindow.regionY * panelWindow.monitorScale} - `
+ `| ${panelWindow.mouseButton === Qt.LeftButton ? "wl-copy" : "swappy -f -"}`]
}
ScreencopyView {
anchors.fill: parent
live: false
captureSource: modelData
focus: panelWindow.visible
Keys.onPressed: (event) => { // Esc to close
if (event.key === Qt.Key_Escape) {
Qt.quit();
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.CrossCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton
hoverEnabled: true
// Controls
onPressed: mouse => {
panelWindow.dragStartX = mouse.x;
panelWindow.dragStartY = mouse.y;
panelWindow.draggingX = mouse.x;
panelWindow.draggingY = mouse.y;
panelWindow.dragging = true;
panelWindow.mouseButton = mouse.button;
}
onReleased: mouse => {
// Detect if it was a click
// Image regions
if (panelWindow.draggingX === panelWindow.dragStartX && panelWindow.draggingY === panelWindow.dragStartY) {
if (panelWindow.targetedRegionX >= 0 && panelWindow.targetedRegionY >= 0) {
panelWindow.regionX = panelWindow.targetedRegionX;
panelWindow.regionY = panelWindow.targetedRegionY;
panelWindow.regionWidth = panelWindow.targetedRegionWidth;
panelWindow.regionHeight = panelWindow.targetedRegionHeight;
}
}
snipProc.snip();
}
onPositionChanged: mouse => {
if (panelWindow.dragging) {
panelWindow.draggingX = mouse.x;
panelWindow.draggingY = mouse.y;
panelWindow.dragDiffX = mouse.x - panelWindow.dragStartX;
panelWindow.dragDiffY = mouse.y - panelWindow.dragStartY;
}
panelWindow.updateTargetedRegion(mouse.x, mouse.y);
}
// Overlay to darken screen
Rectangle { // Base
id: overlayRect
z: 0
anchors.fill: parent
color: root.overlayColor
layer.enabled: true
}
Rectangle {
// TODO: Make this mask the base instead of just overlaying a border
z: 1
anchors {
left: parent.left
top: parent.top
leftMargin: panelWindow.regionX
topMargin: panelWindow.regionY
}
width: panelWindow.regionWidth
height: panelWindow.regionHeight
color: "transparent"
border.color: root.selectionBorderColor
border.width: 2
radius: root.standardRounding
}
// Instructions
Rectangle {
anchors {
top: parent.top
horizontalCenter: parent.horizontalCenter
topMargin: (Appearance.sizes.barHeight - implicitHeight) / 2
}
opacity: panelWindow.dragging ? 0 : 1
visible: opacity > 0
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
color: root.genericContentColor
radius: 10
border.width: 1
border.color: Appearance.m3colors.m3outlineVariant
implicitWidth: instructionsRow.implicitWidth + 10 * 2
implicitHeight: instructionsRow.implicitHeight + 5 * 2
RowLayout {
id: instructionsRow
anchors.centerIn: parent
Item {
Layout.fillHeight: true
implicitWidth: screenshotRegionIcon.implicitWidth
MaterialSymbol {
id: screenshotRegionIcon
anchors.centerIn: parent
iconSize: Appearance.font.pixelSize.larger
text: "screenshot_region"
color: root.genericContentForeground
}
}
StyledText {
text: "Drag or click a region • LMB: Copy • RMB: Edit"
color: root.genericContentForeground
}
}
}
// Window regions
Repeater {
model: ScriptModel {
values: panelWindow.windowRegions
}
delegate: TargetRegion {
z: 2
required property var modelData
targeted: !panelWindow.draggedAway &&
(panelWindow.targetedRegionX === modelData.at[0]
&& panelWindow.targetedRegionY === modelData.at[1]
&& panelWindow.targetedRegionWidth === modelData.size[0]
&& panelWindow.targetedRegionHeight === modelData.size[1])
opacity: panelWindow.draggedAway ? 0 : 1
visible: opacity > 0
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
x: modelData.at[0]
y: modelData.at[1]
width: modelData.size[0]
height: modelData.size[1]
borderColor: root.windowBorderColor
fillColor: targeted ? root.windowFillColor : "transparent"
border.width: targeted ? 4 : 2
text: `${modelData.class}`
radius: Appearance.rounding.windowRounding
}
}
// Layer regions
Repeater {
model: ScriptModel {
values: panelWindow.layerRegions
}
delegate: TargetRegion {
z: 3
required property var modelData
targeted: !panelWindow.draggedAway &&
(panelWindow.targetedRegionX === modelData.at[0]
&& panelWindow.targetedRegionY === modelData.at[1]
&& panelWindow.targetedRegionWidth === modelData.size[0]
&& panelWindow.targetedRegionHeight === modelData.size[1])
opacity: panelWindow.draggedAway ? 0 : 1
visible: opacity > 0
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
x: modelData.at[0]
y: modelData.at[1]
width: modelData.size[0]
height: modelData.size[1]
borderColor: root.windowBorderColor
fillColor: targeted ? root.windowFillColor : "transparent"
border.width: targeted ? 4 : 2
text: `${modelData.namespace}`
radius: Appearance.rounding.windowRounding
}
}
// Image regions
Repeater {
model: ScriptModel {
values: panelWindow.imageRegions
}
delegate: TargetRegion {
z: 4
required property var modelData
targeted: !panelWindow.draggedAway &&
(panelWindow.targetedRegionX === modelData.at[0]
&& panelWindow.targetedRegionY === modelData.at[1]
&& panelWindow.targetedRegionWidth === modelData.size[0]
&& panelWindow.targetedRegionHeight === modelData.size[1])
opacity: panelWindow.draggedAway ? 0 : 1
visible: opacity > 0
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
x: modelData.at[0]
y: modelData.at[1]
width: modelData.size[0]
height: modelData.size[1]
borderColor: root.imageBorderColor
fillColor: targeted ? root.imageFillColor : "transparent"
border.width: targeted ? 4 : 2
text: "Content region"
}
}
}
}
}
}
}
@@ -237,7 +237,7 @@ switch() {
[[ -n "$mode_flag" ]] && matugen_args+=(--mode "$mode_flag") && generate_colors_material_args+=(--mode "$mode_flag") [[ -n "$mode_flag" ]] && matugen_args+=(--mode "$mode_flag") && generate_colors_material_args+=(--mode "$mode_flag")
[[ -n "$type_flag" ]] && matugen_args+=(--type "$type_flag") && generate_colors_material_args+=(--scheme "$type_flag") [[ -n "$type_flag" ]] && matugen_args+=(--type "$type_flag") && generate_colors_material_args+=(--scheme "$type_flag")
generate_colors_material_args+=(--termscheme "$terminalscheme" --blend_bg_fg) generate_colors_material_args+=(--termscheme "$terminalscheme" --blend_bg_fg)
generate_colors_material_args+=(--cache "$STATE_DIR/user/color.txt") generate_colors_material_args+=(--cache "$STATE_DIR/user/generated/color.txt")
pre_process "$mode_flag" pre_process "$mode_flag"
+120
View File
@@ -0,0 +1,120 @@
#!/usr/bin/env python3
import argparse
import cv2
import json
import numpy as np
import sys
DEFAULT_IMAGE_PATH = '/tmp/quickshell/media/screenshot/image'
def iou(boxA, boxB):
# Compute intersection over union for two boxes
xA = max(boxA['x'], boxB['x'])
yA = max(boxA['y'], boxB['y'])
xB = min(boxA['x'] + boxA['width'], boxB['x'] + boxB['width'])
yB = min(boxA['y'] + boxA['height'], boxB['y'] + boxB['height'])
interW = max(0, xB - xA)
interH = max(0, yB - yA)
interArea = interW * interH
boxAArea = boxA['width'] * boxA['height']
boxBArea = boxB['width'] * boxB['height']
iou = interArea / float(boxAArea + boxBArea - interArea) if (boxAArea + boxBArea - interArea) > 0 else 0
return iou
def non_max_suppression(regions, iou_threshold=0.7):
# Sort by area (largest first)
regions = sorted(regions, key=lambda r: r['width'] * r['height'], reverse=True)
keep = []
while regions:
current = regions.pop(0)
keep.append(current)
regions = [r for r in regions if iou(current, r) < iou_threshold]
return keep
def find_regions(image_path, min_width, min_height, max_width=None, max_height=None, quality=False, k=150, min_size=20, sigma=0.8, resize_factor=1.0):
image = cv2.imread(image_path)
if image is None:
print(f'Error: Could not load image {image_path}', file=sys.stderr)
sys.exit(1)
orig_h, orig_w = image.shape[:2]
if resize_factor != 1.0:
image = cv2.resize(image, (int(orig_w * resize_factor), int(orig_h * resize_factor)), interpolation=cv2.INTER_AREA)
ss = cv2.ximgproc.segmentation.createSelectiveSearchSegmentation()
ss.setBaseImage(image)
if quality:
ss.switchToSelectiveSearchQuality(k, min_size, sigma)
else:
ss.switchToSelectiveSearchFast(k, min_size, sigma)
rects = ss.process()
regions = []
for (x, y, w, h) in rects:
# Scale regions back to original image size if resized
if resize_factor != 1.0:
x = int(x / resize_factor)
y = int(y / resize_factor)
w = int(w / resize_factor)
h = int(h / resize_factor)
# Filter out region that is exactly the same size as the original image
if w == orig_w and h == orig_h and x == 0 and y == 0:
continue
if w > min_width and h > min_height:
if (max_width is None or w < max_width) and (max_height is None or h < max_height):
regions.append({'x': int(x), 'y': int(y), 'width': int(w), 'height': int(h)})
# Remove duplicates/overlaps
regions = non_max_suppression(regions, iou_threshold=0.7)
return regions, cv2.imread(image_path) # Return original image for drawing
def draw_regions(image, regions, output_path):
for region in regions:
if 'x' in region:
x, y, w, h = region['x'], region['y'], region['width'], region['height']
elif 'at' in region and 'size' in region:
x, y = region['at']
w, h = region['size']
else:
continue
cv2.rectangle(image, (x, y), (x + w, y + h), (0, 0, 255), 2)
cv2.imwrite(output_path, image)
def main():
parser = argparse.ArgumentParser(description='Find regions of interest in an image using selective search.')
parser.add_argument('-i', '--image', default=DEFAULT_IMAGE_PATH, help='Path to input image')
parser.add_argument('-do', '--debug-output', help='Path to save debug image with rectangles')
parser.add_argument('--min-width', type=int, default=200, help='Minimum width of detected region')
parser.add_argument('--min-height', type=int, default=100, help='Minimum height of detected region')
parser.add_argument('--max-width', type=int, help='Maximum width of detected region')
parser.add_argument('--max-height', type=int, help='Maximum height of detected region')
parser.add_argument('--single', action='store_true', help='Only output the most likely (largest) region')
parser.add_argument('--quality', action='store_true', help='Use quality mode for selective search (slower, less sensitive)')
parser.add_argument('--k', type=int, default=3000, help='Segmentation parameter k (default: 150)')
parser.add_argument('--min-size', type=int, default=50, help='Segmentation parameter min_size (default: 20)')
parser.add_argument('--sigma', type=float, default=0.6, help='Segmentation parameter sigma (default: 0.8)')
parser.add_argument('--resize-factor', type=float, default=0.1, help='Resize factor for input image before processing (default: 1.0, e.g. 0.5 for half size)')
parser.add_argument('--hyprctl', action='store_true', help='Mimics hyprctl\'s window output, like {"at": [x, y], "size": [w, h]}')
args = parser.parse_args()
regions, image = find_regions(
args.image,
min_width=args.min_width,
min_height=args.min_height,
max_width=args.max_width,
max_height=args.max_height,
quality=args.quality,
k=args.k,
min_size=args.min_size,
sigma=args.sigma,
resize_factor=args.resize_factor
)
if args.single and regions:
largest = max(regions, key=lambda r: r['width'] * r['height'])
regions = [largest]
if args.hyprctl:
regions = [{"at": [r['x'], r['y']], "size": [r['width'], r['height']]} for r in regions]
print(json.dumps(regions))
if args.debug_output:
draw_regions(image, regions, args.debug_output)
if __name__ == '__main__':
main()
+64 -13
View File
@@ -19,14 +19,14 @@ Singleton {
readonly property string interfaceRole: "interface" readonly property string interfaceRole: "interface"
readonly property string apiKeyEnvVarName: "API_KEY" readonly property string apiKeyEnvVarName: "API_KEY"
property Component aiMessageComponent: AiMessageData {} property Component aiMessageComponent: AiMessageData {}
property string systemPrompt: ConfigOptions?.ai?.systemPrompt ?? "" property string systemPrompt: Config.options?.ai?.systemPrompt ?? ""
property var messages: [] property var messages: []
property var messageIDs: [] property var messageIDs: []
property var messageByID: ({}) property var messageByID: ({})
readonly property var apiKeys: KeyringStorage.keyringData?.apiKeys ?? {} readonly property var apiKeys: KeyringStorage.keyringData?.apiKeys ?? {}
readonly property var apiKeysLoaded: KeyringStorage.loaded readonly property var apiKeysLoaded: KeyringStorage.loaded
property var postResponseHook property var postResponseHook
property real temperature: PersistentStates?.ai?.temperature ?? 0.5 property real temperature: Persistent.states?.ai?.temperature ?? 0.5
function idForMessage(message) { function idForMessage(message) {
// Generate a unique ID using timestamp and random value // Generate a unique ID using timestamp and random value
@@ -37,6 +37,10 @@ Singleton {
return modelName.replace(/:/g, "_").replace(/\./g, "_") return modelName.replace(/:/g, "_").replace(/\./g, "_")
} }
property list<var> defaultPrompts: []
property list<var> userPrompts: []
property list<var> promptFiles: [...defaultPrompts, ...userPrompts]
// Model properties: // Model properties:
// - name: Name of the model // - name: Name of the model
// - icon: Icon name of the model // - icon: Icon name of the model
@@ -203,11 +207,10 @@ Singleton {
}, },
} }
property var modelList: Object.keys(root.models) property var modelList: Object.keys(root.models)
property var currentModelId: PersistentStates?.ai?.model || modelList[0] property var currentModelId: Persistent.states?.ai?.model || modelList[0]
Component.onCompleted: { Component.onCompleted: {
setModel(currentModelId, false); // Do necessary setup for model setModel(currentModelId, false, false); // Do necessary setup for model
getOllamaModels.running = true
} }
function guessModelLogo(model) { function guessModelLogo(model) {
@@ -233,6 +236,7 @@ Singleton {
Process { Process {
id: getOllamaModels id: getOllamaModels
running: true
command: ["bash", "-c", `${Directories.config}/quickshell/scripts/ai/show-installed-ollama-models.sh`.replace(/file:\/\//, "")] command: ["bash", "-c", `${Directories.config}/quickshell/scripts/ai/show-installed-ollama-models.sh`.replace(/file:\/\//, "")]
stdout: SplitParser { stdout: SplitParser {
onRead: data => { onRead: data => {
@@ -261,6 +265,54 @@ Singleton {
} }
} }
Process {
id: getDefaultPrompts
running: true
command: ["ls", "-1", Directories.defaultAiPrompts]
stdout: StdioCollector {
onStreamFinished: {
if (text.length === 0) return;
root.defaultPrompts = text.split("\n")
.filter(fileName => fileName.endsWith(".md") || fileName.endsWith(".txt"))
.map(fileName => `${Directories.defaultAiPrompts}/${fileName}`)
}
}
}
Process {
id: getUserPrompts
running: true
command: ["ls", "-1", Directories.userAiPrompts]
stdout: StdioCollector {
onStreamFinished: {
if (text.length === 0) return;
root.userPrompts = text.split("\n")
.filter(fileName => fileName.endsWith(".md") || fileName.endsWith(".txt"))
.map(fileName => `${Directories.userAiPrompts}/${fileName}`)
}
}
}
FileView {
id: promptLoader
watchChanges: false;
onLoadedChanged: {
if (!promptLoader.loaded) return;
Config.options.ai.systemPrompt = promptLoader.text();
root.addMessage(StringUtils.format("Loaded the following system prompt\n\n---\n\n{0}", Config.options.ai.systemPrompt), root.interfaceRole);
}
}
function printPrompt() {
root.addMessage(StringUtils.format("The current system prompt is\n\n---\n\n{0}", Config.options.ai.systemPrompt), root.interfaceRole);
}
function loadPrompt(filePath) {
promptLoader.path = "" // Unload
promptLoader.path = filePath; // Load
promptLoader.reload();
}
function addMessage(message, role) { function addMessage(message, role) {
if (message.length === 0) return; if (message.length === 0) return;
const aiMessage = aiMessageComponent.createObject(root, { const aiMessage = aiMessageComponent.createObject(root, {
@@ -294,7 +346,7 @@ Singleton {
return models[currentModelId]; return models[currentModelId];
} }
function setModel(modelId, feedback = true) { function setModel(modelId, feedback = true, setPersistentState = true) {
if (!modelId) modelId = "" if (!modelId) modelId = ""
modelId = modelId.toLowerCase() modelId = modelId.toLowerCase()
if (modelList.indexOf(modelId) !== -1) { if (modelList.indexOf(modelId) !== -1) {
@@ -302,12 +354,12 @@ Singleton {
// Fetch API keys if needed // Fetch API keys if needed
if (model?.requires_key) KeyringStorage.fetchKeyringData(); if (model?.requires_key) KeyringStorage.fetchKeyringData();
// See if policy prevents online models // See if policy prevents online models
if (ConfigOptions.policies.ai === 2 && !model.endpoint.includes("localhost")) { if (Config.options.policies.ai === 2 && !model.endpoint.includes("localhost")) {
root.addMessage(StringUtils.format(StringUtils.format("Online models disallowed\n\nControlled by `policies.ai` config option"), model.name), root.interfaceRole); root.addMessage(StringUtils.format(StringUtils.format("Online models disallowed\n\nControlled by `policies.ai` config option"), model.name), root.interfaceRole);
return; return;
} }
PersistentStateManager.setState("ai.model", modelId); if (setPersistentState) Persistent.states.ai.model = modelId;
if (feedback) root.addMessage(StringUtils.format(StringUtils.format("Model set to {0}"), model.name), root.interfaceRole); if (feedback) root.addMessage(StringUtils.format("Model set to {0}", model.name), root.interfaceRole);
if (model.requires_key) { if (model.requires_key) {
// If key not there show advice // If key not there show advice
if (root.apiKeysLoaded && (!root.apiKeys[model.key_id] || root.apiKeys[model.key_id].length === 0)) { if (root.apiKeysLoaded && (!root.apiKeys[model.key_id] || root.apiKeys[model.key_id].length === 0)) {
@@ -328,7 +380,7 @@ Singleton {
root.addMessage(Translation.tr("Temperature must be between 0 and 2"), Ai.interfaceRole); root.addMessage(Translation.tr("Temperature must be between 0 and 2"), Ai.interfaceRole);
return; return;
} }
PersistentStateManager.setState("ai.temperature", value); Persistent.states.ai.temperature = value;
root.temperature = value; root.temperature = value;
root.addMessage(StringUtils.format(Translation.tr("Temperature set to {0}"), value), Ai.interfaceRole); root.addMessage(StringUtils.format(Translation.tr("Temperature set to {0}"), value), Ai.interfaceRole);
} }
@@ -705,7 +757,7 @@ Singleton {
addFunctionOutputMessage(name, Translation.tr("Switched to search mode. Continue with the user's request.")) addFunctionOutputMessage(name, Translation.tr("Switched to search mode. Continue with the user's request."))
requester.makeRequest(); requester.makeRequest();
} else if (name === "get_shell_config") { } else if (name === "get_shell_config") {
const configJson = ObjectUtils.toPlainObject(ConfigOptions) const configJson = ObjectUtils.toPlainObject(Config.options)
addFunctionOutputMessage(name, JSON.stringify(configJson)); addFunctionOutputMessage(name, JSON.stringify(configJson));
requester.makeRequest(); requester.makeRequest();
} else if (name === "set_shell_config") { } else if (name === "set_shell_config") {
@@ -715,8 +767,7 @@ Singleton {
} }
const key = args.key; const key = args.key;
const value = args.value; const value = args.value;
ConfigLoader.setLiveConfigValue(key, value); Config.setNestedValue(key, value);
ConfigLoader.saveConfig();
} }
else root.addMessage(Translation.tr("Unknown function call: {0}"), "assistant"); else root.addMessage(Translation.tr("Unknown function call: {0}"), "assistant");
} }
+1 -1
View File
@@ -12,7 +12,7 @@ import Quickshell.Io
*/ */
Singleton { Singleton {
id: root id: root
property bool sloppySearch: ConfigOptions?.search.sloppy ?? false property bool sloppySearch: Config.options?.search.sloppy ?? false
property real scoreThreshold: 0.2 property real scoreThreshold: 0.2
property var substitutions: ({ property var substitutions: ({
"code-url-handler": "visual-studio-code", "code-url-handler": "visual-studio-code",
+3 -3
View File
@@ -26,15 +26,15 @@ Singleton {
property bool lastReady: false property bool lastReady: false
property real lastVolume: 0 property real lastVolume: 0
function onVolumeChanged() { function onVolumeChanged() {
if (!ConfigOptions.audio.protection.enable) return; if (!Config.options.audio.protection.enable) return;
if (!lastReady) { if (!lastReady) {
lastVolume = sink.audio.volume; lastVolume = sink.audio.volume;
lastReady = true; lastReady = true;
return; return;
} }
const newVolume = sink.audio.volume; const newVolume = sink.audio.volume;
const maxAllowedIncrease = ConfigOptions.audio.protection.maxAllowedIncrease / 100; const maxAllowedIncrease = Config.options.audio.protection.maxAllowedIncrease / 100;
const maxAllowed = ConfigOptions.audio.protection.maxAllowed / 100; const maxAllowed = Config.options.audio.protection.maxAllowed / 100;
if (newVolume - lastVolume > maxAllowedIncrease) { if (newVolume - lastVolume > maxAllowedIncrease) {
sink.audio.volume = lastVolume; sink.audio.volume = lastVolume;
+5 -5
View File
@@ -12,11 +12,11 @@ Singleton {
property bool isCharging: chargeState == UPowerDeviceState.Charging property bool isCharging: chargeState == UPowerDeviceState.Charging
property bool isPluggedIn: isCharging || chargeState == UPowerDeviceState.PendingCharge property bool isPluggedIn: isCharging || chargeState == UPowerDeviceState.PendingCharge
property real percentage: UPower.displayDevice.percentage property real percentage: UPower.displayDevice.percentage
readonly property bool allowAutomaticSuspend: ConfigOptions.battery.automaticSuspend readonly property bool allowAutomaticSuspend: Config.options.battery.automaticSuspend
property bool isLow: percentage <= ConfigOptions.battery.low / 100 property bool isLow: percentage <= Config.options.battery.low / 100
property bool isCritical: percentage <= ConfigOptions.battery.critical / 100 property bool isCritical: percentage <= Config.options.battery.critical / 100
property bool isSuspending: percentage <= ConfigOptions.battery.suspend / 100 property bool isSuspending: percentage <= Config.options.battery.suspend / 100
property bool isLowAndNotCharging: isLow && !isCharging property bool isLowAndNotCharging: isLow && !isCharging
property bool isCriticalAndNotCharging: isCritical && !isCharging property bool isCriticalAndNotCharging: isCritical && !isCharging
@@ -29,7 +29,7 @@ Singleton {
onIsCriticalAndNotChargingChanged: { onIsCriticalAndNotChargingChanged: {
if (available && isCriticalAndNotCharging) if (available && isCriticalAndNotCharging)
Quickshell.execDetached(["bash", "-c", `notify-send "Critically low battery" "🙏 I beg for pleas charg\nAutomatic suspend triggers at ${ConfigOptions.battery.suspend}%" -u critical -a "Shell"`]); Quickshell.execDetached(["bash", "-c", `notify-send "Critically low battery" "🙏 I beg for pleas charg\nAutomatic suspend triggers at ${Config.options.battery.suspend}%" -u critical -a "Shell"`]);
} }
onIsSuspendingAndNotChargingChanged: { onIsSuspendingAndNotChargingChanged: {
+4 -4
View File
@@ -20,7 +20,7 @@ Singleton {
property string failMessage: Translation.tr("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") property string failMessage: Translation.tr("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")
property var responses: [] property var responses: []
property int runningRequests: 0 property int runningRequests: 0
property var defaultUserAgent: ConfigOptions?.networking?.userAgent || "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36" property var defaultUserAgent: Config.options?.networking?.userAgent || "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
property var providerList: Object.keys(providers).filter(provider => provider !== "system" && providers[provider].api) property var providerList: Object.keys(providers).filter(provider => provider !== "system" && providers[provider].api)
property var providers: { property var providers: {
"system": { "name": Translation.tr("System") }, "system": { "name": Translation.tr("System") },
@@ -275,7 +275,7 @@ Singleton {
}, },
} }
} }
property var currentProvider: PersistentStates.booru.provider property var currentProvider: Persistent.states.booru.provider
function getWorkingImageSource(url) { function getWorkingImageSource(url) {
if (url.includes('pximg.net')) { if (url.includes('pximg.net')) {
@@ -287,7 +287,7 @@ Singleton {
function setProvider(provider) { function setProvider(provider) {
provider = provider.toLowerCase() provider = provider.toLowerCase()
if (providerList.indexOf(provider) !== -1) { if (providerList.indexOf(provider) !== -1) {
PersistentStateManager.setState("booru.provider", provider) Persistent.states.booru.provider = provider
root.addSystemMessage(Translation.tr("Provider set to ") + providers[provider].name root.addSystemMessage(Translation.tr("Provider set to ") + providers[provider].name
+ (provider == "zerochan" ? Translation.tr(". 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.)!") : "")) + (provider == "zerochan" ? Translation.tr(". 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.)!") : ""))
} else { } else {
@@ -409,7 +409,7 @@ Singleton {
xhr.setRequestHeader("User-Agent", defaultUserAgent) xhr.setRequestHeader("User-Agent", defaultUserAgent)
} }
else if (currentProvider == "zerochan") { else if (currentProvider == "zerochan") {
const userAgent = ConfigOptions?.sidebar?.booru?.zerochan?.username ? `Desktop sidebar booru viewer - username: ${ConfigOptions.sidebar.booru.zerochan.username}` : defaultUserAgent const userAgent = Config.options?.sidebar?.booru?.zerochan?.username ? `Desktop sidebar booru viewer - username: ${Config.options.sidebar.booru.zerochan.username}` : defaultUserAgent
xhr.setRequestHeader("User-Agent", userAgent) xhr.setRequestHeader("User-Agent", userAgent)
} }
root.runningRequests++; root.runningRequests++;
+2 -2
View File
@@ -11,7 +11,7 @@ import Quickshell.Io
Singleton { Singleton {
id: root id: root
property bool sloppySearch: ConfigOptions?.search.sloppy ?? false property bool sloppySearch: Config.options?.search.sloppy ?? false
property real scoreThreshold: 0.2 property real scoreThreshold: 0.2
property list<string> entries: [] property list<string> entries: []
readonly property var preparedEntries: entries.map(a => ({ readonly property var preparedEntries: entries.map(a => ({
@@ -51,7 +51,7 @@ Singleton {
Timer { Timer {
id: delayedUpdateTimer id: delayedUpdateTimer
interval: ConfigOptions.hacks.arbitraryRaceConditionDelay interval: Config.options.hacks.arbitraryRaceConditionDelay
repeat: false repeat: false
onTriggered: { onTriggered: {
root.refresh() root.refresh()
@@ -1,137 +0,0 @@
pragma Singleton
pragma ComponentBehavior: Bound
import "root:/modules/common"
import "root:/modules/common/functions/file_utils.js" as FileUtils
import "root:/modules/common/functions/string_utils.js" as StringUtils
import "root:/modules/common/functions/object_utils.js" as ObjectUtils
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Hyprland
import Qt.labs.platform
/**
* Loads and manages the shell configuration file.
* The config file is by default at XDG_CONFIG_HOME/illogical-impulse/config.json.
* Automatically reloaded when the file changes.
*/
Singleton {
id: root
property string filePath: Directories.shellConfigPath
property bool firstLoad: true
property bool preventNextLoad: false
property var preventNextNotification: false
function loadConfig() {
configFileView.reload()
}
function applyConfig(fileContent) {
try {
if (fileContent.trim() === "") {
console.warn("[ConfigLoader] Config file is empty, skipping load.");
return;
}
const json = JSON.parse(fileContent);
ObjectUtils.applyToQtObject(ConfigOptions, json);
if (root.firstLoad) {
root.firstLoad = false;
root.preventNextLoad = true;
root.saveConfig(); // Make sure new properties are added to the user's config file
}
} catch (e) {
console.error("[ConfigLoader] Error reading file:", e);
console.log("[ConfigLoader] File content was:", fileContent);
Quickshell.execDetached(["bash", "-c", `notify-send '${Translation.tr("Shell configuration failed to load")}' '${root.filePath}'`])
return;
}
}
function setLiveConfigValue(nestedKey, value) {
let keys = nestedKey.split(".");
let obj = ConfigOptions;
let parents = [obj];
// Traverse and collect parent objects
for (let i = 0; i < keys.length - 1; ++i) {
if (!obj[keys[i]] || typeof obj[keys[i]] !== "object") {
obj[keys[i]] = {};
}
obj = obj[keys[i]];
parents.push(obj);
}
// Convert value to correct type using JSON.parse when safe
let convertedValue = value;
if (typeof value === "string") {
let trimmed = value.trim();
if (trimmed === "true" || trimmed === "false" || !isNaN(Number(trimmed))) {
try {
convertedValue = JSON.parse(trimmed);
} catch (e) {
convertedValue = value;
}
}
}
obj[keys[keys.length - 1]] = convertedValue;
}
function saveConfig() {
const plainConfig = ObjectUtils.toPlainObject(ConfigOptions)
Quickshell.execDetached(["bash", "-c", `echo '${StringUtils.shellSingleQuoteEscape(JSON.stringify(plainConfig, null, 2))}' > '${FileUtils.trimFileProtocol(root.filePath)}'`])
}
function setConfigValueAndSave(nestedKey, value, preventNextNotification = true) {
setLiveConfigValue(nestedKey, value);
root.preventNextNotification = preventNextNotification;
saveConfig();
}
Timer {
id: delayedFileRead
interval: ConfigOptions.hacks.arbitraryRaceConditionDelay
running: false
onTriggered: {
if (root.preventNextLoad) {
root.preventNextLoad = false;
return;
}
if (root.firstLoad) {
root.applyConfig(configFileView.text())
} else {
root.applyConfig(configFileView.text())
if (!root.preventNextNotification) {
// Quickshell.execDetached(["bash", "-c", `notify-send '${qsTr("Shell configuration reloaded")}' '${root.filePath}'`])
} else {
root.preventNextNotification = false;
}
}
}
}
FileView {
id: configFileView
path: Qt.resolvedUrl(root.filePath)
watchChanges: true
onFileChanged: {
this.reload()
delayedFileRead.start()
}
onLoadedChanged: {
const fileContent = configFileView.text()
delayedFileRead.start()
}
onLoadFailed: (error) => {
if(error == FileViewError.FileNotFound) {
console.log("[ConfigLoader] File not found, creating new file.")
root.saveConfig()
Quickshell.execDetached(["bash", "-c", `notify-send '${Translation.tr("Shell configuration created")}' '${root.filePath}'`])
} else {
Quickshell.execDetached(["bash", "-c", `notify-send '${Translation.tr("Shell configuration failed to load")}' '${root.filePath}'`])
}
}
}
}
+3 -3
View File
@@ -9,8 +9,8 @@ pragma ComponentBehavior: Bound
* A nice wrapper for date and time strings. * A nice wrapper for date and time strings.
*/ */
Singleton { Singleton {
property string time: Qt.locale().toString(clock.date, ConfigOptions?.time.format ?? "hh:mm") property string time: Qt.locale().toString(clock.date, Config.options?.time.format ?? "hh:mm")
property string date: Qt.locale().toString(clock.date, ConfigOptions?.time.dateFormat ?? "dddd, dd/MM") property string date: Qt.locale().toString(clock.date, Config.options?.time.dateFormat ?? "dddd, dd/MM")
property string collapsedCalendarFormat: Qt.locale().toString(clock.date, "dd MMMM yyyy") property string collapsedCalendarFormat: Qt.locale().toString(clock.date, "dd MMMM yyyy")
property string uptime: "0h, 0m" property string uptime: "0h, 0m"
@@ -39,7 +39,7 @@ Singleton {
if (hours > 0) formatted += `${formatted ? ", " : ""}${hours}h` if (hours > 0) formatted += `${formatted ? ", " : ""}${hours}h`
if (minutes > 0 || !formatted) formatted += `${formatted ? ", " : ""}${minutes}m` if (minutes > 0 || !formatted) formatted += `${formatted ? ", " : ""}${minutes}m`
uptime = formatted uptime = formatted
interval = ConfigOptions?.resources?.updateInterval ?? 3000 interval = Config.options?.resources?.updateInterval ?? 3000
} }
} }
@@ -16,14 +16,20 @@ Singleton {
property var addresses: [] property var addresses: []
property var windowByAddress: ({}) property var windowByAddress: ({})
property var monitors: [] property var monitors: []
property var layers: ({})
function updateWindowList() { function updateWindowList() {
getClients.running = true getClients.running = true
getMonitors.running = true getMonitors.running = true
} }
function updateLayers() {
getLayers.running = true
}
Component.onCompleted: { Component.onCompleted: {
updateWindowList() updateWindowList()
updateLayers()
} }
Connections { Connections {
@@ -56,6 +62,7 @@ Singleton {
} }
} }
} }
Process { Process {
id: getMonitors id: getMonitors
command: ["bash", "-c", "hyprctl monitors -j | jq -c"] command: ["bash", "-c", "hyprctl monitors -j | jq -c"]
@@ -65,5 +72,15 @@ Singleton {
} }
} }
} }
Process {
id: getLayers
command: ["bash", "-c", "hyprctl layers -j | jq -c"]
stdout: SplitParser {
onRead: (data) => {
root.layers = JSON.parse(data)
}
}
}
} }
@@ -34,7 +34,7 @@ Singleton {
Timer { Timer {
id: delayedFileRead id: delayedFileRead
interval: ConfigOptions?.hacks?.arbitraryRaceConditionDelay ?? 100 interval: Config.options?.hacks?.arbitraryRaceConditionDelay ?? 100
repeat: false repeat: false
running: false running: false
onTriggered: { onTriggered: {
@@ -1,105 +0,0 @@
pragma Singleton
pragma ComponentBehavior: Bound
import "root:/modules/common"
import "root:/modules/common/functions/object_utils.js" as ObjectUtils
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Hyprland
import Qt.labs.platform
/**
* Manages persistent states across sessions.
* Run loadStates() once at startup to load the states, then use setState() and getState() to modify and access them.
*/
Singleton {
id: root
property string fileDir: Directories.state
property string fileName: "states.json"
property string filePath: `${root.fileDir}/${root.fileName}`
property bool allowWriteback: false
function getState(nestedKey) {
let keys = nestedKey.split(".");
let obj = PersistentStates;
for (let i = 0; i < keys.length; ++i) {
if (obj[keys[i]] === undefined) {
console.error(`[PersistentStateManager] Key "${keys[i]}" not found in PersistentStates`);
return null;
}
obj = obj[keys[i]];
}
return obj;
}
function setState(nestedKey, value) {
if (!root.allowWriteback) return;
let keys = nestedKey.split(".");
let obj = PersistentStates;
let parents = [obj];
// Traverse and collect parent objects
for (let i = 0; i < keys.length - 1; ++i) {
if (!obj[keys[i]] || typeof obj[keys[i]] !== "object") {
obj[keys[i]] = {};
}
obj = obj[keys[i]];
parents.push(obj);
}
// Set the value at the innermost key
obj[keys[keys.length - 1]] = value;
saveStates()
}
function loadStates() {
stateFileView.reload()
}
function saveStates() {
const plainStates = ObjectUtils.toPlainObject(PersistentStates)
stateFileView.setText(JSON.stringify(plainStates, null, 2))
}
function applyStates(fileContent) {
try {
const json = JSON.parse(fileContent);
ObjectUtils.applyToQtObject(PersistentStates, json);
root.allowWriteback = true
} catch (e) {
console.error("[PersistentStateManager] Error reading file:", e);
return;
}
}
Timer {
id: delayedFileRead
interval: ConfigOptions?.hacks?.arbitraryRaceConditionDelay ?? 100
repeat: false
running: false
onTriggered: {
root.applyStates(stateFileView.text())
}
}
FileView {
id: stateFileView
path: root.filePath
watchChanges: true
// onFileChanged: {
// console.log("[PersistentStateManager] File changed, reloading...")
// this.reload()
// delayedFileRead.start()
// }
onLoadedChanged: {
const fileContent = stateFileView.text()
root.applyStates(fileContent)
}
onLoadFailed: (error) => {
console.log("[PersistentStateManager] File not found, creating new file")
root.saveStates()
}
}
}
@@ -53,7 +53,7 @@ Singleton {
previousCpuStats = { total, idle } previousCpuStats = { total, idle }
} }
interval = ConfigOptions?.resources?.updateInterval ?? 3000 interval = Config.options?.resources?.updateInterval ?? 3000
} }
} }
+3 -4
View File
@@ -56,7 +56,6 @@ ApplicationWindow {
Component.onCompleted: { Component.onCompleted: {
MaterialThemeLoader.reapplyTheme() MaterialThemeLoader.reapplyTheme()
ConfigLoader.loadConfig()
} }
minimumWidth: 600 minimumWidth: 600
@@ -93,15 +92,15 @@ ApplicationWindow {
} }
Item { // Titlebar Item { // Titlebar
visible: ConfigOptions?.windows.showTitlebar visible: Config.options?.windows.showTitlebar
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: false Layout.fillHeight: false
implicitHeight: Math.max(titleText.implicitHeight, windowControlsRow.implicitHeight) implicitHeight: Math.max(titleText.implicitHeight, windowControlsRow.implicitHeight)
StyledText { StyledText {
id: titleText id: titleText
anchors { anchors {
left: ConfigOptions.windows.centerTitle ? undefined : parent.left left: Config.options.windows.centerTitle ? undefined : parent.left
horizontalCenter: ConfigOptions.windows.centerTitle ? parent.horizontalCenter : undefined horizontalCenter: Config.options.windows.centerTitle ? parent.horizontalCenter : undefined
verticalCenter: parent.verticalCenter verticalCenter: parent.verticalCenter
leftMargin: 12 leftMargin: 12
} }
+2 -4
View File
@@ -32,7 +32,7 @@ ShellRoot {
property bool enableBar: true property bool enableBar: true
property bool enableBackgroundWidgets: true property bool enableBackgroundWidgets: true
property bool enableCheatsheet: true property bool enableCheatsheet: true
property bool enableDock: false property bool enableDock: true
property bool enableMediaControls: true property bool enableMediaControls: true
property bool enableNotificationPopup: true property bool enableNotificationPopup: true
property bool enableOnScreenDisplayBrightness: true property bool enableOnScreenDisplayBrightness: true
@@ -48,8 +48,6 @@ ShellRoot {
// Force initialization of some singletons // Force initialization of some singletons
Component.onCompleted: { Component.onCompleted: {
MaterialThemeLoader.reapplyTheme() MaterialThemeLoader.reapplyTheme()
ConfigLoader.loadConfig()
PersistentStateManager.loadStates()
Cliphist.refresh() Cliphist.refresh()
FirstRunExperience.load() FirstRunExperience.load()
} }
@@ -57,7 +55,7 @@ ShellRoot {
LazyLoader { active: enableBar; component: Bar {} } LazyLoader { active: enableBar; component: Bar {} }
LazyLoader { active: enableBackgroundWidgets; component: BackgroundWidgets {} } LazyLoader { active: enableBackgroundWidgets; component: BackgroundWidgets {} }
LazyLoader { active: enableCheatsheet; component: Cheatsheet {} } LazyLoader { active: enableCheatsheet; component: Cheatsheet {} }
LazyLoader { active: enableDock; component: Dock {} } LazyLoader { active: enableDock && Config.options.dock.enable; component: Dock {} }
LazyLoader { active: enableMediaControls; component: MediaControls {} } LazyLoader { active: enableMediaControls; component: MediaControls {} }
LazyLoader { active: enableNotificationPopup; component: NotificationPopup {} } LazyLoader { active: enableNotificationPopup; component: NotificationPopup {} }
LazyLoader { active: enableOnScreenDisplayBrightness; component: OnScreenDisplayBrightness {} } LazyLoader { active: enableOnScreenDisplayBrightness; component: OnScreenDisplayBrightness {} }
+25 -9
View File
@@ -5,7 +5,6 @@
// Adjust this to make the app smaller or larger // Adjust this to make the app smaller or larger
//@ pragma Env QT_SCALE_FACTOR=1 //@ pragma Env QT_SCALE_FACTOR=1
import Qt5Compat.GraphicalEffects
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
@@ -32,7 +31,6 @@ ApplicationWindow {
Component.onCompleted: { Component.onCompleted: {
MaterialThemeLoader.reapplyTheme() MaterialThemeLoader.reapplyTheme()
ConfigLoader.loadConfig()
} }
minimumWidth: 600 minimumWidth: 600
@@ -60,14 +58,14 @@ ApplicationWindow {
} }
Item { // Titlebar Item { // Titlebar
visible: ConfigOptions?.windows.showTitlebar visible: Config.options?.windows.showTitlebar
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: Math.max(welcomeText.implicitHeight, windowControlsRow.implicitHeight) implicitHeight: Math.max(welcomeText.implicitHeight, windowControlsRow.implicitHeight)
StyledText { StyledText {
id: welcomeText id: welcomeText
anchors { anchors {
left: ConfigOptions.windows.centerTitle ? undefined : parent.left left: Config.options.windows.centerTitle ? undefined : parent.left
horizontalCenter: ConfigOptions.windows.centerTitle ? parent.horizontalCenter : undefined horizontalCenter: Config.options.windows.centerTitle ? parent.horizontalCenter : undefined
verticalCenter: parent.verticalCenter verticalCenter: parent.verticalCenter
leftMargin: 12 leftMargin: 12
} }
@@ -123,6 +121,24 @@ ApplicationWindow {
ContentPage { ContentPage {
id: contentColumn id: contentColumn
anchors.fill: parent anchors.fill: parent
ContentSection {
title: "Bar style"
ConfigSelectionArray {
currentValue: Config.options.bar.cornerStyle
configOptionName: "bar.cornerStyle"
onSelected: (newValue) => {
Config.options.bar.cornerStyle = newValue; // Update local copy
}
options: [
{ displayName: "Hug", value: 0 },
{ displayName: "Float", value: 1 },
{ displayName: "Plain rectangle", value: 2 }
]
}
}
ContentSection { ContentSection {
title: "Style & wallpaper" title: "Style & wallpaper"
@@ -206,10 +222,10 @@ ApplicationWindow {
text: "Weeb" text: "Weeb"
} }
ConfigSelectionArray { ConfigSelectionArray {
currentValue: ConfigOptions.policies.weeb currentValue: Config.options.policies.weeb
configOptionName: "policies.weeb" configOptionName: "policies.weeb"
onSelected: (newValue) => { onSelected: (newValue) => {
ConfigLoader.setConfigValueAndSave("policies.weeb", newValue); Config.options.policies.weeb = newValue;
} }
options: [ options: [
{ displayName: "No", value: 0 }, { displayName: "No", value: 0 },
@@ -224,10 +240,10 @@ ApplicationWindow {
text: "AI" text: "AI"
} }
ConfigSelectionArray { ConfigSelectionArray {
currentValue: ConfigOptions.policies.ai currentValue: Config.options.policies.ai
configOptionName: "policies.ai" configOptionName: "policies.ai"
onSelected: (newValue) => { onSelected: (newValue) => {
ConfigLoader.setConfigValueAndSave("policies.ai", newValue); Config.options.policies.ai = newValue;
} }
options: [ options: [
{ displayName: "No", value: 0 }, { displayName: "No", value: 0 },
@@ -7,6 +7,7 @@ license=(None)
depends=( depends=(
xdg-desktop-portal xdg-desktop-portal
xdg-desktop-portal-kde xdg-desktop-portal-kde
xdg-desktop-portal-gtk
xdg-desktop-portal-hyprland xdg-desktop-portal-hyprland
) )
@@ -6,8 +6,8 @@ arch=(any)
license=(None) license=(None)
depends=( depends=(
hyprshot hyprshot
ksnip
slurp slurp
swappy
tesseract tesseract
tesseract-data-eng tesseract-data-eng
wf-recorder wf-recorder
+1 -1
View File
@@ -144,7 +144,7 @@ esac
v sudo usermod -aG video,i2c,input "$(whoami)" v sudo usermod -aG video,i2c,input "$(whoami)"
v bash -c "echo i2c-dev | sudo tee /etc/modules-load.d/i2c-dev.conf" v bash -c "echo i2c-dev | sudo tee /etc/modules-load.d/i2c-dev.conf"
v sudo pacman -S archlinux-xdg-menu && XDG_MENU_PREFIX=arch- kbuildsycoca6; sudo ln -s /etc/xdg/menus/plasma-applications.menu /etc/xdg/menus/applications.menu v sudo pacman -S archlinux-xdg-menu && XDG_MENU_PREFIX=arch- kbuildsycoca6; sudo ln -sf /etc/xdg/menus/plasma-applications.menu /etc/xdg/menus/applications.menu
v systemctl --user enable ydotool --now v systemctl --user enable ydotool --now
v sudo systemctl enable bluetooth --now v sudo systemctl enable bluetooth --now
v gsettings set org.gnome.desktop.interface font-name 'Rubik 11' v gsettings set org.gnome.desktop.interface font-name 'Rubik 11'