diff --git a/.config/chrome-flags.conf b/.config/chrome-flags.conf index 592cd96fe..4f1b51862 100644 --- a/.config/chrome-flags.conf +++ b/.config/chrome-flags.conf @@ -1,5 +1,5 @@ --password-store=gnome-libsecret -# --ozone-platform-hint=wayland +--ozone-platform-hint=wayland --gtk-version=4 --ignore-gpu-blocklist --enable-features=TouchpadOverscrollHistoryNavigation diff --git a/.config/code-flags.conf b/.config/code-flags.conf index 49624fa78..0786a0c69 100644 --- a/.config/code-flags.conf +++ b/.config/code-flags.conf @@ -1,4 +1,4 @@ -# --ozone-platform-hint=wayland +--ozone-platform-hint=wayland --gtk-version=4 --ignore-gpu-blocklist --enable-features=TouchpadOverscrollHistoryNavigation diff --git a/.config/hypr/hyprland/general.conf b/.config/hypr/hyprland/general.conf index e8895efdc..b03a83040 100644 --- a/.config/hypr/hyprland/general.conf +++ b/.config/hypr/hyprland/general.conf @@ -97,10 +97,10 @@ animations { animation = border, 1, 10, emphasizedDecel # layers animation = layersIn, 1, 2.7, emphasizedDecel, popin 93% - animation = layersOut, 1, 2, menu_accel, popin 94% + animation = layersOut, 1, 2.4, menu_accel, popin 94% # fade animation = fadeLayersIn, 1, 0.5, menu_decel - animation = fadeLayersOut, 1, 2.2, menu_accel + animation = fadeLayersOut, 1, 2.7, menu_accel # workspaces animation = workspaces, 1, 7, menu_decel, slide ## specialWorkspace @@ -133,7 +133,7 @@ misc { key_press_enables_dpms = true animate_manual_resizes = false animate_mouse_windowdragging = false - enable_swallow = true + enable_swallow = false swallow_regex = (foot|kitty|allacritty|Alacritty) new_window_takes_over_fullscreen = 2 allow_session_lock_restore = true diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf index 806db1523..820a67f2b 100644 --- a/.config/hypr/hyprland/keybinds.conf +++ b/.config/hypr/hyprland/keybinds.conf @@ -51,7 +51,7 @@ bind = Ctrl+Super, R, exec, killall ags agsv1 gjs ydotool qs quickshell; qs & # 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)" - | swappy -f - # Screen snip and annotate +bindd = Super+Shift+Alt, S, Screen snip and annotate, exec, pidof slurp || grim -g "$(slurp)" - | ksnip -e - # Screen snip and annotate # 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 @@ -64,7 +64,7 @@ bindd = Super+Alt, R, Record region (no sound), exec, ~/.config/hypr/hyprland/sc bindd = Ctrl+Alt, R, Record screen (no sound), exec, ~/.config/hypr/hyprland/scripts/record.sh --fullscreen # [hidden] Record screen (no sound) bindd = Super+Shift+Alt, R, Record screen (with sound), exec, ~/.config/hypr/hyprland/scripts/record.sh --fullscreen-sound # Record screen (with sound) # AI -bindd = Super+Shift+Alt, mouse:273, Generate AI summary for selected text, exec, ~/.config/ags/scripts/ai/primary-buffer-query.sh # AI summary for selected text +bindd = Super+Shift+Alt, mouse:273, Generate AI summary for selected text, exec, ~/.config/hypr/hyprland/scripts/ai/primary-buffer-query.sh # AI summary for selected text #! ##! Window @@ -202,13 +202,13 @@ bindl= ,XF86AudioPause, exec, playerctl play-pause # [hidden] bind = Super, Return, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "kitty -1" "foot" "alacritty" "wezterm" "konsole" "kgx" "uxterm" "xterm" # Terminal bind = Super, T, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "kitty -1" "foot" "alacritty" "wezterm" "konsole" "kgx" "uxterm" "xterm" # [hidden] Kitty (terminal) (alt) bind = Ctrl+Alt, T, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "kitty -1" "foot" "alacritty" "wezterm" "konsole" "kgx" "uxterm" "xterm" # [hidden] Kitty (for Ubuntu people) -bind = Super, E, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "dolphin" "nautilus" "nemo" "thunar" # File manager +bind = Super, E, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "dolphin" "nautilus" "nemo" "thunar" "kitty -1 fish -c yazi" # File manager bind = Super, W, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "zen-browser" "firefox" "brave" "chromium" "google-chrome-stable" "microsoft-edge-stable" "opera" # Browser bind = Super, 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 "systemsettings" "gnome-control-center" "better-control" # Settings app +bind = Super, I, exec, XDG_CURRENT_DESKTOP=gnome ~/.config/hypr/hyprland/scripts/launch_first_available.sh "qs -p ~/.config/quickshell/settings.qml" "systemsettings" "gnome-control-center" "better-control" # Settings app bind = Ctrl+Shift, Escape, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "gnome-system-monitor" "plasma-systemmonitzor --page-name Processes" "command -v btop && kitty -1 fish -c btop" # System monitor # Cursed stuff diff --git a/.config/hypr/hyprland/rules.conf b/.config/hypr/hyprland/rules.conf index 6ca0989a6..bed26a999 100644 --- a/.config/hypr/hyprland/rules.conf +++ b/.config/hypr/hyprland/rules.conf @@ -22,7 +22,8 @@ windowrulev2 = center, class:^(nm-connection-editor)$ windowrulev2 = float, class:.*plasmawindowed.* windowrulev2 = float, class:kcm_.* windowrulev2 = float, class:.*bluedevilwizard -windowrulev2 = float, title:.*Welcome.* +windowrulev2 = float, title:.*Welcome +windowrulev2 = float, title:^(illogical-impulse Settings)$ # 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. diff --git a/.config/hypr/hyprland/scripts/ai/license_show-loaded-ollama-models.txt b/.config/hypr/hyprland/scripts/ai/license_show-loaded-ollama-models.txt new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/.config/hypr/hyprland/scripts/ai/license_show-loaded-ollama-models.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/.config/hypr/hyprland/scripts/ai/primary-buffer-query.sh b/.config/hypr/hyprland/scripts/ai/primary-buffer-query.sh new file mode 100755 index 000000000..794414554 --- /dev/null +++ b/.config/hypr/hyprland/scripts/ai/primary-buffer-query.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +# Default system prompt +SYSTEM_PROMPT="You are a helpful, quick assistant that provides brief and concise explanation \ +to given content in at most 100 characters. If the given content is not in English, translate \ +it to English. If the content is an English word, provide its meaning. If the content is a name, \ +provide some info about it. For a math expression, provide a simplification, \ +each step on a line following this style: \`2x=11 (subtract 7 from both sides)\`. \ +If you do not know the answer, simply say 'No info available'. \ +Only respond for the appropriate case and use as little text as possible.\ +The content:" + +first_loaded_model=$("$(dirname "$0")/show-loaded-ollama-models.sh" -j | jq -r '.[0].model' 2>/dev/null) || first_loaded_model="" +model=${first_loaded_model:-"llama3.2"} + +# Parse command-line arguments +while [[ "$#" -gt 0 ]]; do + case $1 in + --model) model="$2"; shift ;; # Set the model from the flag + *) echo "Unknown parameter: $1"; exit 1 ;; + esac + shift +done + +# Combine the system prompt with the clipboard content +content=$(wl-paste -p | tr '\n' ' ') +prompt="$SYSTEM_PROMPT $content" + +# Make the API call with the specified or default model +response=$(curl http://localhost:11434/api/generate -d \ + "{\"model\": \"$model\",\"prompt\": \"$prompt\",\"stream\": false}" \ + | jq -r '.response') + +# Check if content is a single line and no longer than 30 characters +if [[ ${#content} -le 30 && "$content" != *$'\n'* ]]; then + notify-send --app-name="Text selection query" --expire-time=10000 \ + "$content" "$response" +else + notify-send --app-name="Text selection query" --expire-time=10000 \ + "AI Response" "$response" +fi diff --git a/.config/hypr/hyprland/scripts/ai/show-loaded-ollama-models.sh b/.config/hypr/hyprland/scripts/ai/show-loaded-ollama-models.sh new file mode 100755 index 000000000..8dc88780d --- /dev/null +++ b/.config/hypr/hyprland/scripts/ai/show-loaded-ollama-models.sh @@ -0,0 +1,99 @@ +#!/bin/bash + +# From strikeoncmputrz/LLM_Scripts +# License: Apache-2.0, can be found in the same folder as this script + +# Global Vars +ollama_url=http://localhost +port="11434" +blobs=() +model_name_paths=() + + +#Parse arguments +while [ "$#" -gt 0 ]; do + case $1 in + -h|--help) + echo + echo " Identifies Ollama models running on this operating system by parsing running processes." + echo + echo " Usage: $0 [options]" + echo + echo " Options:" + echo " -j, --json_output Prints result as a json object. Other output disabled. (Default: false)" + echo " -p, --port [port number] Specify Ollama Server port (Default: 11434)" + echo " -u, --ollama_url [url] Specify Ollama Server URL (Default: http://localhost)" + echo + echo " Dependencies: jq" + exit 0 + ;; + -j|--json_output) + json_out=1 + shift 1 + ;; + -u|--ollama_url) + ollama_url=$2 + shift 2 + ;; + -p|--port) + port=$2 + shift 2 + ;; + *) + echo "Unknown option: $1" + exit 1 + ;; + esac +done + +compare_running_models_and_modelfiles() { + json_match=() + json_output=() + local matching_models=() + OLDIFS=$IFS + for ((i=0; i<${#model_name_paths[@]}; i++)); do # Iterate over the array of modelname,blob-path + for blob in "${blobs[@]}"; do + IFS=',', read -ra fields <<< "${model_name_paths[i]}" # Split the string into parts + if [ "${fields[1]}" == "$blob" ]; then # Check if current 'field' matches a blob + matching_models+=( '{ "model": "'"${fields[0]}"'", "path": "'"${fields[1]}"'"}') # Add to list of matching models + fi + done + done + + if [ -z "$json_out" ]; then + echo -e "\nModel Found: \n $(echo ${matching_models[*]} | jq '.' | sed s/[{}]//g) \n" + else + local json_match="${matching_models[*]}" + json_output=$(echo $json_match | jq -c -s .) + echo "$json_output" + fi + IFS=$OLDIFS +} + +get_running_model_paths() { + blobs=$(ps aux | grep -- '--model' | grep -v grep | grep -Po '(?<=--model\s).*' | cut -d ' ' -f1) + if [ -z "$blobs" ]; then + echo -e "\n\n Warning: No running Ollama models detected!\n" + exit 0 + fi +} + +parse_modelfiles() { + if [ -z "$json_out" ]; then + echo -e "\nConnecting to $ollama_url:$port\n" + if [ -z "$(curl -s $ollama_url:$port)" ]; then + echo -e "Could not connect to Ollama. Check the ollama_url parameter and that the server is running\n" + exit 1 + fi + curl -s "$ollama_url:$port" + fi + local models=( $(curl -s "$ollama_url:$port/api/tags" | jq -r '.models[].name') ) + for model in "${models[@]}"; do + local modelfile=$(curl -s "$ollama_url:$port/api/show" -d '{ "name": "'"$model"'", "modelfile": true }' | jq -r '.modelfile') + model_name_paths+=($model,$(echo "$modelfile" | awk '/^FROM/{print $2}')) + done +} + +parse_modelfiles +get_running_model_paths +compare_running_models_and_modelfiles diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index 01945cab3..37a479a4a 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -1,7 +1,7 @@ import "root:/" -import "root:/modules/common" -import "root:/modules/common/widgets" import "root:/services" +import "root:/modules/common/" +import "root:/modules/common/widgets" import "root:/modules/common/functions/color_utils.js" as ColorUtils import QtQuick import QtQuick.Controls @@ -240,25 +240,36 @@ Scope { VerticalBarSeparator {visible: ConfigOptions?.bar.borderless} - BarGroup { + MouseArea { id: rightCenterGroup + implicitWidth: rightCenterGroupContent.implicitWidth + implicitHeight: rightCenterGroupContent.implicitHeight Layout.preferredWidth: barRoot.centerSideModuleWidth Layout.fillHeight: true - - ClockWidget { - showDate: (ConfigOptions.bar.verbose && barRoot.useShortenedForm < 2) - Layout.alignment: Qt.AlignVCenter - Layout.fillWidth: true + + onPressed: { + Hyprland.dispatch('global quickshell:sidebarRightToggle') } - UtilButtons { - visible: (ConfigOptions.bar.verbose && barRoot.useShortenedForm === 0) - Layout.alignment: Qt.AlignVCenter - } + BarGroup { + id: rightCenterGroupContent + anchors.fill: parent + + ClockWidget { + showDate: (ConfigOptions.bar.verbose && barRoot.useShortenedForm < 2) + Layout.alignment: Qt.AlignVCenter + Layout.fillWidth: true + } - BatteryIndicator { - visible: (barRoot.useShortenedForm < 2 && UPower.displayDevice.isLaptopBattery) - Layout.alignment: Qt.AlignVCenter + UtilButtons { + visible: (ConfigOptions.bar.verbose && barRoot.useShortenedForm === 0) + Layout.alignment: Qt.AlignVCenter + } + + BatteryIndicator { + visible: (barRoot.useShortenedForm < 2 && UPower.displayDevice.isLaptopBattery) + Layout.alignment: Qt.AlignVCenter + } } } @@ -445,6 +456,7 @@ Scope { bottom: ConfigOptions.bar.bottom ? barContent.top : undefined } height: Appearance.rounding.screenRounding + visible: showBarBackground RoundCorner { anchors.top: parent.top @@ -452,6 +464,7 @@ Scope { size: Appearance.rounding.screenRounding corner: ConfigOptions.bar.bottom ? cornerEnum.bottomLeft : cornerEnum.topLeft color: showBarBackground ? Appearance.colors.colLayer0 : "transparent" + opacity: 1.0 - Appearance.transparency } RoundCorner { anchors.top: parent.top @@ -459,6 +472,7 @@ Scope { size: Appearance.rounding.screenRounding corner: ConfigOptions.bar.bottom ? cornerEnum.bottomRight : cornerEnum.topRight color: showBarBackground ? Appearance.colors.colLayer0 : "transparent" + opacity: 1.0 - Appearance.transparency } } diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index b496f4d17..202230a3c 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -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) : 0 - property real contentTransparency: ConfigOptions?.appearance.transparency ? (m3colors.darkmode ? 0.55 : 0) : 0 + 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 m3colors: QtObject { property bool darkmode: false @@ -126,10 +126,11 @@ Singleton { property color colPrimaryContainer: m3colors.m3primaryContainer property color colPrimaryContainerHover: ColorUtils.mix(colors.colPrimaryContainer, colLayer1Hover, 0.7) property color colPrimaryContainerActive: ColorUtils.mix(colors.colPrimaryContainer, colLayer1Active, 0.6) + property color colOnPrimaryContainer: m3colors.m3onPrimaryContainer property color colSecondary: m3colors.m3secondary property color colSecondaryHover: ColorUtils.mix(m3colors.m3secondary, colLayer1Hover, 0.85) property color colSecondaryActive: ColorUtils.mix(m3colors.m3secondary, colLayer1Active, 0.4) - property color colSecondaryContainer: ColorUtils.transparentize(m3colors.m3secondaryContainer, root.contentTransparency) + property color colSecondaryContainer: m3colors.m3secondaryContainer property color colSecondaryContainerHover: ColorUtils.mix(m3colors.m3secondaryContainer, colLayer1Hover, 0.6) property color colSecondaryContainerActive: ColorUtils.mix(m3colors.m3secondaryContainer, colLayer1Active, 0.54) property color colOnSecondaryContainer: m3colors.m3onSecondaryContainer @@ -177,7 +178,7 @@ Singleton { property int larger: 19 property int huge: 22 property int hugeass: 23 - property int title: 28 + property int title: huge } } @@ -187,11 +188,17 @@ Singleton { readonly property list expressiveSlowSpatial: [0.39, 1.29, 0.35, 0.98, 1, 1] // Default, 650ms readonly property list expressiveEffects: [0.34, 0.80, 0.34, 1.00, 1, 1] // Default, 200ms readonly property list emphasized: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1] + readonly property list emphasizedFirstHalf: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82] + readonly property list emphasizedLastHalf: [5 / 24, 0.82, 0.25, 1, 1, 1] readonly property list emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1] readonly property list emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1] readonly property list standard: [0.2, 0, 0, 1, 1, 1] readonly property list standardAccel: [0.3, 0, 1, 1, 1, 1] readonly property list standardDecel: [0, 0, 0, 1, 1, 1] + readonly property real expressiveFastSpatialDuration: 350 + readonly property real expressiveDefaultSpatialDuration: 500 + readonly property real expressiveSlowSpatialDuration: 650 + readonly property real expressiveEffectsDuration: 200 } animation: QtObject { diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index c268f1d95..1c2484f3e 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -31,10 +31,8 @@ Singleton { property QtObject apps: QtObject { property string bluetooth: "kcmshell6 kcm_bluetooth" - property string imageViewer: "loupe" property string network: "plasmawindowed org.kde.plasma.networkmanagement" property string networkEthernet: "kcmshell6 kcm_networkmanagement" - property string settings: "systemsettings" property string taskManager: "plasma-systemmonitor --page-name Processes" property string terminal: "kitty -1" // This is only for shell actions } @@ -157,6 +155,7 @@ Singleton { property QtObject windows: QtObject { property bool showTitlebar: true // Client-side decoration for shell apps + property bool centerTitle: true } property QtObject hacks: QtObject { diff --git a/.config/quickshell/modules/common/Directories.qml b/.config/quickshell/modules/common/Directories.qml index 32c905621..05741f204 100644 --- a/.config/quickshell/modules/common/Directories.qml +++ b/.config/quickshell/modules/common/Directories.qml @@ -32,12 +32,12 @@ Singleton { property string wallpaperSwitchScriptPath: FileUtils.trimFileProtocol(`${Directories.config}/quickshell/scripts/colors/switchwall.sh`) // Cleanup on init Component.onCompleted: { - Hyprland.dispatch(`exec mkdir -p '${shellConfig}'`) - Hyprland.dispatch(`exec mkdir -p '${favicons}'`) - Hyprland.dispatch(`exec rm -rf '${coverArt}'; mkdir -p '${coverArt}'`) - Hyprland.dispatch(`exec rm -rf '${booruPreviews}'; mkdir -p '${booruPreviews}'`) - Hyprland.dispatch(`exec mkdir -p '${booruDownloads}' && mkdir -p '${booruDownloadsNsfw}'`) - Hyprland.dispatch(`exec rm -rf '${latexOutput}'; mkdir -p '${latexOutput}'`) - Hyprland.dispatch(`exec rm -rf '${cliphistDecode}'; mkdir -p '${cliphistDecode}'`) + Quickshell.execDetached(["bash", "-c", `mkdir -p '${shellConfig}'`]) + Quickshell.execDetached(["bash", "-c", `mkdir -p '${favicons}'`]) + Quickshell.execDetached(["bash", "-c", `rm -rf '${coverArt}'; mkdir -p '${coverArt}'`]) + Quickshell.execDetached(["bash", "-c", `rm -rf '${booruPreviews}'; mkdir -p '${booruPreviews}'`]) + Quickshell.execDetached(["bash", "-c", `mkdir -p '${booruDownloads}' && mkdir -p '${booruDownloadsNsfw}'`]) + Quickshell.execDetached(["bash", "-c", `rm -rf '${latexOutput}'; mkdir -p '${latexOutput}'`]) + Quickshell.execDetached(["bash", "-c", `rm -rf '${cliphistDecode}'; mkdir -p '${cliphistDecode}'`]) } } diff --git a/.config/quickshell/modules/common/widgets/CliphistImage.qml b/.config/quickshell/modules/common/widgets/CliphistImage.qml index 9de344507..62d948cf3 100644 --- a/.config/quickshell/modules/common/widgets/CliphistImage.qml +++ b/.config/quickshell/modules/common/widgets/CliphistImage.qml @@ -8,6 +8,7 @@ import Qt.labs.platform import QtQuick import QtQuick.Controls import QtQuick.Layouts +import Quickshell import Quickshell.Io import Quickshell.Widgets import Quickshell.Hyprland @@ -71,7 +72,7 @@ Rectangle { } Component.onDestruction: { - Hyprland.dispatch(`exec bash -c "[ -f '${imageDecodeFilePath}' ] && rm -f '${imageDecodeFilePath}'"`) + Quickshell.execDetached(["bash", "-c", `[ -f '${imageDecodeFilePath}' ] && rm -f '${imageDecodeFilePath}'`]) } Image { diff --git a/.config/quickshell/modules/common/widgets/ConfigRow.qml b/.config/quickshell/modules/common/widgets/ConfigRow.qml new file mode 100644 index 000000000..3cdc3f80f --- /dev/null +++ b/.config/quickshell/modules/common/widgets/ConfigRow.qml @@ -0,0 +1,8 @@ +import QtQuick +import QtQuick.Layouts + +RowLayout { + property bool uniform: false + spacing: 10 + uniformCellSizes: uniform +} diff --git a/.config/quickshell/modules/common/widgets/ConfigSelectionArray.qml b/.config/quickshell/modules/common/widgets/ConfigSelectionArray.qml new file mode 100644 index 000000000..e62831a48 --- /dev/null +++ b/.config/quickshell/modules/common/widgets/ConfigSelectionArray.qml @@ -0,0 +1,48 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Hyprland +import "root:/services/" +import "root:/modules/common/" +import "root:/modules/common/widgets/" +import "root:/modules/common/functions/color_utils.js" as ColorUtils +import "root:/modules/common/functions/file_utils.js" as FileUtils + +Flow { + id: root + Layout.fillWidth: true + spacing: 2 + property list options: [] + property string configOptionName: "" + property var currentValue: null + + signal selected(var newValue) + + Repeater { + model: root.options + delegate: SelectionGroupButton { + id: paletteButton + required property var modelData + required property int index + onYChanged: { + if (index === 0) { + paletteButton.leftmost = true + } else { + var prev = root.children[index - 1] + var thisIsOnNewLine = prev && prev.y !== paletteButton.y + paletteButton.leftmost = thisIsOnNewLine + prev.rightmost = thisIsOnNewLine + } + } + leftmost: index === 0 + rightmost: index === root.options.length - 1 + buttonText: modelData.displayName; + toggled: root.currentValue === modelData.value + onClicked: { + root.selected(modelData.value); + } + } + } +} diff --git a/.config/quickshell/modules/common/widgets/ConfigSwitch.qml b/.config/quickshell/modules/common/widgets/ConfigSwitch.qml new file mode 100644 index 000000000..01dda8a2a --- /dev/null +++ b/.config/quickshell/modules/common/widgets/ConfigSwitch.qml @@ -0,0 +1,31 @@ +import "root:/modules/common/widgets/" +import "root:/modules/common/" +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls + +RippleButton { + id: root + Layout.fillWidth: true + implicitHeight: contentItem.implicitHeight + 8 * 2 + + contentItem: RowLayout { + spacing: 10 + StyledText { + id: labelWidget + Layout.fillWidth: true + text: root.text + font.pixelSize: Appearance.font.pixelSize.small + color: Appearance.colors.colOnSecondaryContainer + } + StyledSwitch { + id: switchWidget + down: root.down + scale: 0.6 + Layout.fillWidth: false + checked: root.checked + onClicked: root.clicked() + } + } +} + diff --git a/.config/quickshell/modules/common/widgets/ContentPage.qml b/.config/quickshell/modules/common/widgets/ContentPage.qml new file mode 100644 index 000000000..07d889492 --- /dev/null +++ b/.config/quickshell/modules/common/widgets/ContentPage.qml @@ -0,0 +1,29 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import "root:/modules/common/" +import "root:/modules/common/widgets/" + +Flickable { + id: root + property real baseWidth: 500 + property bool forceWidth: false + property real bottomContentPadding: 100 + + default property alias data: contentColumn.data + + clip: true + contentHeight: contentColumn.implicitHeight + root.bottomContentPadding // Add some padding at the bottom + implicitWidth: contentColumn.implicitWidth + + ColumnLayout { + id: contentColumn + width: root.forceWidth ? root.baseWidth : Math.max(root.baseWidth, implicitWidth) + anchors { + top: parent.top + horizontalCenter: parent.horizontalCenter + margins: 10 + } + spacing: 20 + } +} diff --git a/.config/quickshell/modules/common/widgets/ContentSection.qml b/.config/quickshell/modules/common/widgets/ContentSection.qml new file mode 100644 index 000000000..8c423e37e --- /dev/null +++ b/.config/quickshell/modules/common/widgets/ContentSection.qml @@ -0,0 +1,22 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import "root:/modules/common/" +import "root:/modules/common/widgets/" + +ColumnLayout { + id: root + property string title + default property alias data: sectionContent.data + + Layout.fillWidth: true + spacing: 8 + StyledText { + text: root.title + font.pixelSize: Appearance.font.pixelSize.larger + } + ColumnLayout { + id: sectionContent + spacing: 4 + } +} diff --git a/.config/quickshell/modules/common/widgets/FloatingActionButton.qml b/.config/quickshell/modules/common/widgets/FloatingActionButton.qml new file mode 100644 index 000000000..bc6031e79 --- /dev/null +++ b/.config/quickshell/modules/common/widgets/FloatingActionButton.qml @@ -0,0 +1,60 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import "root:/modules/common/" +import "root:/modules/common/widgets/" + +/** + * Material 3 FAB. + */ +RippleButton { + id: root + property string iconText: "add" + property bool expanded: false + property real baseSize: 56 + property real elementSpacing: 5 + implicitWidth: Math.max(contentRowLayout.implicitWidth + 10 * 2, baseSize) + implicitHeight: baseSize + buttonRadius: Appearance.rounding.small + colBackground: Appearance.colors.colPrimaryContainer + colBackgroundHover: Appearance.colors.colPrimaryContainerHover + colRipple: Appearance.colors.colPrimaryContainerActive + contentItem: RowLayout { + id: contentRowLayout + property real horizontalMargins: (root.baseSize - icon.width) / 2 + anchors { + verticalCenter: parent?.verticalCenter + left: parent?.left + leftMargin: contentRowLayout.horizontalMargins + } + spacing: 0 + + MaterialSymbol { + id: icon + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + iconSize: 24 + color: Appearance.colors.colOnPrimaryContainer + text: root.iconText + } + Loader { + active: true + sourceComponent: Revealer { + visible: root.expanded || implicitWidth > 0 + reveal: root.expanded + implicitWidth: reveal ? (buttonText.implicitWidth + root.elementSpacing + contentRowLayout.horizontalMargins) : 0 + StyledText { + id: buttonText + anchors { + left: parent.left + leftMargin: root.elementSpacing + } + text: root.buttonText + color: Appearance.colors.colOnPrimaryContainer + font.pixelSize: 14 + font.weight: 450 + } + } + } + } +} diff --git a/.config/quickshell/modules/common/widgets/KeyboardKey.qml b/.config/quickshell/modules/common/widgets/KeyboardKey.qml index 0cc80429e..d6ba5ba08 100644 --- a/.config/quickshell/modules/common/widgets/KeyboardKey.qml +++ b/.config/quickshell/modules/common/widgets/KeyboardKey.qml @@ -15,7 +15,7 @@ Rectangle { property real extraBottomBorderWidth: 2 property color borderColor: Appearance.colors.colOnLayer0 property real borderRadius: 5 - property color keyColor: Appearance.colors.colSurfaceContainerLow + property color keyColor: Appearance.m3colors.m3surfaceContainerLow implicitWidth: keyFace.implicitWidth + borderWidth * 2 implicitHeight: keyFace.implicitHeight + borderWidth * 2 + extraBottomBorderWidth radius: borderRadius diff --git a/.config/quickshell/modules/common/widgets/LightDarkPreferenceButton.qml b/.config/quickshell/modules/common/widgets/LightDarkPreferenceButton.qml new file mode 100644 index 000000000..1733bca37 --- /dev/null +++ b/.config/quickshell/modules/common/widgets/LightDarkPreferenceButton.qml @@ -0,0 +1,124 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/modules/common/functions/color_utils.js" as ColorUtils +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Hyprland + +GroupButton { + id: lightDarkButtonRoot + required property bool dark + property color previewBg: dark ? ColorUtils.colorWithHueOf("#3f3838", Appearance.m3colors.m3primary) : + ColorUtils.colorWithHueOf("#F7F9FF", Appearance.m3colors.m3primary) + property color previewFg: dark ? Qt.lighter(previewBg, 2.2) : ColorUtils.mix(previewBg, "#292929", 0.85) + padding: 5 + Layout.fillWidth: true + colBackground: Appearance.colors.colLayer2 + toggled: Appearance.m3colors.darkmode === dark + onClicked: { + Quickshell.execDetached(["bash", "-c", `${Directories.wallpaperSwitchScriptPath} --mode ${dark ? "dark" : "light"} --noswitch`]) + } + contentItem: Item { + anchors.centerIn: parent + implicitWidth: buttonContentLayout.implicitWidth + implicitHeight: buttonContentLayout.implicitHeight + ColumnLayout { + id: buttonContentLayout + anchors.centerIn: parent + Rectangle { + Layout.alignment: Qt.AlignHCenter + implicitWidth: 250 + implicitHeight: skeletonColumnLayout.implicitHeight + 10 * 2 + radius: lightDarkButtonRoot.buttonRadius - lightDarkButtonRoot.padding + color: lightDarkButtonRoot.previewBg + border { + width: 1 + color: Appearance.m3colors.m3outlineVariant + } + + // Some skeleton items + ColumnLayout { + id: skeletonColumnLayout + anchors.fill: parent + anchors.margins: 10 + spacing: 10 + RowLayout { + Rectangle { + radius: Appearance.rounding.full + color: lightDarkButtonRoot.previewFg + implicitWidth: 50 + implicitHeight: 50 + } + ColumnLayout { + spacing: 4 + Rectangle { + radius: Appearance.rounding.unsharpenmore + color: lightDarkButtonRoot.previewFg + Layout.fillWidth: true + implicitHeight: 22 + } + Rectangle { + radius: Appearance.rounding.unsharpenmore + color: lightDarkButtonRoot.previewFg + Layout.fillWidth: true + Layout.rightMargin: 45 + implicitHeight: 18 + } + } + } + StyledProgressBar { + Layout.topMargin: 5 + Layout.bottomMargin: 5 + Layout.fillWidth: true + value: 0.7 + sperm: true + animateSperm: lightDarkButtonRoot.toggled + highlightColor: lightDarkButtonRoot.toggled ? Appearance.m3colors.m3primary : lightDarkButtonRoot.previewFg + trackColor: ColorUtils.mix(lightDarkButtonRoot.previewBg, lightDarkButtonRoot.previewFg, 0.5) + } + RowLayout { + spacing: 2 + Rectangle { + radius: Appearance.rounding.full + color: lightDarkButtonRoot.toggled ? Appearance.m3colors.m3primary : lightDarkButtonRoot.previewFg + Layout.fillWidth: true + implicitHeight: 30 + MaterialSymbol { + visible: lightDarkButtonRoot.toggled + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + text: "check" + iconSize: 20 + color: lightDarkButtonRoot.toggled ? Appearance.m3colors.m3onPrimary : lightDarkButtonRoot.previewBg + } + } + Rectangle { + radius: Appearance.rounding.unsharpenmore + color: lightDarkButtonRoot.toggled ? Appearance.m3colors.m3secondaryContainer : lightDarkButtonRoot.previewFg + Layout.fillWidth: true + implicitHeight: 30 + } + Rectangle { + topLeftRadius: Appearance.rounding.unsharpenmore + bottomLeftRadius: Appearance.rounding.unsharpenmore + topRightRadius: Appearance.rounding.full + bottomRightRadius: Appearance.rounding.full + color: lightDarkButtonRoot.toggled ? Appearance.m3colors.m3secondaryContainer : lightDarkButtonRoot.previewFg + Layout.fillWidth: true + implicitHeight: 30 + } + } + } + } + StyledText { + Layout.fillWidth: true + text: dark ? "Dark" : "Light" + color: lightDarkButtonRoot.toggled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer2 + horizontalAlignment: Text.AlignHCenter + } + } + } +} diff --git a/.config/quickshell/modules/common/widgets/MaterialSymbol.qml b/.config/quickshell/modules/common/widgets/MaterialSymbol.qml index aac0b0315..639427aae 100644 --- a/.config/quickshell/modules/common/widgets/MaterialSymbol.qml +++ b/.config/quickshell/modules/common/widgets/MaterialSymbol.qml @@ -12,6 +12,7 @@ Text { hintingPreference: Font.PreferFullHinting family: Appearance?.font.family.iconMaterial ?? "Material Symbols Rounded" pixelSize: iconSize + weight: Font.Normal + (Font.DemiBold - Font.Normal) * fill } verticalAlignment: Text.AlignVCenter color: Appearance.m3colors.m3onBackground diff --git a/.config/quickshell/modules/common/widgets/NavRailButton.qml b/.config/quickshell/modules/common/widgets/NavRailButton.qml deleted file mode 100644 index 0a241553f..000000000 --- a/.config/quickshell/modules/common/widgets/NavRailButton.qml +++ /dev/null @@ -1,60 +0,0 @@ -import "root:/modules/common" -import "root:/modules/common/widgets" -import "root:/modules/common/functions/color_utils.js" as ColorUtils -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import Quickshell.Io - -Button { - id: button - - property bool toggled - property string buttonIcon - property string buttonText - - Layout.alignment: Qt.AlignHCenter - implicitHeight: columnLayout.implicitHeight - implicitWidth: columnLayout.implicitWidth - - background: null - PointingHandInteraction {} - - // Real stuff - ColumnLayout { - id: columnLayout - spacing: 5 - Rectangle { - width: 62 - implicitHeight: navRailButtonIcon.height + 2 * 2 - Layout.alignment: Qt.AlignHCenter - radius: Appearance.rounding.full - color: toggled ? - (button.down ? Appearance.colors.colSecondaryContainerActive : button.hovered ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colSecondaryContainer) : - (button.down ? Appearance.colors.colLayer1Active : button.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1)) - - Behavior on color { - animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) - } - MaterialSymbol { - id: navRailButtonIcon - anchors.centerIn: parent - iconSize: Appearance.font.pixelSize.hugeass - fill: toggled ? 1 : 0 - text: buttonIcon - color: toggled ? Appearance.m3colors.m3onSecondaryContainer : Appearance.colors.colOnLayer1 - - Behavior on color { - animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) - } - } - } - - StyledText { - Layout.alignment: Qt.AlignHCenter - text: buttonText - color: Appearance.colors.colOnLayer1 - } - } - -} diff --git a/.config/quickshell/modules/common/widgets/NavigationRail.qml b/.config/quickshell/modules/common/widgets/NavigationRail.qml new file mode 100644 index 000000000..fe3c64a20 --- /dev/null +++ b/.config/quickshell/modules/common/widgets/NavigationRail.qml @@ -0,0 +1,12 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import "root:/modules/common/" +import "root:/modules/common/widgets/" + +ColumnLayout { // Window content with navigation rail and content pane + id: root + property bool expanded: true + property int currentIndex: 0 + spacing: 5 +} diff --git a/.config/quickshell/modules/common/widgets/NavigationRailButton.qml b/.config/quickshell/modules/common/widgets/NavigationRailButton.qml new file mode 100644 index 000000000..615cb35eb --- /dev/null +++ b/.config/quickshell/modules/common/widgets/NavigationRailButton.qml @@ -0,0 +1,149 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/modules/common/functions/color_utils.js" as ColorUtils +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell.Io + +TabButton { + id: root + + property bool toggled: TabBar.tabBar.currentIndex === TabBar.index + property string buttonIcon + property string buttonText + property bool expanded: false + property bool showToggledHighlight: true + readonly property real visualWidth: root.expanded ? root.baseSize + 20 + itemText.implicitWidth : root.baseSize + + property real baseSize: 56 + property real baseHighlightHeight: 32 + property real highlightCollapsedTopMargin: 8 + padding: 0 + + // The navigation item’s target area always spans the full width of the + // nav rail, even if the item container hugs its contents. + Layout.fillWidth: true + // implicitWidth: contentItem.implicitWidth + implicitHeight: baseSize + + background: null + PointingHandInteraction {} + + // Real stuff + contentItem: Item { + id: buttonContent + anchors { + top: parent.top + bottom: parent.bottom + left: parent.left + right: undefined + } + + implicitWidth: root.visualWidth + implicitHeight: root.expanded ? itemIconBackground.implicitHeight : itemIconBackground.implicitHeight + itemText.implicitHeight + + Rectangle { + id: itemBackground + anchors.top: itemIconBackground.top + anchors.left: itemIconBackground.left + anchors.bottom: itemIconBackground.bottom + implicitWidth: root.visualWidth + radius: Appearance.rounding.full + color: toggled ? + root.showToggledHighlight ? + (root.down ? Appearance.colors.colSecondaryContainerActive : root.hovered ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colSecondaryContainer) + : ColorUtils.transparentize(Appearance.colors.colSecondaryContainer) : + (root.down ? Appearance.colors.colLayer1Active : root.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1)) + + states: State { + name: "expanded" + when: root.expanded + AnchorChanges { + target: itemBackground + anchors.top: buttonContent.top + anchors.left: buttonContent.left + anchors.bottom: buttonContent.bottom + } + PropertyChanges { + target: itemBackground + implicitWidth: root.visualWidth + } + } + transitions: Transition { + AnchorAnimation { + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve + } + PropertyAnimation { + target: itemBackground + property: "implicitWidth" + duration: Appearance.animation.elementMove.duration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve + } + } + + Behavior on color { + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) + } + } + + Item { + id: itemIconBackground + implicitWidth: root.baseSize + implicitHeight: root.baseHighlightHeight + anchors { + left: parent.left + verticalCenter: parent.verticalCenter + } + MaterialSymbol { + id: navRailButtonIcon + anchors.centerIn: parent + iconSize: 24 + fill: toggled ? 1 : 0 + font.weight: (toggled || root.hovered) ? Font.DemiBold : Font.Normal + text: buttonIcon + color: toggled ? Appearance.m3colors.m3onSecondaryContainer : Appearance.colors.colOnLayer1 + + Behavior on color { + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) + } + } + } + + StyledText { + id: itemText + anchors { + top: itemIconBackground.bottom + topMargin: 2 + horizontalCenter: itemIconBackground.horizontalCenter + } + states: State { + name: "expanded" + when: root.expanded + AnchorChanges { + target: itemText + anchors { + top: undefined + horizontalCenter: undefined + left: itemIconBackground.right + verticalCenter: itemIconBackground.verticalCenter + } + } + } + transitions: Transition { + AnchorAnimation { + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve + } + } + text: buttonText + font.pixelSize: 14 + color: Appearance.colors.colOnLayer1 + } + } + +} diff --git a/.config/quickshell/modules/common/widgets/NavigationRailExpandButton.qml b/.config/quickshell/modules/common/widgets/NavigationRailExpandButton.qml new file mode 100644 index 000000000..2c138b879 --- /dev/null +++ b/.config/quickshell/modules/common/widgets/NavigationRailExpandButton.qml @@ -0,0 +1,25 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import "root:/modules/common/" +import "root:/modules/common/widgets/" + +RippleButton { + id: root + Layout.alignment: Qt.AlignLeft + implicitWidth: 40 + implicitHeight: 40 + Layout.leftMargin: 8 + onClicked: { + parent.expanded = !parent.expanded; + } + buttonRadius: Appearance.rounding.full + contentItem: MaterialSymbol { + id: icon + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + iconSize: 24 + color: Appearance.colors.colOnLayer1 + text: root.parent.expanded ? "menu_open" : "menu" + } +} diff --git a/.config/quickshell/modules/common/widgets/NavigationRailTabArray.qml b/.config/quickshell/modules/common/widgets/NavigationRailTabArray.qml new file mode 100644 index 000000000..e13ea76c9 --- /dev/null +++ b/.config/quickshell/modules/common/widgets/NavigationRailTabArray.qml @@ -0,0 +1,44 @@ +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/modules/common/functions/color_utils.js" as ColorUtils +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell.Io + +Item { + id: root + property int currentIndex: 0 + property bool expanded: false + default property alias data: tabBarColumn.data + implicitHeight: tabBarColumn.implicitHeight + implicitWidth: tabBarColumn.implicitWidth + Layout.topMargin: 25 + Rectangle { + property real itemHeight: tabBarColumn.children[0].baseSize + property real baseHighlightHeight: tabBarColumn.children[0].baseHighlightHeight + anchors { + top: tabBarColumn.top + left: tabBarColumn.left + topMargin: itemHeight * root.currentIndex + (root.expanded ? 0 : ((itemHeight - baseHighlightHeight) / 2)) + } + radius: Appearance.rounding.full + color: Appearance.colors.colSecondaryContainer + implicitHeight: root.expanded ? itemHeight : baseHighlightHeight + implicitWidth: tabBarColumn.children[root.currentIndex].visualWidth + + Behavior on anchors.topMargin { + NumberAnimation { + duration: Appearance.animationCurves.expressiveFastSpatialDuration + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial + } + } + } + ColumnLayout { + id: tabBarColumn + anchors.fill: parent + spacing: 0 + + } +} diff --git a/.config/quickshell/modules/common/widgets/NotificationItem.qml b/.config/quickshell/modules/common/widgets/NotificationItem.qml index 62e37b8a4..ef51c3503 100644 --- a/.config/quickshell/modules/common/widgets/NotificationItem.qml +++ b/.config/quickshell/modules/common/widgets/NotificationItem.qml @@ -94,12 +94,6 @@ Item { // Notification item area } } - onPressAndHold: (mouse) => { - if (mouse.button === Qt.LeftButton) { - Hyprland.dispatch(`exec wl-copy '${StringUtils.shellSingleQuoteEscape(notificationObject.body)}'`) - notificationSummaryText.text = String.format(Translation.tr("{0} (copied)"), notificationObject.summary) - } - } onDraggingChanged: () => { if (dragging) { root.qmlParent.dragIndex = root.index ?? root.parent.children.indexOf(root); @@ -226,12 +220,8 @@ Item { // Notification item area Qt.openUrlExternally(link) Hyprland.dispatch("global quickshell:sidebarRightClose") } - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.NoButton // Only for hover - hoverEnabled: true - cursorShape: parent.hoveredLink !== "" ? Qt.PointingHandCursor : Qt.ArrowCursor - } + + PointingHandLinkHover {} } Flickable { // Notification actions @@ -295,7 +285,7 @@ Item { // Notification item area (contentItem.implicitWidth + leftPadding + rightPadding) onClicked: { - Hyprland.dispatch(`exec wl-copy '${StringUtils.shellSingleQuoteEscape(notificationObject.body)}'`) + Quickshell.clipboardText = notificationObject.body copyIcon.text = "inventory" copyIconTimer.restart() } diff --git a/.config/quickshell/modules/common/widgets/PointingHandLinkHover.qml b/.config/quickshell/modules/common/widgets/PointingHandLinkHover.qml new file mode 100644 index 000000000..4d14c8165 --- /dev/null +++ b/.config/quickshell/modules/common/widgets/PointingHandLinkHover.qml @@ -0,0 +1,8 @@ +import QtQuick + +MouseArea { + anchors.fill: parent + acceptedButtons: Qt.NoButton // Only for hover + hoverEnabled: true + cursorShape: parent.hoveredLink !== "" ? Qt.PointingHandCursor : Qt.ArrowCursor +} diff --git a/.config/quickshell/modules/common/widgets/Revealer.qml b/.config/quickshell/modules/common/widgets/Revealer.qml index 2fb5dc3a8..620b8d6ef 100644 --- a/.config/quickshell/modules/common/widgets/Revealer.qml +++ b/.config/quickshell/modules/common/widgets/Revealer.qml @@ -13,7 +13,7 @@ Item { implicitWidth: (reveal || vertical) ? childrenRect.width : 0 implicitHeight: (reveal || !vertical) ? childrenRect.height : 0 - visible: reveal && width > 0 && height > 0 + visible: reveal || (width > 0 && height > 0) Behavior on implicitWidth { enabled: !vertical diff --git a/.config/quickshell/modules/common/widgets/RippleButtonWithIcon.qml b/.config/quickshell/modules/common/widgets/RippleButtonWithIcon.qml new file mode 100644 index 000000000..8e5fb6f2d --- /dev/null +++ b/.config/quickshell/modules/common/widgets/RippleButtonWithIcon.qml @@ -0,0 +1,56 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import "root:/modules/common/" +import "root:/modules/common/widgets/" + +RippleButton { + id: buttonWithIconRoot + property string nerdIcon + property string materialIcon + property bool materialIconFill: true + property string mainText: "Button text" + property Component mainContentComponent: Component { + StyledText { + text: buttonWithIconRoot.mainText + font.pixelSize: Appearance.font.pixelSize.small + color: Appearance.colors.colOnSecondaryContainer + } + } + implicitHeight: 35 + horizontalPadding: 15 + buttonRadius: Appearance.rounding.small + colBackground: Appearance.colors.colLayer2 + + contentItem: RowLayout { + Item { + implicitWidth: Math.max(materialIconLoader.implicitWidth, nerdIconLoader.implicitWidth) + Loader { + id: materialIconLoader + anchors.centerIn: parent + active: !nerdIcon + sourceComponent: MaterialSymbol { + text: buttonWithIconRoot.materialIcon + iconSize: Appearance.font.pixelSize.larger + color: Appearance.colors.colOnSecondaryContainer + fill: buttonWithIconRoot.materialIconFill ? 1 : 0 + } + } + Loader { + id: nerdIconLoader + anchors.centerIn: parent + active: nerdIcon + sourceComponent: StyledText { + text: buttonWithIconRoot.nerdIcon + font.pixelSize: Appearance.font.pixelSize.larger + font.family: Appearance.font.family.iconNerd + color: Appearance.colors.colOnSecondaryContainer + } + } + } + Loader { + sourceComponent: buttonWithIconRoot.mainContentComponent + Layout.alignment: Qt.AlignVCenter + } + } +} diff --git a/.config/quickshell/modules/common/widgets/SelectionGroupButton.qml b/.config/quickshell/modules/common/widgets/SelectionGroupButton.qml new file mode 100644 index 000000000..77695ce3b --- /dev/null +++ b/.config/quickshell/modules/common/widgets/SelectionGroupButton.qml @@ -0,0 +1,24 @@ +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Hyprland +import "root:/services/" +import "root:/modules/common/" +import "root:/modules/common/widgets/" + +GroupButton { + id: root + horizontalPadding: 12 + verticalPadding: 8 + bounce: false + property bool leftmost: false + property bool rightmost: false + leftRadius: (toggled || leftmost) ? (height / 2) : Appearance.rounding.unsharpenmore + rightRadius: (toggled || rightmost) ? (height / 2) : Appearance.rounding.unsharpenmore + colBackground: Appearance.colors.colSecondaryContainer + contentItem: StyledText { + color: parent.toggled ? Appearance.colors.colOnPrimary : Appearance.colors.colOnSecondaryContainer + text: root.buttonText + } +} diff --git a/.config/quickshell/modules/common/widgets/StyledProgressBar.qml b/.config/quickshell/modules/common/widgets/StyledProgressBar.qml index 28f517bc8..1ea8a93ae 100644 --- a/.config/quickshell/modules/common/widgets/StyledProgressBar.qml +++ b/.config/quickshell/modules/common/widgets/StyledProgressBar.qml @@ -41,8 +41,7 @@ ProgressBar { } contentItem: Item { - implicitWidth: parent.width - implicitHeight: parent.height + anchors.fill: parent Canvas { id: wavyFill diff --git a/.config/quickshell/modules/common/widgets/StyledRectangularShadow.qml b/.config/quickshell/modules/common/widgets/StyledRectangularShadow.qml index 6e1f2e16e..66a626f1e 100644 --- a/.config/quickshell/modules/common/widgets/StyledRectangularShadow.qml +++ b/.config/quickshell/modules/common/widgets/StyledRectangularShadow.qml @@ -6,7 +6,8 @@ RectangularShadow { required property var target anchors.fill: target radius: target.radius - blur: 1.2 * Appearance.sizes.elevationMargin + blur: 0.9 * Appearance.sizes.elevationMargin + offset: Qt.vector2d(0.0, 1.0) spread: 1 color: Appearance.colors.colShadow cached: true diff --git a/.config/quickshell/modules/common/widgets/StyledSwitch.qml b/.config/quickshell/modules/common/widgets/StyledSwitch.qml index e980d7f88..453806f31 100644 --- a/.config/quickshell/modules/common/widgets/StyledSwitch.qml +++ b/.config/quickshell/modules/common/widgets/StyledSwitch.qml @@ -36,13 +36,13 @@ Switch { // Custom thumb styling indicator: Rectangle { - width: root.pressed ? (28 * root.scale) : root.checked ? (24 * root.scale) : (16 * root.scale) - height: root.pressed ? (28 * root.scale) : root.checked ? (24 * root.scale) : (16 * root.scale) + width: (root.pressed || root.down) ? (28 * root.scale) : root.checked ? (24 * root.scale) : (16 * root.scale) + height: (root.pressed || root.down) ? (28 * root.scale) : root.checked ? (24 * root.scale) : (16 * root.scale) radius: Appearance.rounding.full color: root.checked ? Appearance.m3colors.m3onPrimary : Appearance.m3colors.m3outline anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left - anchors.leftMargin: root.checked ? (root.pressed ? (22 * root.scale) : 24 * root.scale) : (root.pressed ? (2 * root.scale) : 8 * root.scale) + anchors.leftMargin: root.checked ? ((root.pressed || root.down) ? (22 * root.scale) : 24 * root.scale) : ((root.pressed || root.down) ? (2 * root.scale) : 8 * root.scale) Behavior on anchors.leftMargin { animation: Appearance.animation.elementMove.numberAnimation.createObject(this) diff --git a/.config/quickshell/modules/common/widgets/StyledTextArea.qml b/.config/quickshell/modules/common/widgets/StyledTextArea.qml index 67d417576..447ae41c3 100644 --- a/.config/quickshell/modules/common/widgets/StyledTextArea.qml +++ b/.config/quickshell/modules/common/widgets/StyledTextArea.qml @@ -2,6 +2,9 @@ import "root:/modules/common" import QtQuick import QtQuick.Controls +/** + * Does not include visual layout, but includes the easily neglected colors. + */ TextArea { renderType: Text.NativeRendering selectedTextColor: Appearance.m3colors.m3onSecondaryContainer diff --git a/.config/quickshell/modules/mediaControls/MediaControls.qml b/.config/quickshell/modules/mediaControls/MediaControls.qml index 047f14174..8b3c6d85f 100644 --- a/.config/quickshell/modules/mediaControls/MediaControls.qml +++ b/.config/quickshell/modules/mediaControls/MediaControls.qml @@ -54,7 +54,9 @@ Scope { for (let j = i + 1; j < players.length; ++j) { let p2 = players[j]; if (p1.trackTitle && p2.trackTitle && - (p1.trackTitle.includes(p2.trackTitle) || p2.trackTitle.includes(p1.trackTitle))) { + (p1.trackTitle.includes(p2.trackTitle) + || p2.trackTitle.includes(p1.trackTitle)) + || (p1.position - p2.position <= 2 && p1.length - p2.length <= 2)) { group.push(j); } } diff --git a/.config/quickshell/modules/overview/OverviewWidget.qml b/.config/quickshell/modules/overview/OverviewWidget.qml index e0999d6e3..5faa8699d 100644 --- a/.config/quickshell/modules/overview/OverviewWidget.qml +++ b/.config/quickshell/modules/overview/OverviewWidget.qml @@ -111,7 +111,6 @@ Item { acceptedButtons: Qt.LeftButton onClicked: { if (root.draggingTargetWorkspace === -1) { - // Hyprland.dispatch(`exec qs ipc call overview close`) GlobalStates.overviewOpen = false Hyprland.dispatch(`workspace ${workspaceValue}`) } diff --git a/.config/quickshell/modules/overview/SearchWidget.qml b/.config/quickshell/modules/overview/SearchWidget.qml index 5d43f07ad..f52710c5b 100644 --- a/.config/quickshell/modules/overview/SearchWidget.qml +++ b/.config/quickshell/modules/overview/SearchWidget.qml @@ -303,7 +303,9 @@ Item { // Wrapper clickActionName: "", type: `#${entry.match(/^\s*(\S+)/)?.[1] || ""}`, execute: () => { - Hyprland.dispatch(`exec echo '${StringUtils.shellSingleQuoteEscape(entry)}' | cliphist decode | wl-copy`); + Quickshell.execDetached( + ["bash", "-c", `echo '${StringUtils.shellSingleQuoteEscape(entry)}' | cliphist decode | wl-copy`] + ); } }; }).filter(Boolean); @@ -318,7 +320,7 @@ Item { // Wrapper clickActionName: "", type: "Emoji", execute: () => { - Hyprland.dispatch(`exec wl-copy '${StringUtils.shellSingleQuoteEscape(entry.match(/^\s*(\S+)/)?.[1])}'`); + Quickshell.clipboardText = entry.match(/^\s*(\S+)/)?.[1] } }; }).filter(Boolean); @@ -334,7 +336,7 @@ Item { // Wrapper fontType: "monospace", materialSymbol: 'calculate', execute: () => { - Hyprland.dispatch(`exec wl-copy '${StringUtils.shellSingleQuoteEscape(root.mathResult)}'`) + Quickshell.clipboardText = root.mathResult; } } const commandResultObject = { diff --git a/.config/quickshell/modules/session/Session.qml b/.config/quickshell/modules/session/Session.qml index ae4dfee5e..c6d3735a8 100644 --- a/.config/quickshell/modules/session/Session.qml +++ b/.config/quickshell/modules/session/Session.qml @@ -122,7 +122,7 @@ Scope { id: sessionTaskManager buttonIcon: "browse_activity" buttonText: Translation.tr("Task Manager") - onClicked: { Hyprland.dispatch(`exec ${ConfigOptions.apps.taskManager}`); sessionRoot.hide() } + onClicked: { Quickshell.execDetached(["bash", "-c", `${ConfigOptions.apps.taskManager}`]); sessionRoot.hide() } onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } KeyNavigation.left: sessionLogout KeyNavigation.down: sessionFirmwareReboot @@ -132,7 +132,7 @@ Scope { id: sessionHibernate buttonIcon: "downloading" buttonText: Translation.tr("Hibernate") - onClicked: { Hyprland.dispatch("exec systemctl hibernate || loginctl hibernate"); sessionRoot.hide() } + onClicked: { Quickshell.execDetached(["bash", "-c", `systemctl hibernate || loginctl hibernate`]); sessionRoot.hide() } onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } KeyNavigation.up: sessionLock KeyNavigation.right: sessionShutdown @@ -141,7 +141,7 @@ Scope { id: sessionShutdown buttonIcon: "power_settings_new" buttonText: Translation.tr("Shutdown") - onClicked: { Hyprland.dispatch("exec systemctl poweroff || loginctl poweroff"); sessionRoot.hide() } + onClicked: { Quickshell.execDetached(["bash", "-c", `systemctl poweroff || loginctl poweroff`]); sessionRoot.hide() } onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } KeyNavigation.left: sessionHibernate KeyNavigation.right: sessionReboot @@ -151,7 +151,7 @@ Scope { id: sessionReboot buttonIcon: "restart_alt" buttonText: Translation.tr("Reboot") - onClicked: { Hyprland.dispatch("exec reboot || loginctl reboot"); sessionRoot.hide() } + onClicked: { Quickshell.execDetached(["bash", "-c", `reboot || loginctl reboot`]); sessionRoot.hide() } onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } KeyNavigation.left: sessionShutdown KeyNavigation.right: sessionFirmwareReboot @@ -161,7 +161,7 @@ Scope { id: sessionFirmwareReboot buttonIcon: "settings_applications" buttonText: Translation.tr("Reboot to firmware settings") - onClicked: { Hyprland.dispatch("exec systemctl reboot --firmware-setup || loginctl reboot --firmware-setup"); sessionRoot.hide() } + onClicked: { Quickshell.execDetached(["bash", "-c", `systemctl reboot --firmware-setup || loginctl reboot --firmware-setup`]); sessionRoot.hide() } onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } KeyNavigation.up: sessionTaskManager KeyNavigation.left: sessionReboot diff --git a/.config/quickshell/modules/settings/About.qml b/.config/quickshell/modules/settings/About.qml new file mode 100644 index 000000000..7868c0e7b --- /dev/null +++ b/.config/quickshell/modules/settings/About.qml @@ -0,0 +1,151 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Widgets +import "root:/services/" +import "root:/modules/common/" +import "root:/modules/common/widgets/" + +ContentPage { + forceWidth: true + + ContentSection { + title: "Distro" + + RowLayout { + Layout.alignment: Qt.AlignHCenter + spacing: 20 + Layout.topMargin: 10 + Layout.bottomMargin: 10 + IconImage { + implicitSize: 100 + source: Quickshell.iconPath(SystemInfo.logo) + } + ColumnLayout { + Layout.alignment: Qt.AlignVCenter + // spacing: 10 + StyledText { + text: SystemInfo.distroName + font.pixelSize: Appearance.font.pixelSize.title + } + StyledText { + font.pixelSize: Appearance.font.pixelSize.normal + text: SystemInfo.homeUrl + textFormat: Text.MarkdownText + onLinkActivated: (link) => { + Qt.openUrlExternally(link) + } + PointingHandLinkHover {} + } + } + } + + Flow { + Layout.fillWidth: true + spacing: 5 + + RippleButtonWithIcon { + materialIcon: "auto_stories" + mainText: "Documentation" + onClicked: { + Qt.openUrlExternally(SystemInfo.documentationUrl) + } + } + RippleButtonWithIcon { + materialIcon: "support" + mainText: "Help & Support" + onClicked: { + Qt.openUrlExternally(SystemInfo.supportUrl) + } + } + RippleButtonWithIcon { + materialIcon: "bug_report" + mainText: "Report a Bug" + onClicked: { + Qt.openUrlExternally(SystemInfo.bugReportUrl) + } + } + RippleButtonWithIcon { + materialIcon: "policy" + materialIconFill: false + mainText: "Privacy Policy" + onClicked: { + Qt.openUrlExternally(SystemInfo.privacyPolicyUrl) + } + } + + } + + } + ContentSection { + title: "Dotfiles" + + RowLayout { + Layout.alignment: Qt.AlignHCenter + spacing: 20 + Layout.topMargin: 10 + Layout.bottomMargin: 10 + MaterialSymbol { + iconSize: 70 + text: "files" + color: Appearance.colors.colOnSecondaryContainer + } + ColumnLayout { + Layout.alignment: Qt.AlignVCenter + // spacing: 10 + StyledText { + text: "illogical-impulse" + font.pixelSize: Appearance.font.pixelSize.title + } + StyledText { + text: "https://github.com/end-4/dots-hyprland" + font.pixelSize: Appearance.font.pixelSize.normal + textFormat: Text.MarkdownText + onLinkActivated: (link) => { + Qt.openUrlExternally(link) + } + PointingHandLinkHover {} + } + } + } + + Flow { + Layout.fillWidth: true + spacing: 5 + + RippleButtonWithIcon { + materialIcon: "auto_stories" + mainText: "Documentation" + onClicked: { + Qt.openUrlExternally("https://end-4.github.io/dots-hyprland-wiki/en/ii-qs/02usage/") + } + } + RippleButtonWithIcon { + materialIcon: "adjust" + materialIconFill: false + mainText: "Issues" + onClicked: { + Qt.openUrlExternally("https://github.com/end-4/dots-hyprland/issues") + } + } + RippleButtonWithIcon { + materialIcon: "forum" + mainText: "Discussions" + onClicked: { + Qt.openUrlExternally("https://github.com/end-4/dots-hyprland/discussions") + } + } + RippleButtonWithIcon { + materialIcon: "favorite" + mainText: "Donate" + onClicked: { + Qt.openUrlExternally("https://github.com/sponsors/end-4") + } + } + + + } + } +} diff --git a/.config/quickshell/modules/settings/BehaviorConfig.qml b/.config/quickshell/modules/settings/BehaviorConfig.qml new file mode 100644 index 000000000..9cec75c3d --- /dev/null +++ b/.config/quickshell/modules/settings/BehaviorConfig.qml @@ -0,0 +1,54 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import "root:/services/" +import "root:/modules/common/" +import "root:/modules/common/widgets/" + +ContentPage { + ContentSection { + title: "Policies" + + ConfigRow { + ColumnLayout { // Weeb policy + StyledText { + text: "Weeb" + color: Appearance.colors.colSubtext + } + ConfigSelectionArray { + currentValue: ConfigOptions.policies.weeb + configOptionName: "policies.weeb" + onSelected: (newValue) => { + ConfigLoader.setConfigValueAndSave("policies.weeb", newValue); + } + options: [ + { displayName: "No", value: 0 }, + { displayName: "Yes", value: 1 }, + { displayName: "Closet", value: 2 } + ] + } + } + + ColumnLayout { // AI policy + StyledText { + text: "AI" + color: Appearance.colors.colSubtext + } + ConfigSelectionArray { + currentValue: ConfigOptions.policies.ai + configOptionName: "policies.ai" + onSelected: (newValue) => { + ConfigLoader.setConfigValueAndSave("policies.ai", newValue); + } + options: [ + { displayName: "No", value: 0 }, + { displayName: "Yes", value: 1 }, + { displayName: "Local only", value: 2 } + ] + } + } + } + + + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/settings/Style.qml b/.config/quickshell/modules/settings/Style.qml new file mode 100644 index 000000000..87775072a --- /dev/null +++ b/.config/quickshell/modules/settings/Style.qml @@ -0,0 +1,214 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Hyprland +import "root:/services/" +import "root:/modules/common/" +import "root:/modules/common/widgets/" +import "root:/modules/common/functions/color_utils.js" as ColorUtils +import "root:/modules/common/functions/file_utils.js" as FileUtils + +ContentPage { + baseWidth: lightDarkButtonGroup.implicitWidth + forceWidth: true + + Process { + id: konachanWallProc + property string status: "" + command: ["bash", "-c", FileUtils.trimFileProtocol(`${Directories.config}/quickshell/scripts/colors/random_konachan_wall.sh`)] + stdout: SplitParser { + onRead: data => { + console.log(`Konachan wall proc output: ${data}`); + konachanWallProc.status = data.trim(); + } + } + } + + ContentSection { + title: "Colors & Wallpaper" + + // Light/Dark mode preference + ButtonGroup { + id: lightDarkButtonGroup + Layout.fillWidth: true + LightDarkPreferenceButton { + dark: false + } + LightDarkPreferenceButton { + dark: true + } + } + + // Material palette selection + StyledText { + text: "Material palette" + color: Appearance.colors.colSubtext + } + + ConfigSelectionArray { + currentValue: ConfigOptions.appearance.palette.type + configOptionName: "appearance.palette.type" + onSelected: (newValue) => { + ConfigLoader.setConfigValueAndSave("appearance.palette.type", newValue); + } + options: [ + {"value": "auto", "displayName": "Auto"}, + {"value": "scheme-content", "displayName": "Content"}, + {"value": "scheme-expressive", "displayName": "Expressive"}, + {"value": "scheme-fidelity", "displayName": "Fidelity"}, + {"value": "scheme-fruit-salad", "displayName": "Fruit Salad"}, + {"value": "scheme-monochrome", "displayName": "Monochrome"}, + {"value": "scheme-neutral", "displayName": "Neutral"}, + {"value": "scheme-rainbow", "displayName": "Rainbow"}, + {"value": "scheme-tonal-spot", "displayName": "Tonal Spot"} + ] + } + + // Wallpaper selection + StyledText { + text: "Wallpaper" + color: Appearance.colors.colSubtext + } + RowLayout { + Layout.alignment: Qt.AlignHCenter + RippleButtonWithIcon { + id: rndWallBtn + Layout.alignment: Qt.AlignHCenter + buttonRadius: Appearance.rounding.small + materialIcon: "wallpaper" + mainText: konachanWallProc.running ? "Be patient..." : "Random: Konachan" + onClicked: { + console.log(konachanWallProc.command.join(" ")) + konachanWallProc.running = true; + } + StyledToolTip { + content: "Random SFW Anime wallpaper from Konachan\nImage is saved to ~/Pictures/Wallpapers" + } + } + RippleButtonWithIcon { + materialIcon: "wallpaper" + StyledToolTip { + content: "Pick wallpaper image on your system" + } + onClicked: { + Quickshell.execDetached(`${Directories.wallpaperSwitchScriptPath}`) + } + mainContentComponent: Component { + RowLayout { + spacing: 10 + StyledText { + font.pixelSize: Appearance.font.pixelSize.small + text: "Choose file" + color: Appearance.colors.colOnSecondaryContainer + } + RowLayout { + spacing: 3 + KeyboardKey { + key: "Ctrl" + } + KeyboardKey { + key: "󰖳" + } + StyledText { + Layout.alignment: Qt.AlignVCenter + text: "+" + } + KeyboardKey { + key: "T" + } + } + } + } + } + } + + StyledText { + Layout.alignment: Qt.AlignHCenter + text: "Change any time later with /dark, /light, /img in the launcher" + font.pixelSize: Appearance.font.pixelSize.smaller + color: Appearance.colors.colSubtext + } + + } + + ContentSection { + title: "Shell style" + + ColumnLayout { // Fake screen rounding + StyledText { + text: "Fake screen rounding" + color: Appearance.colors.colSubtext + } + ButtonGroup { + id: fakeScreenRoundingButtonGroup + property int selectedPolicy: ConfigOptions.appearance.fakeScreenRounding + spacing: 2 + SelectionGroupButton { + property int value: 0 + leftmost: true + buttonText: "No" + toggled: (fakeScreenRoundingButtonGroup.selectedPolicy === value) + onClicked: { + ConfigLoader.setConfigValueAndSave("appearance.fakeScreenRounding", value); + } + } + SelectionGroupButton { + property int value: 1 + buttonText: "Yes" + toggled: (fakeScreenRoundingButtonGroup.selectedPolicy === value) + onClicked: { + ConfigLoader.setConfigValueAndSave("appearance.fakeScreenRounding", value); + } + } + SelectionGroupButton { + property int value: 2 + rightmost: true + buttonText: "When not fullscreen" + toggled: (fakeScreenRoundingButtonGroup.selectedPolicy === value) + onClicked: { + ConfigLoader.setConfigValueAndSave("appearance.fakeScreenRounding", value); + } + } + } + } + + ConfigSwitch { + text: "Transparency" + checked: ConfigOptions.appearance.transparency + onClicked: checked = !checked; + onCheckedChanged: { + ConfigLoader.setConfigValueAndSave("appearance.transparency", checked); + } + StyledToolTip { + content: "Might look ass. Unsupported." + } + } + } + + ContentSection { + title: "Shell windows" + spacing: 4 + + ConfigRow { + uniform: true + ConfigSwitch { + text: "Title bar" + checked: ConfigOptions.windows.showTitlebar + onClicked: checked = !checked; + onCheckedChanged: { + ConfigLoader.setConfigValueAndSave("windows.showTitlebar", checked); + } + } + ConfigSwitch { + text: "Center title" + checked: ConfigOptions.windows.centerTitle + onClicked: checked = !checked; + onCheckedChanged: { + ConfigLoader.setConfigValueAndSave("windows.centerTitle", checked); + } + } + } + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml index 18e5935e7..0105ad5a9 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml @@ -185,7 +185,7 @@ Rectangle { buttonIcon: activated ? "inventory" : "content_copy" onClicked: { - Hyprland.dispatch(`exec wl-copy '${StringUtils.shellSingleQuoteEscape(root.messageData?.content)}'`) + Quickshell.clipboardText = root.messageData?.content copyButton.activated = true copyIconTimer.restart() } diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml b/.config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml index d0a434dbd..957958ef4 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml @@ -73,7 +73,7 @@ ColumnLayout { buttonIcon: activated ? "inventory" : "content_copy" onClicked: { - Hyprland.dispatch(`exec wl-copy '${StringUtils.shellSingleQuoteEscape(segmentContent)}'`) + Quickshell.clipboardText = segmentContent copyCodeButton.activated = true copyIconTimer.restart() } @@ -96,8 +96,10 @@ ColumnLayout { onClicked: { const downloadPath = FileUtils.trimFileProtocol(Directories.downloads) - Hyprland.dispatch(`exec echo '${StringUtils.shellSingleQuoteEscape(segmentContent)}' > '${downloadPath}/code.${segmentLang || "txt"}'`) - Hyprland.dispatch(`exec notify-send 'Code saved to file' '${downloadPath}/code.${segmentLang || "txt"}' -a Shell`) + Quickshell.execDetached(["bash", "-c", + `echo '${StringUtils.shellSingleQuoteEscape(segmentContent)}' > '${downloadPath}/code.${segmentLang || "txt"}'` + ]) + Quickshell.execDetached(["bash", "-c", `notify-send 'Code saved to file' '${downloadPath}/code.${segmentLang || "txt"}' -a Shell`]) saveCodeButton.activated = true saveIconTimer.restart() } diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml index 6cdee99c0..a260dec5b 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml @@ -180,7 +180,9 @@ Button { buttonText: Translation.tr("Download") onClicked: { root.showActions = false - Hyprland.dispatch(`exec curl '${root.imageData.file_url}' -o '${root.imageData.is_nsfw ? root.nsfwPath : root.downloadPath}/${root.fileName}' && notify-send '${Translation.tr("Download complete")}' '${root.downloadPath}/${root.fileName}' -a 'Shell'`) + Quickshell.execDetached(["bash", "-c", + `curl '${root.imageData.file_url}' -o '${root.imageData.is_nsfw ? root.nsfwPath : root.downloadPath}/${root.fileName}' && notify-send '${qsTr("Download complete")}' '${root.downloadPath}/${root.fileName}' -a 'Shell'` + ]) } } } diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml index 58df4fa47..574d57e2d 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml @@ -163,12 +163,7 @@ Rectangle { Qt.openUrlExternally(link) Hyprland.dispatch("global quickshell:sidebarLeftClose") } - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.NoButton // Only for hover - hoverEnabled: true - cursorShape: parent.hoveredLink !== "" ? Qt.PointingHandCursor : Qt.ArrowCursor - } + PointingHandLinkHover {} } Repeater { diff --git a/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml index a29dddeda..d36d6eb89 100644 --- a/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml +++ b/.config/quickshell/modules/sidebarRight/BottomWidgetGroup.qml @@ -130,15 +130,17 @@ Rectangle { Layout.topMargin: 10 width: tabBar.width // Navigation rail buttons - ColumnLayout { + NavigationRailTabArray { + id: tabBar anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left anchors.leftMargin: 5 - id: tabBar - spacing: 15 + currentIndex: root.selectedTab + expanded: false Repeater { model: root.tabs - NavRailButton { + NavigationRailButton { + showToggledHighlight: false toggled: root.selectedTab == index buttonText: modelData.name buttonIcon: modelData.icon diff --git a/.config/quickshell/modules/sidebarRight/SidebarRight.qml b/.config/quickshell/modules/sidebarRight/SidebarRight.qml index 042322870..fb868e7ca 100644 --- a/.config/quickshell/modules/sidebarRight/SidebarRight.qml +++ b/.config/quickshell/modules/sidebarRight/SidebarRight.qml @@ -3,6 +3,7 @@ import "root:/services" import "root:/modules/common" import "root:/modules/common/widgets" import "root:/modules/common/functions/string_utils.js" as StringUtils +import "root:/modules/common/functions/file_utils.js" as FileUtils import "./quickToggles/" import "root:/services/" import QtQuick @@ -17,8 +18,10 @@ import Quickshell.Wayland import Quickshell.Hyprland Scope { + id: root property int sidebarWidth: Appearance.sizes.sidebarWidth property int sidebarPadding: 15 + property string settingsQmlPath: FileUtils.trimFileProtocol(`${Directories.config}/quickshell/settings.qml`) PanelWindow { id: sidebarRoot @@ -144,11 +147,11 @@ Scope { toggled: false buttonIcon: "settings" onClicked: { - Hyprland.dispatch(`exec ${ConfigOptions.apps.settings}`) - Hyprland.dispatch(`global quickshell:sidebarRightClose`) + Hyprland.dispatch("global quickshell:sidebarRightClose") + Quickshell.execDetached(["qs", "-p", root.settingsQmlPath]) } StyledToolTip { - content: Translation.tr("Plasma Settings") + content: Translation.tr("Settings") } } QuickToggleButton { diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/BluetoothToggle.qml b/.config/quickshell/modules/sidebarRight/quickToggles/BluetoothToggle.qml index bd0295c40..218c187bc 100644 --- a/.config/quickshell/modules/sidebarRight/quickToggles/BluetoothToggle.qml +++ b/.config/quickshell/modules/sidebarRight/quickToggles/BluetoothToggle.qml @@ -15,8 +15,8 @@ QuickToggleButton { toggleBluetooth.running = true } altAction: () => { - Hyprland.dispatch(`exec ${ConfigOptions.apps.bluetooth}`) - Hyprland.dispatch("global quickshell:sidebarRightClose") + Quickshell.execDetached(["bash", "-c", `${ConfigOptions.apps.bluetooth}`]) + Hyprland.dispatch("global quickshell:sidebarRightClose") } Process { id: toggleBluetooth diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/GameMode.qml b/.config/quickshell/modules/sidebarRight/quickToggles/GameMode.qml index de6f210c5..51417ec98 100644 --- a/.config/quickshell/modules/sidebarRight/quickToggles/GameMode.qml +++ b/.config/quickshell/modules/sidebarRight/quickToggles/GameMode.qml @@ -7,20 +7,27 @@ import Quickshell.Io import Quickshell.Hyprland QuickToggleButton { - property bool enabled: false + id: root buttonIcon: "gamepad" - toggled: enabled + toggled: toggled onClicked: { - enabled = !enabled - if (enabled) { - // gameModeOn.running = true - Hyprland.dispatch(`exec hyprctl --batch "keyword animations:enabled 0; keyword decoration:shadow:enabled 0; keyword decoration:blur:enabled 0; keyword general:gaps_in 0; keyword general:gaps_out 0; keyword general:border_size 1; keyword decoration:rounding 0; keyword general:allow_tearing 1"`) + root.toggled = !root.toggled + if (root.toggled) { + Quickshell.execDetached(["bash", "-c", `hyprctl --batch "keyword animations:enabled 0; keyword decoration:shadow:enabled 0; keyword decoration:blur:enabled 0; keyword general:gaps_in 0; keyword general:gaps_out 0; keyword general:border_size 1; keyword decoration:rounding 0; keyword general:allow_tearing 1"`]) } else { - Hyprland.dispatch("exec hyprctl reload") + Quickshell.execDetached(["hyprctl", "reload"]) + } + } + Process { + id: fetchActiveState + 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 } } - StyledToolTip { content: Translation.tr("Game mode") } diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml b/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml index 0ba09b72e..e15c49a6e 100644 --- a/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml +++ b/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml @@ -16,7 +16,7 @@ QuickToggleButton { toggleNetwork.running = true } altAction: () => { - Hyprland.dispatch(`exec ${Network.ethernet ? ConfigOptions.apps.networkEthernet : ConfigOptions.apps.network}`) + Quickshell.execDetached(["bash", "-c", `${Network.ethernet ? ConfigOptions.apps.networkEthernet : ConfigOptions.apps.network}`]) Hyprland.dispatch("global quickshell:sidebarRightClose") } Process { diff --git a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml index ea5701176..fa72061f5 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml @@ -154,31 +154,18 @@ Item { // + FAB StyledRectangularShadow { target: fabButton - radius: Appearance.rounding.normal + radius: fabButton.buttonRadius + blur: 0.6 * Appearance.sizes.elevationMargin } - Button { + FloatingActionButton { id: fabButton anchors.right: parent.right anchors.bottom: parent.bottom anchors.rightMargin: root.fabMargins anchors.bottomMargin: root.fabMargins - width: root.fabSize - height: root.fabSize - PointingHandInteraction {} onClicked: root.showAddDialog = true - background: Rectangle { - id: fabBackground - anchors.fill: parent - radius: Appearance.rounding.normal - color: (fabButton.down) ? Appearance.colors.colPrimaryContainerActive : (fabButton.hovered ? Appearance.colors.colPrimaryContainerHover : Appearance.colors.colPrimaryContainer) - - Behavior on color { - animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) - } - } - contentItem: MaterialSymbol { text: "add" horizontalAlignment: Text.AlignHCenter diff --git a/.config/quickshell/scripts/colors/switchwall.sh b/.config/quickshell/scripts/colors/switchwall.sh index 0a633e242..80adb169e 100755 --- a/.config/quickshell/scripts/colors/switchwall.sh +++ b/.config/quickshell/scripts/colors/switchwall.sh @@ -219,7 +219,7 @@ switch() { # Set wallpaper with swww swww img "$imgpath" --transition-step 100 --transition-fps 120 \ --transition-type grow --transition-angle 30 --transition-duration 1 \ - --transition-pos "$cursorposx, $cursorposy_inverted" + --transition-pos "$cursorposx, $cursorposy_inverted" & remove_restore fi fi diff --git a/.config/quickshell/services/Battery.qml b/.config/quickshell/services/Battery.qml index 08bdee7cd..b19910d85 100644 --- a/.config/quickshell/services/Battery.qml +++ b/.config/quickshell/services/Battery.qml @@ -21,10 +21,12 @@ Singleton { property bool isCriticalAndNotCharging: isCritical && !isCharging onIsLowAndNotChargingChanged: { - if (available && isLowAndNotCharging) Hyprland.dispatch(`exec notify-send "Low battery" "Consider plugging in your device" -u critical -a "Shell"`) + if (available && isLowAndNotCharging) + Quickshell.execDetached(["bash", "-c", `notify-send "Low battery" "Consider plugging in your device" -u critical -a "Shell"`]); } onIsCriticalAndNotChargingChanged: { - if (available && isCriticalAndNotCharging) Hyprland.dispatch(`exec notify-send "Critically low battery" "🙏 I beg for pleas charg\nAutomatic suspend triggers at ${ConfigOptions.battery.suspend}%" -u critical -a "Shell"`) + 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"`]); } } diff --git a/.config/quickshell/services/ConfigLoader.qml b/.config/quickshell/services/ConfigLoader.qml index 01a6625e9..383ddec44 100644 --- a/.config/quickshell/services/ConfigLoader.qml +++ b/.config/quickshell/services/ConfigLoader.qml @@ -44,9 +44,8 @@ Singleton { } catch (e) { console.error("[ConfigLoader] Error reading file:", e); console.log("[ConfigLoader] File content was:", fileContent); - Hyprland.dispatch(`exec notify-send "${Translation.tr("Shell configuration failed to load")}" "${root.filePath}"`) + Quickshell.execDetached(["bash", "-c", `notify-send '${Translation.tr("Shell configuration failed to load")}' '${root.filePath}'`]) return; - } } @@ -82,7 +81,7 @@ Singleton { function saveConfig() { const plainConfig = ObjectUtils.toPlainObject(ConfigOptions) - Hyprland.dispatch(`exec echo '${StringUtils.shellSingleQuoteEscape(JSON.stringify(plainConfig, null, 2))}' > '${root.filePath}'`) + Quickshell.execDetached(["bash", "-c", `echo '${StringUtils.shellSingleQuoteEscape(JSON.stringify(plainConfig, null, 2))}' > '${FileUtils.trimFileProtocol(root.filePath)}'`]) } function setConfigValueAndSave(nestedKey, value, preventNextNotification = true) { @@ -105,7 +104,7 @@ Singleton { } else { root.applyConfig(configFileView.text()) if (!root.preventNextNotification) { - // Hyprland.dispatch(`exec notify-send "${Translation.tr("Shell configuration reloaded")}" "${root.filePath}"`) + // Quickshell.execDetached(["bash", "-c", `notify-send '${qsTr("Shell configuration reloaded")}' '${root.filePath}'`]) } else { root.preventNextNotification = false; } @@ -129,9 +128,9 @@ Singleton { if(error == FileViewError.FileNotFound) { console.log("[ConfigLoader] File not found, creating new file.") root.saveConfig() - Hyprland.dispatch(`exec notify-send "${Translation.tr("Shell configuration created")}" "${root.filePath}"`) + Quickshell.execDetached(["bash", "-c", `notify-send '${Translation.tr("Shell configuration created")}' '${root.filePath}'`]) } else { - Hyprland.dispatch(`exec notify-send "${Translation.tr("Shell configuration failed to load")}" "${root.filePath}"`) + Quickshell.execDetached(["bash", "-c", `notify-send '${Translation.tr("Shell configuration failed to load")}' '${root.filePath}'`]) } } } diff --git a/.config/quickshell/services/DateTime.qml b/.config/quickshell/services/DateTime.qml index 4f24e9447..17fb1c8a6 100644 --- a/.config/quickshell/services/DateTime.qml +++ b/.config/quickshell/services/DateTime.qml @@ -9,9 +9,9 @@ pragma ComponentBehavior: Bound * A nice wrapper for date and time strings. */ Singleton { - property string time: Qt.formatDateTime(clock.date, ConfigOptions?.time.format ?? "hh:mm") - property string date: Qt.formatDateTime(clock.date, ConfigOptions?.time.dateFormat ?? "dddd, dd/MM") - property string collapsedCalendarFormat: Qt.formatDateTime(clock.date, "dd MMMM yyyy") + 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 collapsedCalendarFormat: Qt.locale().toString(clock.date, "dd MMMM yyyy") property string uptime: "0h, 0m" SystemClock { diff --git a/.config/quickshell/services/FirstRunExperience.qml b/.config/quickshell/services/FirstRunExperience.qml index 86ccb98b8..4b1f034cd 100644 --- a/.config/quickshell/services/FirstRunExperience.qml +++ b/.config/quickshell/services/FirstRunExperience.qml @@ -20,15 +20,15 @@ Singleton { } function enableNextTime() { - Hyprland.dispatch(`exec rm -f '${root.firstRunFilePath}'`) + Quickshell.execDetached(["rm", "-f", root.firstRunFilePath]) } function disableNextTime() { - Hyprland.dispatch(`exec echo '${root.firstRunFileContent}' > '${root.firstRunFilePath}'`) + Quickshell.execDetached(["bash", "-c", `echo '${root.firstRunFileContent}' > '${root.firstRunFilePath}'`]) } function handleFirstRun() { - Hyprland.dispatch(`exec swww query | grep 'image' || '${Directories.wallpaperSwitchScriptPath}' '${root.defaultWallpaperPath}'`) - Hyprland.dispatch(`exec qs -p '${root.welcomeQmlPath}'`) + Quickshell.execDetached(["bash", "-c", `swww query | grep 'image' || '${Directories.wallpaperSwitchScriptPath}' '${root.defaultWallpaperPath}'`]) + Quickshell.execDetached(["bash", "-c", `qs -p '${root.welcomeQmlPath}'`]) } FileView { diff --git a/.config/quickshell/services/Notifications.qml b/.config/quickshell/services/Notifications.qml index dc2d2206d..6f04b7cd6 100644 --- a/.config/quickshell/services/Notifications.qml +++ b/.config/quickshell/services/Notifications.qml @@ -243,7 +243,7 @@ Singleton { root.list = JSON.parse(fileContents).map((notif) => { return notifComponent.createObject(root, { "id": notif.id, - "actions": notif.actions, + "actions": [], // Notification actions are meaningless if they're not tracked by the server or the sender is dead "appIcon": notif.appIcon, "appName": notif.appName, "body": notif.body, diff --git a/.config/quickshell/services/SystemInfo.qml b/.config/quickshell/services/SystemInfo.qml index ffd478b65..cd3d9f383 100644 --- a/.config/quickshell/services/SystemInfo.qml +++ b/.config/quickshell/services/SystemInfo.qml @@ -9,10 +9,17 @@ import Quickshell.Io * Provides some system info: distro, username. */ Singleton { + id: root property string distroName: "Unknown" property string distroId: "unknown" property string distroIcon: "linux-symbolic" property string username: "user" + property string homeUrl: "" + property string documentationUrl: "" + property string supportUrl: "" + property string bugReportUrl: "" + property string privacyPolicyUrl: "" + property string logo: "" Timer { triggeredOnStart: true @@ -33,6 +40,20 @@ Singleton { const logoMatch = textOsRelease.match(/^LOGO=(.+)$/m) distroId = logoMatch ? logoMatch[1].replace(/"/g, "") : "unknown" + // Extract additional URLs and logo + const homeUrlMatch = textOsRelease.match(/^HOME_URL="(.+?)"/m) + homeUrl = homeUrlMatch ? homeUrlMatch[1] : "" + const documentationUrlMatch = textOsRelease.match(/^DOCUMENTATION_URL="(.+?)"/m) + documentationUrl = documentationUrlMatch ? documentationUrlMatch[1] : "" + const supportUrlMatch = textOsRelease.match(/^SUPPORT_URL="(.+?)"/m) + supportUrl = supportUrlMatch ? supportUrlMatch[1] : "" + const bugReportUrlMatch = textOsRelease.match(/^BUG_REPORT_URL="(.+?)"/m) + bugReportUrl = bugReportUrlMatch ? bugReportUrlMatch[1] : "" + const privacyPolicyUrlMatch = textOsRelease.match(/^PRIVACY_POLICY_URL="(.+?)"/m) + privacyPolicyUrl = privacyPolicyUrlMatch ? privacyPolicyUrlMatch[1] : "" + const logoFieldMatch = textOsRelease.match(/^LOGO="?(.+?)"?$/m) + logo = logoFieldMatch ? logoFieldMatch[1] : "" + // Update the distroIcon property based on distroId switch (distroId) { case "arch": distroIcon = "arch-symbolic"; break; @@ -57,7 +78,7 @@ Singleton { command: ["whoami"] stdout: SplitParser { onRead: data => { - username = data.trim() + root.username = data.trim() } } } diff --git a/.config/quickshell/services/Ydotool.qml b/.config/quickshell/services/Ydotool.qml index 7cafcbbe2..f702010d1 100644 --- a/.config/quickshell/services/Ydotool.qml +++ b/.config/quickshell/services/Ydotool.qml @@ -20,23 +20,36 @@ Singleton { function releaseAllKeys() { const keycodes = Array.from(Array(249).keys()); - const releaseCommand = `ydotool key --key-delay 0 ${keycodes.map(keycode => `${keycode}:0`).join(" ")}` - Hyprland.dispatch(`exec ${releaseCommand}`) + Quickshell.execDetached([ + "ydotool", + "key", "--key-delay", "0", + ...keycodes.map(keycode => `${keycode}:0`) + ]) root.shiftMode = 0; // Reset shift mode } function releaseShiftKeys() { - const releaseCommand = `ydotool key --key-delay 0 ${root.shiftKeys.map(keycode => `${keycode}:0`).join(" ")}` - Hyprland.dispatch(`exec ${releaseCommand}`) + Quickshell.execDetached([ + "ydotool", + "key", "--key-delay", "0", + ...root.shiftKeys.map(keycode => `${keycode}:0`) + ]) root.shiftMode = 0; // Reset shift mode } function press(keycode) { - Hyprland.dispatch(`exec ydotool key --key-delay 0 ${keycode}:1`); + Quickshell.execDetached([ + "ydotool", + "key", "--key-delay", "0", + `${keycode}:1` + ]); } function release(keycode) { - Hyprland.dispatch(`exec ydotool key --key-delay 0 ${keycode}:0`); + Quickshell.execDetached([ + "ydotool", + "key", "--key-delay", "0", + `${keycode}:0` + ]); } } - diff --git a/.config/quickshell/settings.qml b/.config/quickshell/settings.qml new file mode 100644 index 000000000..eac2e65b9 --- /dev/null +++ b/.config/quickshell/settings.qml @@ -0,0 +1,219 @@ +//@ pragma UseQApplication +//@ pragma Env QS_NO_RELOAD_POPUP=1 +//@ pragma Env QT_QUICK_CONTROLS_STYLE=Basic + +// 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 +import QtQuick.Window +import Quickshell +import Quickshell.Io +import Quickshell.Hyprland +import "root:/services/" +import "root:/modules/common/" +import "root:/modules/common/widgets/" +import "root:/modules/common/functions/color_utils.js" as ColorUtils +import "root:/modules/common/functions/file_utils.js" as FileUtils +import "root:/modules/common/functions/string_utils.js" as StringUtils + +ApplicationWindow { + id: root + property string firstRunFilePath: FileUtils.trimFileProtocol(`${Directories.state}/user/first_run.txt`) + property string firstRunFileContent: "This file is just here to confirm you've been greeted :>" + property real contentPadding: 8 + property bool showNextTime: false + property var pages: [ + { + name: "Style", + icon: "palette", + component: "modules/settings/Style.qml" + }, + { + name: "Behavior", + icon: "settings", + component: "modules/settings/BehaviorConfig.qml" + }, + { + name: "About", + icon: "info", + component: "modules/settings/About.qml" + } + ] + property int currentPage: 0 + + visible: true + onClosing: Qt.quit() + title: "illogical-impulse Settings" + + Component.onCompleted: { + MaterialThemeLoader.reapplyTheme() + ConfigLoader.loadConfig() + } + + minimumWidth: 600 + minimumHeight: 400 + width: 900 + height: 650 + color: Appearance.m3colors.m3background + + ColumnLayout { + anchors { + fill: parent + margins: contentPadding + } + + Item { // Titlebar + visible: ConfigOptions?.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 + verticalCenter: parent.verticalCenter + leftMargin: 12 + } + color: Appearance.colors.colOnLayer0 + text: "Settings" + font.pixelSize: Appearance.font.pixelSize.title + font.family: Appearance.font.family.title + } + RowLayout { // Window controls row + id: windowControlsRow + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + RippleButton { + buttonRadius: Appearance.rounding.full + implicitWidth: 35 + implicitHeight: 35 + onClicked: root.close() + contentItem: MaterialSymbol { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + text: "close" + iconSize: 20 + } + } + } + } + + RowLayout { // Window content with navigation rail and content pane + Layout.fillWidth: true + Layout.fillHeight: true + spacing: contentPadding + Item { + id: navRailWrapper + Layout.fillHeight: true + Layout.margins: 5 + implicitWidth: navRail.expanded ? 150 : fab.baseSize + Behavior on implicitWidth { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + NavigationRail { // Window content with navigation rail and content pane + id: navRail + anchors { + left: parent.left + top: parent.top + bottom: parent.bottom + } + spacing: 10 + expanded: root.width > 900 + + NavigationRailExpandButton {} + + FloatingActionButton { + id: fab + iconText: "edit" + buttonText: "Edit config" + expanded: navRail.expanded + onClicked: { + Qt.openUrlExternally(`${Directories.config}/illogical-impulse/config.json`); + } + + StyledToolTip { + extraVisibleCondition: !navRail.expanded + content: "Edit shell config file" + } + } + + NavigationRailTabArray { + currentIndex: root.currentPage + expanded: navRail.expanded + Repeater { + model: root.pages + NavigationRailButton { + required property var index + required property var modelData + toggled: root.currentPage === index + onClicked: root.currentPage = index; + expanded: navRail.expanded + buttonIcon: modelData.icon + buttonText: modelData.name + showToggledHighlight: false + } + } + } + + Item { + Layout.fillHeight: true + } + } + } + Rectangle { // Content container + Layout.fillWidth: true + Layout.fillHeight: true + color: Appearance.m3colors.m3surfaceContainerLow + radius: Appearance.rounding.windowRounding - root.contentPadding + + Loader { + id: pageLoader + anchors.fill: parent + opacity: 1.0 + source: root.pages[0].component + Connections { + target: root + function onCurrentPageChanged() { + if (pageLoader.sourceComponent !== root.pages[root.currentPage].component) { + switchAnim.restart(); + } + } + } + + SequentialAnimation { + id: switchAnim + + NumberAnimation { + target: pageLoader + properties: "opacity" + from: 1 + to: 0 + duration: 150 + easing.type: Appearance.animation.elementMoveExit.type + easing.bezierCurve: Appearance.animationCurves.emphasizedFirstHalf + } + PropertyAction { + target: pageLoader + property: "source" + value: root.pages[root.currentPage].component + } + NumberAnimation { + target: pageLoader + properties: "opacity" + from: 0 + to: 1 + duration: 250 + easing.type: Appearance.animation.elementMoveEnter.type + easing.bezierCurve: Appearance.animationCurves.emphasizedLastHalf + } + } + } + } + } + } +} diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml index 113181d94..4edfde51f 100644 --- a/.config/quickshell/shell.qml +++ b/.config/quickshell/shell.qml @@ -32,7 +32,7 @@ ShellRoot { property bool enableBar: true property bool enableBackgroundWidgets: true property bool enableCheatsheet: true - property bool enableDock: true + property bool enableDock: false property bool enableMediaControls: true property bool enableNotificationPopup: true property bool enableOnScreenDisplayBrightness: true diff --git a/.config/quickshell/translations/en_US.json b/.config/quickshell/translations/en_US.json index 34887b99b..b294db6ea 100644 --- a/.config/quickshell/translations/en_US.json +++ b/.config/quickshell/translations/en_US.json @@ -44,7 +44,6 @@ "Disable NSFW content": "Disable NSFW content", "Done": "Done", "Download": "Download", - "Download complete": "Download complete", "Edit": "Edit", "Enter text to translate...": "Enter text to translate...", "Finished tasks will go here": "Finished tasks will go here", @@ -90,7 +89,6 @@ "Opens session screen on press": "Opens session screen on press", "Output": "Output", "Page {0}": "Page {0}", - "Plasma Settings": "Plasma Settings", "Reboot": "Reboot", "Reboot to firmware settings": "Reboot to firmware settings", "Reload Hyprland & Quickshell": "Reload Hyprland & Quickshell", @@ -110,7 +108,6 @@ "Set the current API provider": "Set the current API provider", "Shell configuration created": "Shell configuration created", "Shell configuration failed to load": "Shell configuration failed to load", - "Shell configuration reloaded": "Shell configuration reloaded", "Shutdown": "Shutdown", "Silent": "Silent", "Sleep": "Sleep", @@ -151,7 +148,6 @@ "Waifus only | Excellent quality, limited quantity": "Waifus only | Excellent quality, limited quantity", "Waiting for response...": "Waiting for response...", "Workspace": "Workspace", - "{0} (copied)": "{0} (copied)", "{0} Safe Storage": "{0} Safe Storage", "{0} does not require an API key": "{0} does not require an API key", "{0} queries pending": "{0} queries pending", @@ -172,5 +168,6 @@ "Switched to search mode. Continue with the user's request.": "Switched to search mode. Continue with the user's request.", "Experimental | Online | Google's model\nCan do a little more but doesn't search quickly": "Experimental | Online | Google's model\nCan do a little more but doesn't search quickly", "Message the model... \"{0}\" for commands": "Message the model... \"{0}\" for commands", - "To set an API key, pass it with the command\n\nTo view the key, pass \"get\" with the command
\n\n### For {0}:\n\n**Link**: {1}\n\n{2}": "To set an API key, pass it with the command\n\nTo view the key, pass \"get\" with the command
\n\n### For {0}:\n\n**Link**: {1}\n\n{2}" + "To set an API key, pass it with the command\n\nTo view the key, pass \"get\" with the command
\n\n### For {0}:\n\n**Link**: {1}\n\n{2}": "To set an API key, pass it with the command\n\nTo view the key, pass \"get\" with the command
\n\n### For {0}:\n\n**Link**: {1}\n\n{2}", + "Settings": "Settings" } \ No newline at end of file diff --git a/.config/quickshell/translations/zh_CN.json b/.config/quickshell/translations/zh_CN.json index a0d099397..1c2f1b7bf 100644 --- a/.config/quickshell/translations/zh_CN.json +++ b/.config/quickshell/translations/zh_CN.json @@ -44,7 +44,6 @@ "Disable NSFW content": "禁用 NSFW 内容", "Done": "完成", "Download": "下载", - "Download complete": "下载完成", "Edit": "编辑", "Enter text to translate...": "输入要翻译的文本...", "Finished tasks will go here": "已完成的任务将显示在这里", @@ -90,7 +89,6 @@ "Opens session screen on press": "按下时打开会话屏幕", "Output": "输出", "Page {0}": "第 {0} 页", - "Plasma Settings": "Plasma 设置", "Reboot": "重启", "Reboot to firmware settings": "重启到固件设置", "Reload Hyprland & Quickshell": "重新加载 Hyprland 和 Quickshell", @@ -110,7 +108,6 @@ "Set the current API provider": "设置当前 API 提供商", "Shell configuration created": "Shell 配置已创建", "Shell configuration failed to load": "Shell 配置加载失败", - "Shell configuration reloaded": "Shell 配置已重新加载", "Shutdown": "关机", "Silent": "静音", "Sleep": "睡眠", @@ -151,7 +148,6 @@ "Waifus only | Excellent quality, limited quantity": "仅限角色 | 优秀质量,数量有限", "Waiting for response...": "等待响应...", "Workspace": "工作区", - "{0} (copied)": "{0}(已复制)", "{0} Safe Storage": "{0} 安全存储", "{0} does not require an API key": "{0} 不需要 API 密钥", "{0} queries pending": "{0} 个查询等待中", @@ -172,5 +168,6 @@ "Enter tags, or \"{0}\" for commands": "输入标签,或 \"{0}\" 查看命令", "Online via {0} | {1}'s model": "通过 {0} 在线 | {1} 的模型", "That didn't work. Tips:\n- Check your tags and NSFW settings\n- If you don't have a tag in mind, type a page number": "没有找到结果。提示:\n- 检查您的标签和 NSFW 设置\n- 如果没有想到标签,请输入页码", - "Online | Google's model\nGives up-to-date information with search.": "在线 | Google 模型\n通过搜索提供最新信息。" + "Online | Google's model\nGives up-to-date information with search.": "在线 | Google 模型\n通过搜索提供最新信息。", + "Settings": "设置" } \ No newline at end of file diff --git a/.config/quickshell/welcome.qml b/.config/quickshell/welcome.qml index cbc5b4a32..9adaef8c1 100644 --- a/.config/quickshell/welcome.qml +++ b/.config/quickshell/welcome.qml @@ -24,7 +24,7 @@ ApplicationWindow { id: root property string firstRunFilePath: FileUtils.trimFileProtocol(`${Directories.state}/user/first_run.txt`) property string firstRunFileContent: "This file is just here to confirm you've been greeted :>" - property real contentPadding: 5 + property real contentPadding: 8 property bool showNextTime: false visible: true onClosing: Qt.quit() @@ -53,220 +53,27 @@ ApplicationWindow { } } - component SelectionConnectedButton: GroupButton { - id: selectionConnectedButtonRoot - horizontalPadding: 12 - verticalPadding: 8 - bounce: false - property bool leftmost: false - property bool rightmost: false - leftRadius: (toggled || leftmost) ? (height / 2) : Appearance.rounding.unsharpenmore - rightRadius: (toggled || rightmost) ? (height / 2) : Appearance.rounding.unsharpenmore - colBackground: Appearance.colors.colSecondaryContainer - contentItem: StyledText { - color: parent.toggled ? Appearance.colors.colOnPrimary : Appearance.colors.colOnSecondaryContainer - text: selectionConnectedButtonRoot.buttonText - } - } - - component Section: ColumnLayout { - id: sectionRoot - property string title - default property alias data: sectionContent.data - - Layout.fillWidth: true - spacing: 8 - StyledText { - text: sectionRoot.title - font.pixelSize: Appearance.font.pixelSize.larger - } - ColumnLayout { - id: sectionContent - spacing: 5 - } - } - - component ButtonWithIcon: RippleButton { - id: buttonWithIconRoot - property string nerdIcon - property string iconText - property string mainText: "Button text" - property Component mainContentComponent: Component { - StyledText { - text: buttonWithIconRoot.mainText - font.pixelSize: Appearance.font.pixelSize.small - color: Appearance.colors.colOnSecondaryContainer - } - } - implicitHeight: 35 - horizontalPadding: 15 - buttonRadius: Appearance.rounding.small - colBackground: Appearance.colors.colLayer2 - - contentItem: RowLayout { - Item { - implicitWidth: Math.max(materialIconLoader.implicitWidth, nerdIconLoader.implicitWidth) - Loader { - id: materialIconLoader - anchors.centerIn: parent - active: !nerdIcon - sourceComponent: MaterialSymbol { - text: buttonWithIconRoot.iconText - iconSize: Appearance.font.pixelSize.larger - color: Appearance.colors.colOnSecondaryContainer - fill: 1 - } - } - Loader { - id: nerdIconLoader - anchors.centerIn: parent - active: nerdIcon - sourceComponent: StyledText { - text: buttonWithIconRoot.nerdIcon - font.pixelSize: Appearance.font.pixelSize.larger - font.family: Appearance.font.family.iconNerd - color: Appearance.colors.colOnSecondaryContainer - } - } - } - Loader { - sourceComponent: buttonWithIconRoot.mainContentComponent - Layout.alignment: Qt.AlignVCenter - } - } - } - - component LightDarkPrefButton: GroupButton { - id: lightDarkButtonRoot - required property bool dark - property color previewBg: dark ? ColorUtils.colorWithHueOf("#3f3838", Appearance.m3colors.m3primary) : - ColorUtils.colorWithHueOf("#F7F9FF", Appearance.m3colors.m3primary) - property color previewFg: dark ? Qt.lighter(previewBg, 2.2) : ColorUtils.mix(previewBg, "#292929", 0.85) - padding: 5 - Layout.fillWidth: true - colBackground: Appearance.colors.colLayer2 - toggled: Appearance.m3colors.darkmode === dark - onClicked: { - Hyprland.dispatch(`exec ${Directories.wallpaperSwitchScriptPath} --mode ${dark ? "dark" : "light"} --noswitch`) - } - contentItem: Item { - anchors.centerIn: parent - implicitWidth: buttonContentLayout.implicitWidth - implicitHeight: buttonContentLayout.implicitHeight - ColumnLayout { - id: buttonContentLayout - anchors.centerIn: parent - Rectangle { - Layout.alignment: Qt.AlignHCenter - implicitWidth: 250 - implicitHeight: skeletonColumnLayout.implicitHeight + 10 * 2 - radius: lightDarkButtonRoot.buttonRadius - lightDarkButtonRoot.padding - color: lightDarkButtonRoot.previewBg - border { - width: 1 - color: Appearance.m3colors.m3outlineVariant - } - - // Some skeleton items - ColumnLayout { - id: skeletonColumnLayout - anchors.fill: parent - anchors.margins: 10 - spacing: 10 - RowLayout { - Rectangle { - radius: Appearance.rounding.full - color: lightDarkButtonRoot.previewFg - implicitWidth: 50 - implicitHeight: 50 - } - ColumnLayout { - spacing: 4 - Rectangle { - radius: Appearance.rounding.unsharpenmore - color: lightDarkButtonRoot.previewFg - Layout.fillWidth: true - implicitHeight: 22 - } - Rectangle { - radius: Appearance.rounding.unsharpenmore - color: lightDarkButtonRoot.previewFg - Layout.fillWidth: true - Layout.rightMargin: 45 - implicitHeight: 18 - } - } - } - StyledProgressBar { - Layout.topMargin: 5 - Layout.bottomMargin: 5 - Layout.fillWidth: true - value: 0.7 - sperm: true - animateSperm: lightDarkButtonRoot.toggled - highlightColor: lightDarkButtonRoot.toggled ? Appearance.m3colors.m3primary : lightDarkButtonRoot.previewFg - trackColor: ColorUtils.mix(lightDarkButtonRoot.previewBg, lightDarkButtonRoot.previewFg, 0.5) - } - RowLayout { - spacing: 2 - Rectangle { - radius: Appearance.rounding.full - color: lightDarkButtonRoot.toggled ? Appearance.m3colors.m3primary : lightDarkButtonRoot.previewFg - Layout.fillWidth: true - implicitHeight: 30 - MaterialSymbol { - visible: lightDarkButtonRoot.toggled - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - text: "check" - iconSize: 20 - color: lightDarkButtonRoot.toggled ? Appearance.m3colors.m3onPrimary : lightDarkButtonRoot.previewBg - } - } - Rectangle { - radius: Appearance.rounding.unsharpenmore - color: lightDarkButtonRoot.toggled ? Appearance.m3colors.m3secondaryContainer : lightDarkButtonRoot.previewFg - Layout.fillWidth: true - implicitHeight: 30 - } - Rectangle { - topLeftRadius: Appearance.rounding.unsharpenmore - bottomLeftRadius: Appearance.rounding.unsharpenmore - topRightRadius: Appearance.rounding.full - bottomRightRadius: Appearance.rounding.full - color: lightDarkButtonRoot.toggled ? Appearance.m3colors.m3secondaryContainer : lightDarkButtonRoot.previewFg - Layout.fillWidth: true - implicitHeight: 30 - } - } - } - } - StyledText { - Layout.fillWidth: true - text: dark ? "Dark" : "Light" - color: lightDarkButtonRoot.toggled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer2 - horizontalAlignment: Text.AlignHCenter - } - } - } - } - ColumnLayout { anchors { fill: parent margins: contentPadding } - Item { + Item { // Titlebar visible: ConfigOptions?.windows.showTitlebar Layout.fillWidth: true implicitHeight: Math.max(welcomeText.implicitHeight, windowControlsRow.implicitHeight) StyledText { id: welcomeText - anchors.centerIn: parent + anchors { + left: ConfigOptions.windows.centerTitle ? undefined : parent.left + horizontalCenter: ConfigOptions.windows.centerTitle ? parent.horizontalCenter : undefined + verticalCenter: parent.verticalCenter + leftMargin: 12 + } color: Appearance.colors.colOnLayer0 text: "Yooooo hi there" - font.pixelSize: Appearance.font.pixelSize.hugeass + font.pixelSize: Appearance.font.pixelSize.title font.family: Appearance.font.family.title } RowLayout { // Window controls row @@ -284,10 +91,9 @@ ApplicationWindow { Layout.alignment: Qt.AlignVCenter onCheckedChanged: { if (checked) { - Hyprland.dispatch(`exec rm '${StringUtils.shellSingleQuoteEscape(root.firstRunFilePath)}'`) + Quickshell.execDetached(["rm", root.firstRunFilePath]) } else { - console.log(`exec echo '${StringUtils.shellSingleQuoteEscape(root.firstRunFileContent)}' > '${StringUtils.shellSingleQuoteEscape(root.firstRunFilePath)}'`) - Hyprland.dispatch(`exec echo '${StringUtils.shellSingleQuoteEscape(root.firstRunFileContent)}' > '${StringUtils.shellSingleQuoteEscape(root.firstRunFilePath)}'`) + Quickshell.execDetached(["bash", "-c", `echo '${StringUtils.shellSingleQuoteEscape(root.firstRunFileContent)}' > '${StringUtils.shellSingleQuoteEscape(root.firstRunFilePath)}'`]) } } } @@ -305,262 +111,217 @@ ApplicationWindow { } } } - Rectangle { + Rectangle { // Content container color: Appearance.m3colors.m3surfaceContainerLow + radius: Appearance.rounding.windowRounding - root.contentPadding implicitHeight: contentColumn.implicitHeight implicitWidth: contentColumn.implicitWidth Layout.fillWidth: true Layout.fillHeight: true - radius: Appearance.rounding.windowRounding - root.contentPadding - Flickable { - clip: true + + + ContentPage { + id: contentColumn anchors.fill: parent - contentHeight: contentColumn.implicitHeight - implicitWidth: contentColumn.implicitWidth - ColumnLayout { - id: contentColumn - anchors { - top: parent.top - bottom: parent.bottom - horizontalCenter: parent.horizontalCenter - margins: 10 - } - spacing: 20 + ContentSection { + title: "Style & wallpaper" - Section { - title: "Style & wallpaper" - - ButtonGroup { - Layout.fillWidth: true - LightDarkPrefButton { - dark: false - } - LightDarkPrefButton { - dark: true - } - } - - RowLayout { - Layout.alignment: Qt.AlignHCenter - ButtonWithIcon { - id: rndWallBtn - Layout.alignment: Qt.AlignHCenter - buttonRadius: Appearance.rounding.small - iconText: "wallpaper" - mainText: konachanWallProc.running ? "Be patient..." : "Random: Konachan" - onClicked: { - console.log(konachanWallProc.command.join(" ")) - konachanWallProc.running = true; - } - } - ButtonWithIcon { - iconText: "wallpaper" - onClicked: { - Hyprland.dispatch(`exec ${Directories.wallpaperSwitchScriptPath}`) - } - mainContentComponent: Component { - RowLayout { - spacing: 10 - StyledText { - font.pixelSize: Appearance.font.pixelSize.small - text: "Choose file" - color: Appearance.colors.colOnSecondaryContainer - } - RowLayout { - spacing: 3 - KeyboardKey { - key: "Ctrl" - } - KeyboardKey { - key: "󰖳" - } - StyledText { - Layout.alignment: Qt.AlignVCenter - text: "+" - } - KeyboardKey { - key: "T" - } - } - } - } - } - } - - StyledText { - Layout.alignment: Qt.AlignHCenter - text: "Change any time later with /dark, /light, /img in the launcher" - font.pixelSize: Appearance.font.pixelSize.smaller - color: Appearance.colors.colSubtext - } - } - - Section { - title: "Policies" - - RowLayout { - Layout.alignment: Qt.AlignHCenter - spacing: 15 - ColumnLayout { // Weeb policy - StyledText { - text: "Weeb" - color: Appearance.colors.colSubtext - } - ButtonGroup { - id: weebPolicyBtnGroup - property int selectedPolicy: ConfigOptions.policies.weeb - spacing: 2 - SelectionConnectedButton { - property int value: 0 - leftmost: true - buttonText: "No" - toggled: (weebPolicyBtnGroup.selectedPolicy === value) - onClicked: { - ConfigLoader.setConfigValueAndSave("policies.weeb", value); - } - } - SelectionConnectedButton { - property int value: 1 - buttonText: "Yes" - toggled: (weebPolicyBtnGroup.selectedPolicy === value) - onClicked: { - ConfigLoader.setConfigValueAndSave("policies.weeb", value); - } - } - SelectionConnectedButton { - property int value: 2 - rightmost: true - buttonText: "Closet" - toggled: (weebPolicyBtnGroup.selectedPolicy === value) - onClicked: { - ConfigLoader.setConfigValueAndSave("policies.weeb", value); - } - } - } - } - ColumnLayout { // AI policy - StyledText { - text: "AI" - color: Appearance.colors.colSubtext - } - ButtonGroup { - id: aiPolicyBtnGroup - property int selectedPolicy: ConfigOptions.policies.ai - spacing: 2 - SelectionConnectedButton { - property int value: 0 - leftmost: true - buttonText: "No" - toggled: (aiPolicyBtnGroup.selectedPolicy === value) - onClicked: { - ConfigLoader.setConfigValueAndSave("policies.ai", value); - } - } - SelectionConnectedButton { - property int value: 1 - buttonText: "Yes" - toggled: (aiPolicyBtnGroup.selectedPolicy === value) - onClicked: { - ConfigLoader.setConfigValueAndSave("policies.ai", value); - } - } - SelectionConnectedButton { - property int value: 2 - rightmost: true - buttonText: "Local only" - toggled: (aiPolicyBtnGroup.selectedPolicy === value) - onClicked: { - ConfigLoader.setConfigValueAndSave("policies.ai", value); - } - } - } - } - } - } - - Section { - title: "Info" - - Flow { - Layout.fillWidth: true - spacing: 10 - - ButtonWithIcon { - iconText: "keyboard_alt" - onClicked: { - Hyprland.dispatch("global quickshell:cheatsheetOpen") - } - mainContentComponent: Component { - RowLayout { - spacing: 10 - StyledText { - font.pixelSize: Appearance.font.pixelSize.small - text: "Keybinds" - color: Appearance.colors.colOnSecondaryContainer - } - RowLayout { - spacing: 3 - KeyboardKey { - key: "󰖳" - } - StyledText { - Layout.alignment: Qt.AlignVCenter - text: "+" - } - KeyboardKey { - key: "/" - } - } - } - } - } - - ButtonWithIcon { - iconText: "help" - mainText: "Usage" - onClicked: { - Qt.openUrlExternally("https://end-4.github.io/dots-hyprland-wiki/en/ii-qs/02usage/") - } - } - ButtonWithIcon { - iconText: "construction" - mainText: "Configuration" - onClicked: { - Qt.openUrlExternally("https://end-4.github.io/dots-hyprland-wiki/en/ii-qs/03config/") - } - } - } - } - - Section { - title: "Useless buttons" - - Flow { - Layout.fillWidth: true - spacing: 10 - - ButtonWithIcon { - nerdIcon: "󰊤" - mainText: "GitHub" - onClicked: { - Qt.openUrlExternally("https://github.com/end-4/dots-hyprland") - } - } - ButtonWithIcon { - iconText: "favorite" - mainText: "Funny number" - onClicked: { - Qt.openUrlExternally("https://github.com/sponsors/end-4") - } - } - } - } - - Item { + ButtonGroup { Layout.fillWidth: true - Layout.fillHeight: true + LightDarkPreferenceButton { + dark: false + } + LightDarkPreferenceButton { + dark: true + } } + RowLayout { + Layout.alignment: Qt.AlignHCenter + RippleButtonWithIcon { + id: rndWallBtn + Layout.alignment: Qt.AlignHCenter + buttonRadius: Appearance.rounding.small + materialIcon: "wallpaper" + mainText: konachanWallProc.running ? "Be patient..." : "Random: Konachan" + onClicked: { + console.log(konachanWallProc.command.join(" ")) + konachanWallProc.running = true; + } + StyledToolTip { + content: "Random SFW Anime wallpaper from Konachan\nImage is saved to ~/Pictures/Wallpapers" + } + } + RippleButtonWithIcon { + materialIcon: "wallpaper" + StyledToolTip { + content: "Pick wallpaper image on your system" + } + onClicked: { + Quickshell.execDetached([`${Directories.wallpaperSwitchScriptPath}`]) + } + mainContentComponent: Component { + RowLayout { + spacing: 10 + StyledText { + font.pixelSize: Appearance.font.pixelSize.small + text: "Choose file" + color: Appearance.colors.colOnSecondaryContainer + } + RowLayout { + spacing: 3 + KeyboardKey { + key: "Ctrl" + } + KeyboardKey { + key: "󰖳" + } + StyledText { + Layout.alignment: Qt.AlignVCenter + text: "+" + } + KeyboardKey { + key: "T" + } + } + } + } + } + } + + StyledText { + Layout.alignment: Qt.AlignHCenter + text: "Change any time later with /dark, /light, /img in the launcher" + font.pixelSize: Appearance.font.pixelSize.smaller + color: Appearance.colors.colSubtext + } + } + + ContentSection { + title: "Policies" + + ConfigRow { + ColumnLayout { // Weeb policy + StyledText { + text: "Weeb" + color: Appearance.colors.colSubtext + } + ConfigSelectionArray { + currentValue: ConfigOptions.policies.weeb + configOptionName: "policies.weeb" + onSelected: (newValue) => { + ConfigLoader.setConfigValueAndSave("policies.weeb", newValue); + } + options: [ + { displayName: "No", value: 0 }, + { displayName: "Yes", value: 1 }, + { displayName: "Closet", value: 2 } + ] + } + } + + ColumnLayout { // AI policy + StyledText { + text: "AI" + color: Appearance.colors.colSubtext + } + ConfigSelectionArray { + currentValue: ConfigOptions.policies.ai + configOptionName: "policies.ai" + onSelected: (newValue) => { + ConfigLoader.setConfigValueAndSave("policies.ai", newValue); + } + options: [ + { displayName: "No", value: 0 }, + { displayName: "Yes", value: 1 }, + { displayName: "Local only", value: 2 } + ] + } + } + } + } + + ContentSection { + title: "Info" + + Flow { + Layout.fillWidth: true + spacing: 5 + + RippleButtonWithIcon { + materialIcon: "keyboard_alt" + onClicked: { + Hyprland.dispatch("global quickshell:cheatsheetOpen") + } + mainContentComponent: Component { + RowLayout { + spacing: 10 + StyledText { + font.pixelSize: Appearance.font.pixelSize.small + text: "Keybinds" + color: Appearance.colors.colOnSecondaryContainer + } + RowLayout { + spacing: 3 + KeyboardKey { + key: "󰖳" + } + StyledText { + Layout.alignment: Qt.AlignVCenter + text: "+" + } + KeyboardKey { + key: "/" + } + } + } + } + } + + RippleButtonWithIcon { + materialIcon: "help" + mainText: "Usage" + onClicked: { + Qt.openUrlExternally("https://end-4.github.io/dots-hyprland-wiki/en/ii-qs/02usage/") + } + } + RippleButtonWithIcon { + materialIcon: "construction" + mainText: "Configuration" + onClicked: { + Qt.openUrlExternally("https://end-4.github.io/dots-hyprland-wiki/en/ii-qs/03config/") + } + } + } + } + + ContentSection { + title: "Useless buttons" + + Flow { + Layout.fillWidth: true + spacing: 5 + + RippleButtonWithIcon { + nerdIcon: "󰊤" + mainText: "GitHub" + onClicked: { + Qt.openUrlExternally("https://github.com/end-4/dots-hyprland") + } + } + RippleButtonWithIcon { + materialIcon: "favorite" + mainText: "Funny number" + onClicked: { + Qt.openUrlExternally("https://github.com/sponsors/end-4") + } + } + } + } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true } } } diff --git a/.config/starship.toml b/.config/starship.toml index 5ed04489e..26aed07fa 100644 --- a/.config/starship.toml +++ b/.config/starship.toml @@ -4,6 +4,7 @@ add_newline = false # Powerline symbols                                    # Wedges 🭧🭒 🭣🭧🭓 # Random noise 🬖🬥🬔🬗 +# Cool stuff 󰜥    # format = """ # $cmd_duration$username$hostname $directory $git_branch @@ -16,8 +17,8 @@ $character # Replace the "❯" symbol in the prompt with "➜" [character] # The name of the module we are configuring is "character" -success_symbol = "[ 󰜥 ](bold fg:blue)" -error_symbol = "[ 󰜥 ](bold fg:red)" +success_symbol = "[  ](bold fg:blue)" +error_symbol = "[  ](bold fg:red)" # Disable the package module, hiding it from the prompt completely [package] @@ -83,7 +84,7 @@ read_only = "  " style = "bg:green fg:black" truncation_length = 6 truncation_symbol = " ••/" -format = '[](bold fg:green)[󰉋 $path]($style)[](bold fg:green)' +format = '[](bold fg:green)[󰉋 $path]($style)[](bold fg:green)' [directory.substitutions] diff --git a/.config/xdg-desktop-portal/hyprland-portals.conf b/.config/xdg-desktop-portal/hyprland-portals.conf new file mode 100644 index 000000000..62f3d5682 --- /dev/null +++ b/.config/xdg-desktop-portal/hyprland-portals.conf @@ -0,0 +1,3 @@ +[preferred] +default = hyprland;gtk +org.freedesktop.impl.portal.FileChooser = kde diff --git a/arch-packages/illogical-impulse-portal/PKGBUILD b/arch-packages/illogical-impulse-portal/PKGBUILD index 59bec311b..dbc96e2e2 100644 --- a/arch-packages/illogical-impulse-portal/PKGBUILD +++ b/arch-packages/illogical-impulse-portal/PKGBUILD @@ -6,7 +6,7 @@ arch=(any) license=(None) depends=( xdg-desktop-portal - xdg-desktop-portal-gtk + xdg-desktop-portal-kde xdg-desktop-portal-hyprland ) diff --git a/arch-packages/illogical-impulse-screencapture/PKGBUILD b/arch-packages/illogical-impulse-screencapture/PKGBUILD index 8feb1c0d0..6320c0c38 100644 --- a/arch-packages/illogical-impulse-screencapture/PKGBUILD +++ b/arch-packages/illogical-impulse-screencapture/PKGBUILD @@ -5,11 +5,10 @@ pkgdesc='Illogical Impulse Screenshot and Recording Dependencies' arch=(any) license=(None) depends=( - swappy - wf-recorder hyprshot + ksnip + slurp tesseract tesseract-data-eng - slurp + wf-recorder ) - diff --git a/arch-packages/illogical-impulse-widgets/PKGBUILD b/arch-packages/illogical-impulse-widgets/PKGBUILD index 868bea9e6..ae26fc90e 100644 --- a/arch-packages/illogical-impulse-widgets/PKGBUILD +++ b/arch-packages/illogical-impulse-widgets/PKGBUILD @@ -12,7 +12,7 @@ depends=( hyprlock hyprpicker nm-connection-editor - quickshell + quickshell-git swww translate-shell wlogout