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 = 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=, XF86MonBrightnessDown, exec, qs ipc call brightness decrement || 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 || brightnessctl s 5%- # [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]
@@ -50,8 +50,7 @@ bind = Ctrl+Super, R, exec, killall ags agsv1 gjs ydotool qs quickshell; qs & #
# 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, 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+Alt, S, Screen snip and annotate, exec, pidof slurp || grim -g "$(slurp)" - | ksnip -e - # Screen snip and annotate
bindd = Super+Shift, S, Screen snip, exec, qs -p ~/.config/quickshell/screenshot.qml || pidof slurp || hyprshot --freeze --clipboard-only --mode region --silent # Screen snip
# OCR
bindd = Super+Shift, T, Character recognition,exec,grim -g "$(slurp $SLURP_ARGS)" "tmp.png" && tesseract "tmp.png" - | wl-copy && rm "tmp.png" # [hidden]
# Color picker
@@ -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 = 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, 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+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 = 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 = 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
## Make window not amogus large
+7 -2
View File
@@ -3,8 +3,10 @@
# Uncomment to apply global transparency to all windows:
# windowrulev2 = opacity 0.89 override 0.89 override, class:.*
# Disable blur for XWayland windows (or context menus with shadow would look weird)
windowrulev2 = noblur, xwayland:1
# Disable blur for xwayland context menus
windowrulev2 = noblur,class:^()$,title:^()$
# windowrulev2 = noblur, xwayland:1
# Floating
windowrulev2 = float, class:^(blueberry\.py)$
@@ -24,6 +26,7 @@ windowrulev2 = float, class:kcm_.*
windowrulev2 = float, class:.*bluedevilwizard
windowrulev2 = float, title:.*Welcome
windowrulev2 = float, title:^(illogical-impulse Settings)$
windowrulev2 = float, class:org.freedesktop.impl.portal.desktop.kde
# 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.
@@ -61,6 +64,7 @@ windowrulev2 = float, title:^(File Upload)(.*)$
# --- Tearing ---
windowrulev2 = immediate, title:.*\.exe
windowrulev2 = immediate, title:.*minecraft.*
windowrulev2 = immediate, class:^(steam_app)
# 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 = blur, quickshell:backgroundWidgets
layerrule = ignorealpha 0.05, quickshell:backgroundWidgets
layerrule = noanim, quickshell:screenshot
# 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' &
pkill wf-recorder &
else
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
elif [[ "$1" == "--fullscreen-sound" ]]; then
if [[ "$1" == "--fullscreen-sound" ]]; then
notify-send "Starting recording" 'recording_'"$(getdate)"'.mp4' -a 'Recorder'
wf-recorder -o $(getactivemonitor) --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --audio="$(getaudiooutput)" & disown
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
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
@@ -1,6 +1,6 @@
general {
col.active_border = rgba({{colors.on_surface.default.hex_stripped}}39)
col.inactive_border = rgba({{colors.outline.default.hex_stripped}}30)
col.active_border = rgba({{colors.outline.default.hex_stripped}}AA)
col.inactive_border = rgba({{colors.outline_variant.default.hex_stripped}}AA)
}
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 {
id: workspaceShowNumbersTimer
interval: ConfigOptions.bar.workspaces.showNumberDelay
interval: Config.options.bar.workspaces.showNumberDelay
// interval: 0
repeat: false
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 {
id: root
property string filePath: `${Directories.state}/user/generated/wallpaper/least_busy_region.json`
property real defaultX: (ConfigOptions?.background.clockX ?? -500)
property real defaultY: (ConfigOptions?.background.clockY ?? -500)
property real defaultX: (Config.options?.background.clockX ?? -500)
property real defaultY: (Config.options?.background.clockY ?? -500)
property real centerX: defaultX
property real centerY: defaultY
property real effectiveCenterX: ConfigOptions?.background.fixedClockPosition ? defaultX : centerX
property real effectiveCenterY: ConfigOptions?.background.fixedClockPosition ? defaultY : centerY
property real effectiveCenterX: Config.options?.background.fixedClockPosition ? defaultX : centerX
property real effectiveCenterY: Config.options?.background.fixedClockPosition ? defaultY : centerY
property color dominantColor: Appearance.colors.colPrimary
property bool dominantColorIsDark: dominantColor.hslLightness < 0.5
property color colBackground: ColorUtils.transparentize(ColorUtils.mix(Appearance.colors.colPrimary, Appearance.colors.colSecondaryContainer), 1)
@@ -36,7 +36,7 @@ Scope {
Timer {
id: delayedFileRead
interval: ConfigOptions.hacks.arbitraryRaceConditionDelay
interval: Config.options.hacks.arbitraryRaceConditionDelay
running: false
onTriggered: {
root.updateWidgetPosition(leastBusyRegionFileView.text())
@@ -46,7 +46,7 @@ Scope {
FileView {
id: leastBusyRegionFileView
path: Qt.resolvedUrl(root.filePath)
watchChanges: !ConfigOptions?.background.fixedClockPosition
watchChanges: !Config.options?.background.fixedClockPosition
onFileChanged: {
this.reload()
delayedFileRead.start()
+120 -45
View File
@@ -15,13 +15,12 @@ import Quickshell.Services.UPower
Scope {
id: bar
readonly property int barHeight: Appearance.sizes.barHeight
readonly property int osdHideMouseMoveThreshold: 20
property bool showBarBackground: ConfigOptions.bar.showBackground
property bool showBarBackground: Config.options.bar.showBackground
component VerticalBarSeparator: Rectangle {
Layout.topMargin: barHeight / 3
Layout.bottomMargin: barHeight / 3
Layout.topMargin: Appearance.sizes.baseBarHeight / 3
Layout.bottomMargin: Appearance.sizes.baseBarHeight / 3
Layout.fillHeight: true
implicitWidth: 1
color: Appearance.colors.colOutlineVariant
@@ -30,7 +29,7 @@ Scope {
Variants { // For each monitor
model: {
const screens = Quickshell.screens;
const list = ConfigOptions.bar.screenList;
const list = Config.options.bar.screenList;
if (!list || list.length === 0)
return screens;
return screens.filter(screen => list.includes(screen.name));
@@ -38,9 +37,9 @@ Scope {
PanelWindow { // Bar window
id: barRoot
required property ShellScreen modelData
screen: modelData
property ShellScreen modelData
property var brightnessMonitor: Brightness.getMonitorForScreen(modelData)
property real useShortenedForm: (Appearance.sizes.barHellaShortenScreenWidthThreshold >= screen.width) ? 2 :
(Appearance.sizes.barShortenScreenWidthThreshold >= screen.width) ? 1 : 0
@@ -50,35 +49,70 @@ Scope {
Appearance.sizes.barCenterSideModuleWidth
WlrLayershell.namespace: "quickshell:bar"
implicitHeight: barHeight + Appearance.rounding.screenRounding
exclusiveZone: showBarBackground ? barHeight : (barHeight - 4)
implicitHeight: Appearance.sizes.barHeight + Appearance.rounding.screenRounding
exclusiveZone: Appearance.sizes.baseBarHeight + (Config.options.bar.cornerStyle === 1 ? Appearance.sizes.hyprlandGapsOut : 0)
mask: Region {
item: barContent
}
color: "transparent"
anchors {
top: !ConfigOptions.bar.bottom
bottom: ConfigOptions.bar.bottom
top: !Config.options.bar.bottom
bottom: Config.options.bar.bottom
left: true
right: true
}
Rectangle { // Bar background
Item { // Bar content region
id: barContent
anchors {
right: parent.right
left: parent.left
top: !ConfigOptions.bar.bottom ? parent.top : undefined
bottom: ConfigOptions.bar.bottom ? parent.bottom : undefined
top: parent.top
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
id: barLeftSideMouseArea
anchors.left: parent.left
implicitHeight: barHeight
implicitHeight: Appearance.sizes.baseBarHeight
height: Appearance.sizes.barHeight
width: (barRoot.width - middleSection.width) / 2
property bool hovered: false
property real lastScrollX: 0
@@ -170,7 +204,7 @@ Scope {
anchors.centerIn: parent
width: 19.5
height: 19.5
source: ConfigOptions.bar.topLeftIcon == 'distro' ?
source: Config.options.bar.topLeftIcon == 'distro' ?
SystemInfo.distroIcon : "spark-symbolic"
}
@@ -195,7 +229,7 @@ Scope {
RowLayout { // Middle section
id: middleSection
anchors.centerIn: parent
spacing: ConfigOptions?.bar.borderless ? 4 : 8
spacing: Config.options?.bar.borderless ? 4 : 8
BarGroup {
id: leftCenterGroup
@@ -214,7 +248,7 @@ Scope {
}
VerticalBarSeparator {visible: ConfigOptions?.bar.borderless}
VerticalBarSeparator {visible: Config.options?.bar.borderless}
BarGroup {
id: middleCenterGroup
@@ -238,7 +272,7 @@ Scope {
}
}
VerticalBarSeparator {visible: ConfigOptions?.bar.borderless}
VerticalBarSeparator {visible: Config.options?.bar.borderless}
MouseArea {
id: rightCenterGroup
@@ -256,13 +290,13 @@ Scope {
anchors.fill: parent
ClockWidget {
showDate: (ConfigOptions.bar.verbose && barRoot.useShortenedForm < 2)
showDate: (Config.options.bar.verbose && barRoot.useShortenedForm < 2)
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
}
UtilButtons {
visible: (ConfigOptions.bar.verbose && barRoot.useShortenedForm === 0)
visible: (Config.options.bar.verbose && barRoot.useShortenedForm === 0)
Layout.alignment: Qt.AlignVCenter
}
@@ -279,7 +313,8 @@ Scope {
id: barRightSideMouseArea
anchors.right: parent.right
implicitHeight: barHeight
implicitHeight: Appearance.sizes.baseBarHeight
height: Appearance.sizes.barHeight
width: (barRoot.width - middleSection.width) / 2
property bool hovered: false
@@ -354,10 +389,14 @@ Scope {
RippleButton { // Right sidebar button
id: rightSidebarButton
Layout.margins: 4
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
Layout.rightMargin: Appearance.rounding.screenRounding
Layout.fillHeight: true
implicitWidth: indicatorsRowLayout.implicitWidth + 10*2
Layout.fillWidth: false
implicitWidth: indicatorsRowLayout.implicitWidth + 10 * 2
implicitHeight: indicatorsRowLayout.implicitHeight + 5 * 2
buttonRadius: Appearance.rounding.full
colBackground: barRightSideMouseArea.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1)
colBackgroundHover: Appearance.colors.colLayer1Hover
@@ -447,32 +486,68 @@ Scope {
}
// Round decorators
Item {
Loader {
id: roundDecorators
anchors {
left: parent.left
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
visible: showBarBackground
active: showBarBackground && Config.options.bar.cornerStyle === 0 // Hug
RoundCorner {
anchors.top: parent.top
anchors.left: parent.left
size: Appearance.rounding.screenRounding
corner: ConfigOptions.bar.bottom ? cornerEnum.bottomLeft : cornerEnum.topLeft
color: showBarBackground ? Appearance.colors.colLayer0 : "transparent"
opacity: 1.0 - Appearance.transparency
states: State {
name: "bottom"
when: Config.options.bar.bottom
PropertyChanges {
roundDecorators.y: 0
}
}
RoundCorner {
anchors.top: parent.top
anchors.right: parent.right
size: Appearance.rounding.screenRounding
corner: ConfigOptions.bar.bottom ? cornerEnum.bottomRight : cornerEnum.topRight
color: showBarBackground ? Appearance.colors.colLayer0 : "transparent"
opacity: 1.0 - Appearance.transparency
sourceComponent: Item {
implicitHeight: Appearance.rounding.screenRounding
RoundCorner {
id: leftCorner
anchors {
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 {
id: root
property real padding: 5
implicitHeight: 40
implicitHeight: Appearance.sizes.baseBarHeight
height: Appearance.sizes.barHeight
implicitWidth: rowLayout.implicitWidth + padding * 2
default property alias items: rowLayout.children
@@ -18,7 +19,7 @@ Item {
topMargin: 4
bottomMargin: 4
}
color: ConfigOptions?.bar.borderless ? "transparent" : Appearance.colors.colLayer1
color: Config.options?.bar.borderless ? "transparent" : Appearance.colors.colLayer1
radius: Appearance.rounding.small
}
@@ -9,12 +9,12 @@ import Quickshell.Services.UPower
Item {
id: root
property bool borderless: ConfigOptions.bar.borderless
property bool borderless: Config.options.bar.borderless
readonly property var chargeState: Battery.chargeState
readonly property bool isCharging: Battery.isCharging
readonly property bool isPluggedIn: Battery.isPluggedIn
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 batteryLowOnBackground: Appearance.m3colors.darkmode ? Appearance.m3colors.m3errorContainer : Appearance.m3colors.m3error
@@ -6,8 +6,8 @@ import QtQuick.Layouts
Item {
id: root
property bool borderless: ConfigOptions.bar.borderless
property bool showDate: ConfigOptions.bar.verbose
property bool borderless: Config.options.bar.borderless
property bool showDate: Config.options.bar.verbose
implicitWidth: rowLayout.implicitWidth
implicitHeight: 32
+2 -2
View File
@@ -11,13 +11,13 @@ import Quickshell.Hyprland
Item {
id: root
property bool borderless: ConfigOptions.bar.borderless
property bool borderless: Config.options.bar.borderless
readonly property MprisPlayer activePlayer: MprisController.activePlayer
readonly property string cleanedTitle: StringUtils.cleanMusicTitle(activePlayer?.trackTitle) || Translation.tr("No media")
Layout.fillHeight: true
implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2
implicitHeight: 40
implicitHeight: Appearance.sizes.barHeight
Timer {
running: activePlayer?.playbackState == MprisPlaybackState.Playing
+3 -3
View File
@@ -9,7 +9,7 @@ import Quickshell.Services.Mpris
Item {
id: root
property bool borderless: ConfigOptions.bar.borderless
property bool borderless: Config.options.bar.borderless
property bool alwaysShowAllResources: false
implicitWidth: rowLayout.implicitWidth + rowLayout.anchors.leftMargin + rowLayout.anchors.rightMargin
implicitHeight: 32
@@ -30,7 +30,7 @@ Item {
Resource {
iconName: "swap_horiz"
percentage: ResourceUsage.swapUsedPercentage
shown: (ConfigOptions.bar.resources.alwaysShowSwap && percentage > 0) ||
shown: (Config.options.bar.resources.alwaysShowSwap && percentage > 0) ||
(MprisController.activePlayer?.trackTitle == null) ||
root.alwaysShowAllResources
Layout.leftMargin: shown ? 4 : 0
@@ -39,7 +39,7 @@ Item {
Resource {
iconName: "settings_slow_motion"
percentage: ResourceUsage.cpuUsage
shown: ConfigOptions.bar.resources.alwaysShowCpu ||
shown: Config.options.bar.resources.alwaysShowCpu ||
!(MprisController.activePlayer?.trackTitle?.length > 0) ||
root.alwaysShowAllResources
Layout.leftMargin: shown ? 4 : 0
@@ -43,7 +43,7 @@ MouseArea {
IconImage {
id: trayIcon
visible: !ConfigOptions.bar.tray.monochromeIcons
visible: !Config.options.bar.tray.monochromeIcons
source: root.item.icon
anchors.centerIn: parent
width: parent.width
@@ -51,7 +51,7 @@ MouseArea {
}
Loader {
active: ConfigOptions.bar.tray.monochromeIcons
active: Config.options.bar.tray.monochromeIcons
anchors.fill: trayIcon
sourceComponent: Item {
Desaturate {
+11 -11
View File
@@ -9,7 +9,7 @@ import Quickshell.Services.Pipewire
Item {
id: root
property bool borderless: ConfigOptions.bar.borderless
property bool borderless: Config.options.bar.borderless
implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2
implicitHeight: rowLayout.implicitHeight
@@ -20,8 +20,8 @@ Item {
anchors.centerIn: parent
Loader {
active: ConfigOptions.bar.utilButtons.showScreenSnip
visible: ConfigOptions.bar.utilButtons.showScreenSnip
active: Config.options.bar.utilButtons.showScreenSnip
visible: Config.options.bar.utilButtons.showScreenSnip
sourceComponent: CircleUtilButton {
Layout.alignment: Qt.AlignVCenter
onClicked: Hyprland.dispatch("exec hyprshot --freeze --clipboard-only --mode region --silent")
@@ -36,8 +36,8 @@ Item {
}
Loader {
active: ConfigOptions.bar.utilButtons.showColorPicker
visible: ConfigOptions.bar.utilButtons.showColorPicker
active: Config.options.bar.utilButtons.showColorPicker
visible: Config.options.bar.utilButtons.showColorPicker
sourceComponent: CircleUtilButton {
Layout.alignment: Qt.AlignVCenter
onClicked: Hyprland.dispatch("exec hyprpicker -a")
@@ -52,8 +52,8 @@ Item {
}
Loader {
active: ConfigOptions.bar.utilButtons.showKeyboardToggle
visible: ConfigOptions.bar.utilButtons.showKeyboardToggle
active: Config.options.bar.utilButtons.showKeyboardToggle
visible: Config.options.bar.utilButtons.showKeyboardToggle
sourceComponent: CircleUtilButton {
Layout.alignment: Qt.AlignVCenter
onClicked: Hyprland.dispatch("global quickshell:oskToggle")
@@ -68,8 +68,8 @@ Item {
}
Loader {
active: ConfigOptions.bar.utilButtons.showMicToggle
visible: ConfigOptions.bar.utilButtons.showMicToggle
active: Config.options.bar.utilButtons.showMicToggle
visible: Config.options.bar.utilButtons.showMicToggle
sourceComponent: CircleUtilButton {
Layout.alignment: Qt.AlignVCenter
onClicked: Hyprland.dispatch("exec wpctl set-mute @DEFAULT_SOURCE@ toggle")
@@ -84,8 +84,8 @@ Item {
}
Loader {
active: ConfigOptions.bar.utilButtons.showDarkModeToggle
visible: ConfigOptions.bar.utilButtons.showDarkModeToggle
active: Config.options.bar.utilButtons.showDarkModeToggle
visible: Config.options.bar.utilButtons.showDarkModeToggle
sourceComponent: CircleUtilButton {
Layout.alignment: Qt.AlignVCenter
onClicked: event => {
+20 -20
View File
@@ -15,11 +15,11 @@ import Qt5Compat.GraphicalEffects
Item {
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 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 int widgetPadding: 4
property int workspaceButtonWidth: 26
@@ -27,12 +27,12 @@ Item {
property real workspaceIconSizeShrinked: workspaceButtonWidth * 0.55
property real workspaceIconOpacityShrinked: 1
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 updateWorkspaceOccupied() {
workspaceOccupied = Array.from({ length: ConfigOptions.bar.workspaces.shown }, (_, i) => {
return Hyprland.workspaces.values.some(ws => ws.id === workspaceGroup * ConfigOptions.bar.workspaces.shown + i + 1);
workspaceOccupied = Array.from({ length: Config.options.bar.workspaces.shown }, (_, i) => {
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
implicitHeight: 40
implicitHeight: Appearance.sizes.barHeight
// Scroll to switch workspaces
WheelHandler {
@@ -78,10 +78,10 @@ Item {
spacing: 0
anchors.fill: parent
implicitHeight: 40
implicitHeight: Appearance.sizes.barHeight
Repeater {
model: ConfigOptions.bar.workspaces.shown
model: Config.options.bar.workspaces.shown
Rectangle {
z: 1
@@ -157,14 +157,14 @@ Item {
spacing: 0
anchors.fill: parent
implicitHeight: 40
implicitHeight: Appearance.sizes.barHeight
Repeater {
model: ConfigOptions.bar.workspaces.shown
model: Config.options.bar.workspaces.shown
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
onPressed: Hyprland.dispatch(`workspace ${workspaceValue}`)
width: workspaceButtonWidth
@@ -185,8 +185,8 @@ Item {
StyledText { // Workspace number text
opacity: GlobalStates.workspaceShowNumbers
|| ((ConfigOptions?.bar.workspaces.alwaysShowNumbers && (!ConfigOptions?.bar.workspaces.showAppIcons || !workspaceButtonBackground.biggestWindow || GlobalStates.workspaceShowNumbers))
|| (GlobalStates.workspaceShowNumbers && !ConfigOptions?.bar.workspaces.showAppIcons)
|| ((Config.options?.bar.workspaces.alwaysShowNumbers && (!Config.options?.bar.workspaces.showAppIcons || !workspaceButtonBackground.biggestWindow || GlobalStates.workspaceShowNumbers))
|| (GlobalStates.workspaceShowNumbers && !Config.options?.bar.workspaces.showAppIcons)
) ? 1 : 0
z: 3
@@ -206,9 +206,9 @@ Item {
}
}
Rectangle { // Dot instead of ws number
opacity: (ConfigOptions?.bar.workspaces.alwaysShowNumbers
opacity: (Config.options?.bar.workspaces.alwaysShowNumbers
|| GlobalStates.workspaceShowNumbers
|| (ConfigOptions?.bar.workspaces.showAppIcons && workspaceButtonBackground.biggestWindow)
|| (Config.options?.bar.workspaces.showAppIcons && workspaceButtonBackground.biggestWindow)
) ? 0 : 1
visible: opacity > 0
anchors.centerIn: parent
@@ -228,21 +228,21 @@ Item {
anchors.centerIn: parent
width: workspaceButtonWidth
height: workspaceButtonWidth
opacity: !ConfigOptions?.bar.workspaces.showAppIcons ? 0 :
(workspaceButtonBackground.biggestWindow && !GlobalStates.workspaceShowNumbers && ConfigOptions?.bar.workspaces.showAppIcons) ?
opacity: !Config.options?.bar.workspaces.showAppIcons ? 0 :
(workspaceButtonBackground.biggestWindow && !GlobalStates.workspaceShowNumbers && Config.options?.bar.workspaces.showAppIcons) ?
1 : workspaceButtonBackground.biggestWindow ? workspaceIconOpacityShrinked : 0
visible: opacity > 0
IconImage {
id: mainAppIcon
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.bottomMargin: (!GlobalStates.workspaceShowNumbers && ConfigOptions?.bar.workspaces.showAppIcons) ?
anchors.bottomMargin: (!GlobalStates.workspaceShowNumbers && Config.options?.bar.workspaces.showAppIcons) ?
(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
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 {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
@@ -16,8 +16,8 @@ Singleton {
property string syntaxHighlightingTheme
// Extremely conservative transparency values for consistency and readability
property real transparency: ConfigOptions?.appearance.transparency ? (m3colors.darkmode ? 0.1 : 0.07) : 0
property real contentTransparency: ConfigOptions?.appearance.transparency ? (m3colors.darkmode ? 0.55 : 0.55) : 0
property real transparency: Config.options?.appearance.transparency ? (m3colors.darkmode ? 0.1 : 0.07) : 0
property real contentTransparency: Config.options?.appearance.transparency ? (m3colors.darkmode ? 0.55 : 0.55) : 0
m3colors: QtObject {
property bool darkmode: false
@@ -131,7 +131,7 @@ Singleton {
property color colSecondaryHover: ColorUtils.mix(m3colors.m3secondary, colLayer1Hover, 0.85)
property color colSecondaryActive: ColorUtils.mix(m3colors.m3secondary, colLayer1Active, 0.4)
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 colOnSecondaryContainer: m3colors.m3onSecondaryContainer
property color colSurfaceContainerLow: ColorUtils.transparentize(m3colors.m3surfaceContainerLow, root.contentTransparency)
@@ -171,7 +171,7 @@ Singleton {
}
property QtObject pixelSize: QtObject {
property int smallest: 10
property int smaller: 13
property int smaller: 12
property int small: 15
property int normal: 16
property int large: 17
@@ -288,8 +288,10 @@ Singleton {
}
sizes: QtObject {
property real barHeight: 40
property real barCenterSideModuleWidth: ConfigOptions?.bar.verbose ? 360 : 140
property real baseBarHeight: 40
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 barCenterSideModuleWidthHellaShortened: 190
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]
// 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 coverArt: FileUtils.trimFileProtocol(`${Directories.cache}/media/coverart`)
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 generatedMaterialThemePath: FileUtils.trimFileProtocol(`${Directories.state}/user/generated/colors.json`)
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
Component.onCompleted: {
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;
}
/**
* 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 real size: 32
property string downloadUserAgent: ConfigOptions?.networking.userAgent ?? ""
property string downloadUserAgent: Config.options?.networking.userAgent ?? ""
property string faviconDownloadPath: Directories.favicons
property string domainName: url.includes("vertexaisearch") ? displayText : StringUtils.getDomain(url)
property string faviconUrl: `https://www.google.com/s2/favicons?domain=${domainName}&sz=32`
@@ -9,8 +9,8 @@ Rectangle {
id: root
property string key
property real horizontalPadding: 7
property real verticalPadding: 2
property real horizontalPadding: 6
property real verticalPadding: 1
property real borderWidth: 1
property real extraBottomBorderWidth: 2
property color borderColor: Appearance.colors.colOnLayer0
@@ -13,6 +13,12 @@ Text {
family: Appearance?.font.family.iconMaterial ?? "Material Symbols Rounded"
pixelSize: iconSize
weight: Font.Normal + (Font.DemiBold - Font.Normal) * fill
variableAxes: {
"FILL": truncatedFill,
// "wght": font.weight,
// "GRAD": 0,
"opsz": iconSize,
}
}
verticalAlignment: Text.AlignVCenter
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]
// }
// }
font.variableAxes: {
"FILL": truncatedFill,
// "wght": font.weight,
// "GRAD": 0,
"opsz": iconSize,
}
}
@@ -188,6 +188,7 @@ Item { // Notification item area
font.pixelSize: root.fontSize
color: Appearance.colors.colSubtext
elide: Text.ElideRight
maximumLineCount: 1
textFormat: Text.StyledText
text: {
return processNotificationBody(notificationObject.body, notificationObject.appName || notificationObject.summary).replace(/\n/g, "<br/>")
@@ -3,24 +3,21 @@ import QtQuick 2.9
Item {
id: root
enum CornerEnum { TopLeft, TopRight, BottomLeft, BottomRight }
property var corner: RoundCorner.CornerEnum.TopLeft // Default to TopLeft
property int size: 25
property color color: "#000000"
onColorChanged: {
canvas.requestPaint();
}
property QtObject cornerEnum: QtObject {
property int topLeft: 0
property int topRight: 1
property int bottomLeft: 2
property int bottomRight: 3
onCornerChanged: {
canvas.requestPaint();
}
property int corner: cornerEnum.topLeft // Default to TopLeft
width: size
height: size
implicitWidth: size
implicitHeight: size
Canvas {
id: canvas
@@ -31,22 +28,22 @@ Item {
onPaint: {
var ctx = getContext("2d");
var r = root.size;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
switch (root.corner) {
case cornerEnum.topLeft:
case RoundCorner.CornerEnum.TopLeft:
ctx.arc(r, r, r, Math.PI, 3 * Math.PI / 2);
ctx.lineTo(0, 0);
break;
case cornerEnum.topRight:
case RoundCorner.CornerEnum.TopRight:
ctx.arc(0, r, r, 3 * Math.PI / 2, 2 * Math.PI);
ctx.lineTo(r, 0);
break;
case cornerEnum.bottomLeft:
case RoundCorner.CornerEnum.BottomLeft:
ctx.arc(r, 0, r, Math.PI / 2, Math.PI);
ctx.lineTo(0, r);
break;
case cornerEnum.bottomRight:
case RoundCorner.CornerEnum.BottomRight:
ctx.arc(0, 0, r, 0, Math.PI / 2);
ctx.lineTo(r, r);
break;
+5 -5
View File
@@ -14,7 +14,7 @@ import Quickshell.Hyprland
Scope { // Scope
id: root
property bool pinned: ConfigOptions?.dock.pinnedOnStartup ?? false
property bool pinned: Config.options?.dock.pinnedOnStartup ?? false
Variants { // For each monitor
model: Quickshell.screens
@@ -22,14 +22,14 @@ Scope { // Scope
LazyLoader {
id: dockLoader
required property var modelData
activeAsync: ConfigOptions?.dock.hoverToReveal || (!ToplevelManager.activeToplevel?.activated)
activeAsync: Config.options?.dock.hoverToReveal || (!ToplevelManager.activeToplevel?.activated)
component: PanelWindow { // Window
id: dockRoot
screen: dockLoader.modelData
property bool reveal: root.pinned
|| (ConfigOptions?.dock.hoverToReveal && dockMouseArea.containsMouse)
|| (Config.options?.dock.hoverToReveal && dockMouseArea.containsMouse)
|| dockApps.requestDockShow
|| (!ToplevelManager.activeToplevel?.activated)
@@ -47,7 +47,7 @@ Scope { // Scope
WlrLayershell.namespace: "quickshell:dock"
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 {
item: dockMouseArea
@@ -58,7 +58,7 @@ Scope { // Scope
anchors.top: parent.top
height: parent.height
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)
anchors.left: parent.left
+1 -1
View File
@@ -48,7 +48,7 @@ Item {
var map = new Map();
// Pinned apps
const pinnedApps = ConfigOptions?.dock.pinnedApps ?? [];
const pinnedApps = Config.options?.dock.pinnedApps ?? [];
for (const appId of pinnedApps) {
if (!map.has(appId.toLowerCase())) map.set(appId.toLowerCase(), ({
pinned: true,
@@ -108,8 +108,8 @@ Scope {
WlrLayershell.namespace: "quickshell:mediaControls"
anchors {
top: !ConfigOptions.bar.bottom
bottom: ConfigOptions.bar.bottom
top: !Config.options.bar.bottom
bottom: Config.options.bar.bottom
left: true
}
mask: Region {
@@ -22,7 +22,7 @@ Scope {
Timer {
id: osdTimeout
interval: ConfigOptions.osd.timeout
interval: Config.options.osd.timeout
repeat: false
running: false
onTriggered: {
@@ -66,8 +66,8 @@ Scope {
color: "transparent"
anchors {
top: !ConfigOptions.bar.bottom
bottom: ConfigOptions.bar.bottom
top: !Config.options.bar.bottom
bottom: Config.options.bar.bottom
}
mask: Region {
item: osdValuesWrapper
@@ -22,7 +22,7 @@ Scope {
Timer {
id: osdTimeout
interval: ConfigOptions.osd.timeout
interval: Config.options.osd.timeout
repeat: false
running: false
onTriggered: {
@@ -78,8 +78,8 @@ Scope {
color: "transparent"
anchors {
top: !ConfigOptions.bar.bottom
bottom: ConfigOptions.bar.bottom
top: !Config.options.bar.bottom
bottom: Config.options.bar.bottom
}
mask: Region {
item: osdValuesWrapper
@@ -15,7 +15,7 @@ import Quickshell.Hyprland
Scope { // Scope
id: root
property bool pinned: ConfigOptions?.osk.pinnedOnStartup ?? false
property bool pinned: Config.options?.osk.pinnedOnStartup ?? false
component OskControlButton: GroupButton { // Pin button
baseWidth: 40
@@ -13,7 +13,7 @@ import Quickshell.Hyprland
Item {
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 currentLayout: layouts[activeLayoutName]
@@ -73,7 +73,7 @@ Scope {
Timer {
id: delayedGrabTimer
interval: ConfigOptions.hacks.arbitraryRaceConditionDelay
interval: Config.options.hacks.arbitraryRaceConditionDelay
repeat: false
onTriggered: {
if (!grab.canBeActive) return
@@ -205,7 +205,7 @@ Scope {
if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) {
overviewScope.dontAutoCancelSearch = true;
panelWindow.setSearchingText(
ConfigOptions.search.prefix.clipboard
Config.options.search.prefix.clipboard
);
GlobalStates.overviewOpen = true;
return
@@ -228,7 +228,7 @@ Scope {
if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) {
overviewScope.dontAutoCancelSearch = true;
panelWindow.setSearchingText(
ConfigOptions.search.prefix.emojis
Config.options.search.prefix.emojis
);
GlobalStates.overviewOpen = true;
return
@@ -17,14 +17,14 @@ Item {
required property var panelWindow
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(panelWindow.screen)
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)
property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor.id)
property var windows: HyprlandData.windowList
property var windowByAddress: HyprlandData.windowByAddress
property var windowAddresses: HyprlandData.addresses
property var monitorData: HyprlandData.monitors.find(m => m.id === root.monitor.id)
property real scale: ConfigOptions.overview.scale
property real scale: Config.options.overview.scale
property color activeBorderColor: Appearance.colors.colSecondary
property real workspaceImplicitWidth: (monitorData?.transform % 2 === 1) ?
@@ -71,18 +71,18 @@ Item {
anchors.centerIn: parent
spacing: workspaceSpacing
Repeater {
model: ConfigOptions.overview.rows
model: Config.options.overview.rows
delegate: RowLayout {
id: row
property int rowIndex: index
spacing: workspaceSpacing
Repeater { // Workspace repeater
model: ConfigOptions.overview.columns
model: Config.options.overview.columns
Rectangle { // Workspace
id: workspace
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 hoveredWorkspaceColor: ColorUtils.mix(defaultWorkspaceColor, Appearance.colors.colLayer1Hover, 0.1)
property color hoveredBorderColor: Appearance.colors.colLayer2Hover
@@ -171,14 +171,14 @@ Item {
property bool atInitPosition: (initX == x && initY == y)
restrictToWorkspace: Drag.active || atInitPosition
property int workspaceColIndex: (windowData?.workspace.id - 1) % ConfigOptions.overview.columns
property int workspaceRowIndex: Math.floor((windowData?.workspace.id - 1) % root.workspacesShown / 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 / Config.options.overview.columns)
xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex - (monitor?.x * root.scale)
yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * workspaceRowIndex - (monitor?.y * root.scale)
Timer {
id: updateWindowPosition
interval: ConfigOptions.hacks.arbitraryRaceConditionDelay
interval: Config.options.hacks.arbitraryRaceConditionDelay
repeat: false
running: false
onTriggered: {
@@ -245,8 +245,8 @@ Item {
Rectangle { // Focused workspace indicator
id: focusedWorkspaceIndicator
property int activeWorkspaceInGroup: monitor.activeWorkspace?.id - (root.workspaceGroup * root.workspacesShown)
property int activeWorkspaceRowIndex: Math.floor((activeWorkspaceInGroup - 1) / ConfigOptions.overview.columns)
property int activeWorkspaceColIndex: (activeWorkspaceInGroup - 1) % ConfigOptions.overview.columns
property int activeWorkspaceRowIndex: Math.floor((activeWorkspaceInGroup - 1) / Config.options.overview.columns)
property int activeWorkspaceColIndex: (activeWorkspaceInGroup - 1) % Config.options.overview.columns
x: (root.workspaceImplicitWidth + workspaceSpacing) * activeWorkspaceColIndex
y: (root.workspaceImplicitHeight + workspaceSpacing) * activeWorkspaceRowIndex
z: root.windowZ
@@ -80,7 +80,7 @@ Item { // Wrapper
Timer {
id: nonAppResultsTimer
interval: ConfigOptions.search.nonAppResultDelay
interval: Config.options.search.nonAppResultDelay
onTriggered: {
mathProcess.calculateExpression(root.searchingText);
}
@@ -203,7 +203,7 @@ Item { // Wrapper
Layout.leftMargin: 15
iconSize: Appearance.font.pixelSize.huge
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
id: searchInput
@@ -294,8 +294,8 @@ Item { // Wrapper
if(root.searchingText == "") return [];
///////////// Special cases ///////////////
if (root.searchingText.startsWith(ConfigOptions.search.prefix.clipboard)) { // Clipboard
const searchString = root.searchingText.slice(ConfigOptions.search.prefix.clipboard.length);
if (root.searchingText.startsWith(Config.options.search.prefix.clipboard)) { // Clipboard
const searchString = root.searchingText.slice(Config.options.search.prefix.clipboard.length);
return Cliphist.fuzzyQuery(searchString).map(entry => {
return {
cliphistRawString: entry,
@@ -310,8 +310,8 @@ Item { // Wrapper
};
}).filter(Boolean);
}
if (root.searchingText.startsWith(ConfigOptions.search.prefix.emojis)) { // Clipboard
const searchString = root.searchingText.slice(ConfigOptions.search.prefix.emojis.length);
if (root.searchingText.startsWith(Config.options.search.prefix.emojis)) { // Clipboard
const searchString = root.searchingText.slice(Config.options.search.prefix.emojis.length);
return Emojis.fuzzyQuery(searchString).map(entry => {
return {
cliphistRawString: entry,
@@ -346,12 +346,12 @@ Item { // Wrapper
fontType: "monospace",
materialSymbol: 'terminal',
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
.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)) {
return {
name: root.searchingText.startsWith(actionString) ? root.searchingText : actionString,
@@ -399,8 +399,8 @@ Item { // Wrapper
type: Translation.tr("Search the web"),
materialSymbol: 'travel_explore',
execute: () => {
let url = ConfigOptions.search.engineBaseUrl + root.searchingText
for (let site of ConfigOptions.search.excludedSites) {
let url = Config.options.search.engineBaseUrl + root.searchingText
for (let site of Config.options.search.excludedSites) {
url += ` -site:${site}`;
}
Qt.openUrlExternally(url);
@@ -416,8 +416,8 @@ Item { // Wrapper
anchors.left: parent?.left
anchors.right: parent?.right
entry: modelData
query: root.searchingText.startsWith(ConfigOptions.search.prefix.clipboard) ?
root.searchingText.slice(ConfigOptions.search.prefix.clipboard.length) :
query: root.searchingText.startsWith(Config.options.search.prefix.clipboard) ?
root.searchingText.slice(Config.options.search.prefix.clipboard.length) :
root.searchingText;
}
}
@@ -15,8 +15,8 @@ Scope {
model: Quickshell.screens
PanelWindow {
visible: (ConfigOptions.appearance.fakeScreenRounding === 1
|| (ConfigOptions.appearance.fakeScreenRounding === 2
visible: (Config.options.appearance.fakeScreenRounding === 1
|| (Config.options.appearance.fakeScreenRounding === 2
&& !activeWindow?.fullscreen))
property var modelData
@@ -56,28 +56,28 @@ Scope {
anchors.top: parent.top
anchors.left: parent.left
size: Appearance.rounding.screenRounding
corner: cornerEnum.topLeft
corner: RoundCorner.CornerEnum.TopLeft
}
RoundCorner {
id: topRightCorner
anchors.top: parent.top
anchors.right: parent.right
size: Appearance.rounding.screenRounding
corner: cornerEnum.topRight
corner: RoundCorner.CornerEnum.TopRight
}
RoundCorner {
id: bottomLeftCorner
anchors.bottom: parent.bottom
anchors.left: parent.left
size: Appearance.rounding.screenRounding
corner: cornerEnum.bottomLeft
corner: RoundCorner.CornerEnum.BottomLeft
}
RoundCorner {
id: bottomRightCorner
anchors.bottom: parent.bottom
anchors.right: parent.right
size: Appearance.rounding.screenRounding
corner: cornerEnum.bottomRight
corner: RoundCorner.CornerEnum.BottomRight
}
}
@@ -122,7 +122,7 @@ Scope {
id: sessionTaskManager
buttonIcon: "browse_activity"
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 }
KeyNavigation.left: sessionLogout
KeyNavigation.down: sessionFirmwareReboot
@@ -16,10 +16,10 @@ ContentPage {
text: "Weeb"
}
ConfigSelectionArray {
currentValue: ConfigOptions.policies.weeb
currentValue: Config.options.policies.weeb
configOptionName: "policies.weeb"
onSelected: (newValue) => {
ConfigLoader.setConfigValueAndSave("policies.weeb", newValue);
Config.options.policies.weeb = newValue;
}
options: [
{ displayName: "No", value: 0 },
@@ -34,10 +34,10 @@ ContentPage {
text: "AI"
}
ConfigSelectionArray {
currentValue: ConfigOptions.policies.ai
currentValue: Config.options.policies.ai
configOptionName: "policies.ai"
onSelected: (newValue) => {
ConfigLoader.setConfigValueAndSave("policies.ai", newValue);
Config.options.policies.ai = newValue;
}
options: [
{ displayName: "No", value: 0 },
@@ -52,22 +52,35 @@ ContentPage {
ContentSection {
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 {
title: "Appearance"
ConfigRow {
uniform: true
ConfigSwitch {
text: 'Borderless'
checked: ConfigOptions.bar.borderless
checked: Config.options.bar.borderless
onCheckedChanged: {
ConfigLoader.setConfigValueAndSave("bar.borderless", checked);
Config.options.bar.borderless = checked;
}
}
ConfigSwitch {
text: 'Show background'
checked: ConfigOptions.bar.showBackground
checked: Config.options.bar.showBackground
onCheckedChanged: {
ConfigLoader.setConfigValueAndSave("bar.showBackground", checked);
Config.options.bar.showBackground = checked;
}
StyledToolTip {
content: "Note: turning off can hurt readability"
@@ -82,16 +95,16 @@ ContentPage {
uniform: true
ConfigSwitch {
text: "Screen snip"
checked: ConfigOptions.bar.utilButtons.showScreenSnip
checked: Config.options.bar.utilButtons.showScreenSnip
onCheckedChanged: {
ConfigLoader.setConfigValueAndSave("bar.utilButtons.showScreenSnip", checked);
Config.options.bar.utilButtons.showScreenSnip = checked;
}
}
ConfigSwitch {
text: "Color picker"
checked: ConfigOptions.bar.utilButtons.showColorPicker
checked: Config.options.bar.utilButtons.showColorPicker
onCheckedChanged: {
ConfigLoader.setConfigValueAndSave("bar.utilButtons.showColorPicker", checked);
Config.options.bar.utilButtons.showColorPicker = checked;
}
}
}
@@ -99,16 +112,16 @@ ContentPage {
uniform: true
ConfigSwitch {
text: "Mic toggle"
checked: ConfigOptions.bar.utilButtons.showMicToggle
checked: Config.options.bar.utilButtons.showMicToggle
onCheckedChanged: {
ConfigLoader.setConfigValueAndSave("bar.utilButtons.showMicToggle", checked);
Config.options.bar.utilButtons.showMicToggle = checked;
}
}
ConfigSwitch {
text: "Keyboard toggle"
checked: ConfigOptions.bar.utilButtons.showKeyboardToggle
checked: Config.options.bar.utilButtons.showKeyboardToggle
onCheckedChanged: {
ConfigLoader.setConfigValueAndSave("bar.utilButtons.showKeyboardToggle", checked);
Config.options.bar.utilButtons.showKeyboardToggle = checked;
}
}
}
@@ -116,9 +129,9 @@ ContentPage {
uniform: true
ConfigSwitch {
text: "Dark/Light toggle"
checked: ConfigOptions.bar.utilButtons.showDarkModeToggle
checked: Config.options.bar.utilButtons.showDarkModeToggle
onCheckedChanged: {
ConfigLoader.setConfigValueAndSave("bar.utilButtons.showDarkModeToggle", checked);
Config.options.bar.utilButtons.showDarkModeToggle = checked;
}
}
ConfigSwitch {
@@ -136,37 +149,37 @@ ContentPage {
uniform: true
ConfigSwitch {
text: 'Show app icons'
checked: ConfigOptions.bar.workspaces.showAppIcons
checked: Config.options.bar.workspaces.showAppIcons
onCheckedChanged: {
ConfigLoader.setConfigValueAndSave("bar.workspaces.showAppIcons", checked);
Config.options.bar.workspaces.showAppIcons = checked;
}
}
ConfigSwitch {
text: 'Always show numbers'
checked: ConfigOptions.bar.workspaces.alwaysShowNumbers
checked: Config.options.bar.workspaces.alwaysShowNumbers
onCheckedChanged: {
ConfigLoader.setConfigValueAndSave("bar.workspaces.alwaysShowNumbers", checked);
Config.options.bar.workspaces.alwaysShowNumbers = checked;
}
}
}
ConfigSpinBox {
text: "Workspaces shown"
value: ConfigOptions.bar.workspaces.shown
value: Config.options.bar.workspaces.shown
from: 1
to: 30
stepSize: 1
onValueChanged: {
ConfigLoader.setConfigValueAndSave("bar.workspaces.shown", value);
Config.options.bar.workspaces.shown = value;
}
}
ConfigSpinBox {
text: "Number show delay when pressing Super (ms)"
value: ConfigOptions.bar.workspaces.showNumberDelay
value: Config.options.bar.workspaces.showNumberDelay
from: 0
to: 1000
stepSize: 50
onValueChanged: {
ConfigLoader.setConfigValueAndSave("bar.workspaces.showNumberDelay", value);
Config.options.bar.workspaces.showNumberDelay = value;
}
}
}
@@ -179,22 +192,22 @@ ContentPage {
uniform: true
ConfigSpinBox {
text: "Low warning"
value: ConfigOptions.battery.low
value: Config.options.battery.low
from: 0
to: 100
stepSize: 5
onValueChanged: {
ConfigLoader.setConfigValueAndSave("battery.low", value);
Config.options.battery.low = value;
}
}
ConfigSpinBox {
text: "Critical warning"
value: ConfigOptions.battery.critical
value: Config.options.battery.critical
from: 0
to: 100
stepSize: 5
onValueChanged: {
ConfigLoader.setConfigValueAndSave("battery.critical", value);
Config.options.battery.critical = value;
}
}
}
@@ -202,9 +215,9 @@ ContentPage {
uniform: true
ConfigSwitch {
text: "Automatic suspend"
checked: ConfigOptions.battery.automaticSuspend
checked: Config.options.battery.automaticSuspend
onCheckedChanged: {
ConfigLoader.setConfigValueAndSave("battery.automaticSuspend", checked);
Config.options.battery.automaticSuspend = checked;
}
StyledToolTip {
content: "Automatically suspends the system when battery is low"
@@ -212,12 +225,12 @@ ContentPage {
}
ConfigSpinBox {
text: "Suspend at"
value: ConfigOptions.battery.suspend
value: Config.options.battery.suspend
from: 0
to: 100
stepSize: 5
onValueChanged: {
ConfigLoader.setConfigValueAndSave("battery.suspend", value);
Config.options.battery.suspend = value;
}
}
}
@@ -227,34 +240,34 @@ ContentPage {
title: "Overview"
ConfigSpinBox {
text: "Scale (%)"
value: ConfigOptions.overview.scale * 100
value: Config.options.overview.scale * 100
from: 1
to: 100
stepSize: 1
onValueChanged: {
ConfigLoader.setConfigValueAndSave("overview.scale", value / 100);
Config.options.overview.scale = value / 100;
}
}
ConfigRow {
uniform: true
ConfigSpinBox {
text: "Rows"
value: ConfigOptions.overview.rows
value: Config.options.overview.rows
from: 1
to: 20
stepSize: 1
onValueChanged: {
ConfigLoader.setConfigValueAndSave("overview.rows", value);
Config.options.overview.rows = value;
}
}
ConfigSpinBox {
text: "Columns"
value: ConfigOptions.overview.columns
value: Config.options.overview.columns
from: 1
to: 20
stepSize: 1
onValueChanged: {
ConfigLoader.setConfigValueAndSave("overview.columns", value);
Config.options.overview.columns = value;
}
}
}
@@ -13,9 +13,9 @@ ContentPage {
ConfigSwitch {
text: "Earbang protection"
checked: ConfigOptions.audio.protection.enable
checked: Config.options.audio.protection.enable
onCheckedChanged: {
ConfigLoader.setConfigValueAndSave("audio.protection.enable", checked);
Config.options.audio.protection.enable = checked;
}
StyledToolTip {
content: "Prevents abrupt increments and restricts volume limit"
@@ -25,22 +25,22 @@ ContentPage {
// uniform: true
ConfigSpinBox {
text: "Max allowed increase"
value: ConfigOptions.audio.protection.maxAllowedIncrease
value: Config.options.audio.protection.maxAllowedIncrease
from: 0
to: 100
stepSize: 2
onValueChanged: {
ConfigLoader.setConfigValueAndSave("audio.protection.maxAllowedIncrease", value);
Config.options.audio.protection.maxAllowedIncrease = value;
}
}
ConfigSpinBox {
text: "Volume limit"
value: ConfigOptions.audio.protection.maxAllowed
value: Config.options.audio.protection.maxAllowed
from: 0
to: 100
stepSize: 2
onValueChanged: {
ConfigLoader.setConfigValueAndSave("audio.protection.maxAllowed", value);
Config.options.audio.protection.maxAllowed = value;
}
}
}
@@ -50,10 +50,12 @@ ContentPage {
MaterialTextField {
Layout.fillWidth: true
placeholderText: "System prompt"
text: ConfigOptions.ai.systemPrompt
text: Config.options.ai.systemPrompt
wrapMode: TextEdit.Wrap
onTextChanged: {
ConfigLoader.setConfigValueAndSave("ai.systemPrompt", text);
Qt.callLater(() => {
Config.options.ai.systemPrompt = text;
});
}
}
}
@@ -65,22 +67,22 @@ ContentPage {
uniform: true
ConfigSpinBox {
text: "Low warning"
value: ConfigOptions.battery.low
value: Config.options.battery.low
from: 0
to: 100
stepSize: 5
onValueChanged: {
ConfigLoader.setConfigValueAndSave("battery.low", value);
Config.options.battery.low = value;
}
}
ConfigSpinBox {
text: "Critical warning"
value: ConfigOptions.battery.critical
value: Config.options.battery.critical
from: 0
to: 100
stepSize: 5
onValueChanged: {
ConfigLoader.setConfigValueAndSave("battery.critical", value);
Config.options.battery.critical = value;
}
}
}
@@ -88,9 +90,9 @@ ContentPage {
uniform: true
ConfigSwitch {
text: "Automatic suspend"
checked: ConfigOptions.battery.automaticSuspend
checked: Config.options.battery.automaticSuspend
onCheckedChanged: {
ConfigLoader.setConfigValueAndSave("battery.automaticSuspend", checked);
Config.options.battery.automaticSuspend = checked;
}
StyledToolTip {
content: "Automatically suspends the system when battery is low"
@@ -98,12 +100,12 @@ ContentPage {
}
ConfigSpinBox {
text: "Suspend at"
value: ConfigOptions.battery.suspend
value: Config.options.battery.suspend
from: 0
to: 100
stepSize: 5
onValueChanged: {
ConfigLoader.setConfigValueAndSave("battery.suspend", value);
Config.options.battery.suspend = value;
}
}
}
@@ -114,10 +116,10 @@ ContentPage {
MaterialTextField {
Layout.fillWidth: true
placeholderText: "User agent (for services that require it)"
text: ConfigOptions.networking.userAgent
text: Config.options.networking.userAgent
wrapMode: TextEdit.Wrap
onTextChanged: {
ConfigLoader.setConfigValueAndSave("networking.userAgent", text);
Config.options.networking.userAgent = text;
}
}
}
@@ -126,12 +128,12 @@ ContentPage {
title: "Resources"
ConfigSpinBox {
text: "Polling interval (ms)"
value: ConfigOptions.resources.updateInterval
value: Config.options.resources.updateInterval
from: 100
to: 10000
stepSize: 100
onValueChanged: {
ConfigLoader.setConfigValueAndSave("resources.updateInterval", value);
Config.options.resources.updateInterval = value;
}
}
}
@@ -46,10 +46,10 @@ ContentPage {
ContentSubsection {
title: "Material palette"
ConfigSelectionArray {
currentValue: ConfigOptions.appearance.palette.type
currentValue: Config.options.appearance.palette.type
configOptionName: "appearance.palette.type"
onSelected: (newValue) => {
ConfigLoader.setConfigValueAndSave("appearance.palette.type", newValue);
Config.options.appearance.palette.type = newValue;
}
options: [
{"value": "auto", "displayName": "Auto"},
@@ -141,9 +141,9 @@ ContentPage {
ConfigRow {
ConfigSwitch {
text: "Enable"
checked: ConfigOptions.appearance.transparency
checked: Config.options.appearance.transparency
onCheckedChanged: {
ConfigLoader.setConfigValueAndSave("appearance.transparency", checked);
Config.options.appearance.transparency = checked;
}
StyledToolTip {
content: "Might look ass. Unsupported."
@@ -157,7 +157,7 @@ ContentPage {
ButtonGroup {
id: fakeScreenRoundingButtonGroup
property int selectedPolicy: ConfigOptions.appearance.fakeScreenRounding
property int selectedPolicy: Config.options.appearance.fakeScreenRounding
spacing: 2
SelectionGroupButton {
property int value: 0
@@ -165,7 +165,7 @@ ContentPage {
buttonText: "No"
toggled: (fakeScreenRoundingButtonGroup.selectedPolicy === value)
onClicked: {
ConfigLoader.setConfigValueAndSave("appearance.fakeScreenRounding", value);
Config.options.appearance.fakeScreenRounding = value;
}
}
SelectionGroupButton {
@@ -173,7 +173,7 @@ ContentPage {
buttonText: "Yes"
toggled: (fakeScreenRoundingButtonGroup.selectedPolicy === value)
onClicked: {
ConfigLoader.setConfigValueAndSave("appearance.fakeScreenRounding", value);
Config.options.appearance.fakeScreenRounding = value;
}
}
SelectionGroupButton {
@@ -182,7 +182,7 @@ ContentPage {
buttonText: "When not fullscreen"
toggled: (fakeScreenRoundingButtonGroup.selectedPolicy === value)
onClicked: {
ConfigLoader.setConfigValueAndSave("appearance.fakeScreenRounding", value);
Config.options.appearance.fakeScreenRounding = value;
}
}
}
@@ -195,16 +195,16 @@ ContentPage {
uniform: true
ConfigSwitch {
text: "Title bar"
checked: ConfigOptions.windows.showTitlebar
checked: Config.options.windows.showTitlebar
onCheckedChanged: {
ConfigLoader.setConfigValueAndSave("windows.showTitlebar", checked);
Config.options.windows.showTitlebar = checked;
}
}
ConfigSwitch {
text: "Center title"
checked: ConfigOptions.windows.centerTitle
checked: Config.options.windows.centerTitle
onCheckedChanged: {
ConfigLoader.setConfigValueAndSave("windows.centerTitle", checked);
Config.options.windows.centerTitle = checked;
}
}
}
@@ -50,10 +50,14 @@ Item {
}
},
{
name: "clear",
description: Translation.tr("Clear chat history"),
execute: () => {
Ai.clearMessages();
name: "prompt",
description: Translation.tr("Set the system prompt for the model."),
execute: (args) => {
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",
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
visible: descriptionText.text.length > 0
Layout.fillWidth: true
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 ?? ""
}
}
DescriptionBox {
text: root.suggestionList[suggestions.selectedIndex]?.description ?? ""
showArrows: root.suggestionList.length > 1
}
FlowButtonGroup { // Suggestions
@@ -294,7 +281,7 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
}
delegate: ApiCommandButton {
id: commandButton
colBackground: suggestions.selectedIndex === index ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2
colBackground: suggestions.selectedIndex === index ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colSecondaryContainer
bounce: false
contentItem: StyledText {
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}`,
}
})
} 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)) {
root.suggestionQuery = messageInputField.text
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/file_utils.js" as FileUtils
import "./anime/"
import "root:/services/"
import Qt.labs.platform
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell.Io
import Quickshell
import Quickshell.Hyprland
Item {
id: root
@@ -66,14 +62,14 @@ Item {
name: "safe",
description: Translation.tr("Disable NSFW content"),
execute: () => {
PersistentStateManager.setState("booru.allowNsfw", false);
Persistent.states.booru.allowNsfw = false;
}
},
{
name: "lewd",
description: Translation.tr("Allow NSFW content"),
execute: () => {
PersistentStateManager.setState("booru.allowNsfw", true);
Persistent.states.booru.allowNsfw = true;
}
},
]
@@ -107,7 +103,7 @@ Item {
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
visible: tagDescriptionText.text.length > 0
Layout.fillWidth: true
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 ?? ""
}
}
DescriptionBox { // Tag suggestion description
text: root.suggestionList[tagSuggestions.selectedIndex]?.description ?? ""
showArrows: root.suggestionList.length > 1
}
FlowButtonGroup { // Tag suggestions
@@ -296,7 +268,7 @@ Item {
}
delegate: ApiCommandButton {
id: tagButton
colBackground: tagSuggestions.selectedIndex === index ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2
colBackground: tagSuggestions.selectedIndex === index ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colSecondaryContainer
bounce: false
contentItem: RowLayout {
anchors.centerIn: parent
@@ -304,7 +276,7 @@ Item {
StyledText {
Layout.fillWidth: false
font.pixelSize: Appearance.font.pixelSize.small
color: Appearance.m3colors.m3onSurface
color: Appearance.colors.colOnSecondaryContainer
horizontalAlignment: Text.AlignRight
text: modelData.displayName ?? modelData.name
}
@@ -312,7 +284,7 @@ Item {
Layout.fillWidth: false
visible: modelData.count !== undefined
font.pixelSize: Appearance.font.pixelSize.smaller
color: Appearance.m3colors.m3outline
color: Appearance.colors.colOnSecondaryContainer
horizontalAlignment: Text.AlignLeft
text: modelData.count ?? ""
}
@@ -594,10 +566,10 @@ Item {
enabled: Booru.currentProvider !== "zerochan"
scale: 0.6
Layout.alignment: Qt.AlignVCenter
checked: (PersistentStates.booru.allowNsfw && Booru.currentProvider !== "zerochan")
checked: (Persistent.states.booru.allowNsfw && Booru.currentProvider !== "zerochan")
onCheckedChanged: {
if (!nsfwSwitch.enabled) return;
PersistentStateManager.setState("booru.allowNsfw", checked)
Persistent.states.booru.allowNsfw = checked;
}
}
}
@@ -611,7 +583,6 @@ Item {
id: commandRepeater
model: commandButtonsRow.commandsShown
delegate: ApiCommandButton {
id: tagButton
property string commandRepresentation: `${root.commandPrefix}${modelData.name}`
buttonText: commandRepresentation
colBackground: Appearance.colors.colLayer2
@@ -16,7 +16,7 @@ GroupButton {
baseWidth: contentItem.implicitWidth + horizontalPadding * 2
clickedWidth: baseWidth + 20
baseHeight: contentItem.implicitHeight + verticalPadding * 2
buttonRadius: down ? Appearance.rounding.small : baseHeight / 2
buttonRadius: down ? Appearance.rounding.verysmall : Appearance.rounding.small
colBackground: Appearance.colors.colLayer2
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
anchors.fill: parent
property var tabButtonList: [
...(ConfigOptions.policies.ai !== 0 ? [{"icon": "neurology", "name": Translation.tr("Intelligence")}] : []),
{"icon": "translate", "name": Translation.tr("Translator")},
...(ConfigOptions.policies.weeb === 1 ? [{"icon": "bookmark_heart", "name": Translation.tr("Anime")}] : [])
...(Config.options.policies.ai !== 0 ? [{"icon": "neurology", "name": qsTr("Intelligence")}] : []),
{"icon": "translate", "name": qsTr("Translator")},
...(Config.options.policies.weeb === 1 ? [{"icon": "bookmark_heart", "name": qsTr("Anime")}] : [])
]
property int selectedTab: 0
@@ -89,9 +89,9 @@ Item {
}
contentChildren: [
...(ConfigOptions.policies.ai !== 0 ? [aiChat.createObject()] : []),
...(Config.options.policies.ai !== 0 ? [aiChat.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 list<string> languages: []
// Options
property string targetLanguage: ConfigOptions.language.translator.targetLanguage
property string sourceLanguage: ConfigOptions.language.translator.sourceLanguage
property string targetLanguage: Config.options.language.translator.targetLanguage
property string sourceLanguage: Config.options.language.translator.sourceLanguage
property string hostLanguage: targetLanguage
property bool showLanguageSelector: false
@@ -43,7 +43,7 @@ Item {
Timer {
id: translateTimer
interval: ConfigOptions.sidebar.translator.delay
interval: Config.options.sidebar.translator.delay
repeat: false
onTriggered: () => {
if (root.inputField.text.trim().length > 0) {
@@ -155,8 +155,8 @@ Item {
color: searchButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
}
onClicked: {
let url = ConfigOptions.search.engineBaseUrl + outputCanvas.displayedText;
for (let site of ConfigOptions.search.excludedSites) {
let url = Config.options.search.engineBaseUrl + outputCanvas.displayedText;
for (let site of Config.options.search.excludedSites) {
url += ` -site:${site}`;
}
Qt.openUrlExternally(url);
@@ -235,10 +235,10 @@ Item {
if (root.languageSelectorTarget) {
root.targetLanguage = result;
ConfigLoader.setConfigValueAndSave("language.translator.targetLanguage", result); // Save to config
Config.options.language.translator.targetLanguage = result; // Save to config
} else {
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
@@ -15,7 +15,7 @@ Rectangle {
clip: true
implicitHeight: collapsed ? collapsedBottomWidgetGroupRow.implicitHeight : bottomWidgetGroupRow.implicitHeight
property int selectedTab: 0
property bool collapsed: PersistentStates.sidebar.bottomGroup.collapsed
property bool collapsed: Persistent.states.sidebar.bottomGroup.collapsed
property var tabs: [
{"type": "calendar", "name": "Calendar", "icon": "calendar_month", "widget": calendarWidget},
{"type": "todo", "name": "To Do", "icon": "done_outline", "widget": todoWidget}
@@ -30,7 +30,7 @@ Rectangle {
}
function setCollapsed(state) {
PersistentStateManager.setState("sidebar.bottomGroup.collapsed", state)
Persistent.states.sidebar.bottomGroup.collapsed = state
if (collapsed) {
bottomWidgetGroupRow.opacity = 0
}
@@ -15,7 +15,7 @@ QuickToggleButton {
toggleBluetooth.running = true
}
altAction: () => {
Quickshell.execDetached(["bash", "-c", `${ConfigOptions.apps.bluetooth}`])
Quickshell.execDetached(["bash", "-c", `${Config.options.apps.bluetooth}`])
Hyprland.dispatch("global quickshell:sidebarRightClose")
}
Process {
@@ -24,7 +24,6 @@ QuickToggleButton {
running: true
command: ["bash", "-c", `test "$(hyprctl getoption animations:enabled -j | jq ".int")" -ne 0`]
onExited: (exitCode, exitStatus) => {
console.log("Game mode toggle exited with code:", exitCode, "and status:", exitStatus)
root.toggled = exitCode !== 0 // Inverted because enabled = nonzero exit
}
}
@@ -16,7 +16,7 @@ QuickToggleButton {
toggleNetwork.running = true
}
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")
}
Process {
@@ -17,6 +17,10 @@ Item {
property bool deviceSelectorInput
property int dialogMargins: 16
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) {
root.selectedDevice = null
@@ -60,21 +64,13 @@ Item {
anchors.margins: 10
spacing: 10
// Get a list of nodes that output to the default sink
PwNodeLinkTracker {
id: linkTracker
node: Pipewire.defaultAudioSink
}
Repeater {
model: linkTracker.linkGroups
model: root.appPwNodes
VolumeMixerEntry {
Layout.fillWidth: true
// Get links to the default sinnk
required property PwLinkGroup modelData
// Consider sources that output to the default sink
node: modelData.source
required property var modelData
node: modelData
}
}
}
@@ -85,7 +81,7 @@ Item {
anchors.fill: flickable
visible: opacity > 0
opacity: (linkTracker.linkGroups.length === 0) ? 1 : 0
opacity: (root.appPwNodes.length === 0) ? 1 : 0
Behavior on opacity {
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 "$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+=(--cache "$STATE_DIR/user/color.txt")
generate_colors_material_args+=(--cache "$STATE_DIR/user/generated/color.txt")
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 apiKeyEnvVarName: "API_KEY"
property Component aiMessageComponent: AiMessageData {}
property string systemPrompt: ConfigOptions?.ai?.systemPrompt ?? ""
property string systemPrompt: Config.options?.ai?.systemPrompt ?? ""
property var messages: []
property var messageIDs: []
property var messageByID: ({})
readonly property var apiKeys: KeyringStorage.keyringData?.apiKeys ?? {}
readonly property var apiKeysLoaded: KeyringStorage.loaded
property var postResponseHook
property real temperature: PersistentStates?.ai?.temperature ?? 0.5
property real temperature: Persistent.states?.ai?.temperature ?? 0.5
function idForMessage(message) {
// Generate a unique ID using timestamp and random value
@@ -37,6 +37,10 @@ Singleton {
return modelName.replace(/:/g, "_").replace(/\./g, "_")
}
property list<var> defaultPrompts: []
property list<var> userPrompts: []
property list<var> promptFiles: [...defaultPrompts, ...userPrompts]
// Model properties:
// - name: Name of the model
// - icon: Icon name of the model
@@ -203,11 +207,10 @@ Singleton {
},
}
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: {
setModel(currentModelId, false); // Do necessary setup for model
getOllamaModels.running = true
setModel(currentModelId, false, false); // Do necessary setup for model
}
function guessModelLogo(model) {
@@ -233,6 +236,7 @@ Singleton {
Process {
id: getOllamaModels
running: true
command: ["bash", "-c", `${Directories.config}/quickshell/scripts/ai/show-installed-ollama-models.sh`.replace(/file:\/\//, "")]
stdout: SplitParser {
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) {
if (message.length === 0) return;
const aiMessage = aiMessageComponent.createObject(root, {
@@ -294,7 +346,7 @@ Singleton {
return models[currentModelId];
}
function setModel(modelId, feedback = true) {
function setModel(modelId, feedback = true, setPersistentState = true) {
if (!modelId) modelId = ""
modelId = modelId.toLowerCase()
if (modelList.indexOf(modelId) !== -1) {
@@ -302,12 +354,12 @@ Singleton {
// Fetch API keys if needed
if (model?.requires_key) KeyringStorage.fetchKeyringData();
// 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);
return;
}
PersistentStateManager.setState("ai.model", modelId);
if (feedback) root.addMessage(StringUtils.format(StringUtils.format("Model set to {0}"), model.name), root.interfaceRole);
if (setPersistentState) Persistent.states.ai.model = modelId;
if (feedback) root.addMessage(StringUtils.format("Model set to {0}", model.name), root.interfaceRole);
if (model.requires_key) {
// If key not there show advice
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);
return;
}
PersistentStateManager.setState("ai.temperature", value);
Persistent.states.ai.temperature = value;
root.temperature = value;
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."))
requester.makeRequest();
} else if (name === "get_shell_config") {
const configJson = ObjectUtils.toPlainObject(ConfigOptions)
const configJson = ObjectUtils.toPlainObject(Config.options)
addFunctionOutputMessage(name, JSON.stringify(configJson));
requester.makeRequest();
} else if (name === "set_shell_config") {
@@ -715,8 +767,7 @@ Singleton {
}
const key = args.key;
const value = args.value;
ConfigLoader.setLiveConfigValue(key, value);
ConfigLoader.saveConfig();
Config.setNestedValue(key, value);
}
else root.addMessage(Translation.tr("Unknown function call: {0}"), "assistant");
}
+1 -1
View File
@@ -12,7 +12,7 @@ import Quickshell.Io
*/
Singleton {
id: root
property bool sloppySearch: ConfigOptions?.search.sloppy ?? false
property bool sloppySearch: Config.options?.search.sloppy ?? false
property real scoreThreshold: 0.2
property var substitutions: ({
"code-url-handler": "visual-studio-code",
+3 -3
View File
@@ -26,15 +26,15 @@ Singleton {
property bool lastReady: false
property real lastVolume: 0
function onVolumeChanged() {
if (!ConfigOptions.audio.protection.enable) return;
if (!Config.options.audio.protection.enable) return;
if (!lastReady) {
lastVolume = sink.audio.volume;
lastReady = true;
return;
}
const newVolume = sink.audio.volume;
const maxAllowedIncrease = ConfigOptions.audio.protection.maxAllowedIncrease / 100;
const maxAllowed = ConfigOptions.audio.protection.maxAllowed / 100;
const maxAllowedIncrease = Config.options.audio.protection.maxAllowedIncrease / 100;
const maxAllowed = Config.options.audio.protection.maxAllowed / 100;
if (newVolume - lastVolume > maxAllowedIncrease) {
sink.audio.volume = lastVolume;
+5 -5
View File
@@ -12,11 +12,11 @@ Singleton {
property bool isCharging: chargeState == UPowerDeviceState.Charging
property bool isPluggedIn: isCharging || chargeState == UPowerDeviceState.PendingCharge
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 isCritical: percentage <= ConfigOptions.battery.critical / 100
property bool isSuspending: percentage <= ConfigOptions.battery.suspend / 100
property bool isLow: percentage <= Config.options.battery.low / 100
property bool isCritical: percentage <= Config.options.battery.critical / 100
property bool isSuspending: percentage <= Config.options.battery.suspend / 100
property bool isLowAndNotCharging: isLow && !isCharging
property bool isCriticalAndNotCharging: isCritical && !isCharging
@@ -29,7 +29,7 @@ Singleton {
onIsCriticalAndNotChargingChanged: {
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: {
+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 var responses: []
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 providers: {
"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) {
if (url.includes('pximg.net')) {
@@ -287,7 +287,7 @@ Singleton {
function setProvider(provider) {
provider = provider.toLowerCase()
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
+ (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 {
@@ -409,7 +409,7 @@ Singleton {
xhr.setRequestHeader("User-Agent", defaultUserAgent)
}
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)
}
root.runningRequests++;
+2 -2
View File
@@ -11,7 +11,7 @@ import Quickshell.Io
Singleton {
id: root
property bool sloppySearch: ConfigOptions?.search.sloppy ?? false
property bool sloppySearch: Config.options?.search.sloppy ?? false
property real scoreThreshold: 0.2
property list<string> entries: []
readonly property var preparedEntries: entries.map(a => ({
@@ -51,7 +51,7 @@ Singleton {
Timer {
id: delayedUpdateTimer
interval: ConfigOptions.hacks.arbitraryRaceConditionDelay
interval: Config.options.hacks.arbitraryRaceConditionDelay
repeat: false
onTriggered: {
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.
*/
Singleton {
property string time: Qt.locale().toString(clock.date, ConfigOptions?.time.format ?? "hh:mm")
property string date: Qt.locale().toString(clock.date, ConfigOptions?.time.dateFormat ?? "dddd, dd/MM")
property string time: Qt.locale().toString(clock.date, Config.options?.time.format ?? "hh: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 uptime: "0h, 0m"
@@ -39,7 +39,7 @@ Singleton {
if (hours > 0) formatted += `${formatted ? ", " : ""}${hours}h`
if (minutes > 0 || !formatted) formatted += `${formatted ? ", " : ""}${minutes}m`
uptime = formatted
interval = ConfigOptions?.resources?.updateInterval ?? 3000
interval = Config.options?.resources?.updateInterval ?? 3000
}
}
@@ -16,14 +16,20 @@ Singleton {
property var addresses: []
property var windowByAddress: ({})
property var monitors: []
property var layers: ({})
function updateWindowList() {
getClients.running = true
getMonitors.running = true
}
function updateLayers() {
getLayers.running = true
}
Component.onCompleted: {
updateWindowList()
updateLayers()
}
Connections {
@@ -56,6 +62,7 @@ Singleton {
}
}
}
Process {
id: getMonitors
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 {
id: delayedFileRead
interval: ConfigOptions?.hacks?.arbitraryRaceConditionDelay ?? 100
interval: Config.options?.hacks?.arbitraryRaceConditionDelay ?? 100
repeat: false
running: false
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 }
}
interval = ConfigOptions?.resources?.updateInterval ?? 3000
interval = Config.options?.resources?.updateInterval ?? 3000
}
}
+3 -4
View File
@@ -56,7 +56,6 @@ ApplicationWindow {
Component.onCompleted: {
MaterialThemeLoader.reapplyTheme()
ConfigLoader.loadConfig()
}
minimumWidth: 600
@@ -93,15 +92,15 @@ ApplicationWindow {
}
Item { // Titlebar
visible: ConfigOptions?.windows.showTitlebar
visible: Config.options?.windows.showTitlebar
Layout.fillWidth: true
Layout.fillHeight: false
implicitHeight: Math.max(titleText.implicitHeight, windowControlsRow.implicitHeight)
StyledText {
id: titleText
anchors {
left: ConfigOptions.windows.centerTitle ? undefined : parent.left
horizontalCenter: ConfigOptions.windows.centerTitle ? parent.horizontalCenter : undefined
left: Config.options.windows.centerTitle ? undefined : parent.left
horizontalCenter: Config.options.windows.centerTitle ? parent.horizontalCenter : undefined
verticalCenter: parent.verticalCenter
leftMargin: 12
}
+2 -4
View File
@@ -32,7 +32,7 @@ ShellRoot {
property bool enableBar: true
property bool enableBackgroundWidgets: true
property bool enableCheatsheet: true
property bool enableDock: false
property bool enableDock: true
property bool enableMediaControls: true
property bool enableNotificationPopup: true
property bool enableOnScreenDisplayBrightness: true
@@ -48,8 +48,6 @@ ShellRoot {
// Force initialization of some singletons
Component.onCompleted: {
MaterialThemeLoader.reapplyTheme()
ConfigLoader.loadConfig()
PersistentStateManager.loadStates()
Cliphist.refresh()
FirstRunExperience.load()
}
@@ -57,7 +55,7 @@ ShellRoot {
LazyLoader { active: enableBar; component: Bar {} }
LazyLoader { active: enableBackgroundWidgets; component: BackgroundWidgets {} }
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: enableNotificationPopup; component: NotificationPopup {} }
LazyLoader { active: enableOnScreenDisplayBrightness; component: OnScreenDisplayBrightness {} }
+25 -9
View File
@@ -5,7 +5,6 @@
// Adjust this to make the app smaller or larger
//@ pragma Env QT_SCALE_FACTOR=1
import Qt5Compat.GraphicalEffects
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
@@ -32,7 +31,6 @@ ApplicationWindow {
Component.onCompleted: {
MaterialThemeLoader.reapplyTheme()
ConfigLoader.loadConfig()
}
minimumWidth: 600
@@ -60,14 +58,14 @@ ApplicationWindow {
}
Item { // Titlebar
visible: ConfigOptions?.windows.showTitlebar
visible: Config.options?.windows.showTitlebar
Layout.fillWidth: true
implicitHeight: Math.max(welcomeText.implicitHeight, windowControlsRow.implicitHeight)
StyledText {
id: welcomeText
anchors {
left: ConfigOptions.windows.centerTitle ? undefined : parent.left
horizontalCenter: ConfigOptions.windows.centerTitle ? parent.horizontalCenter : undefined
left: Config.options.windows.centerTitle ? undefined : parent.left
horizontalCenter: Config.options.windows.centerTitle ? parent.horizontalCenter : undefined
verticalCenter: parent.verticalCenter
leftMargin: 12
}
@@ -123,6 +121,24 @@ ApplicationWindow {
ContentPage {
id: contentColumn
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 {
title: "Style & wallpaper"
@@ -206,10 +222,10 @@ ApplicationWindow {
text: "Weeb"
}
ConfigSelectionArray {
currentValue: ConfigOptions.policies.weeb
currentValue: Config.options.policies.weeb
configOptionName: "policies.weeb"
onSelected: (newValue) => {
ConfigLoader.setConfigValueAndSave("policies.weeb", newValue);
Config.options.policies.weeb = newValue;
}
options: [
{ displayName: "No", value: 0 },
@@ -224,10 +240,10 @@ ApplicationWindow {
text: "AI"
}
ConfigSelectionArray {
currentValue: ConfigOptions.policies.ai
currentValue: Config.options.policies.ai
configOptionName: "policies.ai"
onSelected: (newValue) => {
ConfigLoader.setConfigValueAndSave("policies.ai", newValue);
Config.options.policies.ai = newValue;
}
options: [
{ displayName: "No", value: 0 },
@@ -7,6 +7,7 @@ license=(None)
depends=(
xdg-desktop-portal
xdg-desktop-portal-kde
xdg-desktop-portal-gtk
xdg-desktop-portal-hyprland
)
@@ -6,8 +6,8 @@ arch=(any)
license=(None)
depends=(
hyprshot
ksnip
slurp
swappy
tesseract
tesseract-data-eng
wf-recorder
+1 -1
View File
@@ -144,7 +144,7 @@ esac
v sudo usermod -aG video,i2c,input "$(whoami)"
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 sudo systemctl enable bluetooth --now
v gsettings set org.gnome.desktop.interface font-name 'Rubik 11'