mirror of
https://github.com/zoriya/noctalia-shell.git
synced 2025-12-06 06:36:15 +00:00
Compare commits
95 Commits
v3.2.0
...
shell-stat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
74ba883dd8 | ||
|
|
01a26fd910 | ||
|
|
0293b8c8dd | ||
|
|
3914c32c96 | ||
|
|
4652691c4c | ||
|
|
679fd5c40e | ||
|
|
48c5435cef | ||
|
|
880ae9c7b9 | ||
|
|
0f650b36f7 | ||
|
|
823042b245 | ||
|
|
9c550af64e | ||
|
|
1bf54de99c | ||
|
|
7a68030f69 | ||
|
|
f46915d2c3 | ||
|
|
50ebc77513 | ||
|
|
522e7e4352 | ||
|
|
9f9e1341fd | ||
|
|
c919c54a32 | ||
|
|
6387dcc6d4 | ||
|
|
455014a39b | ||
|
|
a884f012d8 | ||
|
|
c5b23cc291 | ||
|
|
04e46815f8 | ||
|
|
f3d1e1f3d1 | ||
|
|
e2aa4ca2f8 | ||
|
|
d6edc55d16 | ||
|
|
e5912760ca | ||
|
|
7d981fb55b | ||
|
|
e97c46e96c | ||
|
|
c1afa199e3 | ||
|
|
530992a14b | ||
|
|
5d9cfeb9d0 | ||
|
|
8cb4711629 | ||
|
|
2d856882d2 | ||
|
|
f181bdf21c | ||
|
|
665aa84f70 | ||
|
|
b84452e04d | ||
|
|
d3c200f50c | ||
|
|
a39fbb5639 | ||
|
|
fe40758d4e | ||
|
|
63331c1018 | ||
|
|
9c955cdd39 | ||
|
|
d9e0f2fc10 | ||
|
|
1cbc793087 | ||
|
|
1a2ddbb9e3 | ||
|
|
e46c9cdf0e | ||
|
|
43cdc4494d | ||
|
|
5ed4c97ee5 | ||
|
|
ddd3ae364c | ||
|
|
3b793add39 | ||
|
|
71f4a8eb49 | ||
|
|
2f735eda81 | ||
|
|
ee33da8348 | ||
|
|
f7d7d7ac15 | ||
|
|
972ac47c1b | ||
|
|
0b0860a446 | ||
|
|
e8a27acb63 | ||
|
|
694fefeebd | ||
|
|
088431b20d | ||
|
|
63940703f8 | ||
|
|
e3c171840f | ||
|
|
857d1dbbb6 | ||
|
|
516fc47b68 | ||
|
|
e549cfcb78 | ||
|
|
6a840769ed | ||
|
|
ec92295a98 | ||
|
|
60d37576e0 | ||
|
|
067bbf20bc | ||
|
|
49aab3c487 | ||
|
|
868b14bbc3 | ||
|
|
b435d1f588 | ||
|
|
1fc1fa36aa | ||
|
|
04311f191f | ||
|
|
0726e6b92f | ||
|
|
9a3d04249f | ||
|
|
edd4ba1b15 | ||
|
|
e4e3b1b85c | ||
|
|
4a0c2b7ef3 | ||
|
|
5e2f8c1462 | ||
|
|
97ba831cb4 | ||
|
|
5ade827a4c | ||
|
|
03554120be | ||
|
|
2917f02621 | ||
|
|
a18be7927c | ||
|
|
9bf8fd16d6 | ||
|
|
948c3c7e18 | ||
|
|
b2978113c5 | ||
|
|
87f62b288b | ||
|
|
ed373df99d | ||
|
|
529869f796 | ||
|
|
12766e411d | ||
|
|
79f79e0cff | ||
|
|
ca89a0dc35 | ||
|
|
6eaffb0e65 | ||
|
|
455ef3449e |
@@ -8,7 +8,7 @@
|
||||
"mOnTertiary": "#e0def4",
|
||||
"mError": "#eb6f92",
|
||||
"mOnError": "#191724",
|
||||
"mSurface": "#1f1d2e",
|
||||
"mSurface": "#191724",
|
||||
"mOnSurface": "#e0def4",
|
||||
"mSurfaceVariant": "#26233a",
|
||||
"mOnSurfaceVariant": "#908caa",
|
||||
|
||||
139
Assets/MatugenTemplates/telegram.tdesktop-theme
Normal file
139
Assets/MatugenTemplates/telegram.tdesktop-theme
Normal file
@@ -0,0 +1,139 @@
|
||||
// Material You theme for Telegram Desktop
|
||||
// Generated by matugen
|
||||
|
||||
COLOR_GRAY: {{colors.outline.default.hex}};
|
||||
COLOR_DARK: {{colors.surface_variant.default.hex}};
|
||||
|
||||
windowBg: {{colors.background.default.hex}}; // Main background
|
||||
windowFg: {{colors.on_background.default.hex}}; // Main text
|
||||
windowBgOver: {{colors.surface_variant.default.hex}}; // Generic background on hover
|
||||
windowBgRipple: {{colors.surface_variant.default.hex}}; // Ripple effect
|
||||
windowFgOver: {{colors.on_surface_variant.default.hex}}; // Text on hover
|
||||
windowSubTextFg: {{colors.outline.default.hex}}; // Minor text
|
||||
windowSubTextFgOver: {{colors.outline.default.hex}}; // Minor text on hover
|
||||
windowBoldFg: {{colors.on_background.default.hex}}; // Bold text
|
||||
windowBoldFgOver: {{colors.on_surface_variant.default.hex}}; // Bold text on hover
|
||||
windowBgActive: {{colors.primary.default.hex}}; // Active items background
|
||||
windowFgActive: {{colors.on_primary.default.hex}}; // Active items text
|
||||
windowActiveTextFg: {{colors.primary.default.hex}}; // Active items text
|
||||
windowShadowFg: {{colors.shadow.default.hex}}; // Window shadow
|
||||
windowShadowFgFallback: {{colors.shadow.default.hex}}; // Fallback for shadow
|
||||
historyOutIconFg: {{colors.primary.default.hex}};
|
||||
historyIconFgInverted: {{colors.on_surface.default.hex}};
|
||||
|
||||
msgServiceBg: {{colors.primary_container.default.hex}};
|
||||
msgServiceFg: {{colors.on_surface.default.hex}};
|
||||
msgOutBg: {{colors.primary_container.default.hex}};
|
||||
msgOutBgSelected : {{colors.tertiary_container.default.hex}};
|
||||
msgOutServiceFg: {{colors.on_surface.default.hex}};
|
||||
msgOutDateFg: {{colors.on_surface.default.hex}};
|
||||
historySentIconFg: {{colors.on_surface.default.hex}};
|
||||
msgOutDateFgSelected: {{colors.on_surface.default.hex}};
|
||||
msgDateImgFg: {{colors.on_surface.default.hex}};
|
||||
dialogsSentIconFg: {{colors.primary.default.hex}};
|
||||
dialogsSentIconFgOver: {{colors.primary.default.hex}};
|
||||
dialogsOnlineBadgeFg: {{colors.primary.default.hex}};
|
||||
|
||||
|
||||
shadowFg: {{colors.shadow.default.hex}}; // General shadow
|
||||
slideFadeOutBg: {{colors.background.default.hex}};
|
||||
slideFadeOutShadowFg: {{colors.shadow.default.hex}};
|
||||
|
||||
imageBg: {{colors.surface.default.hex}};
|
||||
imageBgTransparent: {{colors.surface.default.hex}};
|
||||
|
||||
activeButtonBg: {{colors.primary.default.hex}}; // Active button background
|
||||
activeButtonBgOver: {{colors.primary_container.default.hex}}; // Active button hover background
|
||||
activeButtonBgRipple: {{colors.on_primary_container.default.hex}}; // Active button ripple
|
||||
activeButtonFg: {{colors.on_primary.default.hex}}; // Active button text
|
||||
activeButtonFgOver: {{colors.on_primary_container.default.hex}}; // Active button hover text
|
||||
activeButtonSecondaryFg: {{colors.on_primary.default.hex}}; // Active button secondary text
|
||||
activeButtonSecondaryFgOver: {{colors.on_primary_container.default.hex}}; // Active button secondary hover text
|
||||
activeLineFg: {{colors.on_surface.default.hex}};
|
||||
dialogsBgActive: {{colors.primary.default.hex}};
|
||||
|
||||
lightButtonBg: {{colors.surface.default.hex}}; // Light button background
|
||||
lightButtonBgOver: {{colors.surface_variant.default.hex}}; // Light button hover background
|
||||
lightButtonBgRipple: {{colors.primary.default.hex}}; // Light button ripple
|
||||
lightButtonFg: {{colors.on_surface.default.hex}}; // Light button text
|
||||
lightButtonFgOver: {{colors.on_surface_variant.default.hex}}; // Light button hover text
|
||||
|
||||
attentionButtonFg: {{colors.error.default.hex}};
|
||||
attentionButtonFgOver: {{colors.error.default.hex}};
|
||||
attentionButtonBgOver: {{colors.error_container.default.hex}};
|
||||
attentionButtonBgRipple: {{colors.on_error_container.default.hex}};
|
||||
|
||||
outlineButtonBg: {{colors.surface.default.hex}}; // Outline button background
|
||||
outlineButtonBgOver: {{colors.surface_variant.default.hex}}; // Outline button hover background
|
||||
outlineButtonOutlineFg: {{colors.primary.default.hex}}; // Outline button color
|
||||
outlineButtonBgRipple: {{colors.primary.default.hex}}; // Outline button ripple
|
||||
|
||||
menuBg: {{colors.surface.default.hex}};
|
||||
menuBgOver: {{colors.surface_variant.default.hex}};
|
||||
menuBgRipple: {{colors.primary.default.hex}};
|
||||
menuIconFg: {{colors.on_surface.default.hex}};
|
||||
menuIconFgOver: {{colors.on_surface_variant.default.hex}};
|
||||
menuSubmenuArrowFg: {{colors.outline.default.hex}};
|
||||
menuFgDisabled: {{colors.outline.default.hex}};
|
||||
menuSeparatorFg: {{colors.outline.default.hex}};
|
||||
|
||||
scrollBarBg: {{colors.primary.default.hex}}40; // Scroll bar background (40% opacity)
|
||||
scrollBarBgOver: {{colors.primary.default.hex}}60; // Scroll bar hover background (60% opacity)
|
||||
scrollBg: {{colors.surface_variant.default.hex}}40; // Scroll bar track (40% opacity)
|
||||
scrollBgOver: {{colors.surface_variant.default.hex}}60; // Scroll bar track on hover (60% opacity)
|
||||
|
||||
smallCloseIconFg: {{colors.outline.default.hex}};
|
||||
smallCloseIconFgOver: {{colors.on_surface_variant.default.hex}};
|
||||
|
||||
radialFg: {{colors.primary.default.hex}};
|
||||
radialBg: {{colors.surface.default.hex}};
|
||||
|
||||
placeholderFg: {{colors.outline.default.hex}}; // Placeholder text
|
||||
placeholderFgActive: {{colors.primary.default.hex}}; // Active placeholder text
|
||||
inputBorderFg: {{colors.outline.default.hex}}; // Input border
|
||||
filterInputBorderFg: {{colors.outline.default.hex}}; // Search input border
|
||||
filterInputInactiveBg: {{colors.surface.default.hex}}; // Inactive search input background
|
||||
checkboxFg: {{colors.primary.default.hex}}; // Checkbox color
|
||||
|
||||
titleBg: {{colors.surface.default.hex}}; // Window title background
|
||||
titleShadow: {{colors.shadow.default.hex}};
|
||||
titleButtonFg: {{colors.on_surface.default.hex}}; // Title button color
|
||||
titleButtonBgOver: {{colors.surface_variant.default.hex}}; // Title button hover background
|
||||
titleButtonFgOver: {{colors.on_surface_variant.default.hex}}; // Title button hover color
|
||||
titleButtonCloseBgOver: {{colors.error.default.hex}};
|
||||
titleButtonCloseFgOver: {{colors.on_error.default.hex}};
|
||||
titleFgActive: {{colors.on_surface.default.hex}}; // Active title text
|
||||
titleFg: {{colors.on_surface.default.hex}}; // Inactive title text
|
||||
|
||||
trayCounterBg: {{colors.error.default.hex}}; // Tray counter background
|
||||
trayCounterBgMute: {{colors.outline.default.hex}}; // Muted tray counter background
|
||||
trayCounterFg: {{colors.on_error.default.hex}}; // Tray counter text
|
||||
trayCounterBgMacInvert: {{colors.error.default.hex}}; // Mac tray counter
|
||||
trayCounterFgMacInvert: {{colors.on_error.default.hex}}; // Mac tray counter text
|
||||
|
||||
layerBg: {{colors.surface.default.hex}}99; // Layer background (60% opacity)
|
||||
|
||||
cancelIconFg: {{colors.error.default.hex}}; // Cancel icon
|
||||
cancelIconFgOver: {{colors.error.default.hex}}; // Cancel icon on hover
|
||||
|
||||
boxBg: {{colors.surface.default.hex}}; // Box background
|
||||
boxTextFg: {{colors.on_surface.default.hex}}; // Box text
|
||||
boxTextFgGood: {{colors.primary.default.hex}}; // Box good text
|
||||
boxTextFgError: {{colors.error.default.hex}}; // Box error text
|
||||
boxTitleFg: {{colors.on_surface.default.hex}}; // Box title text
|
||||
boxSearchBg: {{colors.surface.default.hex}}; // Box search field background
|
||||
boxSearchCancelIconFg: {{colors.error.default.hex}}; // Box search cancel icon
|
||||
boxSearchCancelIconFgOver: {{colors.error.default.hex}}; // Box search cancel icon on hover
|
||||
|
||||
contactsBg: {{colors.surface.default.hex}}; // Contacts background
|
||||
contactsBgOver: {{colors.surface_variant.default.hex}}; // Contacts background on hover
|
||||
contactsNameFg: {{colors.on_surface.default.hex}}; // Contact name
|
||||
contactsStatusFg: {{colors.outline.default.hex}}; // Contact status
|
||||
contactsStatusFgOver: {{colors.on_surface_variant.default.hex}}; // Contact status on hover
|
||||
contactsStatusFgOnline: {{colors.primary.default.hex}}; // Online contact status
|
||||
|
||||
photoCropFadeBg: {{colors.surface.default.hex}}cc; // Photo crop fade background
|
||||
photoCropPointFg: {{colors.primary.default.hex}}; // Photo crop points
|
||||
|
||||
chat_inBubbleSelected: #313244; // inbox selected chat background
|
||||
chat_outBubbleSelected: #313244; // outbox selected chat background
|
||||
@@ -1,7 +1,6 @@
|
||||
[Unit]
|
||||
Description=Noctalia Shell Service
|
||||
Requisite=graphical-session.target
|
||||
PartOf=graphical-session.target
|
||||
BindsTo=graphical-session.target
|
||||
After=graphical-session.target
|
||||
|
||||
[Service]
|
||||
|
||||
@@ -123,10 +123,6 @@
|
||||
"stream-description": "Geben Sie einen Befehl ein, der kontinuierlich ausgeführt werden soll."
|
||||
},
|
||||
"dynamic-text": "Dynamischer Text",
|
||||
"hide-vertical": {
|
||||
"description": "Wenn aktiviert, wird der Text aus der Befehlsausgabe nicht angezeigt, wenn sich die Leiste in einem vertikalen Layout befindet (links oder rechts).",
|
||||
"label": "Text in vertikaler Leiste ausblenden"
|
||||
},
|
||||
"icon": {
|
||||
"description": "Symbol aus der Bibliothek auswählen.",
|
||||
"label": "Symbol"
|
||||
@@ -136,6 +132,14 @@
|
||||
"label": "Linksklick",
|
||||
"update-text": "Text auf Linksklick aktualisieren"
|
||||
},
|
||||
"max-text-length-horizontal": {
|
||||
"description": "Maximale Anzahl an Zeichen, die in horizontaler Leiste angezeigt werden (0 zum Ausblenden des Textes)",
|
||||
"label": "Max. Textlänge (horizontal)"
|
||||
},
|
||||
"max-text-length-vertical": {
|
||||
"description": "Maximale Anzahl an Zeichen, die in vertikaler Leiste angezeigt werden (0 zum Ausblenden des Textes)",
|
||||
"label": "Max. Textlänge (vertikal)"
|
||||
},
|
||||
"middle-click": {
|
||||
"description": "Befehl, der ausgeführt wird, wenn die Schaltfläche mit der mittleren Maustaste angeklickt wird.",
|
||||
"label": "Mittelklick",
|
||||
@@ -157,6 +161,23 @@
|
||||
"text-stream": {
|
||||
"description": "Gestreamte Zeilen aus dem Befehl werden als Text auf der Schaltfläche angezeigt.",
|
||||
"label": "Stream"
|
||||
},
|
||||
"wheel": {
|
||||
"description": "Befehl, der bei Verwendung des Scrollrads ausgeführt wird.\nVerwenden Sie $delta für die Scrollrad-Delta im Befehl",
|
||||
"label": "Scrollrad",
|
||||
"update-text": "Anzeigetext beim Scrollen aktualisieren"
|
||||
},
|
||||
"wheel-down": {
|
||||
"description": "Befehl, der ausgeführt wird, wenn das Scrollrad heruntergescrollt wird.",
|
||||
"label": "Scrollrad runter Befehl"
|
||||
},
|
||||
"wheel-mode-separate": {
|
||||
"description": "Separate Befehle für Scrollrad hoch und runter aktivieren",
|
||||
"label": "Separate Scrollrad-Befehle"
|
||||
},
|
||||
"wheel-up": {
|
||||
"description": "Befehl, der ausgeführt wird, wenn das Scrollrad hochgescrollt wird.",
|
||||
"label": "Scrollrad hoch Befehl"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
@@ -396,29 +417,29 @@
|
||||
}
|
||||
},
|
||||
"changelog": {
|
||||
"error": {
|
||||
"fetch-failed": "Changelog-Daten konnten nicht geladen werden. Bitte versuche es später erneut.",
|
||||
"rate-limit": "GitHub-Limit erreicht. Bitte versuche es in ein paar Minuten erneut."
|
||||
},
|
||||
"panel": {
|
||||
"title": "Was ist neu in {version}",
|
||||
"buttons": {
|
||||
"discord": "Unserem Discord beitreten",
|
||||
"dismiss": "Ok"
|
||||
},
|
||||
"empty": "Es sind noch keine Versionshinweise verfügbar.",
|
||||
"highlight-title": "Highlights",
|
||||
"section": {
|
||||
"released": "Veröffentlicht am {date}",
|
||||
"version": "Version {version}"
|
||||
},
|
||||
"subtitle": {
|
||||
"fresh": "Danke, dass du Noctalia installiert hast! Das ist in diesem Build enthalten.",
|
||||
"updated": "Aktualisiert von {previousVersion}"
|
||||
},
|
||||
"title": "Was ist neu in {version}",
|
||||
"version": {
|
||||
"new-user": "Neuinstallation"
|
||||
},
|
||||
"highlight-title": "Highlights",
|
||||
"empty": "Es sind noch keine Versionshinweise verfügbar.",
|
||||
"section": {
|
||||
"version": "Version {version}",
|
||||
"released": "Veröffentlicht am {date}"
|
||||
},
|
||||
"buttons": {
|
||||
"discord": "Unserem Discord beitreten",
|
||||
"feedback": "Feedback senden",
|
||||
"dismiss": "Ok"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"rate-limit": "GitHub-Limit erreicht. Bitte versuche es in ein paar Minuten erneut."
|
||||
}
|
||||
},
|
||||
"clock": {
|
||||
@@ -428,10 +449,12 @@
|
||||
"activate-app": "{app} aktivieren",
|
||||
"clear-history": "Verlauf löschen",
|
||||
"close-app": "{app} schließen",
|
||||
"connect-vpn": "Mit {name} verbinden",
|
||||
"cycle-visualizer": "Zyklus-Visualisierer",
|
||||
"disable-bluetooth": "Bluetooth deaktivieren",
|
||||
"disable-dnd": "Bitte nicht stören deaktivieren",
|
||||
"disable-wifi": "WLAN deaktivieren",
|
||||
"disconnect-vpn": "Verbindung zu {name} trennen",
|
||||
"enable-bluetooth": "Bluetooth aktivieren",
|
||||
"enable-dnd": "Nicht stören aktivieren",
|
||||
"enable-wifi": "WLAN aktivieren",
|
||||
@@ -1018,7 +1041,11 @@
|
||||
"description-missing": "Erfordert die Installation von {app}"
|
||||
},
|
||||
"spicetify": {
|
||||
"description": "Schreibe {Dateipfad}. Das Comfy-Theme muss manuell installiert und aktiviert werden",
|
||||
"description": "Schreibe {filepath}. Das Comfy-Theme muss manuell installiert und aktiviert werden",
|
||||
"description-missing": "Benötigt die Installation von {app}"
|
||||
},
|
||||
"telegram": {
|
||||
"description": "Schreibe {filepath}.",
|
||||
"description-missing": "Benötigt die Installation von {app}"
|
||||
},
|
||||
"vicinae": {
|
||||
@@ -1217,6 +1244,10 @@
|
||||
"description": "Hintergrund-Transparenz des Docks anpassen.",
|
||||
"label": "Hintergrund-Transparenz"
|
||||
},
|
||||
"border-radius": {
|
||||
"description": "Den Radius der Dock-Umrandung anpassen.",
|
||||
"label": "Eckenradius"
|
||||
},
|
||||
"colorize-icons": {
|
||||
"description": "Theme-Farben auf Dock-App-Symbole anwenden (nur nicht fokussierte Apps).",
|
||||
"label": "Symbole einfärben"
|
||||
@@ -1368,6 +1399,10 @@
|
||||
"description": "Hintergrund-Transparenz des Starters anpassen.",
|
||||
"label": "Hintergrund-Transparenz"
|
||||
},
|
||||
"clip-preview": {
|
||||
"description": "Zeigt eine Vorschau des Inhalts der Zwischenablage an, wenn der Befehl >clip verwendet wird.",
|
||||
"label": "Clip-Vorschau aktivieren"
|
||||
},
|
||||
"clipboard-history": {
|
||||
"description": "Zugriff auf zuvor kopierte Elemente über den Launcher.",
|
||||
"label": "Zwischenablageverlauf aktivieren"
|
||||
@@ -1475,6 +1510,10 @@
|
||||
"description": "Den Bildschirm beim Suspendieren des Systems automatisch sperren.",
|
||||
"label": "Sperren beim Ruhezustand"
|
||||
},
|
||||
"show-hibernate": {
|
||||
"description": "Die Option 'Ruhezustand' in den Energieaktionen anzeigen.",
|
||||
"label": "Ruhezustand anzeigen"
|
||||
},
|
||||
"title": "Sperrbildschirm"
|
||||
},
|
||||
"network": {
|
||||
@@ -2038,6 +2077,10 @@
|
||||
"title-matugen": "Matugen-Templating-Verarbeitung fehlgeschlagen",
|
||||
"title-predefined": "Die Verarbeitung des vordefinierten Farbschemas ist fehlgeschlagen."
|
||||
},
|
||||
"vpn": {
|
||||
"connected": "Verbunden mit '{name}'",
|
||||
"disconnected": "Verbindung zu '{name}' getrennt"
|
||||
},
|
||||
"wallpaper-colors": {
|
||||
"disabled": "Hintergrundbild-Farben deaktiviert",
|
||||
"enabled": "Hintergrundbild-Farben aktiviert",
|
||||
@@ -2071,6 +2114,7 @@
|
||||
"input-muted": "Audio-Eingabe stummschalten",
|
||||
"keep-awake": "Wach halten",
|
||||
"keyboard-layout": "{layout} Tastaturlayout",
|
||||
"manage-vpn": "VPN-Verbindungen verwalten",
|
||||
"manage-wifi": "WLAN verwalten",
|
||||
"microphone-volume-at": "Mikrofonlautstärke bei {volume}%.\nLinksklick für Einstellungen. Rechtsklick zum Stummschalten.\nScrollen zum Ändern der Lautstärke.",
|
||||
"move-to-center-section": "Zur mittleren Sektion verschieben",
|
||||
@@ -2297,6 +2341,7 @@
|
||||
},
|
||||
"wifi": {
|
||||
"panel": {
|
||||
"available-networks": "Verfügbare Netzwerke",
|
||||
"connect": "Verbinden",
|
||||
"connected": "Verbunden",
|
||||
"disabled": "WLAN ist deaktiviert",
|
||||
@@ -2307,6 +2352,7 @@
|
||||
"forget": "Vergessen",
|
||||
"forget-network": "Dieses Netzwerk vergessen?",
|
||||
"forgetting": "Wird vergessen...",
|
||||
"known-networks": "Bekannte Netzwerke",
|
||||
"no-networks": "Keine Netzwerke gefunden",
|
||||
"password": "Passwort",
|
||||
"saved": "Gespeichert",
|
||||
|
||||
@@ -123,10 +123,6 @@
|
||||
"stream-description": "Enter a command to run continuously."
|
||||
},
|
||||
"dynamic-text": "Dynamic text",
|
||||
"hide-vertical": {
|
||||
"description": "If enabled, the text from the command output will not be shown when the bar is in a vertical layout (left or right).",
|
||||
"label": "Hide text in vertical bar"
|
||||
},
|
||||
"icon": {
|
||||
"description": "Select an icon from the library.",
|
||||
"label": "Icon"
|
||||
@@ -136,6 +132,14 @@
|
||||
"label": "Left click",
|
||||
"update-text": "Update displayed text on left-click"
|
||||
},
|
||||
"max-text-length-horizontal": {
|
||||
"description": "Maximum number of characters to show in horizontal bar (0 to hide text)",
|
||||
"label": "Max text length (horizontal)"
|
||||
},
|
||||
"max-text-length-vertical": {
|
||||
"description": "Maximum number of characters to show in vertical bar (0 to hide text)",
|
||||
"label": "Max text length (vertical)"
|
||||
},
|
||||
"middle-click": {
|
||||
"description": "Command to execute when the button is middle-clicked.",
|
||||
"label": "Middle click",
|
||||
@@ -157,6 +161,23 @@
|
||||
"text-stream": {
|
||||
"description": "Streamed lines from the command will be displayed as text on the button.",
|
||||
"label": "Stream"
|
||||
},
|
||||
"wheel": {
|
||||
"description": "Command to execute when the scroll wheel is used.\nUse $delta for the scroll wheel delta in the command",
|
||||
"label": "Scroll wheel",
|
||||
"update-text": "Update displayed text on scroll"
|
||||
},
|
||||
"wheel-down": {
|
||||
"description": "Command to execute when the scroll wheel is scrolled down.",
|
||||
"label": "Wheel down command"
|
||||
},
|
||||
"wheel-mode-separate": {
|
||||
"description": "Enable separate commands for wheel up and down",
|
||||
"label": "Separate wheel commands"
|
||||
},
|
||||
"wheel-up": {
|
||||
"description": "Command to execute when the scroll wheel is scrolled up.",
|
||||
"label": "Wheel up command"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
@@ -396,28 +417,29 @@
|
||||
}
|
||||
},
|
||||
"changelog": {
|
||||
"error": {
|
||||
"fetch-failed": "Unable to load changelog data. Please try again later.",
|
||||
"rate-limit": "GitHub rate limit exceeded. Please try again in a few minutes."
|
||||
},
|
||||
"panel": {
|
||||
"title": "What's new in {version}",
|
||||
"buttons": {
|
||||
"discord": "Join our Discord",
|
||||
"dismiss": "Ok"
|
||||
},
|
||||
"empty": "Release notes are not available yet.",
|
||||
"highlight-title": "Highlights",
|
||||
"section": {
|
||||
"released": "Released on {date}",
|
||||
"version": "Version {version}"
|
||||
},
|
||||
"subtitle": {
|
||||
"fresh": "Thanks for installing Noctalia! Here is what’s included in this build.",
|
||||
"updated": "Updated from {previousVersion}"
|
||||
},
|
||||
"title": "What's new in {version}",
|
||||
"version": {
|
||||
"new-user": "Fresh install"
|
||||
},
|
||||
"highlight-title": "Highlights",
|
||||
"empty": "Release notes are not available yet.",
|
||||
"section": {
|
||||
"version": "Version {version}",
|
||||
"released": "Released on {date}"
|
||||
},
|
||||
"buttons": {
|
||||
"discord": "Join our Discord",
|
||||
"dismiss": "Ok"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"rate-limit": "GitHub rate limit exceeded. Please try again in a few minutes."
|
||||
}
|
||||
},
|
||||
"clock": {
|
||||
@@ -427,10 +449,12 @@
|
||||
"activate-app": "Activate {app}",
|
||||
"clear-history": "Clear history",
|
||||
"close-app": "Close {app}",
|
||||
"connect-vpn": "Connect to {name}",
|
||||
"cycle-visualizer": "Cycle visualizer",
|
||||
"disable-bluetooth": "Disable Bluetooth",
|
||||
"disable-dnd": "Disable Do Not Disturb",
|
||||
"disable-wifi": "Disable Wi-Fi",
|
||||
"disconnect-vpn": "Disconnect {name}",
|
||||
"enable-bluetooth": "Enable Bluetooth",
|
||||
"enable-dnd": "Enable Do Not Disturb",
|
||||
"enable-wifi": "Enable Wi-Fi",
|
||||
@@ -1020,6 +1044,10 @@
|
||||
"description": "Write {filepath}. Comfy theme needs to be installed and activated manually.",
|
||||
"description-missing": "Requires {app} to be installed"
|
||||
},
|
||||
"telegram": {
|
||||
"description": "Write {filepath}.",
|
||||
"description-missing": "Requires {app} to be installed"
|
||||
},
|
||||
"vicinae": {
|
||||
"description": "Write {filepath} and reload",
|
||||
"description-missing": "Requires {app} to be installed"
|
||||
@@ -1216,6 +1244,10 @@
|
||||
"description": "Adjust the dock's background opacity.",
|
||||
"label": "Background opacity"
|
||||
},
|
||||
"border-radius": {
|
||||
"description": "Adjust the dock's border radius.",
|
||||
"label": "Border radius"
|
||||
},
|
||||
"colorize-icons": {
|
||||
"description": "Apply theme colors to dock app icons (non-focused apps only).",
|
||||
"label": "Colorize Icons"
|
||||
@@ -1367,6 +1399,10 @@
|
||||
"description": "Adjust the background opacity of the launcher.",
|
||||
"label": "Background opacity"
|
||||
},
|
||||
"clip-preview": {
|
||||
"description": "Show a preview of the clipboard content when using the >clip command.",
|
||||
"label": "Enable clip preview"
|
||||
},
|
||||
"clipboard-history": {
|
||||
"description": "Access previously copied items from the launcher.",
|
||||
"label": "Enable clipboard history"
|
||||
@@ -1474,6 +1510,10 @@
|
||||
"description": "Automatically lock the screen when suspending the system.",
|
||||
"label": "Lock on suspend"
|
||||
},
|
||||
"show-hibernate": {
|
||||
"description": "Show the option 'hibernate' in the energy actions.",
|
||||
"label": "Show hibernate"
|
||||
},
|
||||
"title": "Lock screen"
|
||||
},
|
||||
"network": {
|
||||
@@ -2037,6 +2077,10 @@
|
||||
"title-matugen": "Matugen templating processing failed",
|
||||
"title-predefined": "Predefined color cheme processing failed"
|
||||
},
|
||||
"vpn": {
|
||||
"connected": "Connected to '{name}'",
|
||||
"disconnected": "Disconnected from '{name}'"
|
||||
},
|
||||
"wallpaper-colors": {
|
||||
"disabled": "Wallpaper colors disabled",
|
||||
"enabled": "Wallpaper colors enabled",
|
||||
@@ -2070,6 +2114,7 @@
|
||||
"input-muted": "Toggle input mute",
|
||||
"keep-awake": "Keep awake",
|
||||
"keyboard-layout": "{layout} keyboard layout",
|
||||
"manage-vpn": "Manage VPN connections",
|
||||
"manage-wifi": "Manage Wi-Fi",
|
||||
"microphone-volume-at": "Microphone volume at {volume}%\nScroll to modify volume",
|
||||
"move-to-center-section": "Move to center section",
|
||||
@@ -2296,6 +2341,7 @@
|
||||
},
|
||||
"wifi": {
|
||||
"panel": {
|
||||
"available-networks": "Available Networks",
|
||||
"connect": "Connect",
|
||||
"connected": "Connected",
|
||||
"disabled": "Wi-Fi is disabled",
|
||||
@@ -2306,6 +2352,7 @@
|
||||
"forget": "Forget",
|
||||
"forget-network": "Forget this network?",
|
||||
"forgetting": "Forgetting...",
|
||||
"known-networks": "Known Networks",
|
||||
"no-networks": "No networks found",
|
||||
"password": "Password",
|
||||
"saved": "Saved",
|
||||
|
||||
@@ -123,10 +123,6 @@
|
||||
"stream-description": "Introduce un comando para ejecutar continuamente."
|
||||
},
|
||||
"dynamic-text": "Texto dinámico",
|
||||
"hide-vertical": {
|
||||
"description": "Si está activado, el texto de la salida del comando no se mostrará cuando la barra esté en un diseño vertical (izquierda o derecha).",
|
||||
"label": "Ocultar texto en barra vertical"
|
||||
},
|
||||
"icon": {
|
||||
"description": "Selecciona un icono de la biblioteca.",
|
||||
"label": "Icono"
|
||||
@@ -136,6 +132,14 @@
|
||||
"label": "Clic izquierdo",
|
||||
"update-text": "Actualizar el texto mostrado al hacer clic izquierdo"
|
||||
},
|
||||
"max-text-length-horizontal": {
|
||||
"description": "Número máximo de caracteres a mostrar en la barra horizontal (0 para ocultar el texto)",
|
||||
"label": "Longitud máxima de texto (horizontal)"
|
||||
},
|
||||
"max-text-length-vertical": {
|
||||
"description": "Número máximo de caracteres a mostrar en la barra vertical (0 para ocultar el texto)",
|
||||
"label": "Longitud máxima de texto (vertical)"
|
||||
},
|
||||
"middle-click": {
|
||||
"description": "Comando a ejecutar cuando se hace clic medio en el botón.",
|
||||
"label": "Clic medio",
|
||||
@@ -157,6 +161,23 @@
|
||||
"text-stream": {
|
||||
"description": "Las líneas transmitidas desde el comando se mostrarán como texto en el botón.",
|
||||
"label": "Transmisión"
|
||||
},
|
||||
"wheel": {
|
||||
"description": "Comando a ejecutar cuando se usa la rueda de desplazamiento.\nUsa $delta para el delta de la rueda de desplazamiento en el comando",
|
||||
"label": "Rueda de desplazamiento",
|
||||
"update-text": "Actualizar texto mostrado al desplazarse"
|
||||
},
|
||||
"wheel-down": {
|
||||
"description": "Comando a ejecutar cuando la rueda de desplazamiento se desplaza hacia abajo.",
|
||||
"label": "Comando de rueda hacia abajo"
|
||||
},
|
||||
"wheel-mode-separate": {
|
||||
"description": "Habilitar comandos separados para rueda arriba y abajo",
|
||||
"label": "Comandos de rueda separados"
|
||||
},
|
||||
"wheel-up": {
|
||||
"description": "Comando a ejecutar cuando la rueda de desplazamiento se desplaza hacia arriba.",
|
||||
"label": "Comando de rueda hacia arriba"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
@@ -396,29 +417,29 @@
|
||||
}
|
||||
},
|
||||
"changelog": {
|
||||
"error": {
|
||||
"fetch-failed": "No se pudieron cargar los datos del registro de cambios. Inténtalo de nuevo más tarde.",
|
||||
"rate-limit": "Se alcanzó el límite de GitHub. Inténtalo de nuevo en unos minutos."
|
||||
},
|
||||
"panel": {
|
||||
"title": "Novedades en {version}",
|
||||
"buttons": {
|
||||
"discord": "Únete a nuestro Discord",
|
||||
"dismiss": "Ok"
|
||||
},
|
||||
"empty": "Las notas de la versión aún no están disponibles.",
|
||||
"highlight-title": "Cambios destacados",
|
||||
"section": {
|
||||
"released": "Publicado el {date}",
|
||||
"version": "Versión {version}"
|
||||
},
|
||||
"subtitle": {
|
||||
"fresh": "Gracias por instalar Noctalia. Esto es lo que incluye esta compilación.",
|
||||
"updated": "Actualizado desde {previousVersion}"
|
||||
},
|
||||
"title": "Novedades en {version}",
|
||||
"version": {
|
||||
"new-user": "Instalación nueva"
|
||||
},
|
||||
"highlight-title": "Cambios destacados",
|
||||
"empty": "Las notas de la versión aún no están disponibles.",
|
||||
"section": {
|
||||
"version": "Versión {version}",
|
||||
"released": "Publicado el {date}"
|
||||
},
|
||||
"buttons": {
|
||||
"discord": "Únete a nuestro Discord",
|
||||
"feedback": "Enviar comentarios",
|
||||
"dismiss": "Ok"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"rate-limit": "Se alcanzó el límite de GitHub. Inténtalo de nuevo en unos minutos."
|
||||
}
|
||||
},
|
||||
"clock": {
|
||||
@@ -428,10 +449,12 @@
|
||||
"activate-app": "Activar {app}",
|
||||
"clear-history": "Borrar historial",
|
||||
"close-app": "Cerrar {app}",
|
||||
"connect-vpn": "Conectarse a {name}",
|
||||
"cycle-visualizer": "Visualizador de ciclos",
|
||||
"disable-bluetooth": "Desactivar Bluetooth",
|
||||
"disable-dnd": "Desactivar No molestar",
|
||||
"disable-wifi": "Desactivar Wi-Fi",
|
||||
"disconnect-vpn": "Desconectar {name}",
|
||||
"enable-bluetooth": "Activar Bluetooth",
|
||||
"enable-dnd": "Activar No molestar",
|
||||
"enable-wifi": "Activar Wi-Fi",
|
||||
@@ -1021,6 +1044,10 @@
|
||||
"description": "Escribe {filepath}. El tema Comfy debe ser instalado y activado manualmente.",
|
||||
"description-missing": "Requiere que {app} esté instalado/a."
|
||||
},
|
||||
"telegram": {
|
||||
"description": "Escribe {filepath}.",
|
||||
"description-missing": "Requiere que {app} esté instalado/a."
|
||||
},
|
||||
"vicinae": {
|
||||
"description": "Escribir {filepath} y recargar",
|
||||
"description-missing": "Requiere que {app} esté instalado"
|
||||
@@ -1217,6 +1244,10 @@
|
||||
"description": "Ajusta la opacidad del fondo del dock.",
|
||||
"label": "Opacidad del fondo"
|
||||
},
|
||||
"border-radius": {
|
||||
"description": "Ajustar el radio del borde del dock.",
|
||||
"label": "Radio de borde"
|
||||
},
|
||||
"colorize-icons": {
|
||||
"description": "Aplicar colores del tema a los iconos de aplicaciones del dock (solo aplicaciones no enfocadas).",
|
||||
"label": "Colorear iconos"
|
||||
@@ -1368,6 +1399,10 @@
|
||||
"description": "Ajusta la opacidad del fondo del lanzador.",
|
||||
"label": "Opacidad del fondo"
|
||||
},
|
||||
"clip-preview": {
|
||||
"description": "Muestra una vista previa del contenido del portapapeles al usar el comando >clip.",
|
||||
"label": "Activar vista previa del portapapeles"
|
||||
},
|
||||
"clipboard-history": {
|
||||
"description": "Accede a los elementos copiados anteriormente desde el lanzador.",
|
||||
"label": "Activar historial del portapapeles"
|
||||
@@ -1475,6 +1510,10 @@
|
||||
"description": "Bloquear la pantalla automáticamente al suspender el sistema.",
|
||||
"label": "Bloquear al suspender"
|
||||
},
|
||||
"show-hibernate": {
|
||||
"description": "Mostrar la opción 'hibernar' en las acciones de energía.",
|
||||
"label": "Mostrar hibernar"
|
||||
},
|
||||
"title": "Pantalla de bloqueo"
|
||||
},
|
||||
"network": {
|
||||
@@ -2038,6 +2077,10 @@
|
||||
"title-matugen": "Falló el procesamiento de la plantilla Matugen.",
|
||||
"title-predefined": "Falló el procesamiento del esquema de color predefinido."
|
||||
},
|
||||
"vpn": {
|
||||
"connected": "Conectado a '{name}'",
|
||||
"disconnected": "Desconectado de '{name}'"
|
||||
},
|
||||
"wallpaper-colors": {
|
||||
"disabled": "Colores del fondo de pantalla desactivados",
|
||||
"enabled": "Colores del fondo de pantalla activados",
|
||||
@@ -2071,6 +2114,7 @@
|
||||
"input-muted": "Silenciar entrada de audio",
|
||||
"keep-awake": "Mantener despierto",
|
||||
"keyboard-layout": "Distribución de teclado {layout}",
|
||||
"manage-vpn": "Administrar conexiones VPN",
|
||||
"manage-wifi": "Gestionar Wi-Fi",
|
||||
"microphone-volume-at": "Volumen del micrófono al {volume}%.\nClic izquierdo para ajustes. Clic derecho para activar/desactivar el silencio.\nDesplázate para modificar el volumen.",
|
||||
"move-to-center-section": "Mover a la sección central",
|
||||
@@ -2297,6 +2341,7 @@
|
||||
},
|
||||
"wifi": {
|
||||
"panel": {
|
||||
"available-networks": "Redes disponibles",
|
||||
"connect": "Conectar",
|
||||
"connected": "Conectado",
|
||||
"disabled": "Wi-Fi está desactivado",
|
||||
@@ -2307,6 +2352,7 @@
|
||||
"forget": "Olvidar",
|
||||
"forget-network": "¿Olvidar esta red?",
|
||||
"forgetting": "Olvidando...",
|
||||
"known-networks": "Redes conocidas",
|
||||
"no-networks": "No se encontraron redes",
|
||||
"password": "Contraseña",
|
||||
"saved": "Guardado",
|
||||
|
||||
@@ -123,10 +123,6 @@
|
||||
"stream-description": "Entrez une commande à exécuter en continu."
|
||||
},
|
||||
"dynamic-text": "Texte dynamique",
|
||||
"hide-vertical": {
|
||||
"description": "Si activé, le texte de la sortie de la commande ne sera pas affiché lorsque la barre est en disposition verticale (gauche ou droite).",
|
||||
"label": "Masquer le texte dans la barre verticale"
|
||||
},
|
||||
"icon": {
|
||||
"description": "Sélectionnez une icône dans la bibliothèque.",
|
||||
"label": "Icône"
|
||||
@@ -136,6 +132,14 @@
|
||||
"label": "Clic gauche",
|
||||
"update-text": "Mettre à jour le texte affiché au clic gauche."
|
||||
},
|
||||
"max-text-length-horizontal": {
|
||||
"description": "Nombre maximal de caractères à afficher dans la barre horizontale (0 pour masquer le texte)",
|
||||
"label": "Longueur max du texte (horizontal)"
|
||||
},
|
||||
"max-text-length-vertical": {
|
||||
"description": "Nombre maximal de caractères à afficher dans la barre verticale (0 pour masquer le texte)",
|
||||
"label": "Longueur max du texte (vertical)"
|
||||
},
|
||||
"middle-click": {
|
||||
"description": "Commande à exécuter quand le bouton est cliqué au milieu.",
|
||||
"label": "Clic milieu",
|
||||
@@ -157,6 +161,23 @@
|
||||
"text-stream": {
|
||||
"description": "Les lignes diffusées depuis la commande seront affichées sous forme de texte sur le bouton.",
|
||||
"label": "Flux"
|
||||
},
|
||||
"wheel": {
|
||||
"description": "Commande à exécuter lorsque la molette est utilisée.\nUtilisez $delta pour le delta de la molette dans la commande",
|
||||
"label": "Molette",
|
||||
"update-text": "Mettre à jour le texte affiché au défilement"
|
||||
},
|
||||
"wheel-down": {
|
||||
"description": "Commande à exécuter lorsque la molette est défilée vers le bas.",
|
||||
"label": "Commande molette bas"
|
||||
},
|
||||
"wheel-mode-separate": {
|
||||
"description": "Activer des commandes séparées pour la molette haut et bas",
|
||||
"label": "Commandes de molette séparées"
|
||||
},
|
||||
"wheel-up": {
|
||||
"description": "Commande à exécuter lorsque la molette est défilée vers le haut.",
|
||||
"label": "Commande molette haut"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
@@ -396,29 +417,29 @@
|
||||
}
|
||||
},
|
||||
"changelog": {
|
||||
"error": {
|
||||
"fetch-failed": "Impossible de charger les données du journal des modifications. Veuillez réessayer plus tard.",
|
||||
"rate-limit": "Limite de GitHub atteinte. Réessayez dans quelques minutes."
|
||||
},
|
||||
"panel": {
|
||||
"title": "Quoi de neuf dans {version}",
|
||||
"buttons": {
|
||||
"discord": "Rejoindre notre Discord",
|
||||
"dismiss": "Ok"
|
||||
},
|
||||
"empty": "Les notes de version ne sont pas encore disponibles.",
|
||||
"highlight-title": "Points importants",
|
||||
"section": {
|
||||
"released": "Publié le {date}",
|
||||
"version": "Version {version}"
|
||||
},
|
||||
"subtitle": {
|
||||
"fresh": "Merci d’avoir installé Noctalia ! Voici ce que contient cette version.",
|
||||
"updated": "Mise à jour depuis {previousVersion}"
|
||||
},
|
||||
"title": "Quoi de neuf dans {version}",
|
||||
"version": {
|
||||
"new-user": "Nouvelle installation"
|
||||
},
|
||||
"highlight-title": "Points importants",
|
||||
"empty": "Les notes de version ne sont pas encore disponibles.",
|
||||
"section": {
|
||||
"version": "Version {version}",
|
||||
"released": "Publié le {date}"
|
||||
},
|
||||
"buttons": {
|
||||
"discord": "Rejoindre notre Discord",
|
||||
"feedback": "Envoyer un retour",
|
||||
"dismiss": "Ok"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"rate-limit": "Limite de GitHub atteinte. Réessayez dans quelques minutes."
|
||||
}
|
||||
},
|
||||
"clock": {
|
||||
@@ -428,10 +449,12 @@
|
||||
"activate-app": "Activer {app}",
|
||||
"clear-history": "Effacer l'historique",
|
||||
"close-app": "Fermer {app}",
|
||||
"connect-vpn": "Se connecter à {name}",
|
||||
"cycle-visualizer": "Visualiseur de cycle",
|
||||
"disable-bluetooth": "Désactiver le Bluetooth",
|
||||
"disable-dnd": "Désactiver le mode Ne pas déranger",
|
||||
"disable-wifi": "Désactiver le Wi-Fi",
|
||||
"disconnect-vpn": "Se déconnecter de {name}",
|
||||
"enable-bluetooth": "Activer le Bluetooth",
|
||||
"enable-dnd": "Activer le mode Ne pas déranger",
|
||||
"enable-wifi": "Activer le Wi-Fi",
|
||||
@@ -1021,6 +1044,10 @@
|
||||
"description": "Écrire {filepath}. Le thème Comfy doit être installé et activé manuellement.",
|
||||
"description-missing": "Nécessite l'installation de {app}"
|
||||
},
|
||||
"telegram": {
|
||||
"description": "Écrire {filepath}.",
|
||||
"description-missing": "Nécessite l'installation de {app}"
|
||||
},
|
||||
"vicinae": {
|
||||
"description": "Écrire {filepath} et recharger",
|
||||
"description-missing": "Nécessite que le lanceur {app} soit installé"
|
||||
@@ -1217,6 +1244,10 @@
|
||||
"description": "Ajustez l'opacité de l'arrière-plan du dock.",
|
||||
"label": "Opacité de l'arrière-plan"
|
||||
},
|
||||
"border-radius": {
|
||||
"description": "Ajuster le rayon de bordure du dock.",
|
||||
"label": "Rayon de bordure"
|
||||
},
|
||||
"colorize-icons": {
|
||||
"description": "Appliquer les couleurs du thème aux icônes d'applications du dock (applications non focalisées uniquement).",
|
||||
"label": "Coloriser les icônes"
|
||||
@@ -1368,6 +1399,10 @@
|
||||
"description": "Ajustez l'opacité de l'arrière-plan du lanceur.",
|
||||
"label": "Opacité de l'arrière-plan"
|
||||
},
|
||||
"clip-preview": {
|
||||
"description": "Afficher un aperçu du contenu du presse-papiers lors de l'utilisation de la commande >clip.",
|
||||
"label": "Activer l'aperçu du presse-papiers"
|
||||
},
|
||||
"clipboard-history": {
|
||||
"description": "Accédez aux éléments précédemment copiés depuis le lanceur.",
|
||||
"label": "Activer l'historique du presse-papiers"
|
||||
@@ -1475,6 +1510,10 @@
|
||||
"description": "Verrouiller automatiquement l'écran lors de la mise en veille du système.",
|
||||
"label": "Verrouiller à la suspension"
|
||||
},
|
||||
"show-hibernate": {
|
||||
"description": "Afficher l’option 'hiberner' dans les actions d’énergie.",
|
||||
"label": "Afficher l’hibernation"
|
||||
},
|
||||
"title": "Écran de verrouillage"
|
||||
},
|
||||
"network": {
|
||||
@@ -2038,6 +2077,10 @@
|
||||
"title-matugen": "Le traitement du modèle Matugen a échoué.",
|
||||
"title-predefined": "Le traitement du schéma de couleurs prédéfini a échoué."
|
||||
},
|
||||
"vpn": {
|
||||
"connected": "Connecté à '{name}'",
|
||||
"disconnected": "Déconnecté de '{name}'"
|
||||
},
|
||||
"wallpaper-colors": {
|
||||
"disabled": "Couleurs du fond d'écran désactivées",
|
||||
"enabled": "Couleurs du fond d'écran activées",
|
||||
@@ -2071,6 +2114,7 @@
|
||||
"input-muted": "Couper l'entrée audio",
|
||||
"keep-awake": "Rester éveillé",
|
||||
"keyboard-layout": "Disposition du clavier {layout}",
|
||||
"manage-vpn": "Gérer les connexions VPN",
|
||||
"manage-wifi": "Gérer le Wi-Fi",
|
||||
"microphone-volume-at": "Volume du microphone à {volume}%.\nClic gauche pour les paramètres. Clic droit pour activer/désactiver le mode muet.\nFaites défiler pour modifier le volume.",
|
||||
"move-to-center-section": "Déplacer vers la section centrale",
|
||||
@@ -2297,6 +2341,7 @@
|
||||
},
|
||||
"wifi": {
|
||||
"panel": {
|
||||
"available-networks": "Réseaux disponibles",
|
||||
"connect": "Connecter",
|
||||
"connected": "Connecté",
|
||||
"disabled": "Le Wi-Fi est désactivé",
|
||||
@@ -2307,6 +2352,7 @@
|
||||
"forget": "Oublier",
|
||||
"forget-network": "Oublier ce réseau ?",
|
||||
"forgetting": "Oubli en cours...",
|
||||
"known-networks": "Réseaux connus",
|
||||
"no-networks": "Aucun réseau trouvé",
|
||||
"password": "Mot de passe",
|
||||
"saved": "Enregistré",
|
||||
|
||||
@@ -123,10 +123,6 @@
|
||||
"stream-description": "Voer een commando in dat continu wordt uitgevoerd."
|
||||
},
|
||||
"dynamic-text": "Dynamische tekst",
|
||||
"hide-vertical": {
|
||||
"description": "Indien ingeschakeld wordt de tekst uit de commando-uitvoer niet getoond wanneer de balk verticaal is (links of rechts).",
|
||||
"label": "Tekst verbergen in verticale balk"
|
||||
},
|
||||
"icon": {
|
||||
"description": "Selecteer een pictogram uit de bibliotheek.",
|
||||
"label": "Pictogram"
|
||||
@@ -136,6 +132,14 @@
|
||||
"label": "Linkermuisklik",
|
||||
"update-text": "Tekst bijwerken bij linksklik"
|
||||
},
|
||||
"max-text-length-horizontal": {
|
||||
"description": "Maximaal aantal tekens dat moet worden weergegeven in horizontale balk (0 om tekst te verbergen)",
|
||||
"label": "Max. tekstlengte (horizontaal)"
|
||||
},
|
||||
"max-text-length-vertical": {
|
||||
"description": "Maximaal aantal tekens dat moet worden weergegeven in verticale balk (0 om tekst te verbergen)",
|
||||
"label": "Max. tekstlengte (verticaal)"
|
||||
},
|
||||
"middle-click": {
|
||||
"description": "Commando dat wordt uitgevoerd wanneer met de middelste muisknop op de knop wordt geklikt.",
|
||||
"label": "Middelste muisklik",
|
||||
@@ -157,6 +161,23 @@
|
||||
"text-stream": {
|
||||
"description": "Gestreamde regels uit het commando worden als tekst op de knop weergegeven.",
|
||||
"label": "Stream"
|
||||
},
|
||||
"wheel": {
|
||||
"description": "Commando om uit te voeren wanneer het scrollwiel wordt gebruikt.\nGebruik $delta voor de scrollwiel-delta in het commando",
|
||||
"label": "Scrollwiel",
|
||||
"update-text": "Weergegeven tekst bij scrollen bijwerken"
|
||||
},
|
||||
"wheel-down": {
|
||||
"description": "Commando om uit te voeren wanneer het scrollwiel omlaag wordt bewogen.",
|
||||
"label": "Scrollwiel omlaag commando"
|
||||
},
|
||||
"wheel-mode-separate": {
|
||||
"description": "Afzonderlijke commando's inschakelen voor scrollwiel omhoog en omlaag",
|
||||
"label": "Afzonderlijke scrollwielcommando's"
|
||||
},
|
||||
"wheel-up": {
|
||||
"description": "Commando om uit te voeren wanneer het scrollwiel omhoog wordt bewogen.",
|
||||
"label": "Scrollwiel omhoog commando"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
@@ -396,29 +417,29 @@
|
||||
}
|
||||
},
|
||||
"changelog": {
|
||||
"error": {
|
||||
"fetch-failed": "Kan changeloggegevens niet laden. Probeer het later opnieuw.",
|
||||
"rate-limit": "GitHub-limiet bereikt. Probeer het over enkele minuten opnieuw."
|
||||
},
|
||||
"panel": {
|
||||
"title": "Wat is er nieuw in {version}",
|
||||
"buttons": {
|
||||
"discord": "Word lid van onze Discord",
|
||||
"dismiss": "Ok"
|
||||
},
|
||||
"empty": "Er zijn nog geen release-opmerkingen beschikbaar.",
|
||||
"highlight-title": "Hoogtepunten",
|
||||
"section": {
|
||||
"released": "Uitgebracht op {date}",
|
||||
"version": "Versie {version}"
|
||||
},
|
||||
"subtitle": {
|
||||
"fresh": "Bedankt voor het installeren van Noctalia! Dit zit er in deze build.",
|
||||
"updated": "Bijgewerkt vanaf {previousVersion}"
|
||||
},
|
||||
"title": "Wat is er nieuw in {version}",
|
||||
"version": {
|
||||
"new-user": "Nieuwe installatie"
|
||||
},
|
||||
"highlight-title": "Hoogtepunten",
|
||||
"empty": "Er zijn nog geen release-opmerkingen beschikbaar.",
|
||||
"section": {
|
||||
"version": "Versie {version}",
|
||||
"released": "Uitgebracht op {date}"
|
||||
},
|
||||
"buttons": {
|
||||
"discord": "Word lid van onze Discord",
|
||||
"feedback": "Feedback verzenden",
|
||||
"dismiss": "Ok"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"rate-limit": "GitHub-limiet bereikt. Probeer het over enkele minuten opnieuw."
|
||||
}
|
||||
},
|
||||
"clock": {
|
||||
@@ -428,10 +449,12 @@
|
||||
"activate-app": "Activeer {app}",
|
||||
"clear-history": "Geschiedenis wissen",
|
||||
"close-app": "Sluit {app}",
|
||||
"connect-vpn": "Verbinding maken met {name}",
|
||||
"cycle-visualizer": "Cyclusvisualisatie",
|
||||
"disable-bluetooth": "Bluetooth uitschakelen",
|
||||
"disable-dnd": "Niet Storen uitschakelen",
|
||||
"disable-wifi": "Wi-Fi uitschakelen",
|
||||
"disconnect-vpn": "Verbinding met {name} verbreken",
|
||||
"enable-bluetooth": "Bluetooth inschakelen",
|
||||
"enable-dnd": "Niet Storen inschakelen",
|
||||
"enable-wifi": "Wi-Fi inschakelen",
|
||||
@@ -1021,6 +1044,10 @@
|
||||
"description": "Schrijf {filepath}. Het Comfy-thema moet handmatig worden geïnstalleerd en geactiveerd.",
|
||||
"description-missing": "Vereist dat {app} is geïnstalleerd."
|
||||
},
|
||||
"telegram": {
|
||||
"description": "Schrijf {filepath}.",
|
||||
"description-missing": "Vereist dat {app} is geïnstalleerd."
|
||||
},
|
||||
"vicinae": {
|
||||
"description": "Schrijf {filepath} en herlaad.",
|
||||
"description-missing": "Vereist dat {app} is geïnstalleerd."
|
||||
@@ -1217,6 +1244,10 @@
|
||||
"description": "Pas de achtergronddekking van de dock aan.",
|
||||
"label": "Achtergronddekking"
|
||||
},
|
||||
"border-radius": {
|
||||
"description": "Pas de randradius van het dock aan.",
|
||||
"label": "Randradius"
|
||||
},
|
||||
"colorize-icons": {
|
||||
"description": "Pas themakleuren toe op dock-pictogrammen (alleen niet-focuste apps).",
|
||||
"label": "Pictogrammen inkleuren"
|
||||
@@ -1368,6 +1399,10 @@
|
||||
"description": "Pas de achtergronddekking van de launcher aan.",
|
||||
"label": "Achtergronddekking"
|
||||
},
|
||||
"clip-preview": {
|
||||
"description": "Toon een voorbeeld van de inhoud van het klembord bij gebruik van het >clip-commando.",
|
||||
"label": "Klembordvoorbeeld inschakelen"
|
||||
},
|
||||
"clipboard-history": {
|
||||
"description": "Toegang tot eerder gekopieerde items vanuit de launcher.",
|
||||
"label": "Klembordgeschiedenis inschakelen"
|
||||
@@ -1475,6 +1510,10 @@
|
||||
"description": "Vergrendel het scherm automatisch wanneer het systeem wordt onderbroken.",
|
||||
"label": "Vergrendelen bij onderbreken"
|
||||
},
|
||||
"show-hibernate": {
|
||||
"description": "De optie 'sluimerstand' tonen in de energieacties.",
|
||||
"label": "Sluimerstand tonen"
|
||||
},
|
||||
"title": "Vergrendelscherm"
|
||||
},
|
||||
"network": {
|
||||
@@ -2038,6 +2077,10 @@
|
||||
"title-matugen": "Matugen-sjabloonverwerking mislukt",
|
||||
"title-predefined": "Verwerken van vooraf gedefinieerd kleurenschema mislukt"
|
||||
},
|
||||
"vpn": {
|
||||
"connected": "Verbonden met '{name}'",
|
||||
"disconnected": "Verbinding met '{name}' verbroken"
|
||||
},
|
||||
"wallpaper-colors": {
|
||||
"disabled": "Achtergrondkleuren uitgeschakeld",
|
||||
"enabled": "Achtergrondkleuren ingeschakeld",
|
||||
@@ -2071,6 +2114,7 @@
|
||||
"input-muted": "Invoermicrofoon dempen in-/uitschakelen",
|
||||
"keep-awake": "Wakker houden",
|
||||
"keyboard-layout": "{layout}-toetsenbordindeling",
|
||||
"manage-vpn": "VPN-verbindingen beheren",
|
||||
"manage-wifi": "Wi-Fi beheren",
|
||||
"microphone-volume-at": "Microfoonvolume {volume}%\nLinks klikken voor instellingen. Rechts klikken om te dempen.\nScroll om het volume aan te passen.",
|
||||
"move-to-center-section": "Verplaatsen naar middelste sectie",
|
||||
@@ -2297,6 +2341,7 @@
|
||||
},
|
||||
"wifi": {
|
||||
"panel": {
|
||||
"available-networks": "Beschikbare netwerken",
|
||||
"connect": "Verbinden",
|
||||
"connected": "Verbonden",
|
||||
"disabled": "Wi-Fi is uitgeschakeld",
|
||||
@@ -2307,6 +2352,7 @@
|
||||
"forget": "Vergeten",
|
||||
"forget-network": "Dit netwerk vergeten?",
|
||||
"forgetting": "Vergeten...",
|
||||
"known-networks": "Bekende netwerken",
|
||||
"no-networks": "Geen netwerken gevonden",
|
||||
"password": "Wachtwoord",
|
||||
"saved": "Opgeslagen",
|
||||
|
||||
@@ -123,10 +123,6 @@
|
||||
"stream-description": "Insira um comando para executar continuamente."
|
||||
},
|
||||
"dynamic-text": "Texto dinâmico",
|
||||
"hide-vertical": {
|
||||
"description": "Se ativado, o texto da saída do comando não será exibido quando a barra estiver em um layout vertical (esquerda ou direita).",
|
||||
"label": "Ocultar texto na barra vertical"
|
||||
},
|
||||
"icon": {
|
||||
"description": "Selecione um ícone da biblioteca.",
|
||||
"label": "Ícone"
|
||||
@@ -136,6 +132,14 @@
|
||||
"label": "Clique esquerdo",
|
||||
"update-text": "Atualizar texto exibido ao clicar com o botão esquerdo"
|
||||
},
|
||||
"max-text-length-horizontal": {
|
||||
"description": "Número máximo de caracteres a serem exibidos na barra horizontal (0 para ocultar o texto)",
|
||||
"label": "Comprimento máximo do texto (horizontal)"
|
||||
},
|
||||
"max-text-length-vertical": {
|
||||
"description": "Número máximo de caracteres a serem exibidos na barra vertical (0 para ocultar o texto)",
|
||||
"label": "Comprimento máximo do texto (vertical)"
|
||||
},
|
||||
"middle-click": {
|
||||
"description": "Comando a executar quando o botão é clicado com o botão do meio.",
|
||||
"label": "Clique do meio",
|
||||
@@ -157,6 +161,23 @@
|
||||
"text-stream": {
|
||||
"description": "As linhas transmitidas do comando serão exibidas como texto no botão.",
|
||||
"label": "Transmissão"
|
||||
},
|
||||
"wheel": {
|
||||
"description": "Comando a executar quando a roda de rolagem é usada.\nUse $delta para o delta da roda de rolagem no comando",
|
||||
"label": "Roda de rolagem",
|
||||
"update-text": "Atualizar texto exibido ao rolar"
|
||||
},
|
||||
"wheel-down": {
|
||||
"description": "Comando a executar quando a roda de rolagem é rolada para baixo.",
|
||||
"label": "Comando de roda para baixo"
|
||||
},
|
||||
"wheel-mode-separate": {
|
||||
"description": "Ativar comandos separados para roda para cima e para baixo",
|
||||
"label": "Comandos de roda separados"
|
||||
},
|
||||
"wheel-up": {
|
||||
"description": "Comando a executar quando a roda de rolagem é rolada para cima.",
|
||||
"label": "Comando de roda para cima"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
@@ -396,29 +417,29 @@
|
||||
}
|
||||
},
|
||||
"changelog": {
|
||||
"error": {
|
||||
"fetch-failed": "Não foi possível carregar os dados do changelog. Tente novamente mais tarde.",
|
||||
"rate-limit": "Limite do GitHub atingido. Tente novamente em alguns minutos."
|
||||
},
|
||||
"panel": {
|
||||
"title": "Novidades na {version}",
|
||||
"buttons": {
|
||||
"discord": "Entre no nosso Discord",
|
||||
"dismiss": "Ok"
|
||||
},
|
||||
"empty": "As notas da versão ainda não estão disponíveis.",
|
||||
"highlight-title": "Destaques",
|
||||
"section": {
|
||||
"released": "Lançado em {date}",
|
||||
"version": "Versão {version}"
|
||||
},
|
||||
"subtitle": {
|
||||
"fresh": "Obrigado por instalar o Noctalia! Veja o que está incluído nesta compilação.",
|
||||
"updated": "Atualizado a partir da {previousVersion}"
|
||||
},
|
||||
"title": "Novidades na {version}",
|
||||
"version": {
|
||||
"new-user": "Nova instalação"
|
||||
},
|
||||
"highlight-title": "Destaques",
|
||||
"empty": "As notas da versão ainda não estão disponíveis.",
|
||||
"section": {
|
||||
"version": "Versão {version}",
|
||||
"released": "Lançado em {date}"
|
||||
},
|
||||
"buttons": {
|
||||
"discord": "Entre no nosso Discord",
|
||||
"feedback": "Enviar feedback",
|
||||
"dismiss": "Ok"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"rate-limit": "Limite do GitHub atingido. Tente novamente em alguns minutos."
|
||||
}
|
||||
},
|
||||
"clock": {
|
||||
@@ -428,10 +449,12 @@
|
||||
"activate-app": "Ativar {app}",
|
||||
"clear-history": "Limpar histórico",
|
||||
"close-app": "Fechar {app}",
|
||||
"connect-vpn": "Conectar-se a {name}",
|
||||
"cycle-visualizer": "Visualizador de ciclo",
|
||||
"disable-bluetooth": "Desativar Bluetooth",
|
||||
"disable-dnd": "Desativar o Não Perturbe",
|
||||
"disable-wifi": "Desativar Wi-Fi",
|
||||
"disconnect-vpn": "Desconectar {name}",
|
||||
"enable-bluetooth": "Ativar Bluetooth",
|
||||
"enable-dnd": "Ativar Não Perturbe",
|
||||
"enable-wifi": "Ativar Wi-Fi",
|
||||
@@ -1021,6 +1044,10 @@
|
||||
"description": "Escreva em {filepath}. O tema Comfy precisa ser instalado e ativado manualmente.",
|
||||
"description-missing": "Requer que o {app} esteja instalado."
|
||||
},
|
||||
"telegram": {
|
||||
"description": "Escreva em {filepath}.",
|
||||
"description-missing": "Requer que o {app} esteja instalado."
|
||||
},
|
||||
"vicinae": {
|
||||
"description": "Escrever {filepath} e recarregar",
|
||||
"description-missing": "Requer que o {app} esteja instalado"
|
||||
@@ -1217,6 +1244,10 @@
|
||||
"description": "Ajuste a opacidade do fundo da dock.",
|
||||
"label": "Opacidade do fundo"
|
||||
},
|
||||
"border-radius": {
|
||||
"description": "Ajustar o raio da borda da dock.",
|
||||
"label": "Raio da borda"
|
||||
},
|
||||
"colorize-icons": {
|
||||
"description": "Aplicar cores do tema aos ícones de aplicativos da dock (apenas aplicativos não focados).",
|
||||
"label": "Colorir ícones"
|
||||
@@ -1368,6 +1399,10 @@
|
||||
"description": "Ajuste a opacidade do fundo do lançador.",
|
||||
"label": "Opacidade do fundo"
|
||||
},
|
||||
"clip-preview": {
|
||||
"description": "Mostra uma pré-visualização do conteúdo da área de transferência ao usar o comando >clip.",
|
||||
"label": "Ativar pré-visualização da área de transferência"
|
||||
},
|
||||
"clipboard-history": {
|
||||
"description": "Acesse itens copiados anteriormente a partir do lançador.",
|
||||
"label": "Ativar histórico da área de transferência"
|
||||
@@ -1475,6 +1510,10 @@
|
||||
"description": "Bloquear a tela automaticamente ao suspender o sistema.",
|
||||
"label": "Bloquear ao suspender"
|
||||
},
|
||||
"show-hibernate": {
|
||||
"description": "Mostrar a opção 'hibernar' nas ações de energia.",
|
||||
"label": "Mostrar hibernar"
|
||||
},
|
||||
"title": "Tela de bloqueio"
|
||||
},
|
||||
"network": {
|
||||
@@ -2038,6 +2077,10 @@
|
||||
"title-matugen": "Falha no processamento do template Matugen",
|
||||
"title-predefined": "O processamento do esquema de cores predefinido falhou."
|
||||
},
|
||||
"vpn": {
|
||||
"connected": "Conectado a '{name}'",
|
||||
"disconnected": "Desconectado de '{name}'"
|
||||
},
|
||||
"wallpaper-colors": {
|
||||
"disabled": "Cores do papel de parede desativadas",
|
||||
"enabled": "Cores do papel de parede ativadas",
|
||||
@@ -2071,6 +2114,7 @@
|
||||
"input-muted": "Silenciar entrada de áudio",
|
||||
"keep-awake": "Manter acordado",
|
||||
"keyboard-layout": "Layout de teclado {layout}",
|
||||
"manage-vpn": "Gerenciar conexões VPN",
|
||||
"manage-wifi": "Gerenciar Wi-Fi",
|
||||
"microphone-volume-at": "Volume do microfone em {volume}%.\nClique esquerdo para configurações. Clique direito para ativar/desativar o mudo.\nRole para modificar o volume.",
|
||||
"move-to-center-section": "Mover para a seção central",
|
||||
@@ -2297,6 +2341,7 @@
|
||||
},
|
||||
"wifi": {
|
||||
"panel": {
|
||||
"available-networks": "Redes Disponíveis",
|
||||
"connect": "Conectar",
|
||||
"connected": "Conectado",
|
||||
"disabled": "O Wi-Fi está desativado",
|
||||
@@ -2307,6 +2352,7 @@
|
||||
"forget": "Esquecer",
|
||||
"forget-network": "Esquecer esta rede?",
|
||||
"forgetting": "Esquecendo...",
|
||||
"known-networks": "Redes Conhecidas",
|
||||
"no-networks": "Nenhuma rede encontrada",
|
||||
"password": "Senha",
|
||||
"saved": "Salva",
|
||||
|
||||
@@ -123,10 +123,6 @@
|
||||
"stream-description": "Введите команду для непрерывного выполнения."
|
||||
},
|
||||
"dynamic-text": "Динамический текст",
|
||||
"hide-vertical": {
|
||||
"description": "Если включено, текст из вывода команды не будет отображаться, когда панель находится в вертикальном макете (слева или справа).",
|
||||
"label": "Скрывать текст в вертикальной панели"
|
||||
},
|
||||
"icon": {
|
||||
"description": "Выберите иконку из библиотеки.",
|
||||
"label": "Иконка"
|
||||
@@ -136,6 +132,14 @@
|
||||
"label": "Клик левой кнопкой",
|
||||
"update-text": "Обновить отображаемый текст по левому клику"
|
||||
},
|
||||
"max-text-length-horizontal": {
|
||||
"description": "Максимальное количество символов для отображения в горизонтальной панели (0 для скрытия текста)",
|
||||
"label": "Макс. длина текста (горизонтально)"
|
||||
},
|
||||
"max-text-length-vertical": {
|
||||
"description": "Максимальное количество символов для отображения в вертикальной панели (0 для скрытия текста)",
|
||||
"label": "Макс. длина текста (вертикально)"
|
||||
},
|
||||
"middle-click": {
|
||||
"description": "Команда для выполнения при нажатии средней кнопкой мыши.",
|
||||
"label": "Клик средней кнопкой",
|
||||
@@ -157,6 +161,23 @@
|
||||
"text-stream": {
|
||||
"description": "Потоковые строки из команды будут отображаться как текст на кнопке.",
|
||||
"label": "Поток"
|
||||
},
|
||||
"wheel": {
|
||||
"description": "Команда для выполнения при использовании колеса прокрутки.\nИспользуйте $delta для дельты колеса прокрутки в команде",
|
||||
"label": "Колесо прокрутки",
|
||||
"update-text": "Обновить отображаемый текст при прокрутке"
|
||||
},
|
||||
"wheel-down": {
|
||||
"description": "Команда для выполнения при прокрутке колеса вниз.",
|
||||
"label": "Команда прокрутки колеса вниз"
|
||||
},
|
||||
"wheel-mode-separate": {
|
||||
"description": "Включить раздельные команды для колеса прокрутки вверх и вниз",
|
||||
"label": "Раздельные команды колеса прокрутки"
|
||||
},
|
||||
"wheel-up": {
|
||||
"description": "Команда для выполнения при прокрутке колеса вверх.",
|
||||
"label": "Команда прокрутки колеса вверх"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
@@ -396,29 +417,29 @@
|
||||
}
|
||||
},
|
||||
"changelog": {
|
||||
"error": {
|
||||
"fetch-failed": "Не удалось загрузить данные журнала изменений. Пожалуйста, попробуйте позже.",
|
||||
"rate-limit": "Превышен лимит GitHub. Попробуйте снова через несколько минут."
|
||||
},
|
||||
"panel": {
|
||||
"title": "Что нового в {version}",
|
||||
"buttons": {
|
||||
"discord": "Присоединиться к нашему Discord",
|
||||
"dismiss": "Ок"
|
||||
},
|
||||
"empty": "Примечания к выпуску пока недоступны.",
|
||||
"highlight-title": "Основные изменения",
|
||||
"section": {
|
||||
"released": "Выпущено {date}",
|
||||
"version": "Версия {version}"
|
||||
},
|
||||
"subtitle": {
|
||||
"fresh": "Спасибо за установку Noctalia! Вот что входит в этот билд.",
|
||||
"updated": "Обновлено с {previousVersion}"
|
||||
},
|
||||
"title": "Что нового в {version}",
|
||||
"version": {
|
||||
"new-user": "Новая установка"
|
||||
},
|
||||
"highlight-title": "Основные изменения",
|
||||
"empty": "Примечания к выпуску пока недоступны.",
|
||||
"section": {
|
||||
"version": "Версия {version}",
|
||||
"released": "Выпущено {date}"
|
||||
},
|
||||
"buttons": {
|
||||
"discord": "Присоединиться к нашему Discord",
|
||||
"feedback": "Отправить отзыв",
|
||||
"dismiss": "Ок"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"rate-limit": "Превышен лимит GitHub. Попробуйте снова через несколько минут."
|
||||
}
|
||||
},
|
||||
"clock": {
|
||||
@@ -428,10 +449,12 @@
|
||||
"activate-app": "Активировать {app}",
|
||||
"clear-history": "Очистить историю",
|
||||
"close-app": "Закрыть {app}",
|
||||
"connect-vpn": "Подключиться к {name}",
|
||||
"cycle-visualizer": "Визуализатор циклов",
|
||||
"disable-bluetooth": "Отключить Bluetooth",
|
||||
"disable-dnd": "Отключить режим \"Не беспокоить\"",
|
||||
"disable-wifi": "Отключить Wi-Fi",
|
||||
"disconnect-vpn": "Отключить {name}",
|
||||
"enable-bluetooth": "Включить Bluetooth",
|
||||
"enable-dnd": "Не беспокоить",
|
||||
"enable-wifi": "Включить Wi-Fi",
|
||||
@@ -1021,6 +1044,10 @@
|
||||
"description": "Записать {filepath}. Тему Comfy нужно установить и активировать вручную.",
|
||||
"description-missing": "Требуется установка {app}"
|
||||
},
|
||||
"telegram": {
|
||||
"description": "Записать {filepath}.",
|
||||
"description-missing": "Требуется установка {app}"
|
||||
},
|
||||
"vicinae": {
|
||||
"description": "Записать {filepath} и перезагрузить",
|
||||
"description-missing": "Требуется установка {app}"
|
||||
@@ -1217,6 +1244,10 @@
|
||||
"description": "Настройка непрозрачности фона панели приложений.",
|
||||
"label": "Непрозрачность фона"
|
||||
},
|
||||
"border-radius": {
|
||||
"description": "Измените радиус границы дока.",
|
||||
"label": "Радиус скругления границы"
|
||||
},
|
||||
"colorize-icons": {
|
||||
"description": "Применить цвета темы к иконкам приложений на панели (только для нефокусированных приложений).",
|
||||
"label": "Раскрасить иконки"
|
||||
@@ -1368,6 +1399,10 @@
|
||||
"description": "Настройка непрозрачности фона запуска.",
|
||||
"label": "Непрозрачность фона"
|
||||
},
|
||||
"clip-preview": {
|
||||
"description": "Показывать предварительный просмотр содержимого буфера обмена при использовании команды >clip.",
|
||||
"label": "Включить предварительный просмотр буфера обмена"
|
||||
},
|
||||
"clipboard-history": {
|
||||
"description": "Доступ к ранее скопированным элементам из запуска.",
|
||||
"label": "Включить историю буфера обмена"
|
||||
@@ -1475,6 +1510,10 @@
|
||||
"description": "Автоматически блокировать экран при приостановке работы системы.",
|
||||
"label": "Блокировать при приостановке"
|
||||
},
|
||||
"show-hibernate": {
|
||||
"description": "Показывать опцию 'спящий режим' в действиях питания.",
|
||||
"label": "Показывать спящий режим"
|
||||
},
|
||||
"title": "Экран блокировки"
|
||||
},
|
||||
"network": {
|
||||
@@ -2038,6 +2077,10 @@
|
||||
"title-matugen": "Сбой обработки шаблонов Matugen",
|
||||
"title-predefined": "Сбой обработки предопределенной цветовой схемы"
|
||||
},
|
||||
"vpn": {
|
||||
"connected": "Подключено к '{name}'",
|
||||
"disconnected": "Отключено от '{name}'"
|
||||
},
|
||||
"wallpaper-colors": {
|
||||
"disabled": "Цвета обоев отключены",
|
||||
"enabled": "Цвета обоев включены",
|
||||
@@ -2071,6 +2114,7 @@
|
||||
"input-muted": "Переключить заглушение ввода",
|
||||
"keep-awake": "Не засыпать",
|
||||
"keyboard-layout": "Раскладка клавиатуры {layout}",
|
||||
"manage-vpn": "Управлять VPN-подключениями",
|
||||
"manage-wifi": "Управление Wi-Fi",
|
||||
"microphone-volume-at": "Громкость микрофона {volume}%\nЛевый клик для настроек. Правый клик для переключения заглушения.\nПрокрутка для изменения громкости.",
|
||||
"move-to-center-section": "Переместить в центральную секцию",
|
||||
@@ -2297,6 +2341,7 @@
|
||||
},
|
||||
"wifi": {
|
||||
"panel": {
|
||||
"available-networks": "Доступные сети",
|
||||
"connect": "Подключить",
|
||||
"connected": "Подключено",
|
||||
"disabled": "Wi-Fi отключен",
|
||||
@@ -2307,6 +2352,7 @@
|
||||
"forget": "Забыть",
|
||||
"forget-network": "Забыть эту сеть?",
|
||||
"forgetting": "Забывание...",
|
||||
"known-networks": "Известные сети",
|
||||
"no-networks": "Сети не найдены",
|
||||
"password": "Пароль",
|
||||
"saved": "Сохранено",
|
||||
|
||||
@@ -123,10 +123,6 @@
|
||||
"stream-description": "Sürekli çalıştırılacak bir komut girin."
|
||||
},
|
||||
"dynamic-text": "Dinamik metin",
|
||||
"hide-vertical": {
|
||||
"description": "Etkinleştirilirse, komut çıktısındaki metin, çubuk dikey düzende (sol veya sağ) olduğunda gösterilmeyecektir.",
|
||||
"label": "Dikey çubukta metni gizle"
|
||||
},
|
||||
"icon": {
|
||||
"description": "Kütüphaneden bir ikon seçin.",
|
||||
"label": "İkon"
|
||||
@@ -136,6 +132,14 @@
|
||||
"label": "Sol tıklama",
|
||||
"update-text": "Sol tıklamayla görüntülenen metni güncelle"
|
||||
},
|
||||
"max-text-length-horizontal": {
|
||||
"description": "Yatay çubukta gösterilecek maksimum karakter sayısı (metni gizlemek için 0)",
|
||||
"label": "Maks. metin uzunluğu (yatay)"
|
||||
},
|
||||
"max-text-length-vertical": {
|
||||
"description": "Dikey çubukta gösterilecek maksimum karakter sayısı (metni gizlemek için 0)",
|
||||
"label": "Maks. metin uzunluğu (dikey)"
|
||||
},
|
||||
"middle-click": {
|
||||
"description": "Butona orta tıklandığında yürütülecek komut.",
|
||||
"label": "Orta tıklama",
|
||||
@@ -157,6 +161,23 @@
|
||||
"text-stream": {
|
||||
"description": "Komuttan gelen akış satırları butonda metin olarak gösterilecektir.",
|
||||
"label": "Akış"
|
||||
},
|
||||
"wheel": {
|
||||
"description": "Kaydırma tekerleği kullanıldığında yürütülecek komut.\nKomutta kaydırma tekerleği deltası için $delta kullanın",
|
||||
"label": "Kaydırma tekerleği",
|
||||
"update-text": "Kaydırmada gösterilen metni güncelle"
|
||||
},
|
||||
"wheel-down": {
|
||||
"description": "Kaydırma tekerleği aşağı kaydırıldığında yürütülecek komut.",
|
||||
"label": "Kaydırma tekerleği aşağı komutu"
|
||||
},
|
||||
"wheel-mode-separate": {
|
||||
"description": "Kaydırma tekerleği yukarı ve aşağı için ayrı komutları etkinleştir",
|
||||
"label": "Ayrı kaydırma tekerleği komutları"
|
||||
},
|
||||
"wheel-up": {
|
||||
"description": "Kaydırma tekerleği yukarı kaydırıldığında yürütülecek komut.",
|
||||
"label": "Kaydırma tekerleği yukarı komutu"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
@@ -396,29 +417,29 @@
|
||||
}
|
||||
},
|
||||
"changelog": {
|
||||
"error": {
|
||||
"fetch-failed": "Değişiklik günlüğü verileri yüklenemedi. Lütfen daha sonra tekrar dene.",
|
||||
"rate-limit": "GitHub sınırına ulaşıldı. Lütfen birkaç dakika sonra tekrar dene."
|
||||
},
|
||||
"panel": {
|
||||
"title": "{version} sürümünde neler yeni",
|
||||
"buttons": {
|
||||
"discord": "Discord sunucumuza katıl",
|
||||
"dismiss": "Tamam"
|
||||
},
|
||||
"empty": "Sürüm notları henüz hazır değil.",
|
||||
"highlight-title": "Öne çıkanlar",
|
||||
"section": {
|
||||
"released": "{date} tarihinde yayımlandı",
|
||||
"version": "Sürüm {version}"
|
||||
},
|
||||
"subtitle": {
|
||||
"fresh": "Noctalia’yı kurduğun için teşekkürler! Bu sürümde gelenler bunlar.",
|
||||
"updated": "{previousVersion} sürümünden güncellendi"
|
||||
},
|
||||
"title": "{version} sürümünde neler yeni",
|
||||
"version": {
|
||||
"new-user": "Yeni kurulum"
|
||||
},
|
||||
"highlight-title": "Öne çıkanlar",
|
||||
"empty": "Sürüm notları henüz hazır değil.",
|
||||
"section": {
|
||||
"version": "Sürüm {version}",
|
||||
"released": "{date} tarihinde yayımlandı"
|
||||
},
|
||||
"buttons": {
|
||||
"discord": "Discord sunucumuza katıl",
|
||||
"feedback": "Geri bildirim gönder",
|
||||
"dismiss": "Tamam"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"rate-limit": "GitHub sınırına ulaşıldı. Lütfen birkaç dakika sonra tekrar dene."
|
||||
}
|
||||
},
|
||||
"clock": {
|
||||
@@ -428,10 +449,12 @@
|
||||
"activate-app": "{app}'i etkinleştir",
|
||||
"clear-history": "Geçmişi temizle",
|
||||
"close-app": "{app}'i kapat",
|
||||
"connect-vpn": "{name} bağlantısına bağlan",
|
||||
"cycle-visualizer": "Döngü görselleştirici",
|
||||
"disable-bluetooth": "Bluetooth'u kapat",
|
||||
"disable-dnd": "Rahatsız Etmeyin'i Kapat",
|
||||
"disable-wifi": "Wi-Fi'ı kapat",
|
||||
"disconnect-vpn": "{name} bağlantısını kes",
|
||||
"enable-bluetooth": "Bluetooth'u etkinleştir",
|
||||
"enable-dnd": "Rahatsız Etmeyin'i Etkinleştir",
|
||||
"enable-wifi": "Wi-Fi'ı etkinleştir",
|
||||
@@ -1021,6 +1044,10 @@
|
||||
"description": "{filepath} dosyasına yaz. Comfy temasının kurulu ve manuel olarak etkinleştirilmiş olması gerekir.",
|
||||
"description-missing": "Kurulum için {app} gereklidir"
|
||||
},
|
||||
"telegram": {
|
||||
"description": "{filepath} dosyasına yaz.",
|
||||
"description-missing": "Kurulum için {app} gereklidir"
|
||||
},
|
||||
"vicinae": {
|
||||
"description": "{filepath} dosyasına yaz ve yeniden yükle",
|
||||
"description-missing": "Kurulum için {app} gereklidir"
|
||||
@@ -1217,6 +1244,10 @@
|
||||
"description": "Dock'un arka plan opaklığını ayarlayın.",
|
||||
"label": "Arka plan opaklığı"
|
||||
},
|
||||
"border-radius": {
|
||||
"description": "Dock'un kenar yarıçapını ayarla.",
|
||||
"label": "Kenar yarıçapı"
|
||||
},
|
||||
"colorize-icons": {
|
||||
"description": "Dock uygulama simgelerine tema renklerini uygulayın (sadece odaklanılmamış uygulamalar).",
|
||||
"label": "Simgeleri Renklendir"
|
||||
@@ -1368,6 +1399,10 @@
|
||||
"description": "Başlatıcının arka plan opaklığını ayarlayın.",
|
||||
"label": "Arka plan opaklığı"
|
||||
},
|
||||
"clip-preview": {
|
||||
"description": ">clip komutu kullanılırken panodaki içeriğin önizlemesini gösterir.",
|
||||
"label": "Panoyu önizlemeyi etkinleştir"
|
||||
},
|
||||
"clipboard-history": {
|
||||
"description": "Başlatıcıdan daha önce kopyalanan öğelere erişin.",
|
||||
"label": "Pano geçmişini etkinleştir"
|
||||
@@ -1475,6 +1510,10 @@
|
||||
"description": "Sistemi askıya alırken otomatik olarak ekranı kilitler.",
|
||||
"label": "Askıya alırken kilitle"
|
||||
},
|
||||
"show-hibernate": {
|
||||
"description": "Güç işlemlerinde 'hazırda beklet' seçeneğini göster.",
|
||||
"label": "Hazırda beklet seçeneğini göster"
|
||||
},
|
||||
"title": "Ekran Kilit"
|
||||
},
|
||||
"network": {
|
||||
@@ -2038,6 +2077,10 @@
|
||||
"title-matugen": "Matugen şablon işleme başarısız oldu.",
|
||||
"title-predefined": "Önceden tanımlanmış renk şeması işleme başarısız oldu."
|
||||
},
|
||||
"vpn": {
|
||||
"connected": "'{name}' ile bağlantı kuruldu",
|
||||
"disconnected": "'{name}' bağlantısı kesildi"
|
||||
},
|
||||
"wallpaper-colors": {
|
||||
"disabled": "Duvar kağıdı renkleri devre dışı",
|
||||
"enabled": "Duvar kağıdı renkleri etkin",
|
||||
@@ -2071,6 +2114,7 @@
|
||||
"input-muted": "Giriş sessizliğini değiştir",
|
||||
"keep-awake": "Uyanık kal",
|
||||
"keyboard-layout": "{layout} klavye düzeni",
|
||||
"manage-vpn": "VPN bağlantılarını yönet",
|
||||
"manage-wifi": "Wi-Fi yönet",
|
||||
"microphone-volume-at": "Mikrofon sesi %{volume}\nAyarlar için sol tık. Sessize almak için sağ tık.\nSesi değiştirmek için kaydırın.",
|
||||
"move-to-center-section": "Orta bölüme taşı",
|
||||
@@ -2297,6 +2341,7 @@
|
||||
},
|
||||
"wifi": {
|
||||
"panel": {
|
||||
"available-networks": "Kullanılabilir Ağlar",
|
||||
"connect": "Bağlan",
|
||||
"connected": "Bağlı",
|
||||
"disabled": "Wi-Fi devre dışı",
|
||||
@@ -2307,6 +2352,7 @@
|
||||
"forget": "Unut",
|
||||
"forget-network": "Bu ağı unut?",
|
||||
"forgetting": "Unutuluyor...",
|
||||
"known-networks": "Bilinen Ağlar",
|
||||
"no-networks": "Ağ bulunamadı",
|
||||
"password": "Şifre",
|
||||
"saved": "Kaydedildi",
|
||||
|
||||
@@ -123,10 +123,6 @@
|
||||
"stream-description": "Введіть команду для безперервного запуску."
|
||||
},
|
||||
"dynamic-text": "Динамічний текст",
|
||||
"hide-vertical": {
|
||||
"description": "Якщо увімкнено, текст з виводу команди не відображатиметься, коли панель знаходиться у вертикальному розташуванні (ліворуч або праворуч).",
|
||||
"label": "Приховати текст у вертикальній панелі"
|
||||
},
|
||||
"icon": {
|
||||
"description": "Вибрати значок з бібліотеки.",
|
||||
"label": "Значок"
|
||||
@@ -136,6 +132,14 @@
|
||||
"label": "Лівий клік",
|
||||
"update-text": "Оновити текст, що відображається, при натисканні лівою кнопкою миші"
|
||||
},
|
||||
"max-text-length-horizontal": {
|
||||
"description": "Максимальна кількість символів для відображення в горизонтальній панелі (0 щоб приховати текст)",
|
||||
"label": "Макс. довжина тексту (горизонтально)"
|
||||
},
|
||||
"max-text-length-vertical": {
|
||||
"description": "Максимальна кількість символів для відображення в вертикальній панелі (0 щоб приховати текст)",
|
||||
"label": "Макс. довжина тексту (вертикально)"
|
||||
},
|
||||
"middle-click": {
|
||||
"description": "Команда для виконання при середньому кліку на кнопку.",
|
||||
"label": "Середній клік",
|
||||
@@ -157,6 +161,23 @@
|
||||
"text-stream": {
|
||||
"description": "Потокові рядки з команди відображатимуться як текст на кнопці.",
|
||||
"label": "Потік"
|
||||
},
|
||||
"wheel": {
|
||||
"description": "Команда для виконання при використанні колеса прокрутки.\nВикористовуйте $delta для дельти колеса прокрутки в команді",
|
||||
"label": "Колесо прокрутки",
|
||||
"update-text": "Оновити відображуваний текст при прокрутці"
|
||||
},
|
||||
"wheel-down": {
|
||||
"description": "Команда для виконання при прокрутці колеса вниз.",
|
||||
"label": "Команда прокрутки колеса вниз"
|
||||
},
|
||||
"wheel-mode-separate": {
|
||||
"description": "Увімкнути окремі команди для колеса прокрутки вгору та вниз",
|
||||
"label": "Окремі команди колеса прокрутки"
|
||||
},
|
||||
"wheel-up": {
|
||||
"description": "Команда для виконання при прокрутці колеса вгору.",
|
||||
"label": "Команда прокрутки колеса вгору"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
@@ -396,29 +417,29 @@
|
||||
}
|
||||
},
|
||||
"changelog": {
|
||||
"error": {
|
||||
"fetch-failed": "Не вдалося завантажити дані журналу змін. Будь ласка, спробуйте пізніше.",
|
||||
"rate-limit": "Перевищено ліміт GitHub. Спробуйте ще раз за кілька хвилин."
|
||||
},
|
||||
"panel": {
|
||||
"title": "Що нового у {version}",
|
||||
"buttons": {
|
||||
"discord": "Приєднатися до нашого Discord",
|
||||
"dismiss": "Ок"
|
||||
},
|
||||
"empty": "Примітки до релізу ще недоступні.",
|
||||
"highlight-title": "Основні зміни",
|
||||
"section": {
|
||||
"released": "Випущено {date}",
|
||||
"version": "Версія {version}"
|
||||
},
|
||||
"subtitle": {
|
||||
"fresh": "Дякуємо, що встановили Noctalia! Ось що містить цей білд.",
|
||||
"updated": "Оновлено з {previousVersion}"
|
||||
},
|
||||
"title": "Що нового у {version}",
|
||||
"version": {
|
||||
"new-user": "Нове встановлення"
|
||||
},
|
||||
"highlight-title": "Основні зміни",
|
||||
"empty": "Примітки до релізу ще недоступні.",
|
||||
"section": {
|
||||
"version": "Версія {version}",
|
||||
"released": "Випущено {date}"
|
||||
},
|
||||
"buttons": {
|
||||
"discord": "Приєднатися до нашого Discord",
|
||||
"feedback": "Надіслати відгук",
|
||||
"dismiss": "Ок"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"rate-limit": "Перевищено ліміт GitHub. Спробуйте ще раз за кілька хвилин."
|
||||
}
|
||||
},
|
||||
"clock": {
|
||||
@@ -428,10 +449,12 @@
|
||||
"activate-app": "Активувати {app}",
|
||||
"clear-history": "Очистити історію",
|
||||
"close-app": "Закрити {app}",
|
||||
"connect-vpn": "Підключитися до {name}",
|
||||
"cycle-visualizer": "Візуалізатор циклів",
|
||||
"disable-bluetooth": "Вимкнути Bluetooth",
|
||||
"disable-dnd": "Вимкнути режим \"Не турбувати\"",
|
||||
"disable-wifi": "Вимкнути Wi-Fi",
|
||||
"disconnect-vpn": "Відключити {name}",
|
||||
"enable-bluetooth": "Увімкнути Bluetooth",
|
||||
"enable-dnd": "Увімкнути режим \"Не турбувати\"",
|
||||
"enable-wifi": "Увімкнути Wi-Fi",
|
||||
@@ -1021,6 +1044,10 @@
|
||||
"description": "Записати {filepath}. Тему Comfy потрібно встановити та активувати вручну.",
|
||||
"description-missing": "Потрібна установка {app}"
|
||||
},
|
||||
"telegram": {
|
||||
"description": "Записати {filepath}.",
|
||||
"description-missing": "Потрібна установка {app}"
|
||||
},
|
||||
"vicinae": {
|
||||
"description": "Записати {filepath} та перезавантажити",
|
||||
"description-missing": "Потрібна установка {app}"
|
||||
@@ -1217,6 +1244,10 @@
|
||||
"description": "Налаштуйте непрозорість фону дока.",
|
||||
"label": "Непрозорість фону"
|
||||
},
|
||||
"border-radius": {
|
||||
"description": "Налаштуйте радіус заокруглення країв док-панелі.",
|
||||
"label": "Радіус заокруглення"
|
||||
},
|
||||
"colorize-icons": {
|
||||
"description": "Застосувати кольори теми до значків програм у доці (тільки неактивні програми).",
|
||||
"label": "Розфарбувати значки"
|
||||
@@ -1368,6 +1399,10 @@
|
||||
"description": "Налаштуйте непрозорість фону запускача.",
|
||||
"label": "Непрозорість фону"
|
||||
},
|
||||
"clip-preview": {
|
||||
"description": "Показувати попередній перегляд вмісту буфера обміну при використанні команди >clip.",
|
||||
"label": "Увімкнути попередній перегляд буфера обміну"
|
||||
},
|
||||
"clipboard-history": {
|
||||
"description": "Отримати доступ до раніше скопійованих елементів із запускача.",
|
||||
"label": "Увімкнути історію буфера обміну"
|
||||
@@ -1475,6 +1510,10 @@
|
||||
"description": "Автоматично блокувати екран при призупиненні системи.",
|
||||
"label": "Блокувати при призупиненні"
|
||||
},
|
||||
"show-hibernate": {
|
||||
"description": "Показувати опцію 'сплячий режим' у діях живлення.",
|
||||
"label": "Показувати сплячий режим"
|
||||
},
|
||||
"title": "Екран блокування"
|
||||
},
|
||||
"network": {
|
||||
@@ -2038,6 +2077,10 @@
|
||||
"title-matugen": "Помилка обробки шаблонів Matugen",
|
||||
"title-predefined": "Помилка обробки попередньо визначеної колірної схеми"
|
||||
},
|
||||
"vpn": {
|
||||
"connected": "Підключено до '{name}'",
|
||||
"disconnected": "Відключено від '{name}'"
|
||||
},
|
||||
"wallpaper-colors": {
|
||||
"disabled": "Кольори шпалер вимкнено",
|
||||
"enabled": "Кольори шпалер увімкнено",
|
||||
@@ -2071,6 +2114,7 @@
|
||||
"input-muted": "Перемкнути вимкнення входу",
|
||||
"keep-awake": "Не спати",
|
||||
"keyboard-layout": "Розкладка клавіатури {layout}",
|
||||
"manage-vpn": "Керувати підключеннями VPN",
|
||||
"manage-wifi": "Керувати Wi-Fi",
|
||||
"microphone-volume-at": "Гучність мікрофона на {volume}%\nЛівий клік для налаштувань. Правий клік для вимкнення звуку.\nПрокрутка для зміни гучності.",
|
||||
"move-to-center-section": "Перемістити в центральну секцію",
|
||||
@@ -2297,6 +2341,7 @@
|
||||
},
|
||||
"wifi": {
|
||||
"panel": {
|
||||
"available-networks": "Доступні мережі",
|
||||
"connect": "Підключити",
|
||||
"connected": "Підключено",
|
||||
"disabled": "Wi-Fi вимкнено",
|
||||
@@ -2307,6 +2352,7 @@
|
||||
"forget": "Забути",
|
||||
"forget-network": "Забути цю мережу?",
|
||||
"forgetting": "Забування...",
|
||||
"known-networks": "Відомі мережі",
|
||||
"no-networks": "Мереж не знайдено",
|
||||
"password": "Пароль",
|
||||
"saved": "Збережено",
|
||||
|
||||
@@ -123,10 +123,6 @@
|
||||
"stream-description": "输入一个要持续运行的命令。"
|
||||
},
|
||||
"dynamic-text": "动态文本",
|
||||
"hide-vertical": {
|
||||
"description": "如果启用,当栏处于垂直布局(左或右)时,将不显示命令输出的文本。",
|
||||
"label": "在垂直栏中隐藏文本"
|
||||
},
|
||||
"icon": {
|
||||
"description": "从库中选择图标。",
|
||||
"label": "图标"
|
||||
@@ -136,6 +132,14 @@
|
||||
"label": "左键点击",
|
||||
"update-text": "左键单击时更新显示的文本"
|
||||
},
|
||||
"max-text-length-horizontal": {
|
||||
"description": "在水平栏中显示的最大字符数(0 为隐藏文本)",
|
||||
"label": "最大文本长度(水平)"
|
||||
},
|
||||
"max-text-length-vertical": {
|
||||
"description": "在垂直栏中显示的最大字符数(0 为隐藏文本)",
|
||||
"label": "最大文本长度(垂直)"
|
||||
},
|
||||
"middle-click": {
|
||||
"description": "中键点击按钮时执行的命令。",
|
||||
"label": "中键点击",
|
||||
@@ -157,6 +161,23 @@
|
||||
"text-stream": {
|
||||
"description": "来自命令的流式输出行将作为文本显示在按钮上。",
|
||||
"label": "流"
|
||||
},
|
||||
"wheel": {
|
||||
"description": "使用滚轮时执行的命令。\n在命令中使用 $delta 表示滚轮增量",
|
||||
"label": "滚轮",
|
||||
"update-text": "滚轮滚动时更新显示的文本"
|
||||
},
|
||||
"wheel-down": {
|
||||
"description": "滚轮向下滚动时执行的命令。",
|
||||
"label": "滚轮向下命令"
|
||||
},
|
||||
"wheel-mode-separate": {
|
||||
"description": "为滚轮向上和向下启用单独的命令",
|
||||
"label": "分开滚轮命令"
|
||||
},
|
||||
"wheel-up": {
|
||||
"description": "滚轮向上滚动时执行的命令。",
|
||||
"label": "滚轮向上命令"
|
||||
}
|
||||
},
|
||||
"dialog": {
|
||||
@@ -228,10 +249,10 @@
|
||||
"notification-history": {
|
||||
"hide-badge-when-zero": {
|
||||
"description": "当没有未读通知时隐藏通知徽章。",
|
||||
"label": "零时隐藏徽章"
|
||||
"label": "空时隐藏徽章"
|
||||
},
|
||||
"show-unread-badge": {
|
||||
"description": "显示显示未读通知数量的徽章。",
|
||||
"description": "显示一个用于展示未读通知数量的徽章。",
|
||||
"label": "显示未读徽章"
|
||||
}
|
||||
},
|
||||
@@ -396,29 +417,29 @@
|
||||
}
|
||||
},
|
||||
"changelog": {
|
||||
"error": {
|
||||
"fetch-failed": "无法加载更新日志数据,请稍后再试。",
|
||||
"rate-limit": "已达到 GitHub 速率限制,请稍后再试。"
|
||||
},
|
||||
"panel": {
|
||||
"title": "{version} 有哪些更新",
|
||||
"buttons": {
|
||||
"discord": "加入我们的 Discord",
|
||||
"dismiss": "确定"
|
||||
},
|
||||
"empty": "暂时没有可用的发行说明。",
|
||||
"highlight-title": "重点更新",
|
||||
"section": {
|
||||
"released": "{date} 发布",
|
||||
"version": "版本 {version}"
|
||||
},
|
||||
"subtitle": {
|
||||
"fresh": "感谢安装 Noctalia!以下是本次构建包含的内容。",
|
||||
"updated": "已从 {previousVersion} 更新"
|
||||
},
|
||||
"title": "{version} 有哪些更新",
|
||||
"version": {
|
||||
"new-user": "全新安装"
|
||||
},
|
||||
"highlight-title": "重点更新",
|
||||
"empty": "暂时没有可用的发行说明。",
|
||||
"section": {
|
||||
"version": "版本 {version}",
|
||||
"released": "{date} 发布"
|
||||
},
|
||||
"buttons": {
|
||||
"discord": "加入我们的 Discord",
|
||||
"feedback": "发送反馈",
|
||||
"dismiss": "确定"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"rate-limit": "已达到 GitHub 速率限制,请稍后再试。"
|
||||
}
|
||||
},
|
||||
"clock": {
|
||||
@@ -428,22 +449,24 @@
|
||||
"activate-app": "激活 {app}",
|
||||
"clear-history": "清除历史记录",
|
||||
"close-app": "关闭 {app}",
|
||||
"cycle-visualizer": "周期可视化工具",
|
||||
"connect-vpn": "连接 {name}",
|
||||
"cycle-visualizer": "切换可视化器样式",
|
||||
"disable-bluetooth": "禁用蓝牙",
|
||||
"disable-dnd": "关闭勿扰模式",
|
||||
"disable-wifi": "禁用Wi-Fi",
|
||||
"disconnect-vpn": "断开 {name}",
|
||||
"enable-bluetooth": "启用蓝牙",
|
||||
"enable-dnd": "启用勿扰模式",
|
||||
"enable-wifi": "启用 Wi-Fi",
|
||||
"next": "下一个",
|
||||
"next": "下一首",
|
||||
"open-calendar": "打开日历",
|
||||
"open-display-settings": "显示设置",
|
||||
"open-launcher": "打开启动器",
|
||||
"open-mixer": "音频调音台",
|
||||
"open-mixer": "音频混音器",
|
||||
"open-settings": "打开设置",
|
||||
"pause": "暂停",
|
||||
"play": "玩",
|
||||
"previous": "上一个",
|
||||
"play": "播放",
|
||||
"previous": "上一首",
|
||||
"random-wallpaper": "随机壁纸",
|
||||
"toggle-mute": "切换静音",
|
||||
"widget-settings": "小部件设置"
|
||||
@@ -1021,6 +1044,10 @@
|
||||
"description": "写入 {filepath}。Comfy 主题需要手动安装和激活。",
|
||||
"description-missing": "需要安装 {app}"
|
||||
},
|
||||
"telegram": {
|
||||
"description": "写入 {filepath}。",
|
||||
"description-missing": "需要安装 {app}"
|
||||
},
|
||||
"vicinae": {
|
||||
"description": "写入 {filepath} 并重新加载",
|
||||
"description-missing": "需要安装 {app}"
|
||||
@@ -1217,6 +1244,10 @@
|
||||
"description": "调整 Dock 的背景不透明度。",
|
||||
"label": "背景不透明度"
|
||||
},
|
||||
"border-radius": {
|
||||
"description": "调整程序坞的边框半径。",
|
||||
"label": "边框半径"
|
||||
},
|
||||
"colorize-icons": {
|
||||
"description": "将主题颜色应用到 Dock 应用图标(仅限非聚焦应用)。",
|
||||
"label": "着色图标"
|
||||
@@ -1368,6 +1399,10 @@
|
||||
"description": "调整启动器的背景不透明度。",
|
||||
"label": "背景不透明度"
|
||||
},
|
||||
"clip-preview": {
|
||||
"description": "在使用 >clip 命令时显示剪贴板内容的预览。",
|
||||
"label": "启用剪贴板预览"
|
||||
},
|
||||
"clipboard-history": {
|
||||
"description": "从启动器访问之前复制的项目。",
|
||||
"label": "启用剪贴板历史记录"
|
||||
@@ -1475,6 +1510,10 @@
|
||||
"description": "系统挂起时自动锁定屏幕。",
|
||||
"label": "挂起时锁定"
|
||||
},
|
||||
"show-hibernate": {
|
||||
"description": "在电源操作中显示'休眠'选项。",
|
||||
"label": "显示休眠"
|
||||
},
|
||||
"title": "锁屏"
|
||||
},
|
||||
"network": {
|
||||
@@ -2038,6 +2077,10 @@
|
||||
"title-matugen": "Matugen模板处理失败",
|
||||
"title-predefined": "预定义的颜色方案处理失败"
|
||||
},
|
||||
"vpn": {
|
||||
"connected": "已连接到“{name}”",
|
||||
"disconnected": "已断开与“{name}”的连接"
|
||||
},
|
||||
"wallpaper-colors": {
|
||||
"disabled": "壁纸颜色已禁用",
|
||||
"enabled": "壁纸颜色已启用",
|
||||
@@ -2071,6 +2114,7 @@
|
||||
"input-muted": "静音输入设备",
|
||||
"keep-awake": "保持唤醒",
|
||||
"keyboard-layout": "{layout} 键盘布局",
|
||||
"manage-vpn": "管理 VPN 连接",
|
||||
"manage-wifi": "管理 Wi-Fi",
|
||||
"microphone-volume-at": "麦克风音量 {volume}%\n左键点击进入设置。右键点击切换静音。\n滚动滚轮调节音量。",
|
||||
"move-to-center-section": "移动到中央部分",
|
||||
@@ -2297,6 +2341,7 @@
|
||||
},
|
||||
"wifi": {
|
||||
"panel": {
|
||||
"available-networks": "可用网络",
|
||||
"connect": "连接",
|
||||
"connected": "已连接",
|
||||
"disabled": "Wi-Fi 已禁用",
|
||||
@@ -2307,6 +2352,7 @@
|
||||
"forget": "忘记",
|
||||
"forget-network": "忘记此网络?",
|
||||
"forgetting": "正在忘记...",
|
||||
"known-networks": "已知网络",
|
||||
"no-networks": "未找到网络",
|
||||
"password": "密码",
|
||||
"saved": "已保存",
|
||||
|
||||
@@ -87,9 +87,6 @@
|
||||
"panelsAttachedToBar": true,
|
||||
"settingsPanelAttachToBar": false
|
||||
},
|
||||
"changelog": {
|
||||
"lastSeenVersion": ""
|
||||
},
|
||||
"location": {
|
||||
"name": "Tokyo",
|
||||
"weatherEnabled": true,
|
||||
@@ -143,6 +140,7 @@
|
||||
},
|
||||
"appLauncher": {
|
||||
"enableClipboardHistory": false,
|
||||
"enableClipPreview": true,
|
||||
"position": "center",
|
||||
"pinnedExecs": [],
|
||||
"useApp2Unit": false,
|
||||
@@ -223,6 +221,7 @@
|
||||
"enabled": true,
|
||||
"displayMode": "always_visible",
|
||||
"backgroundOpacity": 1,
|
||||
"radiusRatio": 0.1,
|
||||
"floatingRatio": 1,
|
||||
"size": 1,
|
||||
"onlySameOutput": true,
|
||||
@@ -292,7 +291,8 @@
|
||||
"visualizerType": "linear",
|
||||
"visualizerQuality": "high",
|
||||
"mprisBlacklist": [],
|
||||
"preferredPlayer": ""
|
||||
"preferredPlayer": "",
|
||||
"externalMixer": "pwvucontrol || pavucontrol"
|
||||
},
|
||||
"brightness": {
|
||||
"brightnessStep": 5,
|
||||
@@ -336,6 +336,9 @@
|
||||
"manualSunrise": "06:30",
|
||||
"manualSunset": "18:30"
|
||||
},
|
||||
"changelog": {
|
||||
"lastSeenVersion": ""
|
||||
},
|
||||
"hooks": {
|
||||
"enabled": false,
|
||||
"wallpaperChange": "",
|
||||
|
||||
@@ -15,10 +15,48 @@ fi
|
||||
# Create the destination directory if it doesn't exist.
|
||||
mkdir -p "$DEST_DIR"
|
||||
|
||||
# Loop through all files in the source directory ending with .frag
|
||||
for shader in "$SOURCE_DIR"*.frag; do
|
||||
# Check if a file was found (to handle the case of no .frag files).
|
||||
if [ -f "$shader" ]; then
|
||||
# Array to hold the list of full paths to the shaders.
|
||||
SHADERS_TO_COMPILE=()
|
||||
|
||||
# Specific files mode.
|
||||
if [ "$#" -gt 0 ]; then
|
||||
|
||||
# Loop through all command-line arguments ($@ holds all arguments).
|
||||
for SINGLE_FILE in "$@"; do
|
||||
|
||||
# Construct the full path to the source file.
|
||||
FULL_PATH="$SOURCE_DIR$SINGLE_FILE"
|
||||
|
||||
# Check if the specified file exists in the SOURCE_DIR.
|
||||
if [ ! -f "$FULL_PATH" ]; then
|
||||
echo "Error: Specified file '$SINGLE_FILE' not found in $SOURCE_DIR! Skipping."
|
||||
continue
|
||||
fi
|
||||
|
||||
# Add the valid file to the compilation list.
|
||||
SHADERS_TO_COMPILE+=("$FULL_PATH")
|
||||
done
|
||||
|
||||
# Check if any valid files were found to compile.
|
||||
if [ ${#SHADERS_TO_COMPILE[@]} -eq 0 ]; then
|
||||
echo "No valid shaders found to compile."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Whole directory mode (no argument provided).
|
||||
else
|
||||
# Use find to generate the list of files and assign it to the array.
|
||||
while IFS= read -r shader_path; do
|
||||
if [ -n "$shader_path" ]; then
|
||||
SHADERS_TO_COMPILE+=("$shader_path")
|
||||
fi
|
||||
done < <(find "$SOURCE_DIR" -maxdepth 1 -name "*.frag")
|
||||
|
||||
fi
|
||||
|
||||
# Loop through the list of shaders to compile.
|
||||
for shader in "${SHADERS_TO_COMPILE[@]}"; do
|
||||
|
||||
# Get the base name of the file (e.g., wp_fade).
|
||||
shader_name=$(basename "$shader" .frag)
|
||||
|
||||
@@ -29,8 +67,7 @@ for shader in "$SOURCE_DIR"*.frag; do
|
||||
/usr/lib/qt6/bin/qsb --qt6 -o "$output_path" "$shader"
|
||||
|
||||
# Print a message to confirm compilation.
|
||||
echo "Compiled $shader to $output_path"
|
||||
fi
|
||||
echo "Compiled $(basename "$shader") to $output_path"
|
||||
done
|
||||
|
||||
echo "Shader compilation complete."
|
||||
@@ -296,6 +296,7 @@ Singleton {
|
||||
// applauncher
|
||||
property JsonObject appLauncher: JsonObject {
|
||||
property bool enableClipboardHistory: false
|
||||
property bool enableClipPreview: true
|
||||
// Position: center, top_left, top_right, bottom_left, bottom_right, bottom_center, top_center
|
||||
property string position: "center"
|
||||
property list<string> pinnedExecs: []
|
||||
@@ -385,6 +386,7 @@ Singleton {
|
||||
property bool enabled: true
|
||||
property string displayMode: "always_visible" // "always_visible", "auto_hide", "exclusive"
|
||||
property real backgroundOpacity: 1.0
|
||||
property real radiusRatio: 0.1
|
||||
property real floatingRatio: 1.0
|
||||
property real size: 1
|
||||
property bool onlySameOutput: true
|
||||
|
||||
183
Commons/ShellState.qml
Normal file
183
Commons/ShellState.qml
Normal file
@@ -0,0 +1,183 @@
|
||||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
|
||||
// Centralized shell state management for small cache files
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property string stateFile: ""
|
||||
property bool isLoaded: false
|
||||
|
||||
// State properties for different services
|
||||
readonly property alias data: adapter
|
||||
|
||||
// Signals for state changes
|
||||
signal displayStateChanged
|
||||
signal notificationsStateChanged
|
||||
signal changelogStateChanged
|
||||
signal colorSchemesListChanged
|
||||
|
||||
Component.onCompleted: {
|
||||
// Setup state file path (needs Settings to be available)
|
||||
Qt.callLater(() => {
|
||||
if (typeof Settings !== 'undefined' && Settings.cacheDir) {
|
||||
stateFile = Settings.cacheDir + "shell-state.json";
|
||||
stateFileView.path = stateFile;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// FileView for shell state
|
||||
FileView {
|
||||
id: stateFileView
|
||||
printErrors: false
|
||||
watchChanges: false
|
||||
|
||||
adapter: JsonAdapter {
|
||||
id: adapter
|
||||
|
||||
// CompositorService: display scales
|
||||
property var display: ({})
|
||||
|
||||
// NotificationService: notification state
|
||||
property var notificationsState: ({
|
||||
lastSeenTs: 0
|
||||
})
|
||||
|
||||
// UpdateService: changelog state
|
||||
property var changelogState: ({
|
||||
lastSeenVersion: ""
|
||||
})
|
||||
|
||||
// SchemeDownloader: color schemes list
|
||||
property var colorSchemesList: ({
|
||||
schemes: [],
|
||||
timestamp: 0
|
||||
})
|
||||
|
||||
// WallpaperService: current wallpapers per screen
|
||||
property var wallpapers: ({})
|
||||
}
|
||||
|
||||
onLoaded: {
|
||||
root.isLoaded = true;
|
||||
Logger.d("ShellState", "Loaded state file");
|
||||
}
|
||||
|
||||
onLoadFailed: error => {
|
||||
if (error === 2) {
|
||||
// File doesn't exist, will be created on first write
|
||||
root.isLoaded = true;
|
||||
Logger.d("ShellState", "State file doesn't exist, will create on first write");
|
||||
} else {
|
||||
Logger.e("ShellState", "Failed to load state file:", error);
|
||||
root.isLoaded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Debounced save timer
|
||||
Timer {
|
||||
id: saveTimer
|
||||
interval: 300
|
||||
onTriggered: performSave()
|
||||
}
|
||||
|
||||
property bool saveQueued: false
|
||||
|
||||
function save() {
|
||||
saveQueued = true;
|
||||
saveTimer.restart();
|
||||
}
|
||||
|
||||
function performSave() {
|
||||
if (!saveQueued || !stateFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
saveQueued = false;
|
||||
|
||||
try {
|
||||
// Ensure cache directory exists
|
||||
Quickshell.execDetached(["mkdir", "-p", Settings.cacheDir]);
|
||||
|
||||
Qt.callLater(() => {
|
||||
try {
|
||||
stateFileView.writeAdapter();
|
||||
Logger.d("ShellState", "Saved state file");
|
||||
} catch (writeError) {
|
||||
Logger.e("ShellState", "Failed to write state file:", writeError);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
Logger.e("ShellState", "Failed to save state:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Convenience functions for each service
|
||||
|
||||
// Display state (CompositorService)
|
||||
function setDisplay(displayData) {
|
||||
adapter.display = displayData;
|
||||
save();
|
||||
displayStateChanged();
|
||||
}
|
||||
|
||||
function getDisplay() {
|
||||
return adapter.display || {};
|
||||
}
|
||||
|
||||
// Notifications state (NotificationService)
|
||||
function setNotificationsState(stateData) {
|
||||
adapter.notificationsState = stateData;
|
||||
save();
|
||||
notificationsStateChanged();
|
||||
}
|
||||
|
||||
function getNotificationsState() {
|
||||
return adapter.notificationsState || {
|
||||
lastSeenTs: 0
|
||||
};
|
||||
}
|
||||
|
||||
// Changelog state (UpdateService)
|
||||
function setChangelogState(stateData) {
|
||||
adapter.changelogState = stateData;
|
||||
save();
|
||||
changelogStateChanged();
|
||||
}
|
||||
|
||||
function getChangelogState() {
|
||||
return adapter.changelogState || {
|
||||
lastSeenVersion: ""
|
||||
};
|
||||
}
|
||||
|
||||
// Color schemes list (SchemeDownloader)
|
||||
function setColorSchemesList(listData) {
|
||||
adapter.colorSchemesList = listData;
|
||||
save();
|
||||
colorSchemesListChanged();
|
||||
}
|
||||
|
||||
function getColorSchemesList() {
|
||||
return adapter.colorSchemesList || {
|
||||
schemes: [],
|
||||
timestamp: 0
|
||||
};
|
||||
}
|
||||
|
||||
// Wallpapers (WallpaperService)
|
||||
function setWallpapers(wallpapersData) {
|
||||
adapter.wallpapers = wallpapersData;
|
||||
save();
|
||||
}
|
||||
|
||||
function getWallpapers() {
|
||||
return adapter.wallpapers || {};
|
||||
}
|
||||
}
|
||||
|
||||
34
Helpers/TextFormatter.js
Normal file
34
Helpers/TextFormatter.js
Normal file
@@ -0,0 +1,34 @@
|
||||
.pragma library
|
||||
|
||||
/**
|
||||
* Wrap text in a nicely styled HTML container for display
|
||||
* @param {string} text - The text to display
|
||||
* @returns {string} HTML string
|
||||
*/
|
||||
function wrapTextForDisplay(text) {
|
||||
// Escape HTML special characters
|
||||
const escapeHtml = (s) =>
|
||||
s.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
|
||||
return `
|
||||
<div style="
|
||||
font-family: 'Fira Code', 'Courier New', monospace;
|
||||
white-space: pre-wrap;
|
||||
background: linear-gradient(135deg, #2c3e50, #34495e);
|
||||
color: #ecf0f1;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
||||
overflow-x: auto;
|
||||
line-height: 1.5;
|
||||
font-size: 14px;
|
||||
border: 1px solid #3d566e;
|
||||
">
|
||||
${escapeHtml(text)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -20,6 +20,8 @@ Item {
|
||||
property bool oppositeDirection: false
|
||||
property bool hovered: false
|
||||
property bool rotateText: false
|
||||
property color customBackgroundColor: Qt.rgba(0, 0, 0, 0)
|
||||
property color customTextIconColor: Qt.rgba(0, 0, 0, 0)
|
||||
|
||||
readonly property string barPosition: Settings.data.bar.position
|
||||
readonly property bool isVerticalBar: barPosition === "left" || barPosition === "right"
|
||||
@@ -57,6 +59,8 @@ Item {
|
||||
hovered: root.hovered
|
||||
density: root.density
|
||||
rotateText: root.rotateText
|
||||
customBackgroundColor: root.customBackgroundColor
|
||||
customTextIconColor: root.customTextIconColor
|
||||
onShown: root.shown()
|
||||
onHidden: root.hidden()
|
||||
onEntered: root.entered()
|
||||
@@ -82,6 +86,8 @@ Item {
|
||||
oppositeDirection: root.oppositeDirection
|
||||
hovered: root.hovered
|
||||
density: root.density
|
||||
customBackgroundColor: root.customBackgroundColor
|
||||
customTextIconColor: root.customTextIconColor
|
||||
onShown: root.shown()
|
||||
onHidden: root.hidden()
|
||||
onEntered: root.entered()
|
||||
|
||||
@@ -20,6 +20,8 @@ Item {
|
||||
property bool forceClose: false
|
||||
property bool oppositeDirection: false
|
||||
property bool hovered: false
|
||||
property color customBackgroundColor: Qt.rgba(0, 0, 0, 0)
|
||||
property color customTextIconColor: Qt.rgba(0, 0, 0, 0)
|
||||
|
||||
// Effective shown state (true if hovered/animated open or forced)
|
||||
readonly property bool revealed: !forceClose && (forceOpen || showPill)
|
||||
@@ -78,7 +80,7 @@ Item {
|
||||
width: root.width
|
||||
height: pillHeight
|
||||
radius: halfPillHeight
|
||||
color: hovered ? Color.mHover : Style.capsuleColor
|
||||
color: hovered ? (customBackgroundColor.a > 0 ? Qt.lighter(customBackgroundColor, 1.1) : Color.mHover) : (customBackgroundColor.a > 0 ? customBackgroundColor : Style.capsuleColor)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
readonly property int halfPillHeight: Math.round(pillHeight * 0.5)
|
||||
@@ -129,7 +131,7 @@ Item {
|
||||
pointSize: textSize
|
||||
applyUiScale: false
|
||||
font.weight: Style.fontWeightBold
|
||||
color: hovered ? Color.mOnHover : (forceOpen ? Color.mOnSurface : Color.mPrimary)
|
||||
color: hovered ? (customTextIconColor.a > 0 ? customTextIconColor : Color.mOnHover) : (customTextIconColor.a > 0 ? customTextIconColor : (forceOpen ? Color.mOnSurface : Color.mPrimary))
|
||||
visible: revealed
|
||||
}
|
||||
|
||||
@@ -163,7 +165,7 @@ Item {
|
||||
icon: root.icon
|
||||
pointSize: iconSize
|
||||
applyUiScale: false
|
||||
color: hovered ? Color.mOnHover : Color.mOnSurface
|
||||
color: hovered ? (customTextIconColor.a > 0 ? customTextIconColor : Color.mOnHover) : (customTextIconColor.a > 0 ? customTextIconColor : Color.mOnSurface)
|
||||
// Center horizontally
|
||||
x: (iconCircle.width - width) / 2
|
||||
// Center vertically accounting for font metrics
|
||||
|
||||
@@ -21,6 +21,8 @@ Item {
|
||||
property bool oppositeDirection: false
|
||||
property bool hovered: false
|
||||
property bool rotateText: false
|
||||
property color customBackgroundColor: Qt.rgba(0, 0, 0, 0)
|
||||
property color customTextIconColor: Qt.rgba(0, 0, 0, 0)
|
||||
|
||||
// Bar position detection for pill direction
|
||||
readonly property string barPosition: Settings.data.bar.position
|
||||
@@ -49,10 +51,9 @@ Item {
|
||||
// Sizing logic for vertical bars
|
||||
readonly property int buttonSize: Style.capsuleHeight
|
||||
readonly property int pillHeight: buttonSize
|
||||
readonly property int pillPaddingVertical: 3 * 2 // Very precise adjustment don't replace by Style.margin
|
||||
readonly property int pillOverlap: Math.round(buttonSize * 0.5)
|
||||
readonly property int maxPillWidth: rotateText ? Math.max(buttonSize, Math.round(textItem.implicitHeight + pillPaddingVertical * 2)) : buttonSize
|
||||
readonly property int maxPillHeight: rotateText ? Math.max(1, Math.round(textItem.implicitWidth + pillPaddingVertical * 2 + Math.round(iconCircle.height / 4))) : Math.max(1, Math.round(textItem.implicitHeight + pillPaddingVertical * 4))
|
||||
readonly property int maxPillWidth: rotateText ? Math.max(buttonSize, Math.round(textItem.implicitHeight + Style.marginM * 2)) : buttonSize
|
||||
readonly property int maxPillHeight: rotateText ? Math.max(1, Math.round(textItem.implicitWidth + Style.marginM * 2 + Math.round(iconCircle.height / 4))) : Math.max(1, Math.round(textItem.implicitHeight + Style.marginM * 2))
|
||||
|
||||
readonly property real iconSize: {
|
||||
switch (root.density) {
|
||||
@@ -91,7 +92,7 @@ Item {
|
||||
width: buttonSize
|
||||
height: revealed ? (buttonSize + maxPillHeight - pillOverlap) : buttonSize
|
||||
radius: halfButtonSize
|
||||
color: hovered ? Color.mHover : Style.capsuleColor
|
||||
color: hovered ? (customBackgroundColor.a > 0 ? Qt.lighter(customBackgroundColor, 1.1) : Color.mHover) : (customBackgroundColor.a > 0 ? customBackgroundColor : Style.capsuleColor)
|
||||
|
||||
readonly property int halfButtonSize: Math.round(buttonSize * 0.5)
|
||||
|
||||
@@ -130,7 +131,7 @@ Item {
|
||||
id: textItem
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.verticalCenterOffset: rotateText ? Math.round(iconCircle.height / 4) : getVerticalCenterOffset()
|
||||
anchors.verticalCenterOffset: openDownward ? Style.marginXXS : -Style.marginXXS
|
||||
rotation: rotateText ? -90 : 0
|
||||
text: root.text + root.suffix
|
||||
family: Settings.data.ui.fontFixed
|
||||
@@ -139,15 +140,12 @@ Item {
|
||||
font.weight: Style.fontWeightMedium
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: hovered ? Color.mOnHover : (forceOpen ? Color.mOnSurface : Color.mPrimary)
|
||||
color: hovered ? (customTextIconColor.a > 0 ? customTextIconColor : Color.mOnHover) : (customTextIconColor.a > 0 ? customTextIconColor : (forceOpen ? Color.mOnSurface : Color.mPrimary))
|
||||
visible: revealed
|
||||
|
||||
function getVerticalCenterOffset() {
|
||||
var offset = openDownward ? Math.round(pillPaddingVertical * 0.75) : -Math.round(pillPaddingVertical * 0.75);
|
||||
if (forceOpen) {
|
||||
offset += oppositeDirection ? -Style.marginXXS : Style.marginXXS;
|
||||
}
|
||||
return offset;
|
||||
// A small, symmetrical offset to push the text slightly away from the icon's edge.
|
||||
return openDownward ? Style.marginXS : -Style.marginXS;
|
||||
}
|
||||
}
|
||||
Behavior on width {
|
||||
@@ -189,7 +187,7 @@ Item {
|
||||
icon: root.icon
|
||||
pointSize: iconSize
|
||||
applyUiScale: false
|
||||
color: hovered ? Color.mOnHover : Color.mOnSurface
|
||||
color: hovered ? (customTextIconColor.a > 0 ? customTextIconColor : Color.mOnHover) : (customTextIconColor.a > 0 ? customTextIconColor : Color.mOnSurface)
|
||||
// Center horizontally
|
||||
x: (iconCircle.width - width) / 2
|
||||
// Center vertically accounting for font metrics
|
||||
|
||||
@@ -34,10 +34,11 @@ Item {
|
||||
readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
|
||||
readonly property string displayMode: widgetSettings.displayMode !== undefined ? widgetSettings.displayMode : widgetMetadata.displayMode
|
||||
readonly property real warningThreshold: widgetSettings.warningThreshold !== undefined ? widgetSettings.warningThreshold : widgetMetadata.warningThreshold
|
||||
readonly property bool isLowBattery: !charging && percent <= warningThreshold
|
||||
|
||||
// Test mode
|
||||
readonly property bool testMode: false
|
||||
readonly property int testPercent: 100
|
||||
readonly property int testPercent: 15
|
||||
readonly property bool testCharging: false
|
||||
|
||||
// Main properties
|
||||
@@ -120,6 +121,8 @@ Item {
|
||||
autoHide: false
|
||||
forceOpen: isReady && (testMode || battery.isLaptopBattery) && displayMode === "alwaysShow"
|
||||
forceClose: displayMode === "alwaysHide" || !isReady || (!testMode && !battery.isLaptopBattery)
|
||||
customBackgroundColor: isLowBattery ? Color.mError : Qt.rgba(0, 0, 0, 0)
|
||||
customTextIconColor: isLowBattery ? Color.mOnError : Qt.rgba(0, 0, 0, 0)
|
||||
tooltipText: {
|
||||
let lines = [];
|
||||
if (testMode) {
|
||||
|
||||
@@ -133,8 +133,9 @@ Item {
|
||||
|
||||
onWheel: function (angle) {
|
||||
var monitor = getMonitor();
|
||||
if (!monitor)
|
||||
if (!monitor || !monitor.brightnessControlAvailable)
|
||||
return;
|
||||
|
||||
if (angle > 0) {
|
||||
monitor.increaseBrightness();
|
||||
} else if (angle < 0) {
|
||||
@@ -142,11 +143,7 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
var settingsPanel = PanelService.getPanel("settingsPanel", screen);
|
||||
settingsPanel.requestedTab = SettingsPanel.Tab.Display;
|
||||
settingsPanel.open();
|
||||
}
|
||||
onClicked: PanelService.getPanel("brightnessPanel", screen)?.toggle(this)
|
||||
|
||||
onRightClicked: {
|
||||
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
|
||||
|
||||
@@ -39,15 +39,19 @@ Item {
|
||||
readonly property bool rightClickUpdateText: widgetSettings.rightClickUpdateText ?? widgetMetadata.rightClickUpdateText
|
||||
readonly property string middleClickExec: widgetSettings.middleClickExec || widgetMetadata.middleClickExec
|
||||
readonly property bool middleClickUpdateText: widgetSettings.middleClickUpdateText ?? widgetMetadata.middleClickUpdateText
|
||||
readonly property string wheelExec: widgetSettings.wheelExec || widgetMetadata.wheelExec
|
||||
readonly property string wheelUpExec: widgetSettings.wheelUpExec || widgetMetadata.wheelUpExec
|
||||
readonly property string wheelDownExec: widgetSettings.wheelDownExec || widgetMetadata.wheelDownExec
|
||||
readonly property string wheelMode: widgetSettings.wheelMode || widgetMetadata.wheelMode
|
||||
readonly property bool wheelUpdateText: widgetSettings.wheelUpdateText ?? widgetMetadata.wheelUpdateText
|
||||
readonly property bool wheelUpUpdateText: widgetSettings.wheelUpUpdateText ?? widgetMetadata.wheelUpUpdateText
|
||||
readonly property bool wheelDownUpdateText: widgetSettings.wheelDownUpdateText ?? widgetMetadata.wheelDownUpdateText
|
||||
readonly property string textCommand: widgetSettings.textCommand !== undefined ? widgetSettings.textCommand : (widgetMetadata.textCommand || "")
|
||||
readonly property bool textStream: widgetSettings.textStream !== undefined ? widgetSettings.textStream : (widgetMetadata.textStream || false)
|
||||
readonly property int textIntervalMs: widgetSettings.textIntervalMs !== undefined ? widgetSettings.textIntervalMs : (widgetMetadata.textIntervalMs || 3000)
|
||||
readonly property string textCollapse: widgetSettings.textCollapse !== undefined ? widgetSettings.textCollapse : (widgetMetadata.textCollapse || "")
|
||||
readonly property bool parseJson: widgetSettings.parseJson !== undefined ? widgetSettings.parseJson : (widgetMetadata.parseJson || false)
|
||||
readonly property bool hideTextInVerticalBar: widgetSettings.hideTextInVerticalBar !== undefined ? widgetSettings.hideTextInVerticalBar : (widgetMetadata.hideTextInVerticalBar || false)
|
||||
readonly property bool hasExec: (leftClickExec || rightClickExec || middleClickExec)
|
||||
|
||||
readonly property bool shouldShowText: !isVerticalBar || !hideTextInVerticalBar
|
||||
readonly property bool hasExec: (leftClickExec || rightClickExec || middleClickExec || (wheelMode === "unified" && wheelExec) || (wheelMode === "separate" && (wheelUpExec || wheelDownExec)))
|
||||
|
||||
implicitWidth: pill.width
|
||||
implicitHeight: pill.height
|
||||
@@ -58,9 +62,9 @@ Item {
|
||||
screen: root.screen
|
||||
oppositeDirection: BarService.getPillDirection(root)
|
||||
icon: _dynamicIcon !== "" ? _dynamicIcon : customIcon
|
||||
text: shouldShowText ? _dynamicText : ""
|
||||
text: (!isVerticalBar || currentMaxTextLength > 0) ? _dynamicText : ""
|
||||
density: Settings.data.bar.density
|
||||
rotateText: isVerticalBar && !hideTextInVerticalBar
|
||||
rotateText: isVerticalBar && currentMaxTextLength > 0
|
||||
autoHide: false
|
||||
forceOpen: _dynamicText !== ""
|
||||
tooltipText: {
|
||||
@@ -76,6 +80,16 @@ Item {
|
||||
if (middleClickExec !== "") {
|
||||
tooltipLines.push(`Middle click: ${middleClickExec}.`);
|
||||
}
|
||||
if (wheelMode === "unified" && wheelExec !== "") {
|
||||
tooltipLines.push(`Wheel: ${wheelExec}.`);
|
||||
} else if (wheelMode === "separate") {
|
||||
if (wheelUpExec !== "") {
|
||||
tooltipLines.push(`Wheel up: ${wheelUpExec}.`);
|
||||
}
|
||||
if (wheelDownExec !== "") {
|
||||
tooltipLines.push(`Wheel down: ${wheelDownExec}.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_dynamicTooltip !== "") {
|
||||
@@ -95,6 +109,7 @@ Item {
|
||||
onClicked: root.onClicked()
|
||||
onRightClicked: root.onRightClicked()
|
||||
onMiddleClicked: root.onMiddleClicked()
|
||||
onWheel: delta => root.onWheel(delta)
|
||||
}
|
||||
|
||||
// Internal state for dynamic text
|
||||
@@ -102,12 +117,32 @@ Item {
|
||||
property string _dynamicIcon: ""
|
||||
property string _dynamicTooltip: ""
|
||||
|
||||
// Maximum length for text display before scrolling (different values for horizontal and vertical)
|
||||
readonly property var maxTextLength: {
|
||||
"horizontal": ((widgetSettings && widgetSettings.maxTextLength && widgetSettings.maxTextLength.horizontal !== undefined) ? widgetSettings.maxTextLength.horizontal : ((widgetMetadata && widgetMetadata.maxTextLength && widgetMetadata.maxTextLength.horizontal !== undefined) ? widgetMetadata.maxTextLength.horizontal : 10)),
|
||||
"vertical": ((widgetSettings && widgetSettings.maxTextLength && widgetSettings.maxTextLength.vertical !== undefined) ? widgetSettings.maxTextLength.vertical : ((widgetMetadata && widgetMetadata.maxTextLength && widgetMetadata.maxTextLength.vertical !== undefined) ? widgetMetadata.maxTextLength.vertical : 10))
|
||||
}
|
||||
readonly property int _staticDuration: 6 // How many cycles to stay static at start/end
|
||||
|
||||
// Encapsulated state for scrolling text implementation
|
||||
property var _scrollState: {
|
||||
"originalText": "",
|
||||
"needsScrolling": false,
|
||||
"offset": 0,
|
||||
"phase": 0 // 0=static start, 1=scrolling, 2=static end
|
||||
,
|
||||
"phaseCounter": 0
|
||||
}
|
||||
|
||||
// Current max text length based on bar orientation
|
||||
readonly property int currentMaxTextLength: isVerticalBar ? maxTextLength.vertical : maxTextLength.horizontal
|
||||
|
||||
// Periodically run the text command (if set)
|
||||
Timer {
|
||||
id: refreshTimer
|
||||
interval: Math.max(250, textIntervalMs)
|
||||
repeat: true
|
||||
running: shouldShowText && !textStream && textCommand && textCommand.length > 0
|
||||
running: (!isVerticalBar || currentMaxTextLength > 0) && !textStream && textCommand && textCommand.length > 0
|
||||
triggeredOnStart: true
|
||||
onTriggered: root.runTextCommand()
|
||||
}
|
||||
@@ -116,10 +151,61 @@ Item {
|
||||
Timer {
|
||||
id: restartTimer
|
||||
interval: 1000
|
||||
running: shouldShowText && textStream && !textProc.running
|
||||
running: (!isVerticalBar || currentMaxTextLength > 0) && textStream && !textProc.running
|
||||
onTriggered: root.runTextCommand()
|
||||
}
|
||||
|
||||
// Timer for scrolling text display
|
||||
Timer {
|
||||
id: scrollTimer
|
||||
interval: 300
|
||||
repeat: true
|
||||
running: false
|
||||
onTriggered: {
|
||||
if (_scrollState.needsScrolling && _scrollState.originalText.length > currentMaxTextLength) {
|
||||
// Traditional marquee with pause at beginning and end
|
||||
if (_scrollState.phase === 0) {
|
||||
// Static at beginning
|
||||
_dynamicText = _scrollState.originalText.substring(0, Math.min(currentMaxTextLength, _scrollState.originalText.length));
|
||||
_scrollState.phaseCounter++;
|
||||
if (_scrollState.phaseCounter >= _staticDuration) {
|
||||
_scrollState.phaseCounter = 0;
|
||||
_scrollState.phase = 1; // Move to scrolling
|
||||
}
|
||||
} else if (_scrollState.phase === 1) {
|
||||
// Scrolling
|
||||
_scrollState.offset++;
|
||||
var start = _scrollState.offset;
|
||||
var end = start + currentMaxTextLength;
|
||||
|
||||
if (start >= _scrollState.originalText.length - currentMaxTextLength) {
|
||||
// Reached or passed the end, ensure we show the last part
|
||||
var textEnd = _scrollState.originalText.length;
|
||||
var textStart = Math.max(0, textEnd - currentMaxTextLength);
|
||||
_dynamicText = _scrollState.originalText.substring(textStart, textEnd);
|
||||
_scrollState.phase = 2; // Move to static end phase
|
||||
_scrollState.phaseCounter = 0;
|
||||
} else {
|
||||
_dynamicText = _scrollState.originalText.substring(start, end);
|
||||
}
|
||||
} else if (_scrollState.phase === 2) {
|
||||
// Static at end
|
||||
// Ensure end text is displayed correctly
|
||||
var textEnd = _scrollState.originalText.length;
|
||||
var textStart = Math.max(0, textEnd - currentMaxTextLength);
|
||||
_dynamicText = _scrollState.originalText.substring(textStart, textEnd);
|
||||
_scrollState.phaseCounter++;
|
||||
if (_scrollState.phaseCounter >= _staticDuration) {
|
||||
// Do NOT loop back to start, just stop scrolling
|
||||
scrollTimer.stop();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
scrollTimer.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SplitParser {
|
||||
id: textStdoutSplit
|
||||
onRead: line => root.parseDynamicContent(line)
|
||||
@@ -162,16 +248,33 @@ Item {
|
||||
let tooltip = parsed.tooltip || "";
|
||||
|
||||
if (checkCollapse(text)) {
|
||||
_scrollState.originalText = "";
|
||||
_dynamicText = "";
|
||||
_dynamicIcon = "";
|
||||
_dynamicTooltip = "";
|
||||
_scrollState.needsScrolling = false;
|
||||
_scrollState.phase = 0;
|
||||
_scrollState.phaseCounter = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
_scrollState.originalText = text;
|
||||
_scrollState.needsScrolling = text.length > currentMaxTextLength && currentMaxTextLength > 0;
|
||||
if (_scrollState.needsScrolling) {
|
||||
// Start with the beginning of the text
|
||||
_dynamicText = text.substring(0, currentMaxTextLength);
|
||||
_scrollState.phase = 0; // Start at phase 0 (static beginning)
|
||||
_scrollState.phaseCounter = 0;
|
||||
_scrollState.offset = 0;
|
||||
scrollTimer.start(); // Start the scrolling timer
|
||||
} else {
|
||||
_dynamicText = text;
|
||||
scrollTimer.stop();
|
||||
}
|
||||
_dynamicIcon = icon;
|
||||
|
||||
_dynamicTooltip = toHtml(tooltip);
|
||||
_scrollState.offset = 0;
|
||||
return;
|
||||
} catch (e) {
|
||||
Logger.w("CustomButton", `Failed to parse JSON. Content: "${lineToParse}"`);
|
||||
@@ -179,15 +282,32 @@ Item {
|
||||
}
|
||||
|
||||
if (checkCollapse(contentStr)) {
|
||||
_scrollState.originalText = "";
|
||||
_dynamicText = "";
|
||||
_dynamicIcon = "";
|
||||
_dynamicTooltip = "";
|
||||
_scrollState.needsScrolling = false;
|
||||
_scrollState.phase = 0;
|
||||
_scrollState.phaseCounter = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
_scrollState.originalText = contentStr;
|
||||
_scrollState.needsScrolling = contentStr.length > currentMaxTextLength && currentMaxTextLength > 0;
|
||||
if (_scrollState.needsScrolling) {
|
||||
// Start with the beginning of the text
|
||||
_dynamicText = contentStr.substring(0, currentMaxTextLength);
|
||||
_scrollState.phase = 0; // Start at phase 0 (static beginning)
|
||||
_scrollState.phaseCounter = 0;
|
||||
_scrollState.offset = 0;
|
||||
scrollTimer.start(); // Start the scrolling timer
|
||||
} else {
|
||||
_dynamicText = contentStr;
|
||||
scrollTimer.stop();
|
||||
}
|
||||
_dynamicIcon = "";
|
||||
_dynamicTooltip = toHtml(contentStr);
|
||||
_scrollState.offset = 0;
|
||||
}
|
||||
|
||||
function checkCollapse(text) {
|
||||
@@ -215,8 +335,8 @@ Item {
|
||||
if (leftClickExec) {
|
||||
Quickshell.execDetached(["sh", "-c", leftClickExec]);
|
||||
Logger.i("CustomButton", `Executing command: ${leftClickExec}`);
|
||||
} else if (!hasExec && !leftClickUpdateText) {
|
||||
// No script was defined, open settings
|
||||
} else if (!leftClickUpdateText) {
|
||||
// No left click script was defined, open settings
|
||||
var settingsPanel = PanelService.getPanel("settingsPanel", screen);
|
||||
settingsPanel.requestedTab = SettingsPanel.Tab.Bar;
|
||||
settingsPanel.open();
|
||||
@@ -247,17 +367,19 @@ Item {
|
||||
}
|
||||
|
||||
function toHtml(str) {
|
||||
const htmlRegex = /<\/?[a-zA-Z][\s\S]*>/;
|
||||
const htmlTagRegex = /<\/?[a-zA-Z][^>]*>/g;
|
||||
const placeholders = [];
|
||||
let i = 0;
|
||||
const protectedStr = str.replace(htmlTagRegex, tag => {
|
||||
placeholders.push(tag);
|
||||
return `___HTML_TAG_${i++}___`;
|
||||
});
|
||||
|
||||
if (htmlRegex.test(str)) {
|
||||
return str;
|
||||
}
|
||||
let escaped = protectedStr.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'").replace(/\r\n|\r|\n/g, "<br/>");
|
||||
|
||||
const escaped = str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
||||
escaped = escaped.replace(/___HTML_TAG_(\d+)___/g, (_, index) => placeholders[Number(index)]);
|
||||
|
||||
const withBreaks = escaped.replace(/\r\n|\r|\n/g, "<br/>");
|
||||
|
||||
return withBreaks;
|
||||
return escaped;
|
||||
}
|
||||
|
||||
function runTextCommand() {
|
||||
@@ -268,4 +390,99 @@ Item {
|
||||
textProc.command = ["sh", "-lc", textCommand];
|
||||
textProc.running = true;
|
||||
}
|
||||
|
||||
function onWheel(delta) {
|
||||
if (wheelMode === "unified" && wheelExec) {
|
||||
let normalizedDelta = delta > 0 ? 1 : -1;
|
||||
|
||||
let command = wheelExec.replace(/\$delta([+\-*/]\d+)?/g, function (match, operation) {
|
||||
if (operation) {
|
||||
try {
|
||||
let operator = operation.charAt(0);
|
||||
let operand = parseInt(operation.substring(1));
|
||||
|
||||
let result;
|
||||
switch (operator) {
|
||||
case '+':
|
||||
result = normalizedDelta + operand;
|
||||
break;
|
||||
case '-':
|
||||
result = normalizedDelta - operand;
|
||||
break;
|
||||
case '*':
|
||||
result = normalizedDelta * operand;
|
||||
break;
|
||||
case '/':
|
||||
result = Math.floor(normalizedDelta / operand);
|
||||
break;
|
||||
default:
|
||||
result = normalizedDelta;
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
} catch (e) {
|
||||
Logger.w("CustomButton", `Error evaluating expression: ${match}, using normalized value ${normalizedDelta}`);
|
||||
return normalizedDelta.toString();
|
||||
}
|
||||
} else {
|
||||
return normalizedDelta.toString();
|
||||
}
|
||||
});
|
||||
|
||||
Quickshell.execDetached(["sh", "-c", command]);
|
||||
Logger.i("CustomButton", `Executing command: ${command}`);
|
||||
} else if (wheelMode === "separate") {
|
||||
if ((delta > 0 && wheelUpExec) || (delta < 0 && wheelDownExec)) {
|
||||
let commandExec = delta > 0 ? wheelUpExec : wheelDownExec;
|
||||
let normalizedDelta = delta > 0 ? 1 : -1;
|
||||
|
||||
let command = commandExec.replace(/\$delta([+\-*/]\d+)?/g, function (match, operation) {
|
||||
if (operation) {
|
||||
try {
|
||||
let operator = operation.charAt(0);
|
||||
let operand = parseInt(operation.substring(1));
|
||||
|
||||
let result;
|
||||
switch (operator) {
|
||||
case '+':
|
||||
result = normalizedDelta + operand;
|
||||
break;
|
||||
case '-':
|
||||
result = normalizedDelta - operand;
|
||||
break;
|
||||
case '*':
|
||||
result = normalizedDelta * operand;
|
||||
break;
|
||||
case '/':
|
||||
result = Math.floor(normalizedDelta / operand);
|
||||
break;
|
||||
default:
|
||||
result = normalizedDelta;
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
} catch (e) {
|
||||
Logger.w("CustomButton", `Error evaluating expression: ${match}, using normalized value ${normalizedDelta}`);
|
||||
return normalizedDelta.toString();
|
||||
}
|
||||
} else {
|
||||
return normalizedDelta.toString();
|
||||
}
|
||||
});
|
||||
|
||||
Quickshell.execDetached(["sh", "-c", command]);
|
||||
Logger.i("CustomButton", `Executing command: ${command}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!textStream) {
|
||||
if (wheelMode === "unified" && wheelUpdateText) {
|
||||
runTextCommand();
|
||||
} else if (wheelMode === "separate") {
|
||||
if ((delta > 0 && wheelUpUpdateText) || (delta < 0 && wheelDownUpdateText)) {
|
||||
runTextCommand();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.Media
|
||||
@@ -11,7 +12,7 @@ NIconButton {
|
||||
|
||||
property ShellScreen screen
|
||||
|
||||
icon: "camera-video"
|
||||
icon: ScreenRecorderService.isPending ? "" : "camera-video"
|
||||
tooltipText: ScreenRecorderService.isRecording ? I18n.tr("tooltips.click-to-stop-recording") : I18n.tr("tooltips.click-to-start-recording")
|
||||
tooltipDirection: BarService.getTooltipDirection()
|
||||
density: Settings.data.bar.density
|
||||
@@ -31,4 +32,32 @@ NIconButton {
|
||||
}
|
||||
|
||||
onClicked: handleClick()
|
||||
|
||||
// Custom spinner shown only during pending start
|
||||
NIcon {
|
||||
id: pendingSpinner
|
||||
icon: "loader-2"
|
||||
visible: ScreenRecorderService.isPending
|
||||
pointSize: {
|
||||
switch (root.density) {
|
||||
case "compact":
|
||||
return Math.max(1, root.width * 0.65);
|
||||
default:
|
||||
return Math.max(1, root.width * 0.48);
|
||||
}
|
||||
}
|
||||
applyUiScale: root.applyUiScale
|
||||
color: root.enabled && root.hovering ? colorFgHover : colorFg
|
||||
anchors.centerIn: parent
|
||||
transformOrigin: Item.Center
|
||||
|
||||
RotationAnimation on rotation {
|
||||
running: ScreenRecorderService.isPending
|
||||
from: 0
|
||||
to: 360
|
||||
duration: Style.animationSlow
|
||||
loops: Animation.Infinite
|
||||
onStopped: pendingSpinner.rotation = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,11 +34,13 @@ Item {
|
||||
}
|
||||
return {};
|
||||
}
|
||||
readonly property string labelMode: (widgetSettings.labelMode !== undefined) ? widgetSettings.labelMode : widgetMetadata.labelMode
|
||||
readonly property bool hideUnoccupied: (widgetSettings.hideUnoccupied !== undefined) ? widgetSettings.hideUnoccupied : widgetMetadata.hideUnoccupied
|
||||
|
||||
readonly property int characterCount: 2
|
||||
readonly property bool showLabelsOnlyWhenOccupied: (widgetSettings.showLabelsOnlyWhenOccupied !== undefined) ? widgetSettings.showLabelsOnlyWhenOccupied : true
|
||||
readonly property bool hideUnoccupied: (widgetSettings.hideUnoccupied !== undefined) ? widgetSettings.hideUnoccupied : widgetMetadata.hideUnoccupied
|
||||
readonly property string labelMode: (widgetSettings.labelMode !== undefined) ? widgetSettings.labelMode : widgetMetadata.labelMode
|
||||
readonly property bool showLabelsOnlyWhenOccupied: (widgetSettings.showLabelsOnlyWhenOccupied !== undefined) ? widgetSettings.showLabelsOnlyWhenOccupied : widgetMetadata.showLabelsOnlyWhenOccupied
|
||||
readonly property bool colorizeIcons: (widgetSettings.colorizeIcons !== undefined) ? widgetSettings.colorizeIcons : widgetMetadata.colorizeIcons
|
||||
|
||||
property ListModel localWorkspaces: ListModel {}
|
||||
property real masterProgress: 0.0
|
||||
property bool effectsActive: false
|
||||
@@ -270,8 +272,21 @@ Item {
|
||||
hoverEnabled: true
|
||||
enabled: !hasWindows
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: {
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
onPressed: mouse => {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
CompositorService.switchToWorkspace(workspaceModel);
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
TooltipService.hide();
|
||||
root.selectedWindow = "";
|
||||
root.selectedAppName = "";
|
||||
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
|
||||
if (popupMenuWindow) {
|
||||
const pos = BarService.getContextMenuPosition(container, contextMenu.implicitWidth, contextMenu.implicitHeight);
|
||||
contextMenu.openAtItem(container, pos.x, pos.y);
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,7 +360,7 @@ Item {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
|
||||
onPressed: function (mouse) {
|
||||
onPressed: mouse => {
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
@@ -407,7 +422,11 @@ Item {
|
||||
if (hasWindows)
|
||||
return Color.mSecondary;
|
||||
|
||||
return Qt.alpha(Color.mOutline, 0.3);
|
||||
if (Settings.data.colorSchemes.darkMode) {
|
||||
return Qt.darker(Color.mSecondary, 1.5);
|
||||
} else {
|
||||
return Qt.lighter(Color.mSecondary, 1.5);
|
||||
}
|
||||
}
|
||||
|
||||
scale: workspaceModel.isActive ? 1.0 : 0.9
|
||||
@@ -472,21 +491,10 @@ Item {
|
||||
return Color.mOnPrimary;
|
||||
if (workspaceModel.isUrgent)
|
||||
return Color.mOnError;
|
||||
if (hasWindows)
|
||||
// if (hasWindows)
|
||||
// return Color.mOnSecondary;
|
||||
|
||||
return Color.mOnSecondary;
|
||||
|
||||
return Color.mOnSurface;
|
||||
}
|
||||
|
||||
opacity: {
|
||||
if (workspaceModel.isFocused)
|
||||
return 1.0;
|
||||
if (workspaceModel.isUrgent)
|
||||
return 0.9;
|
||||
if (hasWindows)
|
||||
return 0.8;
|
||||
|
||||
return 0.6;
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
|
||||
140
Modules/Bar/Widgets/VPN.qml
Normal file
140
Modules/Bar/Widgets/VPN.qml
Normal file
@@ -0,0 +1,140 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Modules.Bar.Extras
|
||||
import qs.Services.Networking
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property ShellScreen screen
|
||||
|
||||
property string widgetId: ""
|
||||
property string section: ""
|
||||
property int sectionWidgetIndex: -1
|
||||
property int sectionWidgetsCount: 0
|
||||
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
|
||||
readonly property string displayMode: widgetSettings.displayMode !== undefined ? widgetSettings.displayMode : widgetMetadata.displayMode
|
||||
|
||||
implicitWidth: pill.width
|
||||
implicitHeight: pill.height
|
||||
|
||||
NPopupContextMenu {
|
||||
id: contextMenu
|
||||
|
||||
model: {
|
||||
const items = [];
|
||||
const active = VPNService.activeConnections;
|
||||
for (let i = 0; i < active.length; ++i) {
|
||||
const conn = active[i];
|
||||
items.push({
|
||||
"label": I18n.tr("context-menu.disconnect-vpn", {
|
||||
"name": conn.name
|
||||
}),
|
||||
"action": "disconnect:" + conn.uuid,
|
||||
"icon": "shield-off"
|
||||
});
|
||||
}
|
||||
const inactive = VPNService.inactiveConnections;
|
||||
for (let i = 0; i < inactive.length; ++i) {
|
||||
const conn = inactive[i];
|
||||
items.push({
|
||||
"label": I18n.tr("context-menu.connect-vpn", {
|
||||
"name": conn.name
|
||||
}),
|
||||
"action": "connect:" + conn.uuid,
|
||||
"icon": "shield-lock"
|
||||
});
|
||||
}
|
||||
items.push({
|
||||
"label": I18n.tr("context-menu.widget-settings"),
|
||||
"action": "widget-settings",
|
||||
"icon": "settings"
|
||||
});
|
||||
return items;
|
||||
}
|
||||
|
||||
onTriggered: action => {
|
||||
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
|
||||
if (popupMenuWindow) {
|
||||
popupMenuWindow.close();
|
||||
}
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
if (action === "widget-settings") {
|
||||
BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings);
|
||||
return;
|
||||
}
|
||||
if (action.startsWith("connect:")) {
|
||||
const uuid = action.substring("connect:".length);
|
||||
VPNService.connect(uuid);
|
||||
return;
|
||||
}
|
||||
if (action.startsWith("disconnect:")) {
|
||||
const uuid = action.substring("disconnect:".length);
|
||||
VPNService.disconnect(uuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BarPill {
|
||||
id: pill
|
||||
|
||||
screen: root.screen
|
||||
density: Settings.data.bar.density
|
||||
oppositeDirection: BarService.getPillDirection(root)
|
||||
icon: VPNService.hasActiveConnection ? "shield-lock" : "shield"
|
||||
text: {
|
||||
if (VPNService.activeConnections.length > 0) {
|
||||
return VPNService.activeConnections[0].name;
|
||||
}
|
||||
if (VPNService.connectingUuid) {
|
||||
const pending = VPNService.connections[VPNService.connectingUuid];
|
||||
if (pending) {
|
||||
return pending.name;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
suffix: {
|
||||
if (VPNService.activeConnections.length > 1) {
|
||||
return ` + ${VPNService.activeConnections.length - 1}`;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
autoHide: false
|
||||
forceOpen: !isBarVertical && root.displayMode === "alwaysShow"
|
||||
forceClose: isBarVertical || root.displayMode === "alwaysHide" || !pill.text
|
||||
onClicked: PanelService.getPanel("vpnPanel", screen)?.toggle(this)
|
||||
onRightClicked: {
|
||||
var popupMenuWindow = PanelService.getPopupMenuWindow(screen);
|
||||
if (popupMenuWindow) {
|
||||
const pos = BarService.getContextMenuPosition(pill, contextMenu.implicitWidth, contextMenu.implicitHeight);
|
||||
contextMenu.openAtItem(pill, pos.x, pos.y);
|
||||
popupMenuWindow.showContextMenu(contextMenu);
|
||||
}
|
||||
}
|
||||
tooltipText: {
|
||||
if (pill.text !== "") {
|
||||
return pill.text;
|
||||
}
|
||||
return I18n.tr("tooltips.manage-vpn");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -299,7 +299,7 @@ Loader {
|
||||
height: Math.round(iconSize * 1.5)
|
||||
color: Qt.alpha(Color.mSurface, Settings.data.dock.backgroundOpacity)
|
||||
anchors.centerIn: parent
|
||||
radius: Style.radiusL
|
||||
radius: height * 0.5 * Settings.data.dock.radiusRatio
|
||||
border.width: Style.borderS
|
||||
border.color: Qt.alpha(Color.mOutline, Settings.data.dock.backgroundOpacity)
|
||||
|
||||
|
||||
@@ -20,21 +20,19 @@ import qs.Widgets
|
||||
import qs.Widgets.AudioSpectrum
|
||||
|
||||
Loader {
|
||||
id: lockScreen
|
||||
id: root
|
||||
active: false
|
||||
|
||||
// Track if triggered via deprecated IPC call
|
||||
property bool triggeredViaDeprecatedCall: false
|
||||
Component.onCompleted: {
|
||||
// Register with panel service
|
||||
PanelService.lockScreen = this;
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: unloadAfterUnlockTimer
|
||||
interval: 250
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
lockScreen.active = false;
|
||||
// Reset the deprecation flag when unlocking
|
||||
lockScreen.triggeredViaDeprecatedCall = false;
|
||||
}
|
||||
onTriggered: root.active = false
|
||||
}
|
||||
|
||||
function scheduleUnloadAfterUnlock() {
|
||||
@@ -49,7 +47,7 @@ Loader {
|
||||
id: lockContext
|
||||
onUnlocked: {
|
||||
lockSession.locked = false;
|
||||
lockScreen.scheduleUnloadAfterUnlock();
|
||||
root.scheduleUnloadAfterUnlock();
|
||||
lockContext.currentText = "";
|
||||
}
|
||||
onFailed: {
|
||||
@@ -59,7 +57,7 @@ Loader {
|
||||
|
||||
WlSessionLock {
|
||||
id: lockSession
|
||||
locked: lockScreen.active
|
||||
locked: root.active
|
||||
|
||||
WlSessionLockSurface {
|
||||
readonly property var now: Time.now
|
||||
@@ -377,64 +375,8 @@ Loader {
|
||||
backgroundColor: Color.mSurface
|
||||
clockColor: Color.mOnSurface
|
||||
secondHandColor: Color.mPrimary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Deprecation warning (shown above error notification)
|
||||
Rectangle {
|
||||
width: Math.min(650, parent.width - 40)
|
||||
implicitHeight: deprecationContent.implicitHeight + 24
|
||||
height: implicitHeight
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: (Settings.data.general.compactLockScreen ? 320 : 400) * Style.uiScaleRatio
|
||||
radius: Style.radiusL
|
||||
color: Qt.alpha(Color.mTertiary, 0.95)
|
||||
border.color: Color.mTertiary
|
||||
border.width: 2
|
||||
visible: lockScreen.triggeredViaDeprecatedCall
|
||||
opacity: visible ? 1.0 : 0.0
|
||||
|
||||
ColumnLayout {
|
||||
id: deprecationContent
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
spacing: 6
|
||||
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
spacing: 8
|
||||
|
||||
NIcon {
|
||||
icon: "alert-triangle"
|
||||
pointSize: Style.fontSizeL
|
||||
color: Color.mOnTertiary
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Deprecated IPC Call"
|
||||
color: Color.mOnTertiary
|
||||
pointSize: Style.fontSizeL
|
||||
font.weight: Font.Bold
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "The 'lockScreen toggle' IPC call is deprecated. Use 'lockScreen lock' instead."
|
||||
color: Color.mOnTertiary
|
||||
pointSize: Style.fontSizeM
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: 300
|
||||
easing.type: Easing.OutCubic
|
||||
hoursFontSize: Style.fontSizeL
|
||||
minutesFontSize: Style.fontSizeL
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -550,7 +492,7 @@ Loader {
|
||||
|
||||
// Bottom container with weather, password input and controls
|
||||
Rectangle {
|
||||
width: 750
|
||||
id: bottomContainer
|
||||
height: Settings.data.general.compactLockScreen ? 120 : 220
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
@@ -558,6 +500,59 @@ Loader {
|
||||
radius: Style.radiusL
|
||||
color: Color.mSurface
|
||||
|
||||
// Measure text widths to determine minimum button width (for container width calculation)
|
||||
Item {
|
||||
id: buttonRowTextMeasurer
|
||||
visible: false
|
||||
property real iconSize: Settings.data.general.compactLockScreen ? Style.fontSizeM : Style.fontSizeL
|
||||
property real fontSize: Settings.data.general.compactLockScreen ? Style.fontSizeS : Style.fontSizeM
|
||||
property real spacing: 6
|
||||
property real padding: 18 // Approximate horizontal padding per button
|
||||
|
||||
// Measure all button text widths
|
||||
Text {
|
||||
id: logoutText
|
||||
text: I18n.tr("session-menu.logout")
|
||||
font.pointSize: buttonRowTextMeasurer.fontSize
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
Text {
|
||||
id: suspendText
|
||||
text: I18n.tr("session-menu.suspend")
|
||||
font.pointSize: buttonRowTextMeasurer.fontSize
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
Text {
|
||||
id: hibernateText
|
||||
text: I18n.tr("session-menu.hibernate")
|
||||
font.pointSize: buttonRowTextMeasurer.fontSize
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
Text {
|
||||
id: rebootText
|
||||
text: I18n.tr("session-menu.reboot")
|
||||
font.pointSize: buttonRowTextMeasurer.fontSize
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
Text {
|
||||
id: shutdownText
|
||||
text: I18n.tr("session-menu.shutdown")
|
||||
font.pointSize: buttonRowTextMeasurer.fontSize
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
// Calculate maximum width needed
|
||||
property real maxTextWidth: Math.max(logoutText.implicitWidth, Math.max(suspendText.implicitWidth, Math.max(hibernateText.implicitWidth, Math.max(rebootText.implicitWidth, shutdownText.implicitWidth))))
|
||||
property real minButtonWidth: maxTextWidth + iconSize + spacing + padding
|
||||
}
|
||||
|
||||
// Calculate minimum width based on button requirements
|
||||
// Button row needs: margins + 5 buttons + 4 spacings + margins
|
||||
// Plus ColumnLayout margins (14 on each side = 28 total)
|
||||
// Add extra buffer to ensure password input has proper padding
|
||||
property real minButtonRowWidth: buttonRowTextMeasurer.minButtonWidth > 0 ? (5 * buttonRowTextMeasurer.minButtonWidth) + 40 + (2 * Style.marginM) + 28 + (2 * Style.marginM) : 750
|
||||
width: Math.max(750, minButtonRowWidth)
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 14
|
||||
@@ -1091,6 +1086,7 @@ Loader {
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumWidth: buttonRowTextMeasurer.minButtonWidth
|
||||
Layout.preferredHeight: Settings.data.general.compactLockScreen ? 36 : 48
|
||||
radius: Settings.data.general.compactLockScreen ? 18 : 24
|
||||
color: logoutButtonArea.containsMouse ? Color.mHover : "transparent"
|
||||
@@ -1139,6 +1135,7 @@ Loader {
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumWidth: buttonRowTextMeasurer.minButtonWidth
|
||||
Layout.preferredHeight: Settings.data.general.compactLockScreen ? 36 : 48
|
||||
radius: Settings.data.general.compactLockScreen ? 18 : 24
|
||||
color: suspendButtonArea.containsMouse ? Color.mHover : "transparent"
|
||||
@@ -1187,6 +1184,7 @@ Loader {
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumWidth: buttonRowTextMeasurer.minButtonWidth
|
||||
Layout.preferredHeight: Settings.data.general.compactLockScreen ? 36 : 48
|
||||
radius: Settings.data.general.compactLockScreen ? 18 : 24
|
||||
color: hibernateButtonArea.containsMouse ? Color.mHover : "transparent"
|
||||
@@ -1235,6 +1233,7 @@ Loader {
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumWidth: buttonRowTextMeasurer.minButtonWidth
|
||||
Layout.preferredHeight: Settings.data.general.compactLockScreen ? 36 : 48
|
||||
radius: Settings.data.general.compactLockScreen ? 18 : 24
|
||||
color: rebootButtonArea.containsMouse ? Color.mHover : "transparent"
|
||||
@@ -1283,6 +1282,7 @@ Loader {
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumWidth: buttonRowTextMeasurer.minButtonWidth
|
||||
Layout.preferredHeight: Settings.data.general.compactLockScreen ? 36 : 48
|
||||
radius: Settings.data.general.compactLockScreen ? 18 : 24
|
||||
color: shutdownButtonArea.containsMouse ? Color.mError : "transparent"
|
||||
|
||||
@@ -76,6 +76,13 @@ Item {
|
||||
backgroundColor: panelBackgroundColor
|
||||
}
|
||||
|
||||
// Brightness
|
||||
PanelBackground {
|
||||
panel: root.windowRoot.brightnessPanelPlaceholder
|
||||
shapeContainer: backgroundsShape
|
||||
backgroundColor: panelBackgroundColor
|
||||
}
|
||||
|
||||
// Calendar
|
||||
PanelBackground {
|
||||
panel: root.windowRoot.calendarPanelPlaceholder
|
||||
|
||||
@@ -11,6 +11,7 @@ import qs.Modules.Bar
|
||||
import qs.Modules.Bar.Extras
|
||||
import qs.Modules.Panels.Audio
|
||||
import qs.Modules.Panels.Bluetooth
|
||||
import qs.Modules.Panels.Brightness
|
||||
import qs.Modules.Panels.Calendar
|
||||
import qs.Modules.Panels.Changelog
|
||||
import qs.Modules.Panels.ControlCenter
|
||||
@@ -33,6 +34,7 @@ PanelWindow {
|
||||
// Expose panels as readonly property aliases
|
||||
readonly property alias audioPanel: audioPanel
|
||||
readonly property alias bluetoothPanel: bluetoothPanel
|
||||
readonly property alias brightnessPanel: brightnessPanel
|
||||
readonly property alias calendarPanel: calendarPanel
|
||||
readonly property alias changelogPanel: changelogPanel
|
||||
readonly property alias controlCenterPanel: controlCenterPanel
|
||||
@@ -48,6 +50,7 @@ PanelWindow {
|
||||
// Expose panel placeholders for AllBackgrounds
|
||||
readonly property var audioPanelPlaceholder: audioPanel.panelPlaceholder
|
||||
readonly property var bluetoothPanelPlaceholder: bluetoothPanel.panelPlaceholder
|
||||
readonly property var brightnessPanelPlaceholder: brightnessPanel.panelPlaceholder
|
||||
readonly property var calendarPanelPlaceholder: calendarPanel.panelPlaceholder
|
||||
readonly property var changelogPanelPlaceholder: changelogPanel.panelPlaceholder
|
||||
readonly property var controlCenterPanelPlaceholder: controlCenterPanel.panelPlaceholder
|
||||
@@ -171,6 +174,12 @@ PanelWindow {
|
||||
screen: root.screen
|
||||
}
|
||||
|
||||
BrightnessPanel {
|
||||
id: brightnessPanel
|
||||
objectName: "brightnessPanel-" + (root.screen?.name || "unknown")
|
||||
screen: root.screen
|
||||
}
|
||||
|
||||
ControlCenterPanel {
|
||||
id: controlCenterPanel
|
||||
objectName: "controlCenterPanel-" + (root.screen?.name || "unknown")
|
||||
|
||||
@@ -615,11 +615,7 @@ Item {
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: {
|
||||
if (!panelBackground.shouldAnimateHeight)
|
||||
return 0;
|
||||
return root.isClosing ? Style.animationFast : Style.animationNormal;
|
||||
}
|
||||
duration: root.isClosing ? Style.animationFast : Style.animationNormal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: panelBackground.bezierCurve
|
||||
}
|
||||
|
||||
@@ -29,10 +29,13 @@ PanelWindow {
|
||||
|
||||
// Use Top layer (same as MainScreen) for proper event handling
|
||||
WlrLayershell.layer: WlrLayer.Top
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
WlrLayershell.keyboardFocus: hasDialog ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
|
||||
WlrLayershell.namespace: "noctalia-" + windowType + "-" + (screen?.name || "unknown")
|
||||
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
||||
|
||||
// Track if a dialog is currently open (needed for keyboard focus)
|
||||
property bool hasDialog: false
|
||||
|
||||
// Register with PanelService so widgets can find this window
|
||||
Component.onCompleted: {
|
||||
objectName = "popupMenuWindow-" + (screen?.name || "unknown");
|
||||
|
||||
@@ -21,8 +21,8 @@ Variants {
|
||||
|
||||
property ListModel notificationModel: NotificationService.activeList
|
||||
|
||||
// Loader is active when there are notifications
|
||||
active: notificationModel.count > 0 || delayTimer.running
|
||||
// Always create window (but with 0x0 dimensions when no notifications)
|
||||
active: true
|
||||
|
||||
// Keep loader active briefly after last notification to allow animations to complete
|
||||
Timer {
|
||||
@@ -104,8 +104,8 @@ Variants {
|
||||
margins.left: isLeft ? barOffsetLeft : 0
|
||||
margins.right: isRight ? barOffsetRight : 0
|
||||
|
||||
implicitWidth: notifWidth
|
||||
implicitHeight: notificationStack.implicitHeight + Style.marginL
|
||||
implicitWidth: (notificationModel.count > 0 || delayTimer.running) ? notifWidth : 0
|
||||
implicitHeight: (notificationModel.count > 0 || delayTimer.running) ? (notificationStack.implicitHeight + Style.marginL) : 0
|
||||
|
||||
property var animateConnection: null
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ NBox {
|
||||
|
||||
NText {
|
||||
text: root.label
|
||||
pointSize: Style.fontSizeL
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mSecondary
|
||||
font.weight: Style.fontWeightMedium
|
||||
visible: root.model.length > 0
|
||||
|
||||
@@ -18,7 +18,11 @@ SmartPanel {
|
||||
panelContent: Rectangle {
|
||||
color: Color.transparent
|
||||
|
||||
property real contentPreferredHeight: !(BluetoothService.adapter && BluetoothService.adapter.enabled) ? Math.min(preferredHeight, Math.max(280 * Style.uiScaleRatio, mainColumn.implicitHeight + Style.marginL * 2)) : (mainColumn.implicitHeight + Style.marginL * 2)
|
||||
// Calculate content height based on header + devices list (or minimum for empty states)
|
||||
property real headerHeight: headerRow.implicitHeight + Style.marginM * 2
|
||||
property real devicesHeight: devicesList.implicitHeight
|
||||
property real calculatedHeight: headerHeight + devicesHeight + Style.marginL * 2 + Style.marginM
|
||||
property real contentPreferredHeight: (BluetoothService.adapter && BluetoothService.adapter.enabled) ? Math.min(root.preferredHeight, Math.max(280 * Style.uiScaleRatio, calculatedHeight)) : Math.min(root.preferredHeight, 280 * Style.uiScaleRatio)
|
||||
|
||||
ColumnLayout {
|
||||
id: mainColumn
|
||||
@@ -83,6 +87,7 @@ SmartPanel {
|
||||
|
||||
// Adapter not available of disabled
|
||||
NBox {
|
||||
id: disabledBox
|
||||
visible: !(BluetoothService.adapter && BluetoothService.adapter.enabled)
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
@@ -135,6 +140,7 @@ SmartPanel {
|
||||
contentWidth: availableWidth
|
||||
|
||||
ColumnLayout {
|
||||
id: devicesList
|
||||
width: parent.width
|
||||
spacing: Style.marginM
|
||||
|
||||
|
||||
148
Modules/Panels/Brightness/BrightnessPanel.qml
Normal file
148
Modules/Panels/Brightness/BrightnessPanel.qml
Normal file
@@ -0,0 +1,148 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
import qs.Modules.MainScreen
|
||||
import qs.Services.Compositor
|
||||
import qs.Services.Hardware
|
||||
import qs.Widgets
|
||||
|
||||
SmartPanel {
|
||||
id: root
|
||||
|
||||
preferredWidth: Math.round(340 * Style.uiScaleRatio)
|
||||
preferredHeight: Math.round(420 * Style.uiScaleRatio)
|
||||
|
||||
function getIcon(brightness) {
|
||||
return brightness <= 0.5 ? "brightness-low" : "brightness-high";
|
||||
}
|
||||
|
||||
panelContent: Item {
|
||||
property real contentPreferredHeight: mainColumn.implicitHeight + Style.marginL * 2
|
||||
|
||||
ColumnLayout {
|
||||
id: mainColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginL
|
||||
spacing: Style.marginM
|
||||
|
||||
// HEADER
|
||||
NBox {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: headerRow.implicitHeight + (Style.marginM * 2)
|
||||
|
||||
RowLayout {
|
||||
id: headerRow
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginM
|
||||
spacing: Style.marginM
|
||||
|
||||
NIcon {
|
||||
icon: "settings-display"
|
||||
pointSize: Style.fontSizeXXL
|
||||
color: Color.mPrimary
|
||||
}
|
||||
|
||||
NText {
|
||||
text: I18n.tr("settings.display.title")
|
||||
pointSize: Style.fontSizeL
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "close"
|
||||
tooltipText: I18n.tr("tooltips.close")
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: {
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NScrollView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
horizontalPolicy: ScrollBar.AlwaysOff
|
||||
verticalPolicy: ScrollBar.AsNeeded
|
||||
clip: true
|
||||
contentWidth: availableWidth
|
||||
|
||||
// AudioService Devices
|
||||
ColumnLayout {
|
||||
spacing: Style.marginM
|
||||
width: parent.width
|
||||
|
||||
Repeater {
|
||||
model: Quickshell.screens || []
|
||||
delegate: NBox {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: outputColumn.implicitHeight + (Style.marginM * 2)
|
||||
|
||||
property var brightnessMonitor: BrightnessService.getMonitorForScreen(modelData)
|
||||
|
||||
ColumnLayout {
|
||||
id: outputColumn
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: Style.marginM
|
||||
spacing: Style.marginS
|
||||
|
||||
NLabel {
|
||||
label: modelData.name || "Unknown"
|
||||
labelColor: Color.mPrimary
|
||||
description: {
|
||||
const compositorScale = CompositorService.getDisplayScale(modelData.name);
|
||||
I18n.tr("system.monitor-description", {
|
||||
"model": modelData.model,
|
||||
"width": modelData.width * compositorScale,
|
||||
"height": modelData.height * compositorScale,
|
||||
"scale": compositorScale
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginS
|
||||
NIcon {
|
||||
icon: getIcon(brightnessMonitor ? brightnessMonitor.brightness : 0)
|
||||
pointSize: Style.fontSizeXL
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
id: brightnessSlider
|
||||
from: 0
|
||||
to: 1
|
||||
value: brightnessMonitor ? brightnessMonitor.brightness : 0.5
|
||||
stepSize: 0.01
|
||||
enabled: brightnessMonitor ? brightnessMonitor.brightnessControlAvailable : false
|
||||
onMoved: value => {
|
||||
if (brightnessMonitor && brightnessMonitor.brightnessControlAvailable) {
|
||||
brightnessMonitor.setBrightness(value);
|
||||
}
|
||||
}
|
||||
onPressedChanged: (pressed, value) => {
|
||||
if (brightnessMonitor && brightnessMonitor.brightnessControlAvailable) {
|
||||
brightnessMonitor.setBrightness(value);
|
||||
}
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
text: brightnessMonitor ? Math.round(brightnessSlider.value * 100) + "%" : "N/A"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,12 +128,6 @@ SmartPanel {
|
||||
width: parent.width
|
||||
spacing: Style.marginM
|
||||
|
||||
NText {
|
||||
text: I18n.tr("changelog.panel.highlight-title")
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
|
||||
NText {
|
||||
visible: UpdateService.fetchError !== ""
|
||||
text: UpdateService.fetchError
|
||||
@@ -141,62 +135,37 @@ SmartPanel {
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginS
|
||||
|
||||
Repeater {
|
||||
model: releaseHighlights
|
||||
delegate: ColumnLayout {
|
||||
width: parent.width
|
||||
spacing: Style.marginS
|
||||
|
||||
NText {
|
||||
text: I18n.tr("changelog.panel.section.version", {
|
||||
"version": modelData.version || I18n.tr("system.unknown-version")
|
||||
})
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
|
||||
NText {
|
||||
visible: modelData.date && modelData.date.length > 0
|
||||
text: I18n.tr("changelog.panel.section.released", {
|
||||
"date": root.formatReleaseDate(modelData.date)
|
||||
})
|
||||
color: Color.mOnSurfaceVariant
|
||||
pointSize: Style.fontSizeXS
|
||||
}
|
||||
spacing: Style.marginXS
|
||||
|
||||
Repeater {
|
||||
model: modelData.entries
|
||||
delegate: RowLayout {
|
||||
width: parent.width
|
||||
spacing: Style.marginS
|
||||
|
||||
Rectangle {
|
||||
width: Style.marginXL
|
||||
height: Style.marginXL
|
||||
radius: Style.radiusS
|
||||
color: Qt.alpha(Color.mPrimary, 0.12)
|
||||
|
||||
NIcon {
|
||||
anchors.centerIn: parent
|
||||
icon: "check"
|
||||
color: Color.mPrimary
|
||||
pointSize: Style.fontSizeM
|
||||
delegate: NText {
|
||||
readonly property int headingLevel: root.headingLevel(modelData)
|
||||
text: {
|
||||
if (modelData.length === 0)
|
||||
return "\u00A0";
|
||||
if (headingLevel > 0)
|
||||
return modelData.replace(/^#+\s+/, "");
|
||||
return modelData;
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
text: modelData
|
||||
color: Color.mOnSurface
|
||||
wrapMode: Text.WordWrap
|
||||
elide: Text.ElideNone
|
||||
textFormat: Text.PlainText
|
||||
color: headingLevel > 0 ? Color.mPrimary : Color.mOnSurface
|
||||
font.weight: headingLevel > 0 ? Style.fontWeightBold : Style.fontWeightMedium
|
||||
pointSize: headingLevel === 1 ? Style.fontSizeXXL : headingLevel === 2 ? Style.fontSizeXL : Style.fontSizeM
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
visible: index < releaseHighlights.length - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,10 +209,19 @@ SmartPanel {
|
||||
}
|
||||
}
|
||||
|
||||
onClosed: {
|
||||
if (GitHubService && GitHubService.clearReleaseCache) {
|
||||
GitHubService.clearReleaseCache();
|
||||
function headingLevel(text) {
|
||||
if (!text)
|
||||
return 0;
|
||||
const trimmed = text.trim();
|
||||
if (trimmed.length === 0)
|
||||
return 0;
|
||||
const match = trimmed.match(/^(#+)\s+/);
|
||||
if (!match)
|
||||
return 0;
|
||||
return Math.min(match[1].length, 2);
|
||||
}
|
||||
|
||||
onClosed: {
|
||||
if (UpdateService && UpdateService.changelogCurrentVersion) {
|
||||
UpdateService.markChangelogSeen(UpdateService.changelogCurrentVersion);
|
||||
}
|
||||
@@ -262,4 +240,3 @@ SmartPanel {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -117,6 +117,7 @@ NBox {
|
||||
font.weight: Style.fontWeightMedium
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,6 +166,7 @@ NBox {
|
||||
font.weight: Style.fontWeightMedium
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
127
Modules/Panels/Launcher/ClipboardPreview.qml
Normal file
127
Modules/Panels/Launcher/ClipboardPreview.qml
Normal file
@@ -0,0 +1,127 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import "../../../Helpers/TextFormatter.js" as TextFormatter
|
||||
import qs.Commons
|
||||
import qs.Services.Keyboard
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: previewPanel
|
||||
|
||||
property var currentItem: null
|
||||
property string fullContent: ""
|
||||
property string imageDataUrl: ""
|
||||
property bool loadingFullContent: false
|
||||
property bool isImageContent: false
|
||||
|
||||
implicitHeight: contentColumn.implicitHeight + Style.marginL * 2
|
||||
|
||||
Connections {
|
||||
target: previewPanel
|
||||
function onCurrentItemChanged() {
|
||||
fullContent = "";
|
||||
imageDataUrl = "";
|
||||
loadingFullContent = false;
|
||||
isImageContent = currentItem && currentItem.isImage;
|
||||
|
||||
if (currentItem && currentItem.clipboardId) {
|
||||
if (isImageContent) {
|
||||
imageDataUrl = ClipboardService.getImageData(currentItem.clipboardId) || "";
|
||||
loadingFullContent = !imageDataUrl;
|
||||
|
||||
if (!imageDataUrl && currentItem.mime) {
|
||||
ClipboardService.decodeToDataUrl(currentItem.clipboardId, currentItem.mime, null);
|
||||
}
|
||||
} else {
|
||||
loadingFullContent = true;
|
||||
ClipboardService.decode(currentItem.clipboardId, function (content) {
|
||||
fullContent = TextFormatter.wrapTextForDisplay(content);
|
||||
loadingFullContent = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
readonly property int _rev: ClipboardService.revision
|
||||
|
||||
Timer {
|
||||
id: imageUpdateTimer
|
||||
interval: 200
|
||||
running: currentItem && currentItem.isImage && imageDataUrl === ""
|
||||
repeat: currentItem && currentItem.isImage && imageDataUrl === ""
|
||||
|
||||
onTriggered: {
|
||||
if (currentItem && currentItem.clipboardId) {
|
||||
const newData = ClipboardService.getImageData(currentItem.clipboardId) || "";
|
||||
if (newData !== imageDataUrl) {
|
||||
imageDataUrl = newData;
|
||||
if (newData) {
|
||||
loadingFullContent = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Color.mSurface || "#f5f5f5"
|
||||
border.color: Color.mOutlineVariant || "#cccccc"
|
||||
border.width: 1
|
||||
radius: Style.radiusM
|
||||
|
||||
ColumnLayout {
|
||||
id: contentColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginS
|
||||
spacing: Style.marginS
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
color: Color.mSurfaceVariant || "#e0e0e0"
|
||||
border.color: Color.mOutline || "#aaaaaa"
|
||||
border.width: 1
|
||||
radius: Style.radiusS
|
||||
|
||||
BusyIndicator {
|
||||
anchors.centerIn: parent
|
||||
running: loadingFullContent
|
||||
visible: loadingFullContent
|
||||
width: Style.baseWidgetSize
|
||||
height: width
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginS
|
||||
|
||||
NImageRounded {
|
||||
anchors.fill: parent
|
||||
imagePath: imageDataUrl
|
||||
visible: isImageContent && !loadingFullContent && imageDataUrl !== ""
|
||||
imageRadius: Style.radiusS
|
||||
imageFillMode: Image.PreserveAspectFit
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
visible: !isImageContent && !loadingFullContent
|
||||
|
||||
TextArea {
|
||||
text: fullContent
|
||||
readOnly: true
|
||||
wrapMode: Text.Wrap
|
||||
textFormat: TextArea.RichText // Enable HTML rendering
|
||||
font.pointSize: Style.fontSizeM // Adjust font size for readability
|
||||
color: Color.mOnSurface // Consistent text color
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,14 @@ import qs.Widgets
|
||||
SmartPanel {
|
||||
id: root
|
||||
|
||||
readonly property bool previewActive: searchText.startsWith(">clip") && Settings.data.appLauncher.enableClipPreview
|
||||
|
||||
// Panel configuration
|
||||
preferredWidth: Math.round(500 * Style.uiScaleRatio)
|
||||
readonly property int listPanelWidth: Math.round(600 * Style.uiScaleRatio)
|
||||
readonly property int previewPanelWidth: Math.round(400 * Style.uiScaleRatio)
|
||||
readonly property int totalBaseWidth: listPanelWidth + (Style.marginL * 2)
|
||||
|
||||
preferredWidth: totalBaseWidth
|
||||
preferredHeight: Math.round(600 * Style.uiScaleRatio)
|
||||
preferredWidthRatio: 0.3
|
||||
preferredHeightRatio: 0.5
|
||||
@@ -262,13 +268,50 @@ SmartPanel {
|
||||
}
|
||||
}
|
||||
|
||||
// UI
|
||||
panelContent: Rectangle {
|
||||
id: ui
|
||||
color: Color.transparent
|
||||
opacity: resultsReady ? 1.0 : 0.0
|
||||
|
||||
// Global MouseArea to detect mouse movement
|
||||
// Preview Panel (external)
|
||||
NBox {
|
||||
id: previewBox
|
||||
visible: root.previewActive
|
||||
width: root.previewPanelWidth
|
||||
height: Math.round(400 * Style.uiScaleRatio)
|
||||
x: ui.width + Style.marginM
|
||||
y: Math.max(Style.marginL // Minimum y is the top margin of the content area
|
||||
, Math.min(resultsList.mapToItem(ui, 0, (root.selectedIndex * (root.entryHeight + resultsList.spacing)) - resultsList.contentY).y, ui.height - previewBox.height - Style.marginL // Maximum y, considering bottom margin
|
||||
))
|
||||
z: -1 // Draw behind main panel content if it ever overlaps
|
||||
|
||||
opacity: visible ? 1.0 : 0.0
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: clipboardPreviewLoader
|
||||
anchors.fill: parent
|
||||
active: root.previewActive
|
||||
source: active ? "./ClipboardPreview.qml" : ""
|
||||
|
||||
onLoaded: {
|
||||
if (selectedIndex >= 0 && results[selectedIndex] && item) {
|
||||
item.currentItem = results[selectedIndex];
|
||||
}
|
||||
}
|
||||
|
||||
onItemChanged: {
|
||||
if (item && selectedIndex >= 0 && results[selectedIndex]) {
|
||||
item.currentItem = results[selectedIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseMovementDetector
|
||||
anchors.fill: parent
|
||||
@@ -282,7 +325,6 @@ SmartPanel {
|
||||
property bool initialized: false
|
||||
|
||||
onPositionChanged: mouse => {
|
||||
// Store initial position
|
||||
if (!initialized) {
|
||||
lastX = mouse.x;
|
||||
lastY = mouse.y;
|
||||
@@ -290,7 +332,6 @@ SmartPanel {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if mouse actually moved
|
||||
const deltaX = Math.abs(mouse.x - lastX);
|
||||
const deltaY = Math.abs(mouse.y - lastY);
|
||||
if (deltaX > 1 || deltaY > 1) {
|
||||
@@ -300,7 +341,6 @@ SmartPanel {
|
||||
}
|
||||
}
|
||||
|
||||
// Reset when launcher opens
|
||||
Connections {
|
||||
target: root
|
||||
function onOpened() {
|
||||
@@ -329,9 +369,16 @@ SmartPanel {
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginL
|
||||
anchors.margins: Style.marginL // Apply overall margins here
|
||||
spacing: Style.marginM // Apply spacing between elements here
|
||||
|
||||
// Left Pane
|
||||
ColumnLayout {
|
||||
id: leftPane
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredWidth: root.listPanelWidth
|
||||
spacing: Style.marginM
|
||||
|
||||
NTextInput {
|
||||
@@ -363,7 +410,6 @@ SmartPanel {
|
||||
}
|
||||
}
|
||||
|
||||
// Results list
|
||||
NListView {
|
||||
id: resultsList
|
||||
|
||||
@@ -381,6 +427,9 @@ SmartPanel {
|
||||
if (currentIndex >= 0) {
|
||||
positionViewAtIndex(currentIndex, ListView.Contain);
|
||||
}
|
||||
if (clipboardPreviewLoader.item) {
|
||||
clipboardPreviewLoader.item.currentItem = results[currentIndex] || null;
|
||||
}
|
||||
}
|
||||
onModelChanged: {}
|
||||
|
||||
@@ -388,7 +437,6 @@ SmartPanel {
|
||||
id: entry
|
||||
|
||||
property bool isSelected: (!root.ignoreMouseHover && mouseArea.containsMouse) || (index === selectedIndex)
|
||||
// Accessor for app id
|
||||
property string appId: (modelData && modelData.appId) ? String(modelData.appId) : ""
|
||||
|
||||
// Pin helpers
|
||||
@@ -468,7 +516,6 @@ SmartPanel {
|
||||
return ClipboardService.getImageData(modelData.clipboardId) || "";
|
||||
}
|
||||
|
||||
// Loading indicator
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
visible: parent.status === Image.Loading
|
||||
@@ -482,7 +529,6 @@ SmartPanel {
|
||||
}
|
||||
}
|
||||
|
||||
// Error fallback
|
||||
onStatusChanged: status => {
|
||||
if (status === Image.Error) {
|
||||
iconLoader.visible = true;
|
||||
@@ -491,7 +537,6 @@ SmartPanel {
|
||||
}
|
||||
}
|
||||
|
||||
// Icon fallback
|
||||
Loader {
|
||||
id: iconLoader
|
||||
anchors.fill: parent
|
||||
@@ -510,7 +555,6 @@ SmartPanel {
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback text if no icon and no image
|
||||
NText {
|
||||
anchors.centerIn: parent
|
||||
visible: !imagePreview.visible && !iconLoader.visible
|
||||
@@ -609,7 +653,6 @@ SmartPanel {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Status
|
||||
NText {
|
||||
Layout.fillWidth: true
|
||||
text: {
|
||||
@@ -624,4 +667,5 @@ SmartPanel {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,12 +204,9 @@ Item {
|
||||
return results;
|
||||
}
|
||||
|
||||
// Helper: Format image clipboard entry
|
||||
function formatImageEntry(item) {
|
||||
const meta = parseImageMeta(item.preview);
|
||||
|
||||
// The launcher's delegate will now be responsible for fetching the image data.
|
||||
// This function's role is to provide the necessary metadata for that request.
|
||||
return {
|
||||
"name": meta ? `Image ${meta.w}×${meta.h}` : "Image",
|
||||
"description": meta ? `${meta.fmt} • ${meta.size}` : item.mime || "Image data",
|
||||
@@ -218,22 +215,20 @@ Item {
|
||||
"imageWidth": meta ? meta.w : 0,
|
||||
"imageHeight": meta ? meta.h : 0,
|
||||
"clipboardId": item.id,
|
||||
"mime": item.mime
|
||||
"mime": item.mime,
|
||||
"preview": item.preview
|
||||
};
|
||||
}
|
||||
|
||||
// Helper: Format text clipboard entry with preview
|
||||
function formatTextEntry(item) {
|
||||
const preview = (item.preview || "").trim();
|
||||
const lines = preview.split('\n').filter(l => l.trim());
|
||||
|
||||
// Use first line as title, limit length
|
||||
let title = lines[0] || "Empty text";
|
||||
if (title.length > 60) {
|
||||
title = title.substring(0, 57) + "...";
|
||||
}
|
||||
|
||||
// Use second line or character count as description
|
||||
let description = "";
|
||||
if (lines.length > 1) {
|
||||
description = lines[1];
|
||||
@@ -250,11 +245,12 @@ Item {
|
||||
"name": title,
|
||||
"description": description,
|
||||
"icon": "text-x-generic",
|
||||
"isImage": false
|
||||
"isImage": false,
|
||||
"clipboardId": item.id,
|
||||
"preview": preview
|
||||
};
|
||||
}
|
||||
|
||||
// Helper: Parse image metadata from preview string
|
||||
function parseImageMeta(preview) {
|
||||
const re = /\[\[\s*binary data\s+([\d\.]+\s*(?:KiB|MiB|GiB|B))\s+(\w+)\s+(\d+)x(\d+)\s*\]\]/i;
|
||||
const match = (preview || "").match(re);
|
||||
@@ -271,8 +267,6 @@ Item {
|
||||
};
|
||||
}
|
||||
|
||||
// Public method to get image data for a clipboard item
|
||||
// This can be called by the launcher when rendering
|
||||
function getImageForItem(clipboardId) {
|
||||
return ClipboardService.getImageData ? ClipboardService.getImageData(clipboardId) : null;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ Popup {
|
||||
if (widgetData && widgetId) {
|
||||
loadWidgetSettings();
|
||||
}
|
||||
// Request focus to ensure keyboard input works
|
||||
forceActiveFocus();
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
@@ -40,10 +42,13 @@ Popup {
|
||||
border.width: Style.borderM
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
id: content
|
||||
contentItem: FocusScope {
|
||||
id: focusScope
|
||||
focus: true
|
||||
|
||||
width: parent.width
|
||||
ColumnLayout {
|
||||
id: content
|
||||
anchors.fill: parent
|
||||
spacing: Style.marginM
|
||||
|
||||
// Title
|
||||
@@ -79,6 +84,39 @@ Popup {
|
||||
Loader {
|
||||
id: settingsLoader
|
||||
Layout.fillWidth: true
|
||||
onLoaded: {
|
||||
// Try to focus the first focusable item in the loaded settings
|
||||
if (item) {
|
||||
Qt.callLater(() => {
|
||||
var firstInput = findFirstFocusable(item);
|
||||
if (firstInput) {
|
||||
firstInput.forceActiveFocus();
|
||||
} else {
|
||||
focusScope.forceActiveFocus();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function findFirstFocusable(item) {
|
||||
if (!item)
|
||||
return null;
|
||||
// Check if this item can accept focus
|
||||
if (item.focus !== undefined && item.focus === true)
|
||||
return item;
|
||||
// Check children
|
||||
if (item.children) {
|
||||
for (var i = 0; i < item.children.length; i++) {
|
||||
var child = item.children[i];
|
||||
if (child && child.focus !== undefined && child.focus === true)
|
||||
return child;
|
||||
var found = findFirstFocusable(child);
|
||||
if (found)
|
||||
return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Action buttons
|
||||
@@ -110,6 +148,7 @@ Popup {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function loadWidgetSettings() {
|
||||
const source = BarWidgetRegistry.widgetSettingsMap[widgetId];
|
||||
|
||||
@@ -16,7 +16,8 @@ ColumnLayout {
|
||||
property string valueIcon: widgetData.icon !== undefined ? widgetData.icon : widgetMetadata.icon
|
||||
property bool valueTextStream: widgetData.textStream !== undefined ? widgetData.textStream : widgetMetadata.textStream
|
||||
property bool valueParseJson: widgetData.parseJson !== undefined ? widgetData.parseJson : widgetMetadata.parseJson
|
||||
property bool valueHideTextInVerticalBar: widgetData.hideTextInVerticalBar !== undefined ? widgetData.hideTextInVerticalBar : widgetMetadata.hideTextInVerticalBar
|
||||
property int valueMaxTextLengthHorizontal: widgetData?.maxTextLength?.horizontal ?? widgetMetadata?.maxTextLength?.horizontal
|
||||
property int valueMaxTextLengthVertical: widgetData?.maxTextLength?.vertical ?? widgetMetadata?.maxTextLength?.vertical
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
@@ -27,11 +28,21 @@ ColumnLayout {
|
||||
settings.rightClickUpdateText = rightClickUpdateText.checked;
|
||||
settings.middleClickExec = middleClickExecInput.text;
|
||||
settings.middleClickUpdateText = middleClickUpdateText.checked;
|
||||
settings.wheelMode = separateWheelToggle.internalChecked ? "separate" : "unified";
|
||||
settings.wheelExec = wheelExecInput.text;
|
||||
settings.wheelUpExec = wheelUpExecInput.text;
|
||||
settings.wheelDownExec = wheelDownExecInput.text;
|
||||
settings.wheelUpdateText = wheelUpdateText.checked;
|
||||
settings.wheelUpUpdateText = wheelUpUpdateText.checked;
|
||||
settings.wheelDownUpdateText = wheelDownUpdateText.checked;
|
||||
settings.textCommand = textCommandInput.text;
|
||||
settings.textCollapse = textCollapseInput.text;
|
||||
settings.textStream = valueTextStream;
|
||||
settings.parseJson = valueParseJson;
|
||||
settings.hideTextInVerticalBar = valueHideTextInVerticalBar;
|
||||
settings.maxTextLength = {
|
||||
"horizontal": valueMaxTextLengthHorizontal,
|
||||
"vertical": valueMaxTextLengthVertical
|
||||
};
|
||||
settings.textIntervalMs = parseInt(textIntervalInput.text || textIntervalInput.placeholderText, 10);
|
||||
return settings;
|
||||
}
|
||||
@@ -137,6 +148,104 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
|
||||
// Wheel command settings
|
||||
NToggle {
|
||||
id: separateWheelToggle
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.widget-settings.custom-button.wheel-mode-separate.label", "Separate wheel commands")
|
||||
description: I18n.tr("bar.widget-settings.custom-button.wheel-mode-separate.description", "Enable separate commands for wheel up and down")
|
||||
property bool internalChecked: (widgetData?.wheelMode || widgetMetadata?.wheelMode || "unified") === "separate"
|
||||
checked: internalChecked
|
||||
onToggled: checked => {
|
||||
internalChecked = checked;
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: parent.width
|
||||
|
||||
RowLayout {
|
||||
id: unifiedWheelLayout
|
||||
visible: !separateWheelToggle.checked
|
||||
spacing: Style.marginM
|
||||
|
||||
NTextInput {
|
||||
id: wheelExecInput
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.widget-settings.custom-button.wheel.label")
|
||||
description: I18n.tr("bar.widget-settings.custom-button.wheel.description")
|
||||
placeholderText: I18n.tr("placeholders.enter-command")
|
||||
text: widgetData?.wheelExec || widgetMetadata?.wheelExec || ""
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: wheelUpdateText
|
||||
enabled: !valueTextStream
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignBottom
|
||||
Layout.bottomMargin: Style.marginS
|
||||
onEntered: TooltipService.show(wheelUpdateText, I18n.tr("bar.widget-settings.custom-button.wheel.update-text"), "auto")
|
||||
onExited: TooltipService.hide()
|
||||
checked: widgetData?.wheelUpdateText ?? widgetMetadata?.wheelUpdateText
|
||||
onToggled: isChecked => checked = isChecked
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: separatedWheelLayout
|
||||
Layout.fillWidth: true
|
||||
visible: separateWheelToggle.checked
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginM
|
||||
|
||||
NTextInput {
|
||||
id: wheelUpExecInput
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.widget-settings.custom-button.wheel-up.label")
|
||||
description: I18n.tr("bar.widget-settings.custom-button.wheel-up.description")
|
||||
placeholderText: I18n.tr("placeholders.enter-command")
|
||||
text: widgetData?.wheelUpExec || widgetMetadata?.wheelUpExec || ""
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: wheelUpUpdateText
|
||||
enabled: !valueTextStream
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignBottom
|
||||
Layout.bottomMargin: Style.marginS
|
||||
onEntered: TooltipService.show(wheelUpUpdateText, I18n.tr("bar.widget-settings.custom-button.wheel.update-text"), "auto")
|
||||
onExited: TooltipService.hide()
|
||||
checked: (widgetData?.wheelUpUpdateText !== undefined) ? widgetData.wheelUpUpdateText : (widgetMetadata?.wheelUpUpdateText ?? false)
|
||||
onToggled: isChecked => checked = isChecked
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginM
|
||||
|
||||
NTextInput {
|
||||
id: wheelDownExecInput
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.widget-settings.custom-button.wheel-down.label")
|
||||
description: I18n.tr("bar.widget-settings.custom-button.wheel-down.description")
|
||||
placeholderText: I18n.tr("placeholders.enter-command")
|
||||
text: widgetData?.wheelDownExec || widgetMetadata?.wheelDownExec || ""
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: wheelDownUpdateText
|
||||
enabled: !valueTextStream
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignBottom
|
||||
Layout.bottomMargin: Style.marginS
|
||||
onEntered: TooltipService.show(wheelDownUpdateText, I18n.tr("bar.widget-settings.custom-button.wheel.update-text"), "auto")
|
||||
onExited: TooltipService.hide()
|
||||
checked: (widgetData?.wheelDownUpdateText !== undefined) ? widgetData.wheelDownUpdateText : (widgetMetadata?.wheelDownUpdateText ?? false)
|
||||
onToggled: isChecked => checked = isChecked
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
@@ -145,11 +254,22 @@ ColumnLayout {
|
||||
label: I18n.tr("bar.widget-settings.custom-button.dynamic-text")
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("bar.widget-settings.custom-button.hide-vertical.label", "Hide text in vertical bar")
|
||||
description: I18n.tr("bar.widget-settings.custom-button.hide-vertical.description", "If enabled, the text from the command output will not be shown when the bar is in a vertical layout (left or right).")
|
||||
checked: valueHideTextInVerticalBar
|
||||
onToggled: checked => valueHideTextInVerticalBar = checked
|
||||
NSpinBox {
|
||||
label: I18n.tr("bar.widget-settings.custom-button.max-text-length-horizontal.label", "Max text length (horizontal)")
|
||||
description: I18n.tr("bar.widget-settings.custom-button.max-text-length-horizontal.description", "Maximum number of characters to show in horizontal bar (0 to hide text)")
|
||||
from: 0
|
||||
to: 100
|
||||
value: valueMaxTextLengthHorizontal
|
||||
onValueChanged: valueMaxTextLengthHorizontal = value
|
||||
}
|
||||
|
||||
NSpinBox {
|
||||
label: I18n.tr("bar.widget-settings.custom-button.max-text-length-vertical.label", "Max text length (vertical)")
|
||||
description: I18n.tr("bar.widget-settings.custom-button.max-text-length-vertical.description", "Maximum number of characters to show in vertical bar (0 to hide text)")
|
||||
from: 0
|
||||
to: 100
|
||||
value: valueMaxTextLengthVertical
|
||||
onValueChanged: valueMaxTextLengthVertical = value
|
||||
}
|
||||
|
||||
NToggle {
|
||||
|
||||
@@ -12,22 +12,28 @@ ColumnLayout {
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
property string valueLabelMode: widgetData.labelMode !== undefined ? widgetData.labelMode : (widgetMetadata ? widgetMetadata.labelMode : "index")
|
||||
property bool valueHideUnoccupied: widgetData.hideUnoccupied !== undefined ? widgetData.hideUnoccupied : (widgetMetadata ? widgetMetadata.hideUnoccupied : false)
|
||||
property bool valueShowWorkspaceNumbers: widgetData.showWorkspaceNumbers !== undefined ? widgetData.showWorkspaceNumbers : (widgetMetadata ? widgetMetadata.showWorkspaceNumbers : true)
|
||||
property bool valueShowNumbersOnlyWhenOccupied: widgetData.showNumbersOnlyWhenOccupied !== undefined ? widgetData.showNumbersOnlyWhenOccupied : (widgetMetadata ? widgetMetadata.showNumbersOnlyWhenOccupied : true)
|
||||
property bool valueColorizeIcons: widgetData.colorizeIcons !== undefined ? widgetData.colorizeIcons : (widgetMetadata ? widgetMetadata.colorizeIcons : false)
|
||||
property bool valueHideUnoccupied: widgetData.hideUnoccupied !== undefined ? widgetData.hideUnoccupied : widgetMetadata.hideUnoccupied
|
||||
property string valueLabelMode: widgetData.labelMode !== undefined ? widgetData.labelMode : widgetMetadata.labelMode
|
||||
property bool valueShowLabelsOnlyWhenOccupied: widgetData.showLabelsOnlyWhenOccupied !== undefined ? widgetData.showLabelsOnlyWhenOccupied : widgetMetadata.showLabelsOnlyWhenOccupied
|
||||
property bool valueColorizeIcons: widgetData.colorizeIcons !== undefined ? widgetData.colorizeIcons : widgetMetadata.colorizeIcons
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.labelMode = valueLabelMode;
|
||||
|
||||
settings.hideUnoccupied = valueHideUnoccupied;
|
||||
settings.showWorkspaceNumbers = valueShowWorkspaceNumbers;
|
||||
settings.showNumbersOnlyWhenOccupied = valueShowNumbersOnlyWhenOccupied;
|
||||
settings.labelMode = valueLabelMode;
|
||||
settings.showLabelsOnlyWhenOccupied = valueShowLabelsOnlyWhenOccupied;
|
||||
settings.colorizeIcons = valueColorizeIcons;
|
||||
return settings;
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("bar.widget-settings.workspace.hide-unoccupied.label")
|
||||
description: I18n.tr("bar.widget-settings.workspace.hide-unoccupied.description")
|
||||
checked: valueHideUnoccupied
|
||||
onToggled: checked => valueHideUnoccupied = checked
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
id: labelModeCombo
|
||||
label: I18n.tr("bar.widget-settings.workspace.label-mode.label")
|
||||
@@ -50,16 +56,18 @@ ColumnLayout {
|
||||
"name": I18n.tr("options.workspace-labels.index+name")
|
||||
}
|
||||
]
|
||||
currentKey: widgetData.labelMode || widgetMetadata.labelMode
|
||||
currentKey: widgetData.labelMode
|
||||
onSelected: key => valueLabelMode = key
|
||||
minimumWidth: 200
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("bar.widget-settings.workspace.hide-unoccupied.label")
|
||||
description: I18n.tr("bar.widget-settings.workspace.hide-unoccupied.description")
|
||||
checked: valueHideUnoccupied
|
||||
onToggled: checked => valueHideUnoccupied = checked
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.widget-settings.taskbar-grouped.show-labels-only-when-occupied.label")
|
||||
description: I18n.tr("bar.widget-settings.taskbar-grouped.show-labels-only-when-occupied.description")
|
||||
checked: root.valueShowLabelsOnlyWhenOccupied
|
||||
onToggled: checked => root.valueShowLabelsOnlyWhenOccupied = checked
|
||||
visible: !root.valueHideUnoccupied
|
||||
}
|
||||
|
||||
NToggle {
|
||||
@@ -69,13 +77,4 @@ ColumnLayout {
|
||||
checked: root.valueColorizeIcons
|
||||
onToggled: checked => root.valueColorizeIcons = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: I18n.tr("bar.widget-settings.taskbar-grouped.show-labels-only-when-occupied.label")
|
||||
description: I18n.tr("bar.widget-settings.taskbar-grouped.show-labels-only-when-occupied.description")
|
||||
checked: root.valueShowNumbersOnlyWhenOccupied
|
||||
onToggled: checked => root.valueShowNumbersOnlyWhenOccupied = checked
|
||||
visible: root.valueShowWorkspaceNumbers
|
||||
}
|
||||
}
|
||||
|
||||
42
Modules/Panels/Settings/Bar/WidgetSettings/VPNSettings.qml
Normal file
42
Modules/Panels/Settings/Bar/WidgetSettings/VPNSettings.qml
Normal file
@@ -0,0 +1,42 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginM
|
||||
|
||||
property var widgetData: null
|
||||
property var widgetMetadata: null
|
||||
|
||||
property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {});
|
||||
settings.displayMode = valueDisplayMode;
|
||||
return settings;
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: I18n.tr("bar.widget-settings.battery.display-mode.label")
|
||||
description: I18n.tr("bar.widget-settings.battery.display-mode.description")
|
||||
minimumWidth: 134
|
||||
model: [
|
||||
{
|
||||
"key": "onhover",
|
||||
"name": I18n.tr("options.display-mode.on-hover")
|
||||
},
|
||||
{
|
||||
"key": "alwaysShow",
|
||||
"name": I18n.tr("options.display-mode.always-show")
|
||||
},
|
||||
{
|
||||
"key": "alwaysHide",
|
||||
"name": I18n.tr("options.display-mode.always-hide")
|
||||
}
|
||||
]
|
||||
currentKey: root.valueDisplayMode
|
||||
onSelected: key => root.valueDisplayMode = key
|
||||
}
|
||||
}
|
||||
@@ -146,6 +146,7 @@ ColumnLayout {
|
||||
}) : I18n.tr("settings.about.contributors.section.description_plural", {
|
||||
"count": root.contributors.length
|
||||
})
|
||||
enableDescriptionRichText: true
|
||||
}
|
||||
|
||||
GridView {
|
||||
|
||||
@@ -26,8 +26,7 @@ Popup {
|
||||
property var schemeColorsCache: ({})
|
||||
property int cacheVersion: 0
|
||||
|
||||
// Cache for available schemes list
|
||||
property string schemesCacheFile: Settings.cacheDir + "color-schemes-list.json"
|
||||
// Cache for available schemes list (uses ShellState singleton)
|
||||
property int schemesCacheUpdateFrequency: 2 * 60 * 60 // 2 hours in seconds
|
||||
|
||||
// Cache for repo branch info (to reduce API calls during downloads)
|
||||
@@ -99,33 +98,6 @@ Popup {
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
// Cache file for schemes list
|
||||
FileView {
|
||||
id: schemesCacheFileView
|
||||
path: schemesCacheFile
|
||||
printErrors: false
|
||||
|
||||
JsonAdapter {
|
||||
id: schemesCacheAdapter
|
||||
property var schemes: []
|
||||
property real timestamp: 0
|
||||
}
|
||||
|
||||
onLoaded: {
|
||||
loadSchemesFromCache();
|
||||
}
|
||||
|
||||
onLoadFailed: function (error) {
|
||||
if (error.toString().includes("No such file") || error === 2) {
|
||||
// Cache doesn't exist, fetch from API (only if popup is open)
|
||||
if (root.visible) {
|
||||
Qt.callLater(() => {
|
||||
fetchAvailableSchemesFromAPI();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: Color.mSurface
|
||||
@@ -135,10 +107,19 @@ Popup {
|
||||
}
|
||||
|
||||
function loadSchemesFromCache() {
|
||||
try {
|
||||
const now = Time.timestamp;
|
||||
const cacheData = ShellState.getColorSchemesList();
|
||||
const cachedSchemes = cacheData.schemes || [];
|
||||
const cachedTimestamp = cacheData.timestamp || 0;
|
||||
|
||||
// Check if cache is expired or missing
|
||||
if (!schemesCacheAdapter.timestamp || (now >= schemesCacheAdapter.timestamp + schemesCacheUpdateFrequency)) {
|
||||
if (!cachedTimestamp || (now >= cachedTimestamp + schemesCacheUpdateFrequency)) {
|
||||
// Try migration first if cache is empty
|
||||
if (cachedSchemes.length === 0) {
|
||||
migrateFromOldSchemesList();
|
||||
}
|
||||
|
||||
// Only fetch from API if we haven't fetched recently (prevent rapid repeated calls)
|
||||
const timeSinceLastFetch = now - lastApiFetchTime;
|
||||
if (timeSinceLastFetch >= minApiFetchInterval) {
|
||||
@@ -148,8 +129,8 @@ Popup {
|
||||
} else {
|
||||
// Use cached data even if expired, to avoid rate limits
|
||||
Logger.d("ColorSchemeDownload", "Cache expired but recent API call detected, using cached data");
|
||||
if (schemesCacheAdapter.schemes && schemesCacheAdapter.schemes.length > 0) {
|
||||
availableSchemes = schemesCacheAdapter.schemes;
|
||||
if (cachedSchemes.length > 0) {
|
||||
availableSchemes = cachedSchemes;
|
||||
hasInitialData = true;
|
||||
fetching = false;
|
||||
return;
|
||||
@@ -157,11 +138,11 @@ Popup {
|
||||
}
|
||||
}
|
||||
|
||||
const ageMinutes = Math.round((now - schemesCacheAdapter.timestamp) / 60);
|
||||
Logger.d("ColorSchemeDownload", "Loading cached schemes (age:", ageMinutes, "minutes)");
|
||||
const ageMinutes = Math.round((now - cachedTimestamp) / 60);
|
||||
Logger.d("ColorSchemeDownload", "Loading cached schemes from ShellState (age:", ageMinutes, "minutes)");
|
||||
|
||||
if (schemesCacheAdapter.schemes && schemesCacheAdapter.schemes.length > 0) {
|
||||
availableSchemes = schemesCacheAdapter.schemes;
|
||||
if (cachedSchemes.length > 0) {
|
||||
availableSchemes = cachedSchemes;
|
||||
hasInitialData = true;
|
||||
fetching = false;
|
||||
} else {
|
||||
@@ -174,19 +155,48 @@ Popup {
|
||||
fetching = false;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.e("ColorSchemeDownload", "Failed to load schemes from cache:", error);
|
||||
fetching = false;
|
||||
}
|
||||
}
|
||||
|
||||
function migrateFromOldSchemesList() {
|
||||
const oldSchemesPath = Settings.cacheDir + "color-schemes-list.json";
|
||||
const migrationFileView = Qt.createQmlObject(`
|
||||
import QtQuick
|
||||
import Quickshell.Io
|
||||
FileView {
|
||||
id: migrationView
|
||||
path: "${oldSchemesPath}"
|
||||
printErrors: false
|
||||
adapter: JsonAdapter {
|
||||
property var schemes: []
|
||||
property real timestamp: 0
|
||||
}
|
||||
onLoaded: {
|
||||
root.availableSchemes = adapter.schemes || [];
|
||||
root.saveSchemesToCache();
|
||||
Logger.i("ColorSchemeDownload", "Migrated color-schemes-list.json to ShellState");
|
||||
migrationView.destroy();
|
||||
}
|
||||
onLoadFailed: {
|
||||
migrationView.destroy();
|
||||
}
|
||||
}
|
||||
`, root, "schemesMigrationView");
|
||||
}
|
||||
|
||||
function saveSchemesToCache() {
|
||||
schemesCacheAdapter.schemes = availableSchemes;
|
||||
schemesCacheAdapter.timestamp = Time.timestamp;
|
||||
|
||||
// Ensure cache directory exists
|
||||
Quickshell.execDetached(["mkdir", "-p", Settings.cacheDir]);
|
||||
|
||||
Qt.callLater(() => {
|
||||
schemesCacheFileView.writeAdapter();
|
||||
Logger.d("ColorSchemeDownload", "Schemes list saved to cache");
|
||||
try {
|
||||
ShellState.setColorSchemesList({
|
||||
schemes: availableSchemes,
|
||||
timestamp: Time.timestamp
|
||||
});
|
||||
Logger.d("ColorSchemeDownload", "Schemes list saved to ShellState");
|
||||
} catch (error) {
|
||||
Logger.e("ColorSchemeDownload", "Failed to save schemes to cache:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function fetchAvailableSchemes() {
|
||||
@@ -194,19 +204,11 @@ Popup {
|
||||
return;
|
||||
}
|
||||
|
||||
// Path is set when popup becomes visible, so FileView will start loading
|
||||
// Try to load from cache first
|
||||
if (schemesCacheFileView.loaded) {
|
||||
// Try to load from ShellState cache first
|
||||
if (typeof ShellState !== 'undefined' && ShellState.isLoaded) {
|
||||
loadSchemesFromCache();
|
||||
} else if (schemesCacheFileView.path) {
|
||||
// Cache file path is set but not loaded yet, wait for it to load
|
||||
// The FileView will trigger loadSchemesFromCache() when loaded
|
||||
// But if it fails, we should fetch from API
|
||||
if (!schemesCacheFileView.loading) {
|
||||
schemesCacheFileView.reload();
|
||||
}
|
||||
} else {
|
||||
// No cache file path, fetch directly from API
|
||||
// ShellState not ready, fetch directly from API
|
||||
fetchAvailableSchemesFromAPI();
|
||||
}
|
||||
}
|
||||
@@ -521,7 +523,7 @@ Popup {
|
||||
return;
|
||||
}
|
||||
|
||||
var targetDir = ColorSchemeService.schemesDirectory + "/" + schemeName;
|
||||
var targetDir = ColorSchemeService.downloadedSchemesDirectory + "/" + schemeName;
|
||||
var downloadScript = "mkdir -p '" + targetDir + "'\n";
|
||||
|
||||
// Build download script for all files
|
||||
@@ -552,6 +554,7 @@ Popup {
|
||||
var downloadProcess = Qt.createQmlObject(`
|
||||
import QtQuick
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
Process {
|
||||
id: downloadProcess
|
||||
command: ["sh", "-c", ` + JSON.stringify(downloadScript) + `]
|
||||
@@ -611,6 +614,17 @@ Popup {
|
||||
return false;
|
||||
}
|
||||
|
||||
function isSchemeDownloaded(schemeName) {
|
||||
// Check if scheme is in the downloaded directory (not preinstalled)
|
||||
for (var i = 0; i < ColorSchemeService.schemes.length; i++) {
|
||||
var path = ColorSchemeService.schemes[i];
|
||||
if ((path.indexOf("/" + schemeName + "/") !== -1 || path.indexOf("/" + schemeName + ".json") !== -1) && path.indexOf(ColorSchemeService.downloadedSchemesDirectory) !== -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function deleteScheme(schemeName) {
|
||||
if (downloading) {
|
||||
return;
|
||||
@@ -623,7 +637,8 @@ Popup {
|
||||
var deletedSchemeDisplayName = ColorSchemeService.getBasename(schemeName);
|
||||
var needsReset = (currentScheme === deletedSchemeDisplayName);
|
||||
|
||||
var targetDir = ColorSchemeService.schemesDirectory + "/" + schemeName;
|
||||
// Only allow deleting downloaded schemes, not preinstalled ones
|
||||
var targetDir = ColorSchemeService.downloadedSchemesDirectory + "/" + schemeName;
|
||||
var deleteScript = "rm -rf '" + targetDir + "'";
|
||||
|
||||
var deleteProcess = Qt.createQmlObject(`
|
||||
@@ -712,7 +727,25 @@ Popup {
|
||||
}
|
||||
|
||||
onAvailableSchemesChanged: preFetchSchemeColors()
|
||||
onVisibleChanged: preFetchSchemeColors()
|
||||
onVisibleChanged: {
|
||||
preFetchSchemeColors();
|
||||
|
||||
// Load schemes from ShellState when popup becomes visible
|
||||
if (visible) {
|
||||
if (typeof ShellState !== 'undefined' && ShellState.isLoaded) {
|
||||
loadSchemesFromCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: typeof ShellState !== 'undefined' ? ShellState : null
|
||||
function onIsLoadedChanged() {
|
||||
if (root.visible && ShellState.isLoaded) {
|
||||
loadSchemesFromCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
id: contentColumn
|
||||
@@ -896,12 +929,14 @@ Popup {
|
||||
NIconButton {
|
||||
property bool isDownloading: downloading && downloadingScheme === schemeRow.schemeName
|
||||
property bool isInstalled: root.isSchemeInstalled(schemeRow.schemeName)
|
||||
property bool isDownloaded: root.isSchemeDownloaded(schemeRow.schemeName)
|
||||
|
||||
icon: isDownloading ? "" : (isInstalled ? "trash" : "download")
|
||||
tooltipText: isDownloading ? I18n.tr("settings.color-scheme.download.downloading") : (isInstalled ? I18n.tr("settings.color-scheme.download.delete") : I18n.tr("settings.color-scheme.download.download"))
|
||||
icon: isDownloading ? "" : (isDownloaded ? "trash" : "download")
|
||||
tooltipText: isDownloading ? I18n.tr("settings.color-scheme.download.downloading") : (isDownloaded ? I18n.tr("settings.color-scheme.download.delete") : I18n.tr("settings.color-scheme.download.download"))
|
||||
enabled: !downloading
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
onClicked: isInstalled ? root.deleteScheme(schemeRow.schemeName) : root.downloadScheme(modelData)
|
||||
visible: !isInstalled || isDownloaded // Show button only if not installed (can download) or if downloaded (can delete)
|
||||
onClicked: isDownloaded ? root.deleteScheme(schemeRow.schemeName) : root.downloadScheme(modelData)
|
||||
|
||||
NBusyIndicator {
|
||||
anchors.centerIn: parent
|
||||
|
||||
@@ -81,6 +81,25 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
visible: Settings.data.dock.enabled
|
||||
spacing: Style.marginXXS
|
||||
Layout.fillWidth: true
|
||||
NLabel {
|
||||
label: I18n.tr("settings.dock.appearance.border-radius.label")
|
||||
description: I18n.tr("settings.dock.appearance.border-radius.description")
|
||||
}
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
from: 0
|
||||
to: 1
|
||||
stepSize: 0.01
|
||||
value: Settings.data.dock.radiusRatio
|
||||
onMoved: value => Settings.data.dock.radiusRatio = value
|
||||
text: Math.floor(Settings.data.dock.radiusRatio * 100) + "%"
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
visible: Settings.data.dock.enabled
|
||||
spacing: Style.marginXXS
|
||||
|
||||
@@ -65,6 +65,13 @@ ColumnLayout {
|
||||
onToggled: checked => Settings.data.appLauncher.enableClipboardHistory = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.launcher.settings.clip-preview.label")
|
||||
description: I18n.tr("settings.launcher.settings.clip-preview.description")
|
||||
checked: Settings.data.appLauncher.enableClipPreview
|
||||
onToggled: checked => Settings.data.appLauncher.enableClipPreview = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: I18n.tr("settings.launcher.settings.sort-by-usage.label")
|
||||
description: I18n.tr("settings.launcher.settings.sort-by-usage.description")
|
||||
|
||||
@@ -145,17 +145,17 @@ ColumnLayout {
|
||||
},
|
||||
{
|
||||
"key": "6",
|
||||
"name": I18n.locale.dayName(6, Locale.LongFormat)
|
||||
"name": I18n.locale.dayName(6, Locale.LongFormat).trim()
|
||||
} // Saturday
|
||||
,
|
||||
{
|
||||
"key": "0",
|
||||
"name": I18n.locale.dayName(0, Locale.LongFormat)
|
||||
"name": I18n.locale.dayName(0, Locale.LongFormat).trim()
|
||||
} // Sunday
|
||||
,
|
||||
{
|
||||
"key": "1",
|
||||
"name": I18n.locale.dayName(1, Locale.LongFormat)
|
||||
"name": I18n.locale.dayName(1, Locale.LongFormat).trim()
|
||||
} // Monday
|
||||
]
|
||||
onSelected: key => Settings.data.location.firstDayOfWeek = parseInt(key)
|
||||
|
||||
@@ -24,11 +24,24 @@ SmartPanel {
|
||||
|
||||
property int currentStep: 0
|
||||
property int totalSteps: 5
|
||||
property bool isCompleting: false
|
||||
|
||||
onOpened: function () {
|
||||
selectedScaleRatio = Settings.data.general.scaleRatio;
|
||||
selectedBarPosition = Settings.data.bar.position;
|
||||
selectedWallpaperDirectory = Settings.data.wallpaper.directory || Settings.defaultWallpapersDirectory;
|
||||
isCompleting = false;
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Settings
|
||||
function onSettingsSaved() {
|
||||
if (isCompleting) {
|
||||
Logger.i("SetupWizard", "Settings saved, closing panel");
|
||||
isCompleting = false;
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Setup wizard data
|
||||
@@ -366,7 +379,14 @@ SmartPanel {
|
||||
}
|
||||
|
||||
function completeSetup() {
|
||||
if (isCompleting) {
|
||||
Logger.w("SetupWizard", "completeSetup() called while already completing, ignoring");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Logger.i("SetupWizard", "Completing setup with selected options");
|
||||
isCompleting = true;
|
||||
|
||||
if (selectedWallpaperDirectory !== Settings.data.wallpaper.directory) {
|
||||
Settings.data.wallpaper.directory = selectedWallpaperDirectory;
|
||||
@@ -381,10 +401,29 @@ SmartPanel {
|
||||
Settings.data.bar.position = selectedBarPosition;
|
||||
Settings.data.setupCompleted = true;
|
||||
|
||||
// Save settings immediately and wait for settingsSaved signal before closing
|
||||
Settings.saveImmediate();
|
||||
Logger.i("SetupWizard", "Setup completed successfully");
|
||||
Logger.i("SetupWizard", "Setup completed successfully, waiting for settings save confirmation");
|
||||
|
||||
// Fallback: if settingsSaved signal doesn't fire within 2 seconds, close anyway
|
||||
closeTimer.start();
|
||||
} catch (error) {
|
||||
Logger.e("SetupWizard", "Error completing setup:", error);
|
||||
isCompleting = false;
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: closeTimer
|
||||
interval: 2000
|
||||
onTriggered: {
|
||||
if (isCompleting) {
|
||||
Logger.w("SetupWizard", "Settings save timeout, closing panel anyway");
|
||||
isCompleting = false;
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function applyWallpaperSettings() {
|
||||
if (selectedWallpaperDirectory !== Settings.data.wallpaper.directory) {
|
||||
|
||||
@@ -184,9 +184,15 @@ SmartPanel {
|
||||
if (!modelData.onlyMenu) {
|
||||
modelData.activate();
|
||||
}
|
||||
if ((PanelService.openedPanel !== null) && !PanelService.openedPanel.isClosing) {
|
||||
PanelService.openedPanel.close();
|
||||
}
|
||||
} else if (mouse.button === Qt.MiddleButton) {
|
||||
// Middle click: activate with middle button
|
||||
modelData.secondaryActivate && modelData.secondaryActivate();
|
||||
if ((PanelService.openedPanel !== null) && !PanelService.openedPanel.isClosing) {
|
||||
PanelService.openedPanel.close();
|
||||
}
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
// Right click: open context menu
|
||||
TooltipService.hideImmediately();
|
||||
|
||||
370
Modules/Panels/WiFi/WiFiNetworksList.qml
Normal file
370
Modules/Panels/WiFi/WiFiNetworksList.qml
Normal file
@@ -0,0 +1,370 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.Networking
|
||||
import qs.Widgets
|
||||
|
||||
NBox {
|
||||
id: root
|
||||
|
||||
property string label: ""
|
||||
property var model: []
|
||||
property string passwordSsid: ""
|
||||
property string expandedSsid: ""
|
||||
|
||||
signal passwordRequested(string ssid)
|
||||
signal passwordSubmitted(string ssid, string password)
|
||||
signal passwordCancelled
|
||||
signal forgetRequested(string ssid)
|
||||
signal forgetConfirmed(string ssid)
|
||||
signal forgetCancelled
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: column.implicitHeight + Style.marginM * 2
|
||||
visible: root.model.length > 0
|
||||
|
||||
ColumnLayout {
|
||||
id: column
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginM
|
||||
spacing: Style.marginM
|
||||
|
||||
NText {
|
||||
text: root.label
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mSecondary
|
||||
font.weight: Style.fontWeightBold
|
||||
visible: root.model.length > 0
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Style.marginS
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.model
|
||||
|
||||
Rectangle {
|
||||
id: networkItem
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Style.marginXS
|
||||
Layout.rightMargin: Style.marginXS
|
||||
implicitHeight: netColumn.implicitHeight + (Style.marginM * 2)
|
||||
radius: Style.radiusM
|
||||
border.width: Style.borderS
|
||||
border.color: modelData.connected ? Color.mPrimary : Color.mOutline
|
||||
|
||||
opacity: (NetworkService.disconnectingFrom === modelData.ssid || NetworkService.forgettingNetwork === modelData.ssid) ? 0.6 : 1.0
|
||||
|
||||
color: modelData.connected ? Qt.rgba(Color.mPrimary.r, Color.mPrimary.g, Color.mPrimary.b, 0.05) : Color.mSurface
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: netColumn
|
||||
width: parent.width - (Style.marginM * 2)
|
||||
x: Style.marginM
|
||||
y: Style.marginM
|
||||
spacing: Style.marginS
|
||||
|
||||
// Main row
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginS
|
||||
|
||||
NIcon {
|
||||
icon: NetworkService.signalIcon(modelData.signal, modelData.connected)
|
||||
pointSize: Style.fontSizeXXL
|
||||
color: modelData.connected ? Color.mPrimary : Color.mOnSurface
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 2
|
||||
|
||||
NText {
|
||||
text: modelData.ssid
|
||||
pointSize: Style.fontSizeM
|
||||
font.weight: modelData.connected ? Style.fontWeightBold : Style.fontWeightMedium
|
||||
color: Color.mOnSurface
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginXS
|
||||
|
||||
NText {
|
||||
text: I18n.tr("system.signal-strength", {
|
||||
"signal": modelData.signal
|
||||
})
|
||||
pointSize: Style.fontSizeXXS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "•"
|
||||
pointSize: Style.fontSizeXXS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NText {
|
||||
text: NetworkService.isSecured(modelData.security) ? modelData.security : "Open"
|
||||
pointSize: Style.fontSizeXXS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: Style.marginXXS
|
||||
}
|
||||
|
||||
// Status badges
|
||||
Rectangle {
|
||||
visible: modelData.connected && NetworkService.disconnectingFrom !== modelData.ssid
|
||||
color: Color.mPrimary
|
||||
radius: height * 0.5
|
||||
width: connectedText.implicitWidth + (Style.marginS * 2)
|
||||
height: connectedText.implicitHeight + (Style.marginXXS * 2)
|
||||
|
||||
NText {
|
||||
id: connectedText
|
||||
anchors.centerIn: parent
|
||||
text: I18n.tr("wifi.panel.connected")
|
||||
pointSize: Style.fontSizeXXS
|
||||
color: Color.mOnPrimary
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: NetworkService.disconnectingFrom === modelData.ssid
|
||||
color: Color.mError
|
||||
radius: height * 0.5
|
||||
width: disconnectingText.implicitWidth + (Style.marginS * 2)
|
||||
height: disconnectingText.implicitHeight + (Style.marginXXS * 2)
|
||||
|
||||
NText {
|
||||
id: disconnectingText
|
||||
anchors.centerIn: parent
|
||||
text: I18n.tr("wifi.panel.disconnecting")
|
||||
pointSize: Style.fontSizeXXS
|
||||
color: Color.mOnPrimary
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: NetworkService.forgettingNetwork === modelData.ssid
|
||||
color: Color.mError
|
||||
radius: height * 0.5
|
||||
width: forgettingText.implicitWidth + (Style.marginS * 2)
|
||||
height: forgettingText.implicitHeight + (Style.marginXXS * 2)
|
||||
|
||||
NText {
|
||||
id: forgettingText
|
||||
anchors.centerIn: parent
|
||||
text: I18n.tr("wifi.panel.forgetting")
|
||||
pointSize: Style.fontSizeXXS
|
||||
color: Color.mOnPrimary
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: modelData.cached && !modelData.connected && NetworkService.forgettingNetwork !== modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid
|
||||
color: Color.transparent
|
||||
border.color: Color.mOutline
|
||||
border.width: Style.borderS
|
||||
radius: height * 0.5
|
||||
width: savedText.implicitWidth + (Style.marginS * 2)
|
||||
height: savedText.implicitHeight + (Style.marginXXS * 2)
|
||||
|
||||
NText {
|
||||
id: savedText
|
||||
anchors.centerIn: parent
|
||||
text: I18n.tr("wifi.panel.saved")
|
||||
pointSize: Style.fontSizeXXS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Action area
|
||||
RowLayout {
|
||||
spacing: Style.marginS
|
||||
|
||||
NBusyIndicator {
|
||||
visible: NetworkService.connectingTo === modelData.ssid || NetworkService.disconnectingFrom === modelData.ssid || NetworkService.forgettingNetwork === modelData.ssid
|
||||
running: visible
|
||||
color: Color.mPrimary
|
||||
size: Style.baseWidgetSize * 0.5
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
visible: (modelData.existing || modelData.cached) && !modelData.connected && NetworkService.connectingTo !== modelData.ssid && NetworkService.forgettingNetwork !== modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid
|
||||
icon: "trash"
|
||||
tooltipText: I18n.tr("tooltips.forget-network")
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: root.forgetRequested(modelData.ssid)
|
||||
}
|
||||
|
||||
NButton {
|
||||
visible: !modelData.connected && NetworkService.connectingTo !== modelData.ssid && root.passwordSsid !== modelData.ssid && NetworkService.forgettingNetwork !== modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid
|
||||
text: {
|
||||
if (modelData.existing || modelData.cached)
|
||||
return I18n.tr("wifi.panel.connect");
|
||||
if (!NetworkService.isSecured(modelData.security))
|
||||
return I18n.tr("wifi.panel.connect");
|
||||
return I18n.tr("wifi.panel.password");
|
||||
}
|
||||
outlined: !hovered
|
||||
fontSize: Style.fontSizeXS
|
||||
enabled: !NetworkService.connecting
|
||||
onClicked: {
|
||||
if (modelData.existing || modelData.cached || !NetworkService.isSecured(modelData.security)) {
|
||||
NetworkService.connect(modelData.ssid);
|
||||
} else {
|
||||
root.passwordRequested(modelData.ssid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NButton {
|
||||
visible: modelData.connected && NetworkService.disconnectingFrom !== modelData.ssid
|
||||
text: I18n.tr("wifi.panel.disconnect")
|
||||
outlined: !hovered
|
||||
fontSize: Style.fontSizeXS
|
||||
backgroundColor: Color.mError
|
||||
onClicked: NetworkService.disconnect(modelData.ssid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Password input
|
||||
Rectangle {
|
||||
visible: root.passwordSsid === modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid && NetworkService.forgettingNetwork !== modelData.ssid
|
||||
Layout.fillWidth: true
|
||||
height: passwordRow.implicitHeight + Style.marginS * 2
|
||||
color: Color.mSurfaceVariant
|
||||
border.color: Color.mOutline
|
||||
border.width: Style.borderS
|
||||
radius: Style.radiusS
|
||||
|
||||
RowLayout {
|
||||
id: passwordRow
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginS
|
||||
spacing: Style.marginM
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
radius: Style.radiusXS
|
||||
color: Color.mSurface
|
||||
border.color: pwdInput.activeFocus ? Color.mSecondary : Color.mOutline
|
||||
border.width: Style.borderS
|
||||
|
||||
TextInput {
|
||||
id: pwdInput
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.margins: Style.marginS
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeS
|
||||
color: Color.mOnSurface
|
||||
echoMode: TextInput.Password
|
||||
selectByMouse: true
|
||||
focus: visible
|
||||
passwordCharacter: "●"
|
||||
onVisibleChanged: if (visible) {
|
||||
text = "";
|
||||
forceActiveFocus();
|
||||
}
|
||||
onAccepted: {
|
||||
if (text && !NetworkService.connecting) {
|
||||
root.passwordSubmitted(modelData.ssid, text);
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
visible: parent.text.length === 0
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: I18n.tr("wifi.panel.enter-password")
|
||||
color: Color.mOnSurfaceVariant
|
||||
pointSize: Style.fontSizeS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NButton {
|
||||
text: I18n.tr("wifi.panel.connect")
|
||||
fontSize: Style.fontSizeXXS
|
||||
enabled: pwdInput.text.length > 0 && !NetworkService.connecting
|
||||
outlined: true
|
||||
onClicked: root.passwordSubmitted(modelData.ssid, pwdInput.text)
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "close"
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: root.passwordCancelled()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Forget network
|
||||
Rectangle {
|
||||
visible: root.expandedSsid === modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid && NetworkService.forgettingNetwork !== modelData.ssid
|
||||
Layout.fillWidth: true
|
||||
height: forgetRow.implicitHeight + Style.marginS * 2
|
||||
color: Color.mSurfaceVariant
|
||||
radius: Style.radiusS
|
||||
border.width: Style.borderS
|
||||
border.color: Color.mOutline
|
||||
|
||||
RowLayout {
|
||||
id: forgetRow
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginS
|
||||
spacing: Style.marginM
|
||||
|
||||
RowLayout {
|
||||
NIcon {
|
||||
icon: "trash"
|
||||
pointSize: Style.fontSizeL
|
||||
color: Color.mError
|
||||
}
|
||||
|
||||
NText {
|
||||
text: I18n.tr("wifi.panel.forget-network")
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mError
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
NButton {
|
||||
id: forgetButton
|
||||
text: I18n.tr("wifi.panel.forget")
|
||||
fontSize: Style.fontSizeXXS
|
||||
backgroundColor: Color.mError
|
||||
outlined: forgetButton.hovered ? false : true
|
||||
onClicked: root.forgetConfirmed(modelData.ssid)
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "close"
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: root.forgetCancelled()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,15 +14,71 @@ SmartPanel {
|
||||
preferredHeight: Math.round(500 * Style.uiScaleRatio)
|
||||
|
||||
property string passwordSsid: ""
|
||||
property string passwordInput: ""
|
||||
property string expandedSsid: ""
|
||||
property bool hasHadNetworks: false
|
||||
|
||||
onOpened: NetworkService.scan()
|
||||
// Computed network lists
|
||||
readonly property var knownNetworks: {
|
||||
if (!Settings.data.network.wifiEnabled)
|
||||
return [];
|
||||
|
||||
const nets = Object.values(NetworkService.networks);
|
||||
const known = nets.filter(n => n.connected || n.existing || n.cached);
|
||||
|
||||
// Sort: connected first, then by signal strength
|
||||
known.sort((a, b) => {
|
||||
if (a.connected !== b.connected)
|
||||
return b.connected - a.connected;
|
||||
return b.signal - a.signal;
|
||||
});
|
||||
|
||||
return known;
|
||||
}
|
||||
|
||||
readonly property var availableNetworks: {
|
||||
if (!Settings.data.network.wifiEnabled)
|
||||
return [];
|
||||
|
||||
const nets = Object.values(NetworkService.networks);
|
||||
const available = nets.filter(n => !n.connected && !n.existing && !n.cached);
|
||||
|
||||
// Sort by signal strength
|
||||
available.sort((a, b) => b.signal - a.signal);
|
||||
|
||||
return available;
|
||||
}
|
||||
|
||||
onOpened: {
|
||||
hasHadNetworks = false;
|
||||
NetworkService.scan();
|
||||
}
|
||||
|
||||
onKnownNetworksChanged: {
|
||||
if (knownNetworks.length > 0)
|
||||
hasHadNetworks = true;
|
||||
}
|
||||
|
||||
onAvailableNetworksChanged: {
|
||||
if (availableNetworks.length > 0)
|
||||
hasHadNetworks = true;
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Settings.data.network
|
||||
function onWifiEnabledChanged() {
|
||||
if (!Settings.data.network.wifiEnabled)
|
||||
root.hasHadNetworks = false;
|
||||
}
|
||||
}
|
||||
|
||||
panelContent: Rectangle {
|
||||
color: Color.transparent
|
||||
|
||||
property real contentPreferredHeight: Math.min(preferredHeight, Math.max(280 * Style.uiScaleRatio, mainColumn.implicitHeight + Style.marginL * 2))
|
||||
// Calculate content height based on header + networks list (or minimum for empty states)
|
||||
property real headerHeight: headerRow.implicitHeight + Style.marginM * 2
|
||||
property real networksHeight: networksList.implicitHeight
|
||||
property real calculatedHeight: headerHeight + networksHeight + Style.marginL * 2 + Style.marginM
|
||||
property real contentPreferredHeight: Settings.data.network.wifiEnabled && Object.keys(NetworkService.networks).length > 0 ? Math.min(root.preferredHeight, Math.max(280 * Style.uiScaleRatio, calculatedHeight)) : Math.min(root.preferredHeight, 280 * Style.uiScaleRatio)
|
||||
|
||||
ColumnLayout {
|
||||
id: mainColumn
|
||||
@@ -116,14 +172,13 @@ SmartPanel {
|
||||
}
|
||||
}
|
||||
|
||||
// Main content area
|
||||
// WiFi disabled state
|
||||
NBox {
|
||||
visible: !Settings.data.network.wifiEnabled
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
// WiFi disabled state
|
||||
ColumnLayout {
|
||||
visible: !Settings.data.network.wifiEnabled
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginM
|
||||
|
||||
@@ -158,10 +213,15 @@ SmartPanel {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Scanning state (show when no networks and we haven't had any yet)
|
||||
NBox {
|
||||
visible: Settings.data.network.wifiEnabled && Object.keys(NetworkService.networks).length === 0 && !root.hasHadNetworks
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
// Scanning state
|
||||
ColumnLayout {
|
||||
visible: Settings.data.network.wifiEnabled && NetworkService.scanning && Object.keys(NetworkService.networks).length === 0
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginM
|
||||
spacing: Style.marginL
|
||||
@@ -188,374 +248,15 @@ SmartPanel {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
|
||||
// Networks list container
|
||||
NScrollView {
|
||||
visible: Settings.data.network.wifiEnabled && (!NetworkService.scanning || Object.keys(NetworkService.networks).length > 0)
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginM
|
||||
horizontalPolicy: ScrollBar.AlwaysOff
|
||||
verticalPolicy: ScrollBar.AsNeeded
|
||||
clip: true
|
||||
|
||||
ColumnLayout {
|
||||
width: parent.width
|
||||
spacing: Style.marginM
|
||||
|
||||
// Network list
|
||||
Repeater {
|
||||
model: {
|
||||
if (!Settings.data.network.wifiEnabled)
|
||||
return [];
|
||||
|
||||
const nets = Object.values(NetworkService.networks);
|
||||
return nets.sort((a, b) => {
|
||||
if (a.connected !== b.connected)
|
||||
return b.connected - a.connected;
|
||||
return b.signal - a.signal;
|
||||
});
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: netColumn.implicitHeight + (Style.marginM * 2)
|
||||
radius: Style.radiusM
|
||||
|
||||
// Add opacity for operations in progress
|
||||
opacity: (NetworkService.disconnectingFrom === modelData.ssid || NetworkService.forgettingNetwork === modelData.ssid) ? 0.6 : 1.0
|
||||
|
||||
color: modelData.connected ? Qt.rgba(Color.mPrimary.r, Color.mPrimary.g, Color.mPrimary.b, 0.05) : Color.mSurface
|
||||
border.width: Style.borderS
|
||||
border.color: modelData.connected ? Color.mPrimary : Color.mOutline
|
||||
|
||||
// Smooth opacity animation
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: netColumn
|
||||
width: parent.width - (Style.marginM * 2)
|
||||
x: Style.marginM
|
||||
y: Style.marginM
|
||||
spacing: Style.marginS
|
||||
|
||||
// Main row
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginS
|
||||
|
||||
NIcon {
|
||||
icon: NetworkService.signalIcon(modelData.signal, modelData.connected)
|
||||
pointSize: Style.fontSizeXXL
|
||||
color: modelData.connected ? Color.mPrimary : Color.mOnSurface
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 2
|
||||
|
||||
NText {
|
||||
text: modelData.ssid
|
||||
pointSize: Style.fontSizeM
|
||||
font.weight: modelData.connected ? Style.fontWeightBold : Style.fontWeightMedium
|
||||
color: Color.mOnSurface
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginXS
|
||||
|
||||
NText {
|
||||
text: I18n.tr("system.signal-strength", {
|
||||
"signal": modelData.signal
|
||||
})
|
||||
pointSize: Style.fontSizeXXS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "•"
|
||||
pointSize: Style.fontSizeXXS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
NText {
|
||||
text: NetworkService.isSecured(modelData.security) ? modelData.security : "Open"
|
||||
pointSize: Style.fontSizeXXS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: Style.marginXXS
|
||||
}
|
||||
|
||||
// Update the status badges area (around line 237)
|
||||
Rectangle {
|
||||
visible: modelData.connected && NetworkService.disconnectingFrom !== modelData.ssid
|
||||
color: Color.mPrimary
|
||||
radius: height * 0.5
|
||||
width: connectedText.implicitWidth + (Style.marginS * 2)
|
||||
height: connectedText.implicitHeight + (Style.marginXXS * 2)
|
||||
|
||||
NText {
|
||||
id: connectedText
|
||||
anchors.centerIn: parent
|
||||
text: I18n.tr("wifi.panel.connected")
|
||||
pointSize: Style.fontSizeXXS
|
||||
color: Color.mOnPrimary
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: NetworkService.disconnectingFrom === modelData.ssid
|
||||
color: Color.mError
|
||||
radius: height * 0.5
|
||||
width: disconnectingText.implicitWidth + (Style.marginS * 2)
|
||||
height: disconnectingText.implicitHeight + (Style.marginXXS * 2)
|
||||
|
||||
NText {
|
||||
id: disconnectingText
|
||||
anchors.centerIn: parent
|
||||
text: I18n.tr("wifi.panel.disconnecting")
|
||||
pointSize: Style.fontSizeXXS
|
||||
color: Color.mOnPrimary
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: NetworkService.forgettingNetwork === modelData.ssid
|
||||
color: Color.mError
|
||||
radius: height * 0.5
|
||||
width: forgettingText.implicitWidth + (Style.marginS * 2)
|
||||
height: forgettingText.implicitHeight + (Style.marginXXS * 2)
|
||||
|
||||
NText {
|
||||
id: forgettingText
|
||||
anchors.centerIn: parent
|
||||
text: I18n.tr("wifi.panel.forgetting")
|
||||
pointSize: Style.fontSizeXXS
|
||||
color: Color.mOnPrimary
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: modelData.cached && !modelData.connected && NetworkService.forgettingNetwork !== modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid
|
||||
color: Color.transparent
|
||||
border.color: Color.mOutline
|
||||
border.width: Style.borderS
|
||||
radius: height * 0.5
|
||||
width: savedText.implicitWidth + (Style.marginS * 2)
|
||||
height: savedText.implicitHeight + (Style.marginXXS * 2)
|
||||
|
||||
NText {
|
||||
id: savedText
|
||||
anchors.centerIn: parent
|
||||
text: I18n.tr("wifi.panel.saved")
|
||||
pointSize: Style.fontSizeXXS
|
||||
color: Color.mOnSurfaceVariant
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Action area
|
||||
RowLayout {
|
||||
spacing: Style.marginS
|
||||
|
||||
NBusyIndicator {
|
||||
visible: NetworkService.connectingTo === modelData.ssid || NetworkService.disconnectingFrom === modelData.ssid || NetworkService.forgettingNetwork === modelData.ssid
|
||||
running: visible
|
||||
color: Color.mPrimary
|
||||
size: Style.baseWidgetSize * 0.5
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
visible: (modelData.existing || modelData.cached) && !modelData.connected && NetworkService.connectingTo !== modelData.ssid && NetworkService.forgettingNetwork !== modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid
|
||||
icon: "trash"
|
||||
tooltipText: I18n.tr("tooltips.forget-network")
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: expandedSsid = expandedSsid === modelData.ssid ? "" : modelData.ssid
|
||||
}
|
||||
|
||||
NButton {
|
||||
visible: !modelData.connected && NetworkService.connectingTo !== modelData.ssid && passwordSsid !== modelData.ssid && NetworkService.forgettingNetwork !== modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid
|
||||
text: {
|
||||
if (modelData.existing || modelData.cached)
|
||||
return I18n.tr("wifi.panel.connect");
|
||||
if (!NetworkService.isSecured(modelData.security))
|
||||
return I18n.tr("wifi.panel.connect");
|
||||
return I18n.tr("wifi.panel.password");
|
||||
}
|
||||
outlined: !hovered
|
||||
fontSize: Style.fontSizeXS
|
||||
enabled: !NetworkService.connecting
|
||||
onClicked: {
|
||||
if (modelData.existing || modelData.cached || !NetworkService.isSecured(modelData.security)) {
|
||||
NetworkService.connect(modelData.ssid);
|
||||
} else {
|
||||
passwordSsid = modelData.ssid;
|
||||
passwordInput = "";
|
||||
expandedSsid = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NButton {
|
||||
visible: modelData.connected && NetworkService.disconnectingFrom !== modelData.ssid
|
||||
text: I18n.tr("wifi.panel.disconnect")
|
||||
outlined: !hovered
|
||||
fontSize: Style.fontSizeXS
|
||||
backgroundColor: Color.mError
|
||||
onClicked: NetworkService.disconnect(modelData.ssid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Password input
|
||||
Rectangle {
|
||||
visible: passwordSsid === modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid && NetworkService.forgettingNetwork !== modelData.ssid
|
||||
Layout.fillWidth: true
|
||||
height: passwordRow.implicitHeight + Style.marginS * 2
|
||||
color: Color.mSurfaceVariant
|
||||
border.color: Color.mOutline
|
||||
border.width: Style.borderS
|
||||
radius: Style.radiusS
|
||||
|
||||
RowLayout {
|
||||
id: passwordRow
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginS
|
||||
spacing: Style.marginM
|
||||
|
||||
Rectangle {
|
||||
// Empty state when no networks (only show after we've had networks before, meaning a real empty result)
|
||||
NBox {
|
||||
visible: Settings.data.network.wifiEnabled && !NetworkService.scanning && Object.keys(NetworkService.networks).length === 0 && root.hasHadNetworks
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
radius: Style.radiusXS
|
||||
color: Color.mSurface
|
||||
border.color: pwdInput.activeFocus ? Color.mSecondary : Color.mOutline
|
||||
border.width: Style.borderS
|
||||
|
||||
TextInput {
|
||||
id: pwdInput
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.margins: Style.marginS
|
||||
text: passwordInput
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeS
|
||||
color: Color.mOnSurface
|
||||
echoMode: TextInput.Password
|
||||
selectByMouse: true
|
||||
focus: visible
|
||||
passwordCharacter: "●"
|
||||
onTextChanged: passwordInput = text
|
||||
onVisibleChanged: if (visible)
|
||||
forceActiveFocus()
|
||||
onAccepted: {
|
||||
if (text && !NetworkService.connecting) {
|
||||
NetworkService.connect(passwordSsid, text);
|
||||
passwordSsid = "";
|
||||
passwordInput = "";
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
visible: parent.text.length === 0
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: I18n.tr("wifi.panel.enter-password")
|
||||
color: Color.mOnSurfaceVariant
|
||||
pointSize: Style.fontSizeS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NButton {
|
||||
text: I18n.tr("wifi.panel.connect")
|
||||
fontSize: Style.fontSizeXXS
|
||||
enabled: passwordInput.length > 0 && !NetworkService.connecting
|
||||
outlined: true
|
||||
onClicked: {
|
||||
NetworkService.connect(passwordSsid, passwordInput);
|
||||
passwordSsid = "";
|
||||
passwordInput = "";
|
||||
}
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "close"
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: {
|
||||
passwordSsid = "";
|
||||
passwordInput = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Forget network
|
||||
Rectangle {
|
||||
visible: expandedSsid === modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid && NetworkService.forgettingNetwork !== modelData.ssid
|
||||
Layout.fillWidth: true
|
||||
height: forgetRow.implicitHeight + Style.marginS * 2
|
||||
color: Color.mSurfaceVariant
|
||||
radius: Style.radiusS
|
||||
border.width: Style.borderS
|
||||
border.color: Color.mOutline
|
||||
|
||||
RowLayout {
|
||||
id: forgetRow
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginS
|
||||
spacing: Style.marginM
|
||||
|
||||
RowLayout {
|
||||
NIcon {
|
||||
icon: "trash"
|
||||
pointSize: Style.fontSizeL
|
||||
color: Color.mError
|
||||
}
|
||||
|
||||
NText {
|
||||
text: I18n.tr("wifi.panel.forget-network")
|
||||
pointSize: Style.fontSizeS
|
||||
color: Color.mError
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
NButton {
|
||||
id: forgetButton
|
||||
text: I18n.tr("wifi.panel.forget")
|
||||
fontSize: Style.fontSizeXXS
|
||||
backgroundColor: Color.mError
|
||||
outlined: forgetButton.hovered ? false : true
|
||||
onClicked: {
|
||||
NetworkService.forget(modelData.ssid);
|
||||
expandedSsid = "";
|
||||
}
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "close"
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: expandedSsid = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Empty state when no networks
|
||||
ColumnLayout {
|
||||
visible: Settings.data.network.wifiEnabled && !NetworkService.scanning && Object.keys(NetworkService.networks).length === 0
|
||||
anchors.fill: parent
|
||||
spacing: Style.marginL
|
||||
|
||||
@@ -589,6 +290,66 @@ SmartPanel {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Networks list container (no NBox wrapper)
|
||||
NScrollView {
|
||||
visible: Settings.data.network.wifiEnabled && Object.keys(NetworkService.networks).length > 0
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
horizontalPolicy: ScrollBar.AlwaysOff
|
||||
verticalPolicy: ScrollBar.AsNeeded
|
||||
clip: true
|
||||
|
||||
ColumnLayout {
|
||||
id: networksList
|
||||
width: parent.width
|
||||
spacing: Style.marginM
|
||||
|
||||
WiFiNetworksList {
|
||||
label: I18n.tr("wifi.panel.known-networks")
|
||||
model: root.knownNetworks
|
||||
passwordSsid: root.passwordSsid
|
||||
expandedSsid: root.expandedSsid
|
||||
onPasswordRequested: ssid => {
|
||||
root.passwordSsid = ssid;
|
||||
root.expandedSsid = "";
|
||||
}
|
||||
onPasswordSubmitted: (ssid, password) => {
|
||||
NetworkService.connect(ssid, password);
|
||||
root.passwordSsid = "";
|
||||
}
|
||||
onPasswordCancelled: root.passwordSsid = ""
|
||||
onForgetRequested: ssid => root.expandedSsid = root.expandedSsid === ssid ? "" : ssid
|
||||
onForgetConfirmed: ssid => {
|
||||
NetworkService.forget(ssid);
|
||||
root.expandedSsid = "";
|
||||
}
|
||||
onForgetCancelled: root.expandedSsid = ""
|
||||
}
|
||||
|
||||
WiFiNetworksList {
|
||||
label: I18n.tr("wifi.panel.available-networks")
|
||||
model: root.availableNetworks
|
||||
passwordSsid: root.passwordSsid
|
||||
expandedSsid: root.expandedSsid
|
||||
onPasswordRequested: ssid => {
|
||||
root.passwordSsid = ssid;
|
||||
root.expandedSsid = "";
|
||||
}
|
||||
onPasswordSubmitted: (ssid, password) => {
|
||||
NetworkService.connect(ssid, password);
|
||||
root.passwordSsid = "";
|
||||
}
|
||||
onPasswordCancelled: root.passwordSsid = ""
|
||||
onForgetRequested: ssid => root.expandedSsid = root.expandedSsid === ssid ? "" : ssid
|
||||
onForgetConfirmed: ssid => {
|
||||
NetworkService.forget(ssid);
|
||||
root.expandedSsid = "";
|
||||
}
|
||||
onForgetCancelled: root.expandedSsid = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,8 +122,11 @@ PopupWindow {
|
||||
hideImmediately();
|
||||
}
|
||||
|
||||
// Convert \n to <br> for RichText format
|
||||
const processedText = tipText.replace(/\n/g, '<br>');
|
||||
|
||||
// Set properties
|
||||
text = tipText;
|
||||
text = processedText;
|
||||
targetItem = target;
|
||||
|
||||
// Find the correct screen dimensions based on target's global position
|
||||
@@ -326,7 +329,9 @@ PopupWindow {
|
||||
// Update text function
|
||||
function updateText(newText) {
|
||||
if (visible && targetItem) {
|
||||
text = newText;
|
||||
// Convert \n to <br> for RichText format
|
||||
const processedText = newText.replace(/\n/g, '<br>');
|
||||
text = processedText;
|
||||
|
||||
// Recalculate dimensions
|
||||
const tipWidth = Math.min(tooltipText.implicitWidth + (padding * 2), maxWidth);
|
||||
|
||||
@@ -32,21 +32,26 @@ Singleton {
|
||||
// Backend service loader
|
||||
property var backend: null
|
||||
|
||||
// Cache file path
|
||||
property string displayCachePath: ""
|
||||
|
||||
Component.onCompleted: {
|
||||
// Setup cache path (needs Settings to be available)
|
||||
// Load display scales from ShellState
|
||||
Qt.callLater(() => {
|
||||
if (typeof Settings !== 'undefined' && Settings.cacheDir) {
|
||||
displayCachePath = Settings.cacheDir + "display.json";
|
||||
displayCacheFileView.path = displayCachePath;
|
||||
if (typeof ShellState !== 'undefined' && ShellState.isLoaded) {
|
||||
loadDisplayScalesFromState();
|
||||
}
|
||||
});
|
||||
|
||||
detectCompositor();
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: typeof ShellState !== 'undefined' ? ShellState : null
|
||||
function onIsLoadedChanged() {
|
||||
if (ShellState.isLoaded) {
|
||||
loadDisplayScalesFromState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function detectCompositor() {
|
||||
const hyprlandSignature = Quickshell.env("HYPRLAND_INSTANCE_SIGNATURE");
|
||||
const niriSocket = Quickshell.env("NIRI_SOCKET");
|
||||
@@ -99,30 +104,51 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
// Cache FileView for display scales
|
||||
FileView {
|
||||
id: displayCacheFileView
|
||||
printErrors: false
|
||||
watchChanges: false
|
||||
// Load display scales from ShellState
|
||||
function loadDisplayScalesFromState() {
|
||||
try {
|
||||
const cached = ShellState.getDisplay();
|
||||
if (cached && Object.keys(cached).length > 0) {
|
||||
displayScales = cached;
|
||||
displayScalesLoaded = true;
|
||||
Logger.d("CompositorService", "Loaded display scales from ShellState");
|
||||
} else {
|
||||
// Try to migrate from old display.json if it exists
|
||||
migrateFromOldDisplayFile();
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.e("CompositorService", "Failed to load display scales:", error);
|
||||
displayScalesLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Migration from old display.json file
|
||||
function migrateFromOldDisplayFile() {
|
||||
const oldDisplayPath = Settings.cacheDir + "display.json";
|
||||
const migrationFileView = Qt.createQmlObject(`
|
||||
import QtQuick
|
||||
import Quickshell.Io
|
||||
FileView {
|
||||
id: migrationView
|
||||
path: "${oldDisplayPath}"
|
||||
printErrors: false
|
||||
adapter: JsonAdapter {
|
||||
id: displayCacheAdapter
|
||||
property var displays: ({})
|
||||
}
|
||||
|
||||
onLoaded: {
|
||||
// Load cached display scales
|
||||
displayScales = displayCacheAdapter.displays || {};
|
||||
displayScalesLoaded = true;
|
||||
// Logger.i("CompositorService", "Loaded display scales from cache:", JSON.stringify(displayScales))
|
||||
parent.displayScales = adapter.displays || {};
|
||||
parent.displayScalesLoaded = true;
|
||||
parent.saveDisplayScalesToCache();
|
||||
Logger.i("CompositorService", "Migrated display.json to ShellState");
|
||||
migrationView.destroy();
|
||||
}
|
||||
|
||||
onLoadFailed: {
|
||||
// Cache doesn't exist yet, will be created on first update
|
||||
displayScalesLoaded = true;
|
||||
// Logger.i("CompositorService", "No display cache found, will create on first update")
|
||||
parent.displayScalesLoaded = true;
|
||||
migrationView.destroy();
|
||||
}
|
||||
}
|
||||
`, root, "migrationFileView");
|
||||
}
|
||||
|
||||
// Hyprland backend component
|
||||
Component {
|
||||
@@ -234,12 +260,12 @@ Singleton {
|
||||
|
||||
// Save display scales to cache
|
||||
function saveDisplayScalesToCache() {
|
||||
if (!displayCachePath) {
|
||||
return;
|
||||
try {
|
||||
ShellState.setDisplay(displayScales);
|
||||
Logger.d("CompositorService", "Saved display scales to ShellState");
|
||||
} catch (error) {
|
||||
Logger.e("CompositorService", "Failed to save display scales:", error);
|
||||
}
|
||||
|
||||
displayCacheAdapter.displays = displayScales;
|
||||
displayCacheFileView.writeAdapter();
|
||||
}
|
||||
|
||||
// Public function to get scale for a specific display
|
||||
|
||||
@@ -120,24 +120,8 @@ Item {
|
||||
// New preferred method - lock the screen
|
||||
function lock() {
|
||||
// Only lock if not already locked (prevents the red screen issue)
|
||||
// Note: No unlock via IPC for security reasons
|
||||
if (!lockScreen.active) {
|
||||
lockScreen.triggeredViaDeprecatedCall = false;
|
||||
lockScreen.active = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Deprecated: Use 'lockScreen lock' instead
|
||||
function toggle() {
|
||||
// Mark as triggered via deprecated call - warning will show in lock screen
|
||||
lockScreen.triggeredViaDeprecatedCall = true;
|
||||
|
||||
// Log deprecation warning for users checking logs
|
||||
Logger.w("IPC", "The 'lockScreen toggle' IPC call is deprecated. Use 'lockScreen lock' instead.");
|
||||
|
||||
// Still functional for backward compatibility
|
||||
if (!lockScreen.active) {
|
||||
lockScreen.active = true;
|
||||
if (!PanelService.lockScreen.active) {
|
||||
PanelService.lockScreen.active = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,7 +207,7 @@ Singleton {
|
||||
root.coordinatesReady = true;
|
||||
|
||||
isFetchingWeather = false;
|
||||
Logger.i("Location", "Cached weather to disk - stable coordinates updated");
|
||||
Logger.d("Location", "Cached weather to disk - stable coordinates updated");
|
||||
} catch (e) {
|
||||
errorCallback("Location", "Failed to parse weather data");
|
||||
}
|
||||
|
||||
287
Services/Networking/VPNService.qml
Normal file
287
Services/Networking/VPNService.qml
Normal file
@@ -0,0 +1,287 @@
|
||||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property var connections: ({})
|
||||
property bool refreshing: false
|
||||
property bool connecting: false
|
||||
property bool disconnecting: false
|
||||
property string connectingUuid: ""
|
||||
property string disconnectingUuid: ""
|
||||
property string lastError: ""
|
||||
property bool refreshPending: false
|
||||
|
||||
readonly property var activeConnections: {
|
||||
const result = [];
|
||||
const map = connections;
|
||||
for (const key in map) {
|
||||
const conn = map[key];
|
||||
if (conn && conn.active) {
|
||||
result.push(conn);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
readonly property var inactiveConnections: {
|
||||
const result = [];
|
||||
const map = connections;
|
||||
for (const key in map) {
|
||||
const conn = map[key];
|
||||
if (conn && !conn.active) {
|
||||
result.push(conn);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
readonly property bool hasActiveConnection: activeConnections.length > 0
|
||||
|
||||
Timer {
|
||||
id: refreshTimer
|
||||
interval: 5000
|
||||
running: true
|
||||
repeat: true
|
||||
onTriggered: refresh()
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: delayedRefreshTimer
|
||||
interval: 1000
|
||||
repeat: false
|
||||
onTriggered: refresh()
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
Logger.i("VPN", "Service started");
|
||||
refresh();
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
if (refreshing) {
|
||||
refreshPending = true;
|
||||
return;
|
||||
}
|
||||
refreshing = true;
|
||||
lastError = "";
|
||||
refreshProcess.running = true;
|
||||
}
|
||||
|
||||
function connect(uuid) {
|
||||
if (connecting || !uuid) {
|
||||
return;
|
||||
}
|
||||
const conn = connections[uuid];
|
||||
if (!conn) {
|
||||
return;
|
||||
}
|
||||
connecting = true;
|
||||
connectingUuid = uuid;
|
||||
lastError = "";
|
||||
connectProcess.uuid = uuid;
|
||||
connectProcess.name = conn.name;
|
||||
connectProcess.running = true;
|
||||
}
|
||||
|
||||
function disconnect(uuid) {
|
||||
if (disconnecting || !uuid) {
|
||||
return;
|
||||
}
|
||||
const conn = connections[uuid];
|
||||
if (!conn) {
|
||||
return;
|
||||
}
|
||||
disconnecting = true;
|
||||
disconnectingUuid = uuid;
|
||||
lastError = "";
|
||||
disconnectProcess.uuid = uuid;
|
||||
disconnectProcess.name = conn.name;
|
||||
disconnectProcess.running = true;
|
||||
}
|
||||
|
||||
function toggle(uuid) {
|
||||
const conn = connections[uuid];
|
||||
if (!conn) {
|
||||
return;
|
||||
}
|
||||
if (conn.active) {
|
||||
disconnect(uuid);
|
||||
} else {
|
||||
connect(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
function setConnection(uuid, data) {
|
||||
if (!uuid) {
|
||||
return;
|
||||
}
|
||||
const map = Object.assign({}, connections);
|
||||
if (map[uuid]) {
|
||||
map[uuid] = Object.assign({}, map[uuid], data);
|
||||
connections = map;
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleRefresh(interval) {
|
||||
delayedRefreshTimer.interval = interval;
|
||||
delayedRefreshTimer.restart();
|
||||
}
|
||||
|
||||
Process {
|
||||
id: refreshProcess
|
||||
running: false
|
||||
command: ["nmcli", "-t", "-f", "NAME,UUID,TYPE,DEVICE", "connection", "show"]
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const lines = text.split("\n");
|
||||
const map = {};
|
||||
for (let i = 0; i < lines.length; ++i) {
|
||||
const line = lines[i].trim();
|
||||
if (!line) {
|
||||
continue;
|
||||
}
|
||||
const lastColonIdx = line.lastIndexOf(":");
|
||||
if (lastColonIdx === -1) {
|
||||
continue;
|
||||
}
|
||||
const device = line.substring(lastColonIdx + 1);
|
||||
const remaining = line.substring(0, lastColonIdx);
|
||||
const secondLastColonIdx = remaining.lastIndexOf(":");
|
||||
if (secondLastColonIdx === -1) {
|
||||
continue;
|
||||
}
|
||||
const type = remaining.substring(secondLastColonIdx + 1);
|
||||
if (type !== "vpn") {
|
||||
continue;
|
||||
}
|
||||
const remaining2 = remaining.substring(0, secondLastColonIdx);
|
||||
const thirdLastColonIdx = remaining2.lastIndexOf(":");
|
||||
if (thirdLastColonIdx === -1) {
|
||||
continue;
|
||||
}
|
||||
const uuid = remaining2.substring(thirdLastColonIdx + 1);
|
||||
const name = remaining2.substring(0, thirdLastColonIdx);
|
||||
if (!uuid || !name) {
|
||||
continue;
|
||||
}
|
||||
const active = device && device !== "--";
|
||||
map[uuid] = {
|
||||
"uuid": uuid,
|
||||
"name": name,
|
||||
"device": device,
|
||||
"active": active
|
||||
};
|
||||
}
|
||||
connections = map;
|
||||
const pending = refreshPending;
|
||||
refreshing = false;
|
||||
refreshPending = false;
|
||||
if (pending) {
|
||||
scheduleRefresh(200);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stderr: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const pending = refreshPending;
|
||||
refreshing = false;
|
||||
refreshPending = false;
|
||||
if (text.trim()) {
|
||||
lastError = text.split("\n")[0].trim();
|
||||
Logger.w("VPN", "Refresh error: " + text);
|
||||
}
|
||||
if (pending) {
|
||||
scheduleRefresh(2000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: connectProcess
|
||||
property string uuid: ""
|
||||
property string name: ""
|
||||
running: false
|
||||
command: ["nmcli", "connection", "up", "uuid", uuid]
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const output = text.trim();
|
||||
if (!output || (!output.includes("successfully activated") && !output.includes("Connection successfully"))) {
|
||||
return;
|
||||
}
|
||||
setConnection(connectProcess.uuid, {
|
||||
"active": true
|
||||
});
|
||||
connecting = false;
|
||||
connectingUuid = "";
|
||||
lastError = "";
|
||||
Logger.i("VPN", "Connected to " + connectProcess.name);
|
||||
ToastService.showNotice(connectProcess.name, I18n.tr("toast.vpn.connected", {
|
||||
"name": connectProcess.name
|
||||
}), "shield-lock");
|
||||
scheduleRefresh(1000);
|
||||
}
|
||||
}
|
||||
|
||||
stderr: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const trimmed = text.trim();
|
||||
if (trimmed) {
|
||||
lastError = trimmed.split("\n")[0].trim();
|
||||
Logger.w("VPN", "Connect error: " + trimmed);
|
||||
ToastService.showWarning(connectProcess.name, lastError);
|
||||
}
|
||||
connecting = false;
|
||||
connectingUuid = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: disconnectProcess
|
||||
property string uuid: ""
|
||||
property string name: ""
|
||||
running: false
|
||||
command: ["nmcli", "connection", "down", "uuid", uuid]
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
Logger.i("VPN", "Disconnected from " + disconnectProcess.name);
|
||||
setConnection(disconnectProcess.uuid, {
|
||||
"active": false,
|
||||
"device": ""
|
||||
});
|
||||
disconnecting = false;
|
||||
disconnectingUuid = "";
|
||||
lastError = "";
|
||||
ToastService.showNotice(disconnectProcess.name, I18n.tr("toast.vpn.disconnected", {
|
||||
"name": disconnectProcess.name
|
||||
}), "shield-off");
|
||||
scheduleRefresh(1000);
|
||||
}
|
||||
}
|
||||
|
||||
stderr: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const trimmed = text.trim();
|
||||
if (trimmed) {
|
||||
lastError = trimmed.split("\n")[0].trim();
|
||||
Logger.w("VPN", "Disconnect error: " + trimmed);
|
||||
ToastService.showWarning(disconnectProcess.name, lastError);
|
||||
}
|
||||
disconnecting = false;
|
||||
disconnectingUuid = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,22 +5,18 @@ import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
|
||||
// GitHub API logic and caching
|
||||
// GitHub API logic for contributors
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property string githubDataFile: Quickshell.env("NOCTALIA_GITHUB_FILE") || (Settings.cacheDir + "github.json")
|
||||
property int githubUpdateFrequency: 60 * 60 // 1 hour expressed in seconds
|
||||
property bool isFetchingData: false
|
||||
property bool isReleasesFetching: false
|
||||
readonly property alias data: adapter // Used to access via GitHubService.data.xxx.yyy
|
||||
|
||||
// Public properties for easy access
|
||||
property string latestVersion: I18n.tr("system.unknown-version")
|
||||
property var contributors: []
|
||||
property string releaseNotes: ""
|
||||
property var releases: []
|
||||
property string releaseFetchError: ""
|
||||
|
||||
FileView {
|
||||
id: githubDataFileView
|
||||
@@ -48,8 +44,6 @@ Singleton {
|
||||
|
||||
property string version: I18n.tr("system.unknown-version")
|
||||
property var contributors: []
|
||||
property string releaseNotes: ""
|
||||
property var releases: []
|
||||
property real timestamp: 0
|
||||
}
|
||||
}
|
||||
@@ -71,15 +65,6 @@ Singleton {
|
||||
if (data.contributors) {
|
||||
root.contributors = data.contributors;
|
||||
}
|
||||
if (data.releaseNotes) {
|
||||
root.releaseNotes = data.releaseNotes;
|
||||
}
|
||||
if (data.releases && data.releases.length > 0) {
|
||||
root.releases = data.releases;
|
||||
} else {
|
||||
Logger.d("GitHub", "Cached releases missing, scheduling fetch");
|
||||
needsRefetch = true;
|
||||
}
|
||||
|
||||
if (needsRefetch) {
|
||||
fetchFromGitHub();
|
||||
@@ -96,14 +81,13 @@ Singleton {
|
||||
isFetchingData = true;
|
||||
versionProcess.running = true;
|
||||
contributorsProcess.running = true;
|
||||
fetchAllReleases();
|
||||
}
|
||||
|
||||
// --------------------------------
|
||||
function saveData() {
|
||||
data.timestamp = Time.timestamp;
|
||||
Logger.d("GitHub", "Saving data to cache file:", githubDataFile);
|
||||
Logger.d("GitHub", "Data to save - version:", data.version, "contributors:", data.contributors.length, "notes length:", data.releaseNotes ? data.releaseNotes.length : 0, "release count:", data.releases ? data.releases.length : 0);
|
||||
Logger.d("GitHub", "Data to save - version:", data.version, "contributors:", data.contributors.length);
|
||||
|
||||
// Ensure cache directory exists
|
||||
Quickshell.execDetached(["mkdir", "-p", Settings.cacheDir]);
|
||||
@@ -115,25 +99,25 @@ Singleton {
|
||||
});
|
||||
}
|
||||
|
||||
// --------------------------------
|
||||
function checkAndSaveData() {
|
||||
// Only save when all processes are finished
|
||||
if (!versionProcess.running && !contributorsProcess.running) {
|
||||
root.isFetchingData = false;
|
||||
root.saveData();
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------
|
||||
function resetCache() {
|
||||
data.version = I18n.tr("system.unknown-version");
|
||||
data.contributors = [];
|
||||
data.releaseNotes = "";
|
||||
data.releases = [];
|
||||
data.timestamp = 0;
|
||||
|
||||
// Try to fetch immediately
|
||||
fetchFromGitHub();
|
||||
}
|
||||
|
||||
function clearReleaseCache() {
|
||||
Logger.d("GitHub", "Clearing cached release data");
|
||||
data.releases = [];
|
||||
root.releases = [];
|
||||
githubDataFileView.writeAdapter();
|
||||
}
|
||||
|
||||
Process {
|
||||
id: versionProcess
|
||||
|
||||
@@ -152,15 +136,9 @@ Singleton {
|
||||
Logger.d("GitHub", "Latest version fetched from GitHub:", version);
|
||||
} else if (data.message) {
|
||||
Logger.w("GitHub", "Latest release fetch warning:", data.message);
|
||||
handleRateLimitError(data.message);
|
||||
} else {
|
||||
Logger.w("GitHub", "No tag_name in GitHub response");
|
||||
}
|
||||
|
||||
if (data.body) {
|
||||
root.data.releaseNotes = data.body;
|
||||
root.releaseNotes = root.data.releaseNotes;
|
||||
}
|
||||
} else {
|
||||
Logger.w("GitHub", "Empty response from GitHub API");
|
||||
}
|
||||
@@ -206,94 +184,4 @@ Singleton {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------
|
||||
function fetchAllReleases(page, accumulator) {
|
||||
if (isReleasesFetching && page === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const perPage = 100;
|
||||
var currentPage = page || 1;
|
||||
var releasesAccumulator = accumulator || [];
|
||||
isReleasesFetching = true;
|
||||
|
||||
var request = new XMLHttpRequest();
|
||||
request.onreadystatechange = function () {
|
||||
if (request.readyState === XMLHttpRequest.DONE) {
|
||||
if (request.status >= 200 && request.status < 300) {
|
||||
try {
|
||||
const responseText = request.responseText || "";
|
||||
const parsed = responseText ? JSON.parse(responseText) : [];
|
||||
if (Array.isArray(parsed) && parsed.length > 0) {
|
||||
const mapped = parsed.map(rel => ({
|
||||
"version": rel.tag_name || "",
|
||||
"createdAt": rel.published_at || rel.created_at || "",
|
||||
"body": rel.body || ""
|
||||
})).filter(rel => rel.version !== "");
|
||||
releasesAccumulator = releasesAccumulator.concat(mapped);
|
||||
|
||||
if (parsed.length === perPage) {
|
||||
fetchAllReleases(currentPage + 1, releasesAccumulator);
|
||||
return;
|
||||
}
|
||||
}
|
||||
finalizeReleaseFetch(releasesAccumulator);
|
||||
} catch (error) {
|
||||
Logger.e("GitHub", "Failed to parse releases:", error);
|
||||
finalizeReleaseFetch([]);
|
||||
}
|
||||
} else {
|
||||
if (request.status === 403) {
|
||||
handleRateLimitError();
|
||||
}
|
||||
Logger.e("GitHub", "Failed to fetch releases, status:", request.status);
|
||||
finalizeReleaseFetch([]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const url = `https://api.github.com/repos/noctalia-dev/noctalia-shell/releases?per_page=${perPage}&page=${currentPage}`;
|
||||
request.open("GET", url);
|
||||
request.send();
|
||||
}
|
||||
|
||||
function finalizeReleaseFetch(releasesList) {
|
||||
isReleasesFetching = false;
|
||||
|
||||
if (releasesList && releasesList.length > 0) {
|
||||
releasesList.sort(function (a, b) {
|
||||
const dateA = a.createdAt ? Date.parse(a.createdAt) : 0;
|
||||
const dateB = b.createdAt ? Date.parse(b.createdAt) : 0;
|
||||
return dateB - dateA;
|
||||
});
|
||||
root.data.releases = releasesList;
|
||||
root.releases = releasesList;
|
||||
releaseFetchError = "";
|
||||
Logger.d("GitHub", "Fetched releases:", releasesList.length);
|
||||
} else {
|
||||
root.data.releases = [];
|
||||
root.releases = [];
|
||||
if (!releaseFetchError) {
|
||||
Logger.w("GitHub", "No releases fetched");
|
||||
}
|
||||
}
|
||||
|
||||
checkAndSaveData();
|
||||
}
|
||||
|
||||
// --------------------------------
|
||||
function checkAndSaveData() {
|
||||
// Only save when all processes are finished
|
||||
if (!versionProcess.running && !contributorsProcess.running && !isReleasesFetching) {
|
||||
root.isFetchingData = false;
|
||||
root.saveData();
|
||||
}
|
||||
}
|
||||
|
||||
function handleRateLimitError(message) {
|
||||
const limitMessage = message && message.length > 0 ? message : "API rate limit exceeded";
|
||||
Logger.w("GitHub", "Rate limit warning:", limitMessage);
|
||||
releaseFetchError = I18n.tr("changelog.error.rate-limit");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,10 +11,15 @@ Singleton {
|
||||
id: root
|
||||
|
||||
// Version properties
|
||||
property string baseVersion: "3.2.0"
|
||||
property bool isDevelopment: false
|
||||
property string currentVersion: `v${!isDevelopment ? baseVersion : baseVersion + "-dev"}`
|
||||
property string changelogStateFile: Quickshell.env("NOCTALIA_CHANGELOG_STATE_FILE") || (Settings.cacheDir + "changelog-state.json")
|
||||
readonly property string baseVersion: "3.2.0"
|
||||
readonly property bool isDevelopment: true
|
||||
readonly property string developmentSuffix: "-git"
|
||||
readonly property string currentVersion: `v${!isDevelopment ? baseVersion : baseVersion + developmentSuffix}`
|
||||
|
||||
// URLs
|
||||
readonly property string discordUrl: "https://discord.noctalia.dev"
|
||||
readonly property string feedbackUrl: Quickshell.env("NOCTALIA_CHANGELOG_FEEDBACK_URL") || ""
|
||||
readonly property string upgradeLogBaseUrl: Quickshell.env("NOCTALIA_UPGRADELOG_URL") || "https://noctalia.dev:7777/upgradelog"
|
||||
|
||||
// Changelog properties
|
||||
property bool initialized: false
|
||||
@@ -24,11 +29,8 @@ Singleton {
|
||||
property string previousVersion: ""
|
||||
property string changelogCurrentVersion: ""
|
||||
property var releaseHighlights: []
|
||||
property string releaseNotesUrl: ""
|
||||
property string discordUrl: "https://discord.noctalia.dev"
|
||||
property string lastShownVersion: ""
|
||||
property bool popupScheduled: false
|
||||
property string feedbackUrl: Quickshell.env("NOCTALIA_CHANGELOG_FEEDBACK_URL") || ""
|
||||
property string fetchError: ""
|
||||
property string changelogLastSeenVersion: ""
|
||||
property bool changelogStateLoaded: false
|
||||
@@ -41,59 +43,27 @@ Singleton {
|
||||
|
||||
signal popupQueued(string fromVersion, string toVersion)
|
||||
|
||||
// Internal helpers
|
||||
function getVersion() {
|
||||
return root.currentVersion;
|
||||
}
|
||||
|
||||
function checkForUpdates() {
|
||||
// TODO: Implement update checking logic
|
||||
Logger.i("UpdateService", "Checking for updates...");
|
||||
}
|
||||
|
||||
function init() {
|
||||
if (initialized)
|
||||
return;
|
||||
|
||||
initialized = true;
|
||||
Logger.i("UpdateService", "Version:", root.currentVersion);
|
||||
|
||||
// Load changelog state from ShellState
|
||||
Qt.callLater(() => {
|
||||
if (typeof ShellState !== 'undefined' && ShellState.isLoaded) {
|
||||
loadChangelogState();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: GitHubService ? GitHubService : null
|
||||
function onReleaseNotesChanged() {
|
||||
rebuildHighlights();
|
||||
target: typeof ShellState !== 'undefined' ? ShellState : null
|
||||
function onIsLoadedChanged() {
|
||||
if (ShellState.isLoaded) {
|
||||
loadChangelogState();
|
||||
}
|
||||
function onReleasesChanged() {
|
||||
rebuildHighlights();
|
||||
}
|
||||
function onReleaseFetchErrorChanged() {
|
||||
fetchError = GitHubService ? GitHubService.releaseFetchError : "";
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: changelogStateFileView
|
||||
path: root.changelogStateFile
|
||||
printErrors: false
|
||||
onLoaded: loadChangelogState()
|
||||
onLoadFailed: error => {
|
||||
if (error === 2) {
|
||||
// File doesn't exist, create it
|
||||
debouncedSaveChangelogState();
|
||||
} else {
|
||||
Logger.e("UpdateService", "Failed to load changelog state file:", error);
|
||||
}
|
||||
changelogStateLoaded = true;
|
||||
if (pendingShowRequest) {
|
||||
pendingShowRequest = false;
|
||||
Qt.callLater(root.showLatestChangelog);
|
||||
}
|
||||
}
|
||||
|
||||
JsonAdapter {
|
||||
id: changelogStateAdapter
|
||||
property string lastSeenVersion: ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,71 +90,72 @@ Singleton {
|
||||
|
||||
previousVersion = fromVersion;
|
||||
changelogCurrentVersion = toVersion;
|
||||
fetchError = GitHubService ? GitHubService.releaseFetchError : "";
|
||||
releaseHighlights = buildReleaseHighlights(previousVersion, changelogCurrentVersion);
|
||||
releaseNotesUrl = buildReleaseNotesUrl(toVersion);
|
||||
|
||||
// Fetch the upgrade log from the server
|
||||
fetchUpgradeLog(fromVersion, toVersion);
|
||||
|
||||
popupScheduled = true;
|
||||
root.popupQueued(previousVersion, changelogCurrentVersion);
|
||||
|
||||
clearChangelogRequest();
|
||||
openWhenReady();
|
||||
}
|
||||
|
||||
function rebuildHighlights() {
|
||||
if (!changelogCurrentVersion)
|
||||
return;
|
||||
fetchError = GitHubService ? GitHubService.releaseFetchError : "";
|
||||
releaseHighlights = buildReleaseHighlights(previousVersion, changelogCurrentVersion);
|
||||
function fetchUpgradeLog(fromVersion, toVersion) {
|
||||
// Use the last seen version, or default to v3.0.0 if this is a fresh install
|
||||
let from = fromVersion || changelogLastSeenVersion || "v3.0.0";
|
||||
let to = toVersion;
|
||||
|
||||
// Remove potential legacy -dev stuff
|
||||
// TODO: remove in 2026!
|
||||
from = from.replace("-dev", "");
|
||||
to = to.replace("-dev", "");
|
||||
|
||||
// Strip suffix from versions
|
||||
from = from.replace(root.developmentSuffix, "");
|
||||
to = to.replace(root.developmentSuffix, "");
|
||||
|
||||
// 'from' always need to be before 'to'
|
||||
// handle edge case that will show up as we changed -dev to -git
|
||||
if (from === to) {
|
||||
from = "v3.0.0";
|
||||
}
|
||||
|
||||
function buildReleaseHighlights(fromVersion, toVersion) {
|
||||
const releases = GitHubService && GitHubService.releases ? GitHubService.releases : [];
|
||||
const selected = [];
|
||||
const fromNorm = normalizeVersion(fromVersion);
|
||||
const toNorm = normalizeVersion(toVersion);
|
||||
Logger.d("UpdateService", "Fetching upgrade log", "from:", from, "to:", to);
|
||||
|
||||
if (releases.length > 0) {
|
||||
for (var i = 0; i < releases.length; i++) {
|
||||
const rel = releases[i];
|
||||
const tag = rel.version || "";
|
||||
const tagNorm = normalizeVersion(tag);
|
||||
if (!tagNorm)
|
||||
continue;
|
||||
const url = `${upgradeLogBaseUrl}/${from}/${to}`;
|
||||
const request = new XMLHttpRequest();
|
||||
request.onreadystatechange = function () {
|
||||
if (request.readyState === XMLHttpRequest.DONE) {
|
||||
Logger.d("UpdateService", "Request completed with status:", request.status);
|
||||
Logger.d("UpdateService", "Response text length:", request.responseText ? request.responseText.length : 0);
|
||||
|
||||
if (toNorm && compareVersions(tagNorm, toNorm) > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fromNorm && compareVersions(tagNorm, fromNorm) <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
const entries = parseReleaseNotes(rel.body);
|
||||
if (entries.length === 0)
|
||||
continue;
|
||||
|
||||
selected.push({
|
||||
"version": tag,
|
||||
"date": rel.createdAt || "",
|
||||
"entries": entries
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (selected.length === 0 && toVersion) {
|
||||
const fallback = parseReleaseNotes(GitHubService ? GitHubService.releaseNotes : "");
|
||||
if (fallback.length > 0) {
|
||||
selected.push({
|
||||
if (request.status >= 200 && request.status < 300) {
|
||||
const content = request.responseText || "";
|
||||
Logger.d("UpdateService", "Successfully fetched upgrade log, parsing...");
|
||||
const entries = parseReleaseNotes(content);
|
||||
Logger.d("UpdateService", "Parsed entries count:", entries.length);
|
||||
releaseHighlights = [
|
||||
{
|
||||
"version": toVersion,
|
||||
"date": "",
|
||||
"entries": fallback
|
||||
});
|
||||
"entries": entries
|
||||
}
|
||||
];
|
||||
fetchError = "";
|
||||
openWhenReady();
|
||||
} else {
|
||||
Logger.e("UpdateService", "Failed to fetch upgrade log");
|
||||
Logger.e("UpdateService", "Status:", request.status);
|
||||
Logger.e("UpdateService", "Status text:", request.statusText);
|
||||
Logger.e("UpdateService", "Response:", request.responseText);
|
||||
fetchError = I18n.tr("changelog.error.fetch-failed");
|
||||
releaseHighlights = [];
|
||||
openWhenReady();
|
||||
}
|
||||
}
|
||||
|
||||
return selected;
|
||||
};
|
||||
request.open("GET", url);
|
||||
request.send();
|
||||
}
|
||||
|
||||
function normalizeVersion(version) {
|
||||
@@ -217,13 +188,6 @@ Singleton {
|
||||
return 0;
|
||||
}
|
||||
|
||||
function buildReleaseNotesUrl(version) {
|
||||
if (!version)
|
||||
return "";
|
||||
const tag = version.startsWith("v") ? version : `v${version}`;
|
||||
return `https://github.com/noctalia-dev/noctalia-shell/releases/tag/${tag}`;
|
||||
}
|
||||
|
||||
function parseReleaseNotes(body) {
|
||||
if (!body)
|
||||
return [];
|
||||
@@ -232,32 +196,16 @@ Singleton {
|
||||
var entries = [];
|
||||
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var line = lines[i].trim();
|
||||
if (!line)
|
||||
continue;
|
||||
|
||||
if (line.startsWith("- ") || line.startsWith("* ")) {
|
||||
const text = cleanEntry(line.substring(2).trim());
|
||||
if (text.length > 0 && !isVersionLine(text) && !isIgnoredEntry(text)) {
|
||||
entries.push(text);
|
||||
}
|
||||
const line = lines[i];
|
||||
entries.push(line);
|
||||
}
|
||||
|
||||
if (entries.length >= 6)
|
||||
break;
|
||||
// Remove trailing blank lines
|
||||
while (entries.length > 0 && entries[entries.length - 1].trim().length === 0) {
|
||||
entries.pop();
|
||||
}
|
||||
|
||||
var uniqueEntries = [];
|
||||
var seen = {};
|
||||
for (var j = 0; j < entries.length; j++) {
|
||||
const key = entries[j].toLowerCase();
|
||||
if (seen[key])
|
||||
continue;
|
||||
seen[key] = true;
|
||||
uniqueEntries.push(entries[j]);
|
||||
}
|
||||
|
||||
return uniqueEntries;
|
||||
return entries;
|
||||
}
|
||||
|
||||
function isVersionLine(text) {
|
||||
@@ -322,12 +270,6 @@ Singleton {
|
||||
lastShownVersion = changelogCurrentVersion;
|
||||
}
|
||||
|
||||
function openReleaseNotes() {
|
||||
if (!releaseNotesUrl)
|
||||
return;
|
||||
Quickshell.execDetached(["xdg-open", releaseNotesUrl]);
|
||||
}
|
||||
|
||||
function openDiscord() {
|
||||
if (!discordUrl)
|
||||
return;
|
||||
@@ -374,12 +316,21 @@ Singleton {
|
||||
|
||||
function loadChangelogState() {
|
||||
try {
|
||||
changelogLastSeenVersion = changelogStateAdapter.lastSeenVersion || "";
|
||||
const changelog = ShellState.getChangelogState();
|
||||
changelogLastSeenVersion = changelog.lastSeenVersion || "";
|
||||
|
||||
if (!changelogLastSeenVersion) {
|
||||
// Try to migrate from old changelog-state.json
|
||||
migrateFromOldChangelogFile();
|
||||
// Also try settings migration
|
||||
if (!changelogLastSeenVersion && Settings.data && Settings.data.changelog && Settings.data.changelog.lastSeenVersion) {
|
||||
changelogLastSeenVersion = Settings.data.changelog.lastSeenVersion;
|
||||
debouncedSaveChangelogState();
|
||||
Logger.i("UpdateService", "Migrated changelog lastSeenVersion from settings to cache");
|
||||
Logger.i("UpdateService", "Migrated changelog lastSeenVersion from settings to ShellState");
|
||||
}
|
||||
}
|
||||
|
||||
Logger.d("UpdateService", "Loaded changelog state from ShellState");
|
||||
} catch (error) {
|
||||
Logger.e("UpdateService", "Failed to load changelog state:", error);
|
||||
}
|
||||
@@ -390,6 +341,31 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
function migrateFromOldChangelogFile() {
|
||||
const oldChangelogPath = Settings.cacheDir + "changelog-state.json";
|
||||
const migrationFileView = Qt.createQmlObject(`
|
||||
import QtQuick
|
||||
import Quickshell.Io
|
||||
FileView {
|
||||
id: migrationView
|
||||
path: "${oldChangelogPath}"
|
||||
printErrors: false
|
||||
adapter: JsonAdapter {
|
||||
property string lastSeenVersion: ""
|
||||
}
|
||||
onLoaded: {
|
||||
parent.changelogLastSeenVersion = adapter.lastSeenVersion || "";
|
||||
parent.debouncedSaveChangelogState();
|
||||
Logger.i("UpdateService", "Migrated changelog-state.json to ShellState");
|
||||
migrationView.destroy();
|
||||
}
|
||||
onLoadFailed: {
|
||||
migrationView.destroy();
|
||||
}
|
||||
}
|
||||
`, root, "changelogMigrationView");
|
||||
}
|
||||
|
||||
function debouncedSaveChangelogState() {
|
||||
// Queue a save and restart the debounce timer
|
||||
pendingSave = true;
|
||||
@@ -411,26 +387,16 @@ Singleton {
|
||||
saveInProgress = true;
|
||||
|
||||
try {
|
||||
changelogStateAdapter.lastSeenVersion = changelogLastSeenVersion || "";
|
||||
|
||||
// Ensure cache directory exists
|
||||
Quickshell.execDetached(["mkdir", "-p", Settings.cacheDir]);
|
||||
|
||||
// Small delay to ensure directory creation completes
|
||||
Qt.callLater(() => {
|
||||
try {
|
||||
changelogStateFileView.writeAdapter();
|
||||
ShellState.setChangelogState({
|
||||
lastSeenVersion: changelogLastSeenVersion || ""
|
||||
});
|
||||
Logger.d("UpdateService", "Saved changelog state to ShellState");
|
||||
saveInProgress = false;
|
||||
|
||||
// Check if another save was queued while we were saving
|
||||
if (pendingSave) {
|
||||
Qt.callLater(executeSave);
|
||||
}
|
||||
} catch (writeError) {
|
||||
Logger.e("UpdateService", "Failed to write changelog state:", writeError);
|
||||
saveInProgress = false;
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
Logger.e("UpdateService", "Failed to save changelog state:", error);
|
||||
saveInProgress = false;
|
||||
|
||||
@@ -17,7 +17,6 @@ Singleton {
|
||||
property int maxVisible: 5
|
||||
property int maxHistory: 100
|
||||
property string historyFile: Quickshell.env("NOCTALIA_NOTIF_HISTORY_FILE") || (Settings.cacheDir + "notifications.json")
|
||||
property string stateFile: Settings.cacheDir + "notifications-state.json"
|
||||
|
||||
// State
|
||||
property real lastSeenTs: 0
|
||||
@@ -138,6 +137,22 @@ Singleton {
|
||||
if (Settings.isLoaded) {
|
||||
updateNotificationServer();
|
||||
}
|
||||
|
||||
// Load state from ShellState
|
||||
Qt.callLater(() => {
|
||||
if (typeof ShellState !== 'undefined' && ShellState.isLoaded) {
|
||||
loadState();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: typeof ShellState !== 'undefined' ? ShellState : null
|
||||
function onIsLoadedChanged() {
|
||||
if (ShellState.isLoaded) {
|
||||
loadState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
@@ -471,22 +486,6 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
// Persistence - State
|
||||
FileView {
|
||||
id: stateFileView
|
||||
path: stateFile
|
||||
printErrors: false
|
||||
onLoaded: loadState()
|
||||
onLoadFailed: error => {
|
||||
if (error === 2)
|
||||
writeAdapter();
|
||||
}
|
||||
|
||||
JsonAdapter {
|
||||
id: stateAdapter
|
||||
property real lastSeenTs: 0
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: saveTimer
|
||||
@@ -546,22 +545,57 @@ Singleton {
|
||||
|
||||
function loadState() {
|
||||
try {
|
||||
root.lastSeenTs = stateAdapter.lastSeenTs || 0;
|
||||
const notifState = ShellState.getNotificationsState();
|
||||
root.lastSeenTs = notifState.lastSeenTs || 0;
|
||||
|
||||
if (root.lastSeenTs === 0) {
|
||||
// Try to migrate from old notifications-state.json
|
||||
migrateFromOldStateFile();
|
||||
// Also try settings migration
|
||||
if (root.lastSeenTs === 0 && Settings.data.notifications && Settings.data.notifications.lastSeenTs) {
|
||||
root.lastSeenTs = Settings.data.notifications.lastSeenTs;
|
||||
saveState();
|
||||
Logger.i("Notifications", "Migrated lastSeenTs from settings to state file");
|
||||
Logger.i("Notifications", "Migrated lastSeenTs from settings to ShellState");
|
||||
}
|
||||
}
|
||||
|
||||
Logger.d("Notifications", "Loaded state from ShellState");
|
||||
} catch (e) {
|
||||
Logger.e("Notifications", "Load state failed:", e);
|
||||
}
|
||||
}
|
||||
|
||||
function migrateFromOldStateFile() {
|
||||
const oldStatePath = Settings.cacheDir + "notifications-state.json";
|
||||
const migrationFileView = Qt.createQmlObject(`
|
||||
import QtQuick
|
||||
import Quickshell.Io
|
||||
FileView {
|
||||
id: migrationView
|
||||
path: "${oldStatePath}"
|
||||
printErrors: false
|
||||
adapter: JsonAdapter {
|
||||
property real lastSeenTs: 0
|
||||
}
|
||||
onLoaded: {
|
||||
parent.lastSeenTs = adapter.lastSeenTs || 0;
|
||||
parent.saveState();
|
||||
Logger.i("Notifications", "Migrated notifications-state.json to ShellState");
|
||||
migrationView.destroy();
|
||||
}
|
||||
onLoadFailed: {
|
||||
migrationView.destroy();
|
||||
}
|
||||
}
|
||||
`, root, "notificationMigrationView");
|
||||
}
|
||||
|
||||
function saveState() {
|
||||
try {
|
||||
stateAdapter.lastSeenTs = root.lastSeenTs;
|
||||
stateFileView.writeAdapter();
|
||||
ShellState.setNotificationsState({
|
||||
lastSeenTs: root.lastSeenTs
|
||||
});
|
||||
Logger.d("Notifications", "Saved state to ShellState");
|
||||
} catch (e) {
|
||||
Logger.e("Notifications", "Save state failed:", e);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ Singleton {
|
||||
property var schemes: []
|
||||
property bool scanning: false
|
||||
property string schemesDirectory: Quickshell.shellDir + "/Assets/ColorScheme"
|
||||
property string downloadedSchemesDirectory: Settings.configDir + "colorschemes"
|
||||
property string colorsJsonFilePath: Settings.configDir + "colors.json"
|
||||
|
||||
Connections {
|
||||
@@ -43,8 +44,11 @@ Singleton {
|
||||
Logger.d("ColorScheme", "Load colorScheme");
|
||||
scanning = true;
|
||||
schemes = [];
|
||||
// Use find command to locate all scheme.json files
|
||||
findProcess.command = ["find", schemesDirectory, "-name", "*.json", "-type", "f"];
|
||||
// Use find command to locate all scheme.json files in both directories
|
||||
// First ensure the downloaded schemes directory exists
|
||||
Quickshell.execDetached(["mkdir", "-p", downloadedSchemesDirectory]);
|
||||
// Find in both preinstalled and downloaded directories
|
||||
findProcess.command = ["find", schemesDirectory, downloadedSchemesDirectory, "-name", "*.json", "-type", "f"];
|
||||
findProcess.running = true;
|
||||
}
|
||||
|
||||
@@ -81,7 +85,17 @@ Singleton {
|
||||
} else if (schemeName === "Tokyo Night") {
|
||||
schemeName = "Tokyo-Night";
|
||||
}
|
||||
return schemesDirectory + "/" + schemeName + "/" + schemeName + ".json";
|
||||
// Check preinstalled directory first, then downloaded directory
|
||||
var preinstalledPath = schemesDirectory + "/" + schemeName + "/" + schemeName + ".json";
|
||||
var downloadedPath = downloadedSchemesDirectory + "/" + schemeName + "/" + schemeName + ".json";
|
||||
// Try to find the scheme in the loaded schemes list to determine which directory it's in
|
||||
for (var i = 0; i < schemes.length; i++) {
|
||||
if (schemes[i].indexOf("/" + schemeName + "/") !== -1 || schemes[i].indexOf("/" + schemeName + ".json") !== -1) {
|
||||
return schemes[i];
|
||||
}
|
||||
}
|
||||
// Fallback: prefer preinstalled, then downloaded
|
||||
return preinstalledPath;
|
||||
}
|
||||
|
||||
function applyScheme(nameOrPath) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
import qs.Services.System
|
||||
import qs.Services.Theming
|
||||
import qs.Services.UI
|
||||
|
||||
Singleton {
|
||||
@@ -329,7 +330,26 @@ Singleton {
|
||||
extension = ".toml";
|
||||
}
|
||||
|
||||
return `${Quickshell.shellDir}/Assets/ColorScheme/${colorScheme}/terminal/${terminal}/${colorScheme}-${mode}${extension}`;
|
||||
const fileName = `${colorScheme}-${mode}${extension}`;
|
||||
const relativePath = `terminal/${terminal}/${fileName}`;
|
||||
|
||||
// Try to find the scheme in the loaded schemes list to determine which directory it's in
|
||||
for (let i = 0; i < ColorSchemeService.schemes.length; i++) {
|
||||
const schemeJsonPath = ColorSchemeService.schemes[i];
|
||||
// Check if this is the scheme we're looking for
|
||||
if (schemeJsonPath.indexOf(`/${colorScheme}/`) !== -1 || schemeJsonPath.indexOf(`/${colorScheme}.json`) !== -1) {
|
||||
// Extract the scheme directory from the JSON path
|
||||
// JSON path is like: /path/to/scheme/SchemeName/SchemeName.json
|
||||
// We need: /path/to/scheme/SchemeName/terminal/...
|
||||
const schemeDir = schemeJsonPath.substring(0, schemeJsonPath.lastIndexOf('/'));
|
||||
return `${schemeDir}/${relativePath}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: try downloaded first, then preinstalled
|
||||
const downloadedPath = `${ColorSchemeService.downloadedSchemesDirectory}/${colorScheme}/${relativePath}`;
|
||||
const preinstalledPath = `${ColorSchemeService.schemesDirectory}/${colorScheme}/${relativePath}`;
|
||||
return preinstalledPath;
|
||||
}
|
||||
|
||||
// ================================================================================
|
||||
|
||||
@@ -330,8 +330,11 @@ Singleton {
|
||||
Settings.saveImmediate();
|
||||
}
|
||||
});
|
||||
// Enable keyboard focus for the popup menu window when dialog is open
|
||||
popupMenuWindow.hasDialog = true;
|
||||
// Close the popup menu window when dialog closes
|
||||
dialog.closed.connect(() => {
|
||||
popupMenuWindow.hasDialog = false;
|
||||
popupMenuWindow.close();
|
||||
dialog.destroy();
|
||||
});
|
||||
|
||||
@@ -36,6 +36,7 @@ Singleton {
|
||||
"TaskbarGrouped": taskbarGroupedComponent,
|
||||
"Tray": trayComponent,
|
||||
"Volume": volumeComponent,
|
||||
"VPN": vpnComponent,
|
||||
"WiFi": wiFiComponent,
|
||||
"WallpaperSelector": wallpaperSelectorComponent,
|
||||
"Workspace": workspaceComponent
|
||||
@@ -58,12 +59,13 @@ Singleton {
|
||||
"SessionMenu": "WidgetSettings/SessionMenuSettings.qml",
|
||||
"Spacer": "WidgetSettings/SpacerSettings.qml",
|
||||
"SystemMonitor": "WidgetSettings/SystemMonitorSettings.qml",
|
||||
"TaskbarGrouped": "WidgetSettings/TaskbarGroupedSettings.qml",
|
||||
"Volume": "WidgetSettings/VolumeSettings.qml",
|
||||
"WiFi": "WidgetSettings/WiFiSettings.qml",
|
||||
"Workspace": "WidgetSettings/WorkspaceSettings.qml",
|
||||
"Taskbar": "WidgetSettings/TaskbarSettings.qml",
|
||||
"Tray": "WidgetSettings/TraySettings.qml"
|
||||
"TaskbarGrouped": "WidgetSettings/TaskbarGroupedSettings.qml",
|
||||
"Tray": "WidgetSettings/TraySettings.qml",
|
||||
"Volume": "WidgetSettings/VolumeSettings.qml",
|
||||
"VPN": "WidgetSettings/VPNSettings.qml",
|
||||
"WiFi": "WidgetSettings/WiFiSettings.qml",
|
||||
"Workspace": "WidgetSettings/WorkspaceSettings.qml"
|
||||
})
|
||||
|
||||
property var widgetMetadata: ({
|
||||
@@ -124,7 +126,17 @@ Singleton {
|
||||
"textIntervalMs": 3000,
|
||||
"textCollapse": "",
|
||||
"parseJson": false,
|
||||
"hideTextInVerticalBar": false
|
||||
"wheelExec": "",
|
||||
"wheelUpExec": "",
|
||||
"wheelDownExec": "",
|
||||
"wheelMode": "unified",
|
||||
"wheelUpdateText": false,
|
||||
"wheelUpUpdateText": false,
|
||||
"wheelDownUpdateText": false,
|
||||
"maxTextLength": {
|
||||
"horizontal": 10,
|
||||
"vertical": 10
|
||||
}
|
||||
},
|
||||
"KeyboardLayout": {
|
||||
"allowUserSettings": true,
|
||||
@@ -188,11 +200,9 @@ Singleton {
|
||||
},
|
||||
"TaskbarGrouped": {
|
||||
"allowUserSettings": true,
|
||||
"showWorkspaceNumbers": true,
|
||||
"showNumbersOnlyWhenOccupied": true,
|
||||
"labelMode": "index",
|
||||
"hideUnoccupied": false,
|
||||
"characterCount": 2,
|
||||
"labelMode": "index",
|
||||
"showLabelsOnlyWhenOccupied": true,
|
||||
"colorizeIcons": false
|
||||
},
|
||||
"Tray": {
|
||||
@@ -202,6 +212,10 @@ Singleton {
|
||||
"pinned": [],
|
||||
"drawerEnabled": true
|
||||
},
|
||||
"VPN": {
|
||||
"allowUserSettings": true,
|
||||
"displayMode": "onhover"
|
||||
},
|
||||
"WiFi": {
|
||||
"allowUserSettings": true,
|
||||
"displayMode": "onhover"
|
||||
@@ -291,6 +305,9 @@ Singleton {
|
||||
property Component volumeComponent: Component {
|
||||
Volume {}
|
||||
}
|
||||
property Component vpnComponent: Component {
|
||||
VPN {}
|
||||
}
|
||||
property Component wiFiComponent: Component {
|
||||
WiFi {}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,7 @@ import qs.Commons
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
// A ref. to the lockScreen, so it's accessible from anywhere
|
||||
// This is not a panel...
|
||||
// A ref. to the lockScreen, so it's accessible from anywhere.
|
||||
property var lockScreen: null
|
||||
|
||||
// Panels
|
||||
|
||||
@@ -83,18 +83,42 @@ Singleton {
|
||||
|
||||
translateModels();
|
||||
|
||||
// Rebuild cache from settings
|
||||
// Load wallpapers from ShellState first (faster), then fall back to Settings
|
||||
currentWallpapers = ({});
|
||||
|
||||
if (typeof ShellState !== 'undefined' && ShellState.isLoaded) {
|
||||
var cachedWallpapers = ShellState.getWallpapers();
|
||||
if (cachedWallpapers && Object.keys(cachedWallpapers).length > 0) {
|
||||
currentWallpapers = cachedWallpapers;
|
||||
Logger.d("Wallpaper", "Loaded wallpapers from ShellState");
|
||||
} else {
|
||||
// Fall back to Settings if ShellState is empty
|
||||
loadFromSettings();
|
||||
}
|
||||
} else {
|
||||
// ShellState not ready yet, load from Settings
|
||||
loadFromSettings();
|
||||
}
|
||||
|
||||
isInitialized = true;
|
||||
Logger.d("Wallpaper", "Triggering initial wallpaper scan");
|
||||
Qt.callLater(refreshWallpapersList);
|
||||
}
|
||||
|
||||
function loadFromSettings() {
|
||||
var monitors = Settings.data.wallpaper.monitors || [];
|
||||
for (var i = 0; i < monitors.length; i++) {
|
||||
if (monitors[i].name && monitors[i].wallpaper) {
|
||||
currentWallpapers[monitors[i].name] = monitors[i].wallpaper;
|
||||
}
|
||||
}
|
||||
Logger.d("Wallpaper", "Loaded wallpapers from Settings");
|
||||
|
||||
isInitialized = true;
|
||||
Logger.d("Wallpaper", "Triggering initial wallpaper scan");
|
||||
Qt.callLater(refreshWallpapersList);
|
||||
// Migrate to ShellState if we loaded from Settings
|
||||
if (typeof ShellState !== 'undefined' && ShellState.isLoaded && Object.keys(currentWallpapers).length > 0) {
|
||||
ShellState.setWallpapers(currentWallpapers);
|
||||
Logger.i("Wallpaper", "Migrated wallpaper paths from Settings to ShellState");
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------
|
||||
@@ -270,32 +294,10 @@ Singleton {
|
||||
// Update cache directly
|
||||
currentWallpapers[screenName] = path;
|
||||
|
||||
// Update Settings - still need immutable update for Settings persistence
|
||||
// The slice() ensures Settings detects the change and saves properly
|
||||
var monitors = Settings.data.wallpaper.monitors || [];
|
||||
var found = false;
|
||||
|
||||
var newMonitors = monitors.map(function (monitor) {
|
||||
if (monitor.name === screenName) {
|
||||
found = true;
|
||||
return {
|
||||
"name": screenName,
|
||||
"directory": Settings.preprocessPath(monitor.directory) || getMonitorDirectory(screenName),
|
||||
"wallpaper": path
|
||||
};
|
||||
// Save to ShellState (wallpaper paths now only stored here, not in Settings)
|
||||
if (typeof ShellState !== 'undefined' && ShellState.isLoaded) {
|
||||
ShellState.setWallpapers(currentWallpapers);
|
||||
}
|
||||
return monitor;
|
||||
});
|
||||
|
||||
if (!found) {
|
||||
newMonitors.push({
|
||||
"name": screenName,
|
||||
"directory": getMonitorDirectory(screenName),
|
||||
"wallpaper": path
|
||||
});
|
||||
}
|
||||
|
||||
Settings.data.wallpaper.monitors = newMonitors.slice();
|
||||
|
||||
// Emit signal for this specific wallpaper change
|
||||
root.wallpaperChanged(screenName, path);
|
||||
|
||||
@@ -49,6 +49,10 @@ Item {
|
||||
|
||||
property color progressColor: root.secondHandColor
|
||||
|
||||
// Font size properties for digital clock
|
||||
property real hoursFontSize: Style.fontSizeXS
|
||||
property real minutesFontSize: Style.fontSizeXXS
|
||||
|
||||
height: Math.round((Style.fontSizeXXXL * 1.9) / 2 * Style.uiScaleRatio) * 2
|
||||
width: root.height
|
||||
|
||||
@@ -78,6 +82,16 @@ Item {
|
||||
return root.progressColor;
|
||||
});
|
||||
}
|
||||
if (item.hasOwnProperty("hoursFontSize")) {
|
||||
item.hoursFontSize = Qt.binding(function () {
|
||||
return root.hoursFontSize;
|
||||
});
|
||||
}
|
||||
if (item.hasOwnProperty("minutesFontSize")) {
|
||||
item.minutesFontSize = Qt.binding(function () {
|
||||
return root.minutesFontSize;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,6 +201,8 @@ Item {
|
||||
property color backgroundColor: Color.mPrimary
|
||||
property color clockColor: Color.mOnPrimary
|
||||
property color progressColor: Color.mError
|
||||
property real hoursFontSize: Style.fontSizeXS
|
||||
property real minutesFontSize: Style.fontSizeXXS
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -238,7 +254,7 @@ Item {
|
||||
return t.split(" ")[0];
|
||||
}
|
||||
|
||||
pointSize: Style.fontSizeXS
|
||||
pointSize: hoursFontSize
|
||||
font.weight: Style.fontWeightBold
|
||||
color: clockColor
|
||||
family: Settings.data.ui.fontFixed
|
||||
@@ -247,7 +263,7 @@ Item {
|
||||
|
||||
NText {
|
||||
text: Qt.formatTime(now, "mm")
|
||||
pointSize: Style.fontSizeXXS
|
||||
pointSize: minutesFontSize
|
||||
font.weight: Style.fontWeightBold
|
||||
color: clockColor
|
||||
family: Settings.data.ui.fontFixed
|
||||
|
||||
@@ -4,10 +4,12 @@ import Quickshell.Widgets
|
||||
import qs.Commons
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: Style.borderS
|
||||
property bool vertical: false
|
||||
|
||||
width: vertical ? Style.borderS : parent.width
|
||||
height: vertical ? parent.height : Style.borderS
|
||||
gradient: Gradient {
|
||||
orientation: Gradient.Horizontal
|
||||
orientation: vertical ? Gradient.Vertical : Gradient.Horizontal
|
||||
GradientStop {
|
||||
position: 0.0
|
||||
color: Color.transparent
|
||||
|
||||
@@ -7,6 +7,7 @@ ColumnLayout {
|
||||
|
||||
property string label: ""
|
||||
property string description: ""
|
||||
property bool enableDescriptionRichText: false
|
||||
|
||||
spacing: Style.marginXXS
|
||||
Layout.fillWidth: true
|
||||
@@ -27,5 +28,6 @@ ColumnLayout {
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
visible: root.description !== ""
|
||||
richTextEnabled: root.enableDescriptionRichText
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ Rectangle {
|
||||
property color borderColor: Color.transparent
|
||||
property real borderWidth: 0
|
||||
property real imageRadius: width * 0.5
|
||||
property int imageFillMode: Image.PreserveAspectCrop
|
||||
property string fallbackIcon: ""
|
||||
property real fallbackIconSize: Style.fontSizeXXL
|
||||
|
||||
@@ -30,12 +31,14 @@ Rectangle {
|
||||
id: img
|
||||
anchors.fill: parent
|
||||
source: imagePath
|
||||
visible: false // Hide since we're using it as shader source
|
||||
visible: false
|
||||
mipmap: true
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
antialiasing: true
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
fillMode: root.imageFillMode
|
||||
horizontalAlignment: Image.AlignHCenter
|
||||
verticalAlignment: Image.AlignVCenter
|
||||
|
||||
onStatusChanged: root.statusChanged(status)
|
||||
}
|
||||
@@ -51,17 +54,14 @@ Rectangle {
|
||||
format: ShaderEffectSource.RGBA
|
||||
}
|
||||
|
||||
// Use custom property names to avoid conflicts with final properties
|
||||
property real itemWidth: root.width
|
||||
property real itemHeight: root.height
|
||||
property real cornerRadius: root.radius
|
||||
property real imageOpacity: root.opacity
|
||||
fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/rounded_image.frag.qsb")
|
||||
|
||||
// Qt6 specific properties - ensure proper blending
|
||||
supportsAtlasTextures: false
|
||||
blending: true
|
||||
// Make sure the background is transparent
|
||||
Rectangle {
|
||||
id: background
|
||||
anchors.fill: parent
|
||||
@@ -70,7 +70,6 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback icon
|
||||
Loader {
|
||||
active: fallbackIcon !== undefined && fallbackIcon !== "" && (imagePath === undefined || imagePath === "")
|
||||
anchors.centerIn: parent
|
||||
@@ -83,7 +82,6 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
// Border
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: parent.radius
|
||||
|
||||
@@ -26,5 +26,5 @@ Text {
|
||||
wrapMode: Text.NoWrap
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
||||
textFormat: richTextEnabled ? Text.RichText : Text.StyledText
|
||||
textFormat: richTextEnabled ? Text.RichText : Text.PlainText
|
||||
}
|
||||
|
||||
18
flake.lock
generated
18
flake.lock
generated
@@ -18,23 +18,7 @@
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs",
|
||||
"systems": "systems"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
22
flake.nix
22
flake.nix
@@ -3,24 +3,21 @@
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||
systems.url = "github:nix-systems/default";
|
||||
};
|
||||
|
||||
outputs = {
|
||||
self,
|
||||
nixpkgs,
|
||||
systems,
|
||||
...
|
||||
}: let
|
||||
eachSystem = nixpkgs.lib.genAttrs (import systems);
|
||||
eachSystem = nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed;
|
||||
pkgsFor = eachSystem (system: nixpkgs.legacyPackages.${system}.appendOverlays [self.overlays.default]);
|
||||
in {
|
||||
formatter = eachSystem (system: nixpkgs.legacyPackages.${system}.alejandra);
|
||||
formatter = eachSystem (system: pkgsFor.${system}.alejandra);
|
||||
|
||||
packages = eachSystem (
|
||||
system: let
|
||||
pkgs = nixpkgs.legacyPackages.${system}.appendOverlays [self.overlays.default];
|
||||
in {
|
||||
default = pkgs.noctalia-shell;
|
||||
system: {
|
||||
default = pkgsFor.${system}.noctalia-shell;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -42,10 +39,8 @@
|
||||
};
|
||||
|
||||
devShells = eachSystem (
|
||||
system: let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
in {
|
||||
default = pkgs.callPackage ./nix/shell.nix {};
|
||||
system: {
|
||||
default = pkgsFor.${system}.callPackage ./nix/shell.nix {};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -58,9 +53,6 @@
|
||||
programs.noctalia-shell.package =
|
||||
lib.mkDefault
|
||||
self.packages.${pkgs.stdenv.hostPlatform.system}.default;
|
||||
programs.noctalia-shell.app2unit.package =
|
||||
lib.mkDefault
|
||||
nixpkgs.legacyPackages.${pkgs.stdenv.hostPlatform.system}.app2unit;
|
||||
};
|
||||
|
||||
nixosModules.default = {
|
||||
|
||||
@@ -89,6 +89,7 @@ in {
|
||||
|
||||
app2unit.package = lib.mkOption {
|
||||
type = lib.types.package;
|
||||
default = pkgs.app2unit;
|
||||
description = ''
|
||||
The app2unit package to use when appLauncher.useApp2Unit is enabled.
|
||||
'';
|
||||
@@ -96,9 +97,7 @@ in {
|
||||
};
|
||||
|
||||
config = let
|
||||
restart = ''
|
||||
${pkgs.systemd}/bin/systemctl --user try-restart noctalia-shell.service 2>/dev/null || true
|
||||
'';
|
||||
restart = "${pkgs.systemd}/bin/systemctl --user try-restart noctalia-shell.service 2>/dev/null || true";
|
||||
useApp2Unit = cfg.settings.appLauncher.useApp2Unit or false;
|
||||
in
|
||||
lib.mkIf cfg.enable {
|
||||
|
||||
@@ -24,7 +24,7 @@ in {
|
||||
config = lib.mkIf cfg.enable {
|
||||
systemd.user.services.noctalia-shell = {
|
||||
description = "Noctalia Shell - Wayland desktop shell";
|
||||
documentation = ["https://github.com/noctalia-dev/noctalia-shell"];
|
||||
documentation = ["https://docs.noctalia.dev/docs"];
|
||||
after = [cfg.target];
|
||||
partOf = [cfg.target];
|
||||
wantedBy = [cfg.target];
|
||||
@@ -34,17 +34,9 @@ in {
|
||||
PATH = lib.mkForce null;
|
||||
};
|
||||
|
||||
unitConfig = {
|
||||
StartLimitIntervalSec = 60;
|
||||
StartLimitBurst = 3;
|
||||
};
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "${cfg.package}/bin/noctalia-shell";
|
||||
ExecStart = lib.getExe cfg.package;
|
||||
Restart = "on-failure";
|
||||
RestartSec = 3;
|
||||
TimeoutStartSec = 10;
|
||||
TimeoutStopSec = 5;
|
||||
Environment = [
|
||||
"NOCTALIA_SETTINGS_FALLBACK=%h/.config/noctalia/gui-settings.json"
|
||||
];
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
/.github
|
||||
/.gitignore
|
||||
/Assets/Screenshots
|
||||
/Assets/Wallpaper
|
||||
/Bin/dev
|
||||
/nix
|
||||
/LICENSE
|
||||
|
||||
33
shell.qml
33
shell.qml
@@ -26,17 +26,18 @@ import qs.Services.Control
|
||||
import qs.Services.Hardware
|
||||
import qs.Services.Location
|
||||
import qs.Services.Networking
|
||||
import qs.Services.Noctalia
|
||||
import qs.Services.Power
|
||||
import qs.Services.System
|
||||
import qs.Services.Theming
|
||||
import qs.Services.UI
|
||||
import qs.Services.Noctalia
|
||||
|
||||
ShellRoot {
|
||||
id: shellRoot
|
||||
|
||||
property bool i18nLoaded: false
|
||||
property bool settingsLoaded: false
|
||||
property bool shellStateLoaded: false
|
||||
|
||||
Component.onCompleted: {
|
||||
Logger.i("Shell", "---------------------------");
|
||||
@@ -63,8 +64,18 @@ ShellRoot {
|
||||
settingsLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: typeof ShellState !== 'undefined' ? ShellState : null
|
||||
function onIsLoadedChanged() {
|
||||
if (ShellState.isLoaded) {
|
||||
shellStateLoaded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: i18nLoaded && settingsLoaded
|
||||
active: i18nLoaded && settingsLoaded && shellStateLoaded
|
||||
|
||||
sourceComponent: Item {
|
||||
Component.onCompleted: {
|
||||
@@ -92,25 +103,16 @@ ShellRoot {
|
||||
|
||||
Overview {}
|
||||
Background {}
|
||||
AllScreens {}
|
||||
Dock {}
|
||||
Notification {}
|
||||
ToastOverlay {}
|
||||
OSD {}
|
||||
Notification {}
|
||||
|
||||
LockScreen {
|
||||
id: lockScreen
|
||||
Component.onCompleted: {
|
||||
// Save a ref. to our lockScreen so we can access it easily
|
||||
PanelService.lockScreen = lockScreen;
|
||||
}
|
||||
}
|
||||
LockScreen {}
|
||||
|
||||
// IPCService is treated as a service but it's actually an
|
||||
// Item that needs to exists in the shell.
|
||||
// IPCService is treated as a service but it's actually an Item that needs to exists in the shell.
|
||||
IPCService {}
|
||||
|
||||
// MainScreen for each screen
|
||||
AllScreens {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,6 +145,7 @@ ShellRoot {
|
||||
setupWizardTimer.start();
|
||||
} else {
|
||||
Settings.data.setupCompleted = true;
|
||||
Settings.saveImmediate();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user