Merge branch 'end-4:main' into parallax

This commit is contained in:
Ivan Rosinskii
2025-12-10 19:03:44 +01:00
committed by GitHub
76 changed files with 2987 additions and 714 deletions
@@ -0,0 +1,4 @@
<?xml version="1.0" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#000000">
<path d="M13.7045 4.28377C13.3111 3.89615 12.678 3.90084 12.2904 4.29424C11.9027 4.68765 11.9074 5.3208 12.3008 5.70842L17.6712 10.9998H4C3.44771 10.9998 3 11.4475 3 11.9998C3 12.5521 3.44772 12.9998 4 12.9998H17.6646L12.3008 18.2847C11.9074 18.6723 11.9027 19.3055 12.2904 19.6989C12.678 20.0923 13.3111 20.097 13.7045 19.7094L20.6287 12.887C21.1256 12.3974 21.1256 11.5958 20.6287 11.1062L13.7045 4.28377Z" fill="#000000"/>
</svg>

After

Width:  |  Height:  |  Size: 570 B

@@ -0,0 +1,4 @@
<?xml version="1.0" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#000000">
<path d="M13.2673 4.20889C12.9674 3.9232 12.4926 3.93475 12.2069 4.23467C11.9212 4.5346 11.9328 5.00933 12.2327 5.29502L18.4841 11.2496H3.75C3.33579 11.2496 3 11.5854 3 11.9996C3 12.4138 3.33579 12.7496 3.75 12.7496H18.4842L12.2327 18.7043C11.9328 18.99 11.9212 19.4648 12.2069 19.7647C12.4926 20.0646 12.9674 20.0762 13.2673 19.7905L20.6862 12.7238C20.8551 12.5629 20.9551 12.3576 20.9861 12.1443C20.9952 12.0975 21 12.0491 21 11.9996C21 11.9501 20.9952 11.9016 20.986 11.8547C20.955 11.6415 20.855 11.4364 20.6862 11.2756L13.2673 4.20889Z" fill="#000000"/>
</svg>

After

Width:  |  Height:  |  Size: 703 B

@@ -0,0 +1,4 @@
<?xml version="1.0" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#000000">
<path d="M13 3C13.5523 3 14 3.44772 14 4C14 4.55228 13.5523 5 13 5H6.41435L20.7071 19.2928C21.0976 19.6833 21.0976 20.3164 20.7071 20.707C20.3166 21.0975 19.6834 21.0975 19.2929 20.707L5 6.41408V13C5 13.5523 4.55228 14 4 14C3.44772 14 3 13.5523 3 13V4C3 3.44772 3.44772 3 4 3H13Z" fill="#000000"/>
</svg>

After

Width:  |  Height:  |  Size: 442 B

@@ -0,0 +1,4 @@
<?xml version="1.0" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#000000">
<path d="M13.2461 3C13.6603 3 13.9961 3.33579 13.9961 3.75C13.9961 4.16421 13.6603 4.5 13.2461 4.5H5.57699L20.7768 19.6998C21.0753 19.9983 21.0753 20.4824 20.7768 20.781C20.4782 21.0796 19.9941 21.0796 19.6955 20.781L4.49609 5.58158V13.25C4.49609 13.6642 4.16031 14 3.74609 14C3.33188 14 2.99609 13.6642 2.99609 13.25V3.75C2.99609 3.33579 3.33188 3 3.74609 3H13.2461Z" fill="#000000"/>
</svg>

After

Width:  |  Height:  |  Size: 530 B

@@ -0,0 +1,4 @@
<?xml version="1.0" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#000000">
<path d="M11.9999 9.00462C14.209 9.00462 15.9999 10.7955 15.9999 13.0046C15.9999 15.2138 14.209 17.0046 11.9999 17.0046C9.79073 17.0046 7.99987 15.2138 7.99987 13.0046C7.99987 10.7955 9.79073 9.00462 11.9999 9.00462ZM11.9999 5.5C16.6134 5.5 20.596 8.65001 21.701 13.0644C21.8016 13.4662 21.5574 13.8735 21.1556 13.9741C20.7537 14.0746 20.3465 13.8305 20.2459 13.4286C19.307 9.67796 15.9212 7 11.9999 7C8.07681 7 4.68997 9.68026 3.75273 13.4332C3.65237 13.835 3.24523 14.0794 2.84336 13.9791C2.44149 13.8787 2.19707 13.4716 2.29743 13.0697C3.40052 8.65272 7.38436 5.5 11.9999 5.5Z" fill="#000000"/>
</svg>

After

Width:  |  Height:  |  Size: 742 B

@@ -0,0 +1,4 @@
<?xml version="1.0" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#000000">
<path d="M2.21967 2.21967C1.9534 2.48594 1.9292 2.9026 2.14705 3.19621L2.21967 3.28033L6.25424 7.3149C4.33225 8.66437 2.89577 10.6799 2.29888 13.0644C2.1983 13.4662 2.4425 13.8735 2.84431 13.9741C3.24613 14.0746 3.6534 13.8305 3.75399 13.4286C4.28346 11.3135 5.59112 9.53947 7.33416 8.39452L9.14379 10.2043C8.43628 10.9258 8 11.9143 8 13.0046C8 15.2138 9.79086 17.0046 12 17.0046C13.0904 17.0046 14.0788 16.5683 14.8004 15.8608L20.7197 21.7803C21.0126 22.0732 21.4874 22.0732 21.7803 21.7803C22.0466 21.5141 22.0708 21.0974 21.8529 20.8038L21.7803 20.7197L15.6668 14.6055L15.668 14.604L8.71877 7.65782L8.72 7.656L7.58672 6.52549L3.28033 2.21967C2.98744 1.92678 2.51256 1.92678 2.21967 2.21967ZM12 5.5C10.9997 5.5 10.0291 5.64807 9.11109 5.925L10.3481 7.16119C10.8839 7.05532 11.4364 7 12 7C15.9231 7 19.3099 9.68026 20.2471 13.4332C20.3475 13.835 20.7546 14.0794 21.1565 13.9791C21.5584 13.8787 21.8028 13.4716 21.7024 13.0697C20.5994 8.65272 16.6155 5.5 12 5.5ZM12.1947 9.00928L15.996 12.81C15.8942 10.7531 14.2472 9.10764 12.1947 9.00928Z" fill="#000000"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1,4 @@
<?xml version="1.0" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#000000">
<path d="M2.21967 2.21967C1.9534 2.48594 1.9292 2.9026 2.14705 3.19621L2.21967 3.28033L6.25424 7.3149C4.33225 8.66437 2.89577 10.6799 2.29888 13.0644C2.1983 13.4662 2.4425 13.8735 2.84431 13.9741C3.24613 14.0746 3.6534 13.8305 3.75399 13.4286C4.28346 11.3135 5.59112 9.53947 7.33416 8.39452L9.14379 10.2043C8.43628 10.9258 8 11.9143 8 13.0046C8 15.2138 9.79086 17.0046 12 17.0046C13.0904 17.0046 14.0788 16.5683 14.8004 15.8608L20.7197 21.7803C21.0126 22.0732 21.4874 22.0732 21.7803 21.7803C22.0466 21.5141 22.0708 21.0974 21.8529 20.8038L21.7803 20.7197L15.6668 14.6055L15.668 14.604L14.4679 13.4061L11.598 10.5368L11.6 10.536L8.71877 7.65782L8.72 7.656L7.58672 6.52549L3.28033 2.21967C2.98744 1.92678 2.51256 1.92678 2.21967 2.21967ZM10.2041 11.2655L13.7392 14.8006C13.2892 15.2364 12.6759 15.5046 12 15.5046C10.6193 15.5046 9.5 14.3853 9.5 13.0046C9.5 12.3287 9.76824 11.7154 10.2041 11.2655ZM12 5.5C10.9997 5.5 10.0291 5.64807 9.11109 5.925L10.3481 7.16119C10.8839 7.05532 11.4364 7 12 7C15.9231 7 19.3099 9.68026 20.2471 13.4332C20.3475 13.835 20.7546 14.0794 21.1565 13.9791C21.5584 13.8787 21.8028 13.4716 21.7024 13.0697C20.5994 8.65272 16.6155 5.5 12 5.5ZM12.1947 9.00928L15.996 12.81C15.8942 10.7531 14.2472 9.10764 12.1947 9.00928Z" fill="#000000"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -0,0 +1,4 @@
<?xml version="1.0" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#000000">
<path d="M11.9999 9.00462C14.209 9.00462 15.9999 10.7955 15.9999 13.0046C15.9999 15.2138 14.209 17.0046 11.9999 17.0046C9.79073 17.0046 7.99987 15.2138 7.99987 13.0046C7.99987 10.7955 9.79073 9.00462 11.9999 9.00462ZM11.9999 10.5046C10.6192 10.5046 9.49987 11.6239 9.49987 13.0046C9.49987 14.3853 10.6192 15.5046 11.9999 15.5046C13.3806 15.5046 14.4999 14.3853 14.4999 13.0046C14.4999 11.6239 13.3806 10.5046 11.9999 10.5046ZM11.9999 5.5C16.6134 5.5 20.596 8.65001 21.701 13.0644C21.8016 13.4662 21.5574 13.8735 21.1556 13.9741C20.7537 14.0746 20.3465 13.8305 20.2459 13.4286C19.307 9.67796 15.9212 7 11.9999 7C8.07681 7 4.68997 9.68026 3.75273 13.4332C3.65237 13.835 3.24523 14.0794 2.84336 13.9791C2.44149 13.8787 2.19707 13.4716 2.29743 13.0697C3.40052 8.65272 7.38436 5.5 11.9999 5.5Z" fill="#000000"/>
</svg>

After

Width:  |  Height:  |  Size: 951 B

@@ -0,0 +1,4 @@
<?xml version="1.0" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#000000">
<path d="M3 5.75C3 5.33579 3.33579 5 3.75 5C6.41341 5 9.00797 4.05652 11.55 2.15C11.8167 1.95 12.1833 1.95 12.45 2.15C14.992 4.05652 17.5866 5 20.25 5C20.6642 5 21 5.33579 21 5.75V11C21 16.0012 18.0424 19.6757 12.2749 21.9478C12.0982 22.0174 11.9018 22.0174 11.7251 21.9478C5.95756 19.6757 3 16.0012 3 11V5.75Z" fill="#000000"/>
</svg>

After

Width:  |  Height:  |  Size: 473 B

@@ -0,0 +1,4 @@
<?xml version="1.0" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#000000">
<path d="M3 5.75C3 5.33579 3.33579 5 3.75 5C6.41341 5 9.00797 4.05652 11.55 2.15C11.8167 1.95 12.1833 1.95 12.45 2.15C14.992 4.05652 17.5866 5 20.25 5C20.6642 5 21 5.33579 21 5.75V11C21 11.1813 20.9961 11.3609 20.9883 11.5387C20.3539 10.8973 19.4734 10.5 18.5 10.5C16.567 10.5 15 12.067 15 14V14.05C13.8589 14.2816 13 15.2905 13 16.5V21.5C13 21.5492 13.0014 21.598 13.0042 21.6465C12.7663 21.7496 12.5232 21.85 12.2749 21.9478C12.0982 22.0174 11.9018 22.0174 11.7251 21.9478C5.95756 19.6757 3 16.0012 3 11V5.75ZM16 15V14C16 12.6193 17.1193 11.5 18.5 11.5C19.8807 11.5 21 12.6193 21 14V15H21.5C22.3284 15 23 15.6716 23 16.5V21.5C23 22.3284 22.3284 23 21.5 23H15.5C14.6716 23 14 22.3284 14 21.5V16.5C14 15.6716 14.6716 15 15.5 15H16ZM17.5 14V15H19.5V14C19.5 13.4477 19.0523 13 18.5 13C17.9477 13 17.5 13.4477 17.5 14ZM19.5 19C19.5 18.4477 19.0523 18 18.5 18C17.9477 18 17.5 18.4477 17.5 19C17.5 19.5523 17.9477 20 18.5 20C19.0523 20 19.5 19.5523 19.5 19Z" fill="#000000"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,4 @@
<?xml version="1.0" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#000000">
<path d="M3 5.75C3 5.33579 3.33579 5 3.75 5C6.41341 5 9.00797 4.05652 11.55 2.15C11.8167 1.95 12.1833 1.95 12.45 2.15C14.992 4.05652 17.5866 5 20.25 5C20.6642 5 21 5.33579 21 5.75V11C21 11.1813 20.9961 11.3609 20.9883 11.5387C20.5804 11.1263 20.0707 10.8148 19.5 10.6449V6.47793C16.9227 6.32585 14.4192 5.38829 12 3.67782C9.58084 5.38829 7.07735 6.32585 4.5 6.47793V11C4.5 15.2556 6.95337 18.3789 12 20.4419C12.3455 20.3007 12.6788 20.1545 13 20.0033V21.5C13 21.5492 13.0014 21.598 13.0042 21.6465C12.7663 21.7496 12.5232 21.85 12.2749 21.9478C12.0982 22.0174 11.9018 22.0174 11.7251 21.9478C5.95756 19.6757 3 16.0012 3 11V5.75ZM16 15V14C16 12.6193 17.1193 11.5 18.5 11.5C19.8807 11.5 21 12.6193 21 14V15H21.5C22.3284 15 23 15.6716 23 16.5V21.5C23 22.3284 22.3284 23 21.5 23H15.5C14.6716 23 14 22.3284 14 21.5V16.5C14 15.6716 14.6716 15 15.5 15H16ZM17.5 14V15H19.5V14C19.5 13.4477 19.0523 13 18.5 13C17.9477 13 17.5 13.4477 17.5 14ZM19.5 19C19.5 18.4477 19.0523 18 18.5 18C17.9477 18 17.5 18.4477 17.5 19C17.5 19.5523 17.9477 20 18.5 20C19.0523 20 19.5 19.5523 19.5 19Z" fill="#000000"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1,4 @@
<?xml version="1.0" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#000000">
<path d="M3 5.75C3 5.33579 3.33579 5 3.75 5C6.41341 5 9.00797 4.05652 11.55 2.15C11.8167 1.95 12.1833 1.95 12.45 2.15C14.992 4.05652 17.5866 5 20.25 5C20.6642 5 21 5.33579 21 5.75V11C21 16.0012 18.0424 19.6757 12.2749 21.9478C12.0982 22.0174 11.9018 22.0174 11.7251 21.9478C5.95756 19.6757 3 16.0012 3 11V5.75ZM4.5 6.47793V11C4.5 15.2556 6.95337 18.3789 12 20.4419C17.0466 18.3789 19.5 15.2556 19.5 11V6.47793C16.9227 6.32585 14.4192 5.38829 12 3.67782C9.58084 5.38829 7.07735 6.32585 4.5 6.47793Z" fill="#000000"/>
</svg>

After

Width:  |  Height:  |  Size: 660 B

@@ -0,0 +1,4 @@
<?xml version="1.0" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#000000">
<path d="M3 6.25C3 4.45507 4.45507 3 6.25 3H17.75C19.5449 3 21 4.45507 21 6.25V11.9759C20.433 11.7979 19.9204 11.4957 19.5 11.1854V8.5H4.5V17.75C4.5 18.7165 5.2835 19.5 6.25 19.5H12.7324C13.0228 20.04 13.3977 20.5424 13.854 21H6.25C4.45507 21 3 19.5449 3 17.75V6.25ZM17.9896 11.1945C18.6423 11.8454 19.8965 12.8805 21.439 13.0928C21.7466 13.1352 22 13.3779 22 13.6817V16.5203C22 20.3363 18.4206 21.7212 17.6467 21.9761C17.5498 22.008 17.4509 22.008 17.354 21.9761C16.5801 21.7213 13.0003 20.3363 13.0003 16.5203L13 13.6818C13 13.378 13.2534 13.1352 13.561 13.0928C15.1032 12.8804 16.3575 11.8454 17.0103 11.1945C17.2704 10.9351 17.7295 10.9352 17.9896 11.1945Z" fill="#000000"/>
</svg>

After

Width:  |  Height:  |  Size: 823 B

@@ -0,0 +1,4 @@
<?xml version="1.0" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#000000">
<path d="M6.25 3C4.45507 3 3 4.45507 3 6.25V17.75C3 19.5449 4.45507 21 6.25 21H13.854C13.3977 20.5424 13.0228 20.04 12.7324 19.5H6.25C5.2835 19.5 4.5 18.7165 4.5 17.75V8.5H19.5V11.1854C19.9204 11.4957 20.433 11.7979 21 11.9759V6.25C21 4.45507 19.5449 3 17.75 3H6.25ZM19.5 7H4.5V6.25C4.5 5.2835 5.2835 4.5 6.25 4.5H17.75C18.7165 4.5 19.5 5.2835 19.5 6.25V7ZM17.9896 11.1945C18.6423 11.8454 19.8965 12.8805 21.439 13.0928C21.7466 13.1352 22 13.3779 22 13.6817V16.5203C22 20.3363 18.4206 21.7212 17.6467 21.9761C17.5498 22.008 17.4509 22.008 17.354 21.9761C16.5801 21.7213 13.0003 20.3363 13.0003 16.5203L13 13.6818C13 13.378 13.2534 13.1352 13.561 13.0928C15.1032 12.8804 16.3575 11.8454 17.0103 11.1945C17.2704 10.9351 17.7295 10.9352 17.9896 11.1945Z" fill="#000000"/>
</svg>

After

Width:  |  Height:  |  Size: 913 B

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