Merge branch 'main' into pr/refactor-battery-pt5

This commit is contained in:
Turann_
2026-02-04 01:37:38 +03:00
committed by GitHub
65 changed files with 1106 additions and 412 deletions
+10
View File
@@ -211,6 +211,10 @@
"memory-usage-label": "Speicherauslastung",
"network-traffic-description": "Upload- und Download-Geschwindigkeiten anzeigen.",
"network-traffic-label": "Netzwerkverkehr",
"storage-as-percentage-description": "Festplattenspeicher als Prozentsatz statt absoluter Werte anzeigen.",
"storage-as-percentage-label": "Festplatte als Prozentsatz",
"storage-available-description": "Zeigt an, wie viel Speicherplatz verfügbar ist, anstatt wie viel belegt ist.",
"storage-available-label": "Verfügbarer Speicherplatz",
"storage-usage-description": "Festplattenspeicher-Nutzungsinformationen anzeigen.",
"storage-usage-label": "Datenträgerauslastung",
"swap-usage-description": "Swap-Speichernutzung anzeigen.",
@@ -272,6 +276,8 @@
"label-mode-label": "Beschriftungsmodus",
"occupied-color-description": "Lege die Hintergrundfarbe für belegte Arbeitsflächen fest.",
"occupied-color-label": "Farbe für belegte Arbeitsfläche",
"pill-size-description": "Passen Sie die Größe der Arbeitsbereichs-Pillen an (50%-100%).",
"pill-size-label": "Pillengröße",
"reverse-scrolling-description": "Die Richtung des Arbeitsflächenwechsels beim Scrollen umkehren.",
"reverse-scrolling-label": "Scrollen umkehren",
"show-applications-description": "Anwendungssymbole in jeder Arbeitsfläche anzeigen.",
@@ -387,6 +393,7 @@
"disconnecting": "Verbindung wird getrennt...",
"download": "Herunterladen",
"duration": "Dauer",
"dysfunctional": "Dysfunktional",
"edit": "Bearbeiten",
"enabled": "Aktiviert",
"events": "Ereignisse",
@@ -807,6 +814,7 @@
"download-title": "Farbschemata herunterladen",
"method-description": {
"content": "Material Design Schema mit hochauflösender Farbextraktion, das die tatsächlichen Farben des Quellinhalts genau wiedergibt.",
"dysfunctional": "Wie Faithful, wählt aber die zweitdominanteste Farbfamilie als primär.",
"faithful": "Versucht, nah an der Ausgangsfarbe zu bleiben, während gleichzeitig eine harmonische Palette erzeugt wird.",
"fruit-salad": "Material Design-Schema, das eine spielerische, lebendige Palette mit vielfältigen Farbtönen erzeugt.",
"monochrome": "Material Design-Schema mit einer einfarbigen Grauskala und minimalem Farbanteil.",
@@ -1302,6 +1310,7 @@
"sources-placeholder": "Mein cooles Repository",
"sources-remove-tooltip": "Plugin-Quelle entfernen",
"title": "Plugins",
"translations-reloaded": "Übersetzungen neu geladen: {name}",
"uninstall-dialog-description": "Sind Sie sicher, dass Sie {plugin} deinstallieren möchten? Dadurch werden alle Plugin-Daten entfernt.",
"uninstall-dialog-title": "Plugin deinstallieren",
"uninstall-error": "Deinstallation fehlgeschlagen: {error}",
@@ -1349,6 +1358,7 @@
"system-monitor": {
"critical-color-label": "Kritische Farbe",
"custom-highlight-colors-title-label": "Benutzerdefinierte Hervorhebungsfarben",
"disk-available-label": "Verfügbarer Speicherplatz",
"disk-section-label": "Festplattennutzung",
"enable-dgpu-monitoring-description": "Warnung: Dies aktiviert Ihre dedizierte GPU (NVIDIA/AMD), was die Akkulaufzeit von Laptops mit Hybridgrafik erheblich beeinträchtigen kann.",
"enable-dgpu-monitoring-label": "Dedizierte GPU-Überwachung aktivieren",
+10 -1
View File
@@ -211,6 +211,10 @@
"memory-usage-label": "Memory usage",
"network-traffic-description": "Display network upload and download speeds.",
"network-traffic-label": "Network traffic",
"storage-as-percentage-description": "Show disk space as percentage instead of absolute values.",
"storage-as-percentage-label": "Disk as percentage",
"storage-available-description": "Shows how much disk space is available instead of how much is used.",
"storage-available-label": "Disk space available",
"storage-usage-description": "Show disk space usage information.",
"storage-usage-label": "Storage usage",
"swap-usage-description": "Show swap memory usage.",
@@ -272,6 +276,8 @@
"label-mode-label": "Label mode",
"occupied-color-description": "Set the background color for occupied workspaces.",
"occupied-color-label": "Occupied workspace color",
"pill-size-description": "Adjust the size of workspace pills.",
"pill-size-label": "Pill size",
"reverse-scrolling-description": "Reverse the direction of workspace switching when scrolling.",
"reverse-scrolling-label": "Reverse scrolling",
"show-applications-description": "Display application icons inside each workspace.",
@@ -386,6 +392,7 @@
"disconnecting": "Disconnecting...",
"download": "Download",
"duration": "Duration",
"dysfunctional": "Dysfunctional",
"edit": "Edit",
"enabled": "Enabled",
"events": "Events",
@@ -806,6 +813,7 @@
"download-title": "Download Color Schemes",
"method-description": {
"content": "Material Design scheme with high-fidelity color extraction that closely matches the source content's actual colors.",
"dysfunctional": "Like Faithful but picks the second most dominant color family as primary.",
"faithful": "Attempts to stay close to the source color while still generating a harmonious palette.",
"fruit-salad": "Material Design scheme that produces a playful, vibrant palette with diverse and varied hues.",
"monochrome": "Material Design scheme using a single-hue grayscale with minimal chromatic content.",
@@ -1274,7 +1282,6 @@
"hot-reload-description": "Automatically reload plugins when their files change. Useful for plugin development.",
"hot-reload-label": "Hot reload (dev mode)",
"hot-reloaded": "Reloaded plugin: {name}",
"translations-reloaded": "Reloaded translations: {name}",
"install-error": "Failed to install: {error}",
"install-incompatible": "{plugin} requires Noctalia v{version} or higher",
"install-success": "Successfully installed {plugin}",
@@ -1304,6 +1311,7 @@
"sources-placeholder": "My cool repository",
"sources-remove-tooltip": "Remove plugin source",
"title": "Plugins",
"translations-reloaded": "Reloaded translations: {name}",
"uninstall-dialog-description": "Are you sure you want to uninstall {plugin}? This will remove all plugin data.",
"uninstall-dialog-title": "Uninstall plugin",
"uninstall-error": "Failed to uninstall: {error}",
@@ -1351,6 +1359,7 @@
"system-monitor": {
"critical-color-label": "Critical color",
"custom-highlight-colors-title-label": "Custom highlight colors",
"disk-available-label": "Disk available",
"disk-section-label": "Disk usage",
"enable-dgpu-monitoring-description": "Warning: This will wake up your discrete GPU (NVIDIA/AMD), which may significantly impact battery life on laptops with hybrid graphics.",
"enable-dgpu-monitoring-label": "Enable discrete GPU monitoring",
+15 -5
View File
@@ -49,7 +49,7 @@
"device-description": "Seleccione qué dispositivo de batería mostrar.",
"device-label": "Dispositivo de batería",
"hide-if-idle-description": "Ocultar el widget cuando la batería no se esté cargando ni descargando.",
"hide-if-idle-label": "Ocultar cuando esté inactivo",
"hide-if-idle-label": "Ocultar cuando inactivo",
"hide-if-not-detected-description": "Ocultar el widget cuando no se detecte batería en el sistema.",
"hide-if-not-detected-label": "Ocultar si no se detecta",
"low-battery-threshold-description": "Muestra una advertencia cuando la batería cae por debajo de este porcentaje.",
@@ -145,7 +145,7 @@
},
"lock-keys": {
"hide-when-off-description": "Ocultar el indicador cuando la tecla no está activa.",
"hide-when-off-label": "Ocultar cuando está desactivado",
"hide-when-off-label": "Ocultar cuando desactivado",
"show-caps-lock-description": "Mostrar el estado de Bloq Mayús.",
"show-caps-lock-label": "Bloq Mayús",
"show-num-lock-description": "Mostrar el estado de Bloq Num.",
@@ -211,6 +211,10 @@
"memory-usage-label": "Uso de memoria",
"network-traffic-description": "Mostrar las velocidades de carga y descarga de la red.",
"network-traffic-label": "Tráfico de red",
"storage-as-percentage-description": "Mostrar el espacio en disco como porcentaje en lugar de valores absolutos.",
"storage-as-percentage-label": "Disco como porcentaje",
"storage-available-description": "Muestra cuánto espacio en disco está disponible en lugar de cuánto se usa.",
"storage-available-label": "Espacio en disco disponible",
"storage-usage-description": "Mostrar la información del uso del espacio en disco.",
"storage-usage-label": "Uso de almacenamiento",
"swap-usage-description": "Mostrar el uso de la memoria swap.",
@@ -272,6 +276,8 @@
"label-mode-label": "Modo de etiqueta",
"occupied-color-description": "Establecer el color de fondo para los Workspaces ocupados.",
"occupied-color-label": "Color del espacio de trabajo ocupado",
"pill-size-description": "Ajusta el tamaño de las píldoras del espacio de trabajo (50%-100%).",
"pill-size-label": "Tamaño de la píldora",
"reverse-scrolling-description": "Invertir la dirección del cambio de espacios de trabajo al desplazarse.",
"reverse-scrolling-label": "Desplazamiento inverso",
"show-applications-description": "Mostrar los iconos de las aplicaciones dentro de cada espacio de trabajo.",
@@ -387,6 +393,7 @@
"disconnecting": "Desconectando...",
"download": "Descargar",
"duration": "Duración",
"dysfunctional": "Disfuncional",
"edit": "Editar",
"enabled": "Activado",
"events": "Eventos",
@@ -510,8 +517,8 @@
},
"hide-modes": {
"auto-hide": "Ocultación Automática",
"hidden": "Ocultar cuando esté vacío",
"idle": "Ocultar cuando esté inactivo",
"hidden": "Ocultar cuando vacío",
"idle": "Ocultar cuando inactivo",
"transparent": "Transparente cuando esté vacío",
"visible": "Mostrar siempre"
},
@@ -612,7 +619,7 @@
"frame-rates-fps": "{fps} FPS",
"scrolling-modes": {
"always": "Desplazar siempre",
"hover": "Desplazar al posar el puntero",
"hover": "Desplazar encima",
"never": "No desplazar nunca"
},
"session-menu-grid-layout": {
@@ -807,6 +814,7 @@
"download-title": "Descargar Esquemas de Color",
"method-description": {
"content": "Esquema de Material Design con extracción de color de alta fidelidad que coincide estrechamente con los colores reales del contenido de origen.",
"dysfunctional": "Como Faithful, pero elige la segunda familia de colores más dominante como primaria.",
"faithful": "Intenta mantenerse cerca del color original mientras genera una paleta armoniosa.",
"fruit-salad": "Esquema de Material Design que produce una paleta alegre y vibrante con matices diversos y variados.",
"monochrome": "Esquema de Material Design que utiliza una escala de grises de un solo tono con un contenido cromático mínimo.",
@@ -1302,6 +1310,7 @@
"sources-placeholder": "Mi repositorio genial",
"sources-remove-tooltip": "Eliminar fuente del plugin",
"title": "Plugins",
"translations-reloaded": "Traducciones recargadas: {name}",
"uninstall-dialog-description": "¿Está seguro de que quiere desinstalar {plugin}? Esto eliminará todos los datos del plugin.",
"uninstall-dialog-title": "Desinstalar plugin",
"uninstall-error": "Error al desinstalar: {error}",
@@ -1349,6 +1358,7 @@
"system-monitor": {
"critical-color-label": "Color crítico",
"custom-highlight-colors-title-label": "Colores de resaltado personalizados",
"disk-available-label": "Espacio en disco disponible",
"disk-section-label": "Uso de disco",
"enable-dgpu-monitoring-description": "Advertencia: Esto activará tu GPU dedicada (NVIDIA/AMD), lo que podría afectar significativamente la duración de la batería en portátiles con gráficos híbridos.",
"enable-dgpu-monitoring-label": "Habilitar la monitorización de la GPU dedicada",
+10
View File
@@ -211,6 +211,10 @@
"memory-usage-label": "Utilisation de la mémoire",
"network-traffic-description": "Afficher les vitesses de téléchargement et de téléversement du réseau.",
"network-traffic-label": "Trafic réseau",
"storage-as-percentage-description": "Afficher l'espace disque en pourcentage au lieu des valeurs absolues.",
"storage-as-percentage-label": "Disque en pourcentage",
"storage-available-description": "Affiche l'espace disque disponible au lieu de l'espace utilisé.",
"storage-available-label": "Espace disque disponible",
"storage-usage-description": "Afficher les informations d'utilisation de l'espace disque.",
"storage-usage-label": "Utilisation du stockage",
"swap-usage-description": "Afficher l'utilisation de la mémoire swap.",
@@ -272,6 +276,8 @@
"label-mode-label": "Mode d'étiquette",
"occupied-color-description": "Définir la couleur d'arrière-plan pour les Workspaces occupés.",
"occupied-color-label": "Couleur de l'espace de travail occupé",
"pill-size-description": "Ajustez la taille des pilules d'espace de travail (50%-100%).",
"pill-size-label": "Taille de la pilule",
"reverse-scrolling-description": "Inverser la direction du changement d'espace de travail lors du défilement.",
"reverse-scrolling-label": "Défilement inversé",
"show-applications-description": "Afficher les icônes des applications dans chaque espace de travail.",
@@ -387,6 +393,7 @@
"disconnecting": "Déconnexion...",
"download": "Télécharger",
"duration": "Durée",
"dysfunctional": "Dysfonctionnel",
"edit": "Modifier",
"enabled": "Activé",
"events": "Événements",
@@ -807,6 +814,7 @@
"download-title": "Télécharger des jeux de couleurs",
"method-description": {
"content": "Schéma Material Design avec extraction de couleurs haute fidélité qui correspond étroitement aux couleurs réelles du contenu source.",
"dysfunctional": "Comme Faithful, mais choisit la deuxième famille de couleurs la plus dominante comme principale.",
"faithful": "Tente de rester proche de la couleur source tout en générant une palette harmonieuse.",
"fruit-salad": "Schéma Material Design qui produit une palette ludique et vibrante avec des teintes diverses et variées.",
"monochrome": "Schéma Material Design utilisant une échelle de gris à teinte unique avec un contenu chromatique minimal.",
@@ -1302,6 +1310,7 @@
"sources-placeholder": "Mon dépôt cool",
"sources-remove-tooltip": "Supprimer la source du plugin",
"title": "Modules d'extension",
"translations-reloaded": "Traductions rechargées : {name}",
"uninstall-dialog-description": "Êtes-vous sûr de vouloir désinstaller {plugin} ? Cette action supprimera toutes les données du plugin.",
"uninstall-dialog-title": "Désinstaller le plugin",
"uninstall-error": "Échec de la désinstallation : {error}",
@@ -1349,6 +1358,7 @@
"system-monitor": {
"critical-color-label": "Couleur critique",
"custom-highlight-colors-title-label": "Couleurs de surbrillance personnalisées",
"disk-available-label": "Espace disque disponible",
"disk-section-label": "Utilisation disque",
"enable-dgpu-monitoring-description": "Attention : Ceci va activer votre GPU dédié (NVIDIA/AMD), ce qui peut avoir un impact significatif sur l'autonomie de la batterie des ordinateurs portables dotés de cartes graphiques hybrides.",
"enable-dgpu-monitoring-label": "Activer la surveillance du GPU dédié",
+10
View File
@@ -211,6 +211,10 @@
"memory-usage-label": "Memóriahasználat",
"network-traffic-description": "Hálózati feltöltési és letöltési sebességek megjelenítése.",
"network-traffic-label": "Hálózati forgalom",
"storage-as-percentage-description": "Lemezterület megjelenítése százalékban abszolút értékek helyett.",
"storage-as-percentage-label": "Lemez százalékban",
"storage-available-description": "Azt mutatja, mennyi lemezterület áll rendelkezésre, ahelyett, hogy mennyit használnak.",
"storage-available-label": "Rendelkezésre álló lemezterület",
"storage-usage-description": "Lemezterület-használati információk megjelenítése.",
"storage-usage-label": "Tárhelyhasználat",
"swap-usage-description": "Cserehely memória használatának megjelenítése.",
@@ -272,6 +276,8 @@
"label-mode-label": "Címke mód",
"occupied-color-description": "A foglalt Workspaces háttérszínének beállítása.",
"occupied-color-label": "Foglalt munkaterület színe",
"pill-size-description": "Állítsa be a munkaterület-jelzők méretét (50%-100%).",
"pill-size-label": "Pill méret",
"reverse-scrolling-description": "Görgetéskor fordított irányba váltson a munkaterületek között.",
"reverse-scrolling-label": "Görgetés megfordítása",
"show-applications-description": "Alkalmazásikonok megjelenítése minden munkaterületen belül.",
@@ -387,6 +393,7 @@
"disconnecting": "Kapcsolat bontása...",
"download": "Letöltés",
"duration": "Időtartam",
"dysfunctional": "Diszfunkcionális",
"edit": "Szerkesztés",
"enabled": "Engedélyezve",
"events": "Események",
@@ -807,6 +814,7 @@
"download-title": "Színsémák letöltése",
"method-description": {
"content": "Material Design séma nagy pontosságú színkivonással, amely szorosan illeszkedik a forrás tartalom tényleges színeihez.",
"dysfunctional": "Mint a Faithful, de a második legdominánsabb színcsaládot választja elsődlegesnek.",
"faithful": "Igyekszik közel maradni a forrásszínhez, miközben harmonikus palettát generál.",
"fruit-salad": "Material Design séma, amely játékos, élénk palettát hoz létre változatos árnyalatokkal.",
"monochrome": "Material Design séma egyszínű szürkeskálával, minimális színtartalommal.",
@@ -1302,6 +1310,7 @@
"sources-placeholder": "Saját menő tárolóm",
"sources-remove-tooltip": "Bővítményforrás eltávolítása",
"title": "Bővítmények",
"translations-reloaded": "Fordítások újratöltve: {name}",
"uninstall-dialog-description": "Biztosan el szeretné távolítani a(z) {plugin} bővítményt? Ez eltávolítja az összes bővítményadatot.",
"uninstall-dialog-title": "Bővítmény eltávolítása",
"uninstall-error": "Sikertelen eltávolítás: {error}",
@@ -1349,6 +1358,7 @@
"system-monitor": {
"critical-color-label": "Kritikus szín",
"custom-highlight-colors-title-label": "Egyéni kiemelőszínek",
"disk-available-label": "Rendelkezésre álló lemezterület",
"disk-section-label": "Lemezhasználat",
"enable-dgpu-monitoring-description": "Figyelem: Ez felébreszti a dedikált GPU-t (NVIDIA/AMD), ami jelentősen befolyásolhatja az akkumulátor élettartamát a hibrid grafikával rendelkező laptopokon.",
"enable-dgpu-monitoring-label": "Dedikált GPU figyelés engedélyezése",
+10
View File
@@ -211,6 +211,10 @@
"memory-usage-label": "メモリ使用量",
"network-traffic-description": "ネットワークのアップロード・ダウンロード速度を表示します。",
"network-traffic-label": "ネットワークトラフィック",
"storage-as-percentage-description": "ディスク容量を絶対値ではなくパーセンテージで表示する。",
"storage-as-percentage-label": "ディスクをパーセンテージで",
"storage-available-description": "使用量ではなく、利用可能なディスク容量を表示します。",
"storage-available-label": "利用可能なディスク容量",
"storage-usage-description": "ストレージの使用状況を表示します。",
"storage-usage-label": "ストレージ使用量",
"swap-usage-description": "スワップメモリの使用状況を表示します。",
@@ -272,6 +276,8 @@
"label-mode-label": "ラベルモード",
"occupied-color-description": "使用中のWorkspaceの背景色を設定します。",
"occupied-color-label": "使用中のワークスペースの色",
"pill-size-description": "ワークスペースピルのサイズを調整します (50%-100%)。",
"pill-size-label": "ピルサイズ",
"reverse-scrolling-description": "スクロール時のワークスペース切り替え方向を反転する。",
"reverse-scrolling-label": "スクロール方向を反転",
"show-applications-description": "各ワークスペース内にアプリアイコンを表示します。",
@@ -387,6 +393,7 @@
"disconnecting": "切断中...",
"download": "ダウンロード",
"duration": "期間",
"dysfunctional": "機能不全",
"edit": "編集",
"enabled": "有効",
"events": "イベント",
@@ -807,6 +814,7 @@
"download-title": "配色のダウンロード",
"method-description": {
"content": "ソースコンテンツの実際の色と厳密に一致する、高忠実度の色抽出を備えたMaterial Designスキーム。",
"dysfunctional": "Faithfulのように、2番目に支配的な色族をプライマリとして選択します。",
"faithful": "調和のとれたパレットを生成しながら、ソースカラーにできるだけ近い色を維持しようとします。",
"fruit-salad": "多様で多彩な色合いを持つ、遊び心のある鮮やかなパレットを生み出すMaterial Designスキーム。",
"monochrome": "単一色相のグレースケールと最小限の彩度コンテンツを使用した Material Design スキーム。",
@@ -1302,6 +1310,7 @@
"sources-placeholder": "マイリポジトリ",
"sources-remove-tooltip": "プラグインソースを削除",
"title": "プラグイン",
"translations-reloaded": "翻訳をリロードしました: {name}",
"uninstall-dialog-description": "本当に {plugin} をアンインストールしますか?すべてのプラグインデータが削除されます。",
"uninstall-dialog-title": "プラグインのアンインストール",
"uninstall-error": "アンインストールに失敗しました: {error}",
@@ -1349,6 +1358,7 @@
"system-monitor": {
"critical-color-label": "危険時の色",
"custom-highlight-colors-title-label": "カスタムハイライト色",
"disk-available-label": "利用可能なディスク容量",
"disk-section-label": "ストレージ使用量",
"enable-dgpu-monitoring-description": "警告: これにより外部GPU (NVIDIA/AMD) が起動状態になるため、ハイブリッドグラフィックス搭載のノート PC ではバッテリー駆動時間に大きな影響を与える可能性があります。",
"enable-dgpu-monitoring-label": "外部GPUモニタリングを有効にする",
+10
View File
@@ -211,6 +211,10 @@
"memory-usage-label": "메모리 사용량",
"network-traffic-description": "네트워크 업로드 및 다운로드 속도를 표시합니다.",
"network-traffic-label": "네트워크 트래픽",
"storage-as-percentage-description": "디스크 공간을 절대값 대신 백분율로 표시합니다.",
"storage-as-percentage-label": "디스크 백분율",
"storage-available-description": "사용된 공간 대신 사용 가능한 디스크 공간을 표시합니다.",
"storage-available-label": "사용 가능한 디스크 공간",
"storage-usage-description": "디스크 공간 사용량 정보를 표시합니다.",
"storage-usage-label": "저장소 사용량",
"swap-usage-description": "스왑 메모리 사용량을 표시합니다.",
@@ -272,6 +276,8 @@
"label-mode-label": "레이블 모드",
"occupied-color-description": "사용 중인 작업 공간의 배경색을 설정합니다.",
"occupied-color-label": "사용 중인 작업 공간 색상",
"pill-size-description": "작업 공간 알약의 크기를 조절합니다 (50%-100%).",
"pill-size-label": "필 크기",
"reverse-scrolling-description": "스크롤할 때 작업 공간 전환 방향을 반대로 합니다.",
"reverse-scrolling-label": "스크롤 반전",
"show-applications-description": "각 작업 공간 내부에 애플리케이션 아이콘을 표시합니다.",
@@ -387,6 +393,7 @@
"disconnecting": "연결 해제 중...",
"download": "다운로드",
"duration": "지속 시간",
"dysfunctional": "기능 장애",
"edit": "편집",
"enabled": "활성화됨",
"events": "일정",
@@ -807,6 +814,7 @@
"download-title": "색상 구성 다운로드",
"method-description": {
"content": "소스 콘텐츠의 실제 색상을 매우 유사하게 재현하는 고품질 색상 추출을 사용하는 머티리얼 디자인 구성입니다.",
"dysfunctional": "Faithful과 비슷하지만, 두 번째로 지배적인 색상 계열을 기본으로 선택합니다.",
"faithful": "소스 색상에 가깝게 유지하면서 조화로운 팔레트 생성을 시도합니다.",
"fruit-salad": "다양하고 다채로운 색조로 유쾌하고 생동감 넘치는 팔레트를 생성하는 머티리얼 디자인 구성입니다.",
"monochrome": "채도가 최소화된 단일 색조 그레이스케일을 사용하는 머티리얼 디자인 구성입니다.",
@@ -1302,6 +1310,7 @@
"sources-placeholder": "내 멋진 저장소",
"sources-remove-tooltip": "플러그인 소스 제거",
"title": "플러그인",
"translations-reloaded": "번역 다시 로드됨: {name}",
"uninstall-dialog-description": "정말 {plugin}을(를) 제거하시겠습니까? 모든 플러그인 데이터가 삭제됩니다.",
"uninstall-dialog-title": "플러그인 제거",
"uninstall-error": "제거 실패: {error}",
@@ -1349,6 +1358,7 @@
"system-monitor": {
"critical-color-label": "위험 색상",
"custom-highlight-colors-title-label": "사용자 지정 강조 색상",
"disk-available-label": "사용 가능한 디스크 공간",
"disk-section-label": "디스크 사용량",
"enable-dgpu-monitoring-description": "경고: 이 기능은 외장 GPU(NVIDIA/AMD)를 깨워 하이브리드 그래픽 노트북의 배터리 수명에 상당한 영향을 줄 수 있습니다.",
"enable-dgpu-monitoring-label": "외장 GPU 모니터링 활성화",
+10
View File
@@ -211,6 +211,10 @@
"memory-usage-label": "Geheugengebruik",
"network-traffic-description": "Toon uploadsnelheid en downloadsnelheid van het netwerk.",
"network-traffic-label": "Netwerkverkeer",
"storage-as-percentage-description": "Toon schijfruimte als percentage in plaats van absolute waarden.",
"storage-as-percentage-label": "Schijf als percentage",
"storage-available-description": "Toont hoeveel schijfruimte beschikbaar is in plaats van hoeveel er wordt gebruikt.",
"storage-available-label": "Beschikbare schijfruimte",
"storage-usage-description": "Toon informatie over schijfruimtegebruik.",
"storage-usage-label": "Opslaggebruik",
"swap-usage-description": "Toon swap geheugengebruik.",
@@ -272,6 +276,8 @@
"label-mode-label": "Labelmodus",
"occupied-color-description": "Stel de achtergrondkleur in voor bezette Workspaces.",
"occupied-color-label": "Kleur van bezette workspace",
"pill-size-description": "Pas de grootte van de werkruimte-pillen aan (50%-100%).",
"pill-size-label": "Pilgrootte",
"reverse-scrolling-description": "Keer de richting van het wisselen van werkruimtes om bij het scrollen.",
"reverse-scrolling-label": "Omgekeerd scrollen",
"show-applications-description": "Toon applicatiepictogrammen in elke werkruimte.",
@@ -387,6 +393,7 @@
"disconnecting": "Verbinding verbroken...",
"download": "Downloaden",
"duration": "Duur",
"dysfunctional": "Dysfunctioneel",
"edit": "Bewerken",
"enabled": "Ingeschakeld",
"events": "Evenementen",
@@ -807,6 +814,7 @@
"download-title": "Kleurenschema's downloaden",
"method-description": {
"content": "Material Design-schema met high-fidelity kleurextractie die nauw aansluit bij de werkelijke kleuren van de broninhoud.",
"dysfunctional": "Zoals Faithful, maar kiest de op één na meest dominante kleurenfamilie als primair.",
"faithful": "Probeert dicht bij de bronkleur te blijven en tegelijkertijd een harmonieus palet te genereren.",
"fruit-salad": "Material Design-schema dat een speels, levendig palet produceert met diverse en gevarieerde tinten.",
"monochrome": "Material Design-schema met een grijswaarde met één tint en minimale chromatische inhoud.",
@@ -1302,6 +1310,7 @@
"sources-placeholder": "Mijn coole repository",
"sources-remove-tooltip": "Pluginbron verwijderen",
"title": "Plugins",
"translations-reloaded": "Vertalingen opnieuw geladen: {name}",
"uninstall-dialog-description": "Weet je zeker dat je {plugin} wilt verwijderen? Hiermee worden alle plugin-gegevens verwijderd.",
"uninstall-dialog-title": "Plugin verwijderen",
"uninstall-error": "Verwijderen mislukt: {error}",
@@ -1349,6 +1358,7 @@
"system-monitor": {
"critical-color-label": "Kritische kleur",
"custom-highlight-colors-title-label": "Aangepaste markeerkleuren",
"disk-available-label": "Beschikbare schijfruimte",
"disk-section-label": "Schijfgebruik",
"enable-dgpu-monitoring-description": "Waarschuwing: Dit zal uw dedicated GPU (NVIDIA/AMD) activeren, wat een aanzienlijke impact kan hebben op de accuduur van laptops met hybride grafische kaarten.",
"enable-dgpu-monitoring-label": "Dedicated GPU-monitoring inschakelen",
+10
View File
@@ -211,6 +211,10 @@
"memory-usage-label": "Użycie pamięci",
"network-traffic-description": "Wyświetl prędkość wysyłania i pobierania danych w sieci.",
"network-traffic-label": "Ruch sieciowy",
"storage-as-percentage-description": "Wyświetlaj miejsce na dysku jako procent zamiast wartości bezwzględnych.",
"storage-as-percentage-label": "Dysk jako procent",
"storage-available-description": "Pokazuje, ile miejsca na dysku jest dostępne, zamiast ile jest używane.",
"storage-available-label": "Dostępne miejsce na dysku",
"storage-usage-description": "Pokaż informacje o użyciu miejsca na dysku.",
"storage-usage-label": "Użycie dysku",
"swap-usage-description": "Pokaż użycie pamięci wymiany.",
@@ -272,6 +276,8 @@
"label-mode-label": "Tryb etykiet",
"occupied-color-description": "Ustaw kolor tła dla zajętych Workspace'ów.",
"occupied-color-label": "Kolor zajętego obszaru roboczego",
"pill-size-description": "Dostosuj rozmiar pigułek obszaru roboczego (50%-100%).",
"pill-size-label": "Rozmiar pigułki",
"reverse-scrolling-description": "Odwróć kierunek przełączania obszarów roboczych podczas przewijania.",
"reverse-scrolling-label": "Odwróć przewijanie",
"show-applications-description": "Wyświetl ikony aplikacji wewnątrz każdego obszaru roboczego.",
@@ -387,6 +393,7 @@
"disconnecting": "Rozłączanie...",
"download": "Pobierz",
"duration": "Czas trwania",
"dysfunctional": "Dysfunkcyjny",
"edit": "Edytuj",
"enabled": "Włączone",
"events": "Wydarzenia",
@@ -807,6 +814,7 @@
"download-title": "Pobierz schematy kolorów",
"method-description": {
"content": "Schemat Material Design z ekstrakcją kolorów o wysokiej wierności, która ściśle odpowiada rzeczywistym kolorom treści źródłowej.",
"dysfunctional": "Jak Faithful, ale wybiera drugą najbardziej dominującą rodzinę kolorów jako główną.",
"faithful": "Próbuje pozostać blisko koloru źródłowego, jednocześnie generując harmonijną paletę.",
"fruit-salad": "Schemat Material Design, który tworzy radosną, żywą paletę z różnorodnymi odcieniami.",
"monochrome": "Schemat Material Design wykorzystujący jednobarwną skalę szarości z minimalną zawartością chromatyczną.",
@@ -1302,6 +1310,7 @@
"sources-placeholder": "Moje fajne repozytorium",
"sources-remove-tooltip": "Usuń źródło wtyczek",
"title": "Wtyczki",
"translations-reloaded": "Przeładowano tłumaczenia: {name}",
"uninstall-dialog-description": "Czy na pewno chcesz odinstalować {plugin}? Spowoduje to usunięcie wszystkich danych wtyczki.",
"uninstall-dialog-title": "Odinstaluj wtyczkę",
"uninstall-error": "Błąd odinstalowywania: {error}",
@@ -1349,6 +1358,7 @@
"system-monitor": {
"critical-color-label": "Kolor krytyczny",
"custom-highlight-colors-title-label": "Własne kolory podświetlenia",
"disk-available-label": "Dostępne miejsce na dysku",
"disk-section-label": "Użycie dysku",
"enable-dgpu-monitoring-description": "Ostrzeżenie: To obudzi twoją dedykowaną kartę graficzną (NVIDIA/AMD), co może znacząco wpłynąć na żywotność baterii w laptopach z hybrydową grafiką.",
"enable-dgpu-monitoring-label": "Włącz monitorowanie dedykowanej karty graficznej",
+10
View File
@@ -211,6 +211,10 @@
"memory-usage-label": "Uso de memória",
"network-traffic-description": "Exibir as velocidades de upload e download da rede.",
"network-traffic-label": "Tráfego de rede",
"storage-as-percentage-description": "Mostrar o espaço em disco como percentagem em vez de valores absolutos.",
"storage-as-percentage-label": "Disco como percentagem",
"storage-available-description": "Mostra quanto espaço em disco está disponível em vez de quanto é usado.",
"storage-available-label": "Espaço em disco disponível",
"storage-usage-description": "Mostrar as informações de uso do espaço em disco.",
"storage-usage-label": "Uso de armazenamento",
"swap-usage-description": "Mostrar o uso da memória swap.",
@@ -272,6 +276,8 @@
"label-mode-label": "Modo de rótulo",
"occupied-color-description": "Definir a cor de fundo para Workspaces ocupados.",
"occupied-color-label": "Cor do espaço de trabalho ocupado",
"pill-size-description": "Ajuste o tamanho das pílulas da área de trabalho (50%-100%).",
"pill-size-label": "Tamanho da pílula",
"reverse-scrolling-description": "Inverter a direção da troca de áreas de trabalho ao rolar.",
"reverse-scrolling-label": "Rolagem invertida",
"show-applications-description": "Exibir ícones de aplicativos dentro de cada espaço de trabalho.",
@@ -387,6 +393,7 @@
"disconnecting": "Desconectando...",
"download": "Baixar",
"duration": "Duração",
"dysfunctional": "Disfuncional",
"edit": "Editar",
"enabled": "Ativado",
"events": "Eventos",
@@ -807,6 +814,7 @@
"download-title": "Baixar esquemas de cores",
"method-description": {
"content": "Esquema de Material Design com extração de cores de alta fidelidade que corresponde de perto às cores reais do conteúdo de origem.",
"dysfunctional": "Como Faithful, mas escolhe a segunda família de cores mais dominante como primária.",
"faithful": "Tenta manter-se próximo da cor de origem, enquanto gera uma paleta harmoniosa.",
"fruit-salad": "Esquema de Material Design que produz uma paleta divertida e vibrante com tons diversos e variados.",
"monochrome": "Esquema de Material Design usando uma escala de cinza de tom único com conteúdo cromático mínimo.",
@@ -1302,6 +1310,7 @@
"sources-placeholder": "Meu repositório legal",
"sources-remove-tooltip": "Remover código fonte do plugin",
"title": "Plugins",
"translations-reloaded": "Traduções recarregadas: {name}",
"uninstall-dialog-description": "Tem certeza de que deseja desinstalar {plugin}? Isso removerá todos os dados do plugin.",
"uninstall-dialog-title": "Desinstalar plugin",
"uninstall-error": "Falha ao desinstalar: {error}",
@@ -1349,6 +1358,7 @@
"system-monitor": {
"critical-color-label": "Cor crítica",
"custom-highlight-colors-title-label": "Cores de destaque personalizadas",
"disk-available-label": "Espaço em disco disponível",
"disk-section-label": "Uso do disco",
"enable-dgpu-monitoring-description": "Atenção: Isto irá ativar sua GPU dedicada (NVIDIA/AMD), o que pode impactar significativamente a duração da bateria em laptops com gráficos híbridos.",
"enable-dgpu-monitoring-label": "Ativar o monitoramento da GPU dedicada",
+15 -5
View File
@@ -211,6 +211,10 @@
"memory-usage-label": "Использование памяти",
"network-traffic-description": "Отображать скорости загрузки и скачивания по сети.",
"network-traffic-label": "Сетевой трафик",
"storage-as-percentage-description": "Показывать место на диске в процентах вместо абсолютных значений.",
"storage-as-percentage-label": "Диск в процентах",
"storage-available-description": "Показывает, сколько места на диске доступно, вместо того, сколько используется.",
"storage-available-label": "Доступное место на диске",
"storage-usage-description": "Показывать информацию об использовании дискового пространства.",
"storage-usage-label": "Использование хранилища",
"swap-usage-description": "Показать использование swap-памяти.",
@@ -272,6 +276,8 @@
"label-mode-label": "Режим метки",
"occupied-color-description": "Установить цвет фона для занятых рабочих пространств.",
"occupied-color-label": "Цвет занятого рабочего пространства",
"pill-size-description": "Отрегулируйте размер индикаторов рабочих пространств (50%-100%).",
"pill-size-label": "Размер капсулы",
"reverse-scrolling-description": "Изменить направление переключения рабочих пространств при прокрутке.",
"reverse-scrolling-label": "Обратная прокрутка",
"show-applications-description": "Отображать значки приложений внутри каждого рабочего пространства.",
@@ -387,6 +393,7 @@
"disconnecting": "Отключение...",
"download": "Скачать",
"duration": "Продолжительность",
"dysfunctional": "Дисфункциональный",
"edit": "Редактировать",
"enabled": "Включено",
"events": "События",
@@ -807,6 +814,7 @@
"download-title": "Загрузить цветовые схемы",
"method-description": {
"content": "Схема Material Design с высокоточной экстракцией цветов, которая точно соответствует фактическим цветам исходного контента.",
"dysfunctional": "Как Faithful, но выбирает второе по доминантности цветовое семейство в качестве основного.",
"faithful": "Пытается оставаться близким к исходному цвету, создавая при этом гармоничную палитру.",
"fruit-salad": "Схема Material Design, создающая игривую, яркую палитру с разнообразными оттенками.",
"monochrome": "Схема Material Design, использующая однотонную шкалу серого с минимальным хроматическим содержанием.",
@@ -1302,6 +1310,7 @@
"sources-placeholder": "Мой крутой репозиторий",
"sources-remove-tooltip": "Удалить исходный код плагина",
"title": "Плагины",
"translations-reloaded": "Переводы перезагружены: {name}",
"uninstall-dialog-description": "Вы уверены, что хотите удалить {plugin}? Это удалит все данные плагина.",
"uninstall-dialog-title": "Удалить плагин",
"uninstall-error": "Не удалось удалить: {error}",
@@ -1349,6 +1358,7 @@
"system-monitor": {
"critical-color-label": "Критический цвет",
"custom-highlight-colors-title-label": "Пользовательские цвета выделения",
"disk-available-label": "Доступно места на диске",
"disk-section-label": "Использование диска",
"enable-dgpu-monitoring-description": "Внимание: Это разбудит ваш дискретный графический процессор (NVIDIA/AMD), что может значительно повлиять на время работы от аккумулятора на ноутбуках с гибридной графикой.",
"enable-dgpu-monitoring-label": "Включить мониторинг дискретного GPU",
@@ -1416,17 +1426,17 @@
"automation-change-mode-label": "Режим смены",
"automation-custom-interval-description": "Введите время в формате ЧЧ:ММ (например, 01:30).",
"automation-custom-interval-label": "Пользовательский интервал",
"automation-interval-description": "Как часто автоматически менять обои.",
"automation-interval-description": "Частота автоматической смены обоев.",
"automation-interval-label": "Интервал смены обоев",
"automation-random-wallpaper-description": "Запланировать смену случайных обоев через регулярные интервалы.",
"automation-scheduled-change-description": "Автоматически менять обои через регулярные интервалы.",
"automation-scheduled-change-description": "Автоматически менять обои через регулярные интервалы времени.",
"automation-scheduled-change-label": "Запланированная смена",
"look-feel-edge-smoothness-description": "Применяет мягкий, растушёванный эффект к краю переходов.",
"look-feel-edge-smoothness-label": "Смягчить край перехода",
"look-feel-fill-color-description": "Выберите цвет заливки, который может появиться за обоями.",
"look-feel-fill-mode-description": "Выберите, как изображение должно масштабироваться, чтобы соответствовать разрешению вашего монитора.",
"look-feel-fill-mode-label": "Режим заполнения",
"look-feel-title": "Внешний вид и ощущения",
"look-feel-title": "Внешний вид",
"look-feel-transition-duration-description": "Продолжительность анимации перехода в секундах.",
"look-feel-transition-duration-label": "Продолжительность перехода",
"look-feel-transition-type-description": "Тип анимации при переключении между обоями.",
@@ -1446,9 +1456,9 @@
"settings-recursive-search-description": "Также искать обои во вложенных папках каталога обоев.",
"settings-recursive-search-label": "Искать во вложенных папках",
"settings-select-monitor-folder": "Выбрать папку с обоями для монитора",
"settings-selector-description": "Выберите обои.",
"settings-selector-description": "Перейти на панель выбора обоев.",
"settings-selector-position-description": "Выберите, где появляется панель выбора обоев.",
"settings-show-hidden-files-tooltip-hide": "Скрыть скрытые файлы",
"settings-show-hidden-files-tooltip-hide": "Не показывать скрытые файлы",
"settings-show-hidden-files-tooltip-show": "Показать скрытые файлы",
"settings-title": "Настройки обоев",
"settings-view-mode-description": "Выберите способ отображения обоев из вашей директории.",
+12
View File
@@ -211,6 +211,10 @@
"memory-usage-label": "Minnesanvändning",
"network-traffic-description": "Visa nätverkets uppladdnings- och nedladdningshastigheter.",
"network-traffic-label": "Nätverkstrafik",
"storage-as-percentage-description": "Visa diskutrymme som procentandel istället för absoluta värden.",
"storage-as-percentage-label": "Disk i procent",
"storage-available-description": "Visar hur mycket diskutrymme som är tillgängligt istället för hur mycket som används.",
"storage-available-label": "Tillgängligt diskutrymme",
"storage-usage-description": "Visa information om diskutrymmesanvändning.",
"storage-usage-label": "Lagringsanvändning",
"swap-usage-description": "Visa användning av växlingsminne.",
@@ -272,6 +276,8 @@
"label-mode-label": "Etikettläge",
"occupied-color-description": "Ställ in bakgrundsfärgen för använda arbetsytor.",
"occupied-color-label": "Färg för använda arbetsytor",
"pill-size-description": "Justera storleken på arbetsytepillerna (50%-100%).",
"pill-size-label": "Pillerstorlek",
"reverse-scrolling-description": "Vänd riktningen för arbetsyteväxling vid rullning.",
"reverse-scrolling-label": "Omvänd rullning",
"show-applications-description": "Visa applikationsikoner i varje arbetsyta.",
@@ -387,6 +393,7 @@
"disconnecting": "Kopplar från...",
"download": "Hämta ner",
"duration": "Varaktighet",
"dysfunctional": "Dysfunktionell",
"edit": "Redigera",
"enabled": "Aktiverad",
"events": "Händelser",
@@ -807,6 +814,7 @@
"download-title": "Hämta ner färgscheman",
"method-description": {
"content": "Material Design-schema med högkvalitativ färgutvinning som nära matchar källinnehållets faktiska färger.",
"dysfunctional": "Som Faithful, men väljer den näst mest dominanta färgfamiljen som primär.",
"faithful": "Försöker hålla sig nära källfärgen samtidigt som en harmonisk palett genereras.",
"fruit-salad": "Material Design-schema som ger en lekfull, livfull palett med olika och varierande nyanser.",
"monochrome": "Material Design-schema som använder en enda nyans i gråskala med minimalt kromatiskt innehåll.",
@@ -1143,6 +1151,8 @@
"weather-show-in-calendar-label": "Visa väder i kalendern"
},
"lock-screen": {
"allow-password-with-fprintd-description": "När fprintd (fingeravtrycksautentisering) är aktivt, låter det här alternativet dig fortfarande logga in med ditt lösenord istället för ett fingeravtryck",
"allow-password-with-fprintd-label": "Tillåt lösenordsinloggning med fprintd",
"auto-start-auth-description": "t.ex. startar automatiskt fingeravtrycksautentisering utan att du behöver trycka på en tangent eller knapp.",
"auto-start-auth-label": "Automatisk start av autentisering",
"compact-lockscreen-description": "Visa endast inloggningsfältet och systemkontrollerna, dölj väder- och mediewidgets.",
@@ -1300,6 +1310,7 @@
"sources-placeholder": "Mitt coola arkiv",
"sources-remove-tooltip": "Ta bort insticksmodulkälla",
"title": "Insticksmoduler",
"translations-reloaded": "Översättningar omladdade: {name}",
"uninstall-dialog-description": "Är du säker på att du vill avinstallera {plugin}? Detta kommer att ta bort all insticksmoduldata.",
"uninstall-dialog-title": "Avinstallera insticksmodul",
"uninstall-error": "Avinstallationen misslyckades: {error}",
@@ -1347,6 +1358,7 @@
"system-monitor": {
"critical-color-label": "Kritisk färg",
"custom-highlight-colors-title-label": "Anpassade markeringsfärger",
"disk-available-label": "Tillgängligt diskutrymme",
"disk-section-label": "Diskanvändning",
"enable-dgpu-monitoring-description": "Varning: Detta aktiverar din diskreta GPU (NVIDIA/AMD), vilket kan påverka batteritiden avsevärt på bärbara datorer med hybridgrafik.",
"enable-dgpu-monitoring-label": "Aktivera övervakning av diskret GPU",
+10
View File
@@ -211,6 +211,10 @@
"memory-usage-label": "Bellek kullanımı",
"network-traffic-description": "Ağ yükleme ve indirme hızlarını göster.",
"network-traffic-label": "Ağ trafiği",
"storage-as-percentage-description": "Disk alanını mutlak değerler yerine yüzde olarak göster.",
"storage-as-percentage-label": "Disk yüzde olarak",
"storage-available-description": "Kullanılan miktar yerine ne kadar disk alanı olduğunu gösterir.",
"storage-available-label": "Kullanılabilir disk alanı",
"storage-usage-description": "Disk alanı kullanım bilgilerini göster.",
"storage-usage-label": "Depolama kullanımı",
"swap-usage-description": "Takas belleği kullanımını göster.",
@@ -272,6 +276,8 @@
"label-mode-label": "Etiket Modu",
"occupied-color-description": "Dolu Workspaces için arka plan rengini ayarla.",
"occupied-color-label": "Dolu çalışma alanı rengi",
"pill-size-description": "Çalışma alanı haplarının boyutunu ayarlayın (50%-100%).",
"pill-size-label": "Kapsül boyutu",
"reverse-scrolling-description": "Kaydırırken çalışma alanı geçiş yönünü tersine çevir.",
"reverse-scrolling-label": "Ters kaydırma",
"show-applications-description": "Her çalışma alanının içinde uygulama simgelerini görüntüle.",
@@ -387,6 +393,7 @@
"disconnecting": "Bağlantı kesiliyor...",
"download": "İndir",
"duration": "Süre",
"dysfunctional": "İşlevsiz",
"edit": "Düzenle",
"enabled": "Etkinleştirildi",
"events": "Etkinlikler",
@@ -807,6 +814,7 @@
"download-title": "Renk şemalarını indir",
"method-description": {
"content": "Kaynak içeriğin gerçek renkleriyle yakından eşleşen, yüksek doğruluklu renk çıkarma özelliğine sahip Material Design şeması.",
"dysfunctional": "Faithful gibi, ancak ikinci en baskın renk ailesini birincil olarak seçer.",
"faithful": "Uyumlu bir palet oluştururken kaynak rengine yakın kalmaya çalışır.",
"fruit-salad": "Çeşitli ve farklı tonlara sahip, eğlenceli, canlı bir palet üreten Material Design şeması.",
"monochrome": "Minimal kromatik içerikli tek tonlu gri tonlamalı Material Design şeması.",
@@ -1302,6 +1310,7 @@
"sources-placeholder": "Havalı depom",
"sources-remove-tooltip": "Eklenti kaynağını kaldır",
"title": "Eklentiler",
"translations-reloaded": "Çeviriler yeniden yüklendi: {name}",
"uninstall-dialog-description": "{plugin} eklentisini kaldırmak istediğinizden emin misiniz? Bu, tüm eklenti verilerini kaldıracaktır.",
"uninstall-dialog-title": "Eklentiyi kaldır",
"uninstall-error": "Kaldırma başarısız oldu: {error}",
@@ -1349,6 +1358,7 @@
"system-monitor": {
"critical-color-label": "Kritik renk",
"custom-highlight-colors-title-label": "Özel vurgulama renkleri",
"disk-available-label": "Kullanılabilir disk alanı",
"disk-section-label": "Disk kullanımı",
"enable-dgpu-monitoring-description": "Uyarı: Bu, ayrık GPU'nuzu (NVIDIA/AMD) uyandıracak ve bu da hibrit grafiklere sahip dizüstü bilgisayarlarda pil ömrünü önemli ölçüde etkileyebilir.",
"enable-dgpu-monitoring-label": "Ayrık GPU izlemeyi etkinleştir",
+10
View File
@@ -211,6 +211,10 @@
"memory-usage-label": "Використання пам'яті",
"network-traffic-description": "Відображати швидкість прийому та передачі даних.",
"network-traffic-label": "Мережевий трафік",
"storage-as-percentage-description": "Показувати місце на диску у відсотках замість абсолютних значень.",
"storage-as-percentage-label": "Диск у відсотках",
"storage-available-description": "Показує, скільки місця на диску доступно, замість того, скільки використовується.",
"storage-available-label": "Доступне місце на диску",
"storage-usage-description": "Показувати інформацію про використання дискового простору.",
"storage-usage-label": "Використання сховища",
"swap-usage-description": "Показати використання swap-пам'яті.",
@@ -272,6 +276,8 @@
"label-mode-label": "Режим міток",
"occupied-color-description": "Встановити колір фону для зайнятих Workspace.",
"occupied-color-label": "Колір зайнятого робочого столу",
"pill-size-description": "Налаштуйте розмір пігулок робочого простору (50%-100%).",
"pill-size-label": "Розмір капсули",
"reverse-scrolling-description": "Змінити напрямок перемикання робочих просторів під час прокручування.",
"reverse-scrolling-label": "Зворотне прокручування",
"show-applications-description": "Відображати значки програм у кожному робочому просторі.",
@@ -387,6 +393,7 @@
"disconnecting": "Від'єднання...",
"download": "Завантажити",
"duration": "Тривалість",
"dysfunctional": "Дисфункціональний",
"edit": "Редагувати",
"enabled": "Увімкнено",
"events": "Події",
@@ -807,6 +814,7 @@
"download-title": "Завантажити кольорові схеми",
"method-description": {
"content": "Схема Material Design з високоточною екстракцією кольорів, яка точно відповідає фактичним кольорам вихідного контенту.",
"dysfunctional": "Як Faithful, але вибирає другу за домінантністю колірну родину як основну.",
"faithful": "Намагається залишатися близьким до вихідного кольору, створюючи при цьому гармонійну палітру.",
"fruit-salad": "Схема Material Design, яка створює грайливу, яскраву палітру з різноманітними відтінками.",
"monochrome": "Схема Material Design, що використовує однотонну градацію сірого з мінімальним хроматичним вмістом.",
@@ -1302,6 +1310,7 @@
"sources-placeholder": "Мій крутий репозиторій",
"sources-remove-tooltip": "Видалити джерело плагіна",
"title": "Плагіни",
"translations-reloaded": "Переклади перезавантажено: {name}",
"uninstall-dialog-description": "Ви впевнені, що хочете видалити {plugin}? Це призведе до видалення всіх даних плагіна.",
"uninstall-dialog-title": "Видалити плагін",
"uninstall-error": "Не вдалося видалити: {error}",
@@ -1349,6 +1358,7 @@
"system-monitor": {
"critical-color-label": "Критичний колір",
"custom-highlight-colors-title-label": "Власні кольори підсвічування",
"disk-available-label": "Доступно місця на диску",
"disk-section-label": "Використання диска",
"enable-dgpu-monitoring-description": "Увага: Це розбудить ваш дискретний графічний процесор (NVIDIA/AMD), що може значно вплинути на час роботи акумулятора на ноутбуках з гібридною графікою.",
"enable-dgpu-monitoring-label": "Увімкнути моніторинг дискретного GPU",
+10
View File
@@ -211,6 +211,10 @@
"memory-usage-label": "内存使用率",
"network-traffic-description": "显示网络上传和下载速度。",
"network-traffic-label": "网络流量",
"storage-as-percentage-description": "以百分比而非绝对值显示磁盘空间。",
"storage-as-percentage-label": "磁盘百分比",
"storage-available-description": "显示可用磁盘空间,而非已用空间。",
"storage-available-label": "可用磁盘空间",
"storage-usage-description": "显示磁盘空间使用情况。",
"storage-usage-label": "存储用量",
"swap-usage-description": "显示 Swap 使用情况。",
@@ -272,6 +276,8 @@
"label-mode-label": "标签模式",
"occupied-color-description": "设置已占用Workspace的背景颜色。",
"occupied-color-label": "已占用工作区颜色",
"pill-size-description": "调整工作区指示器的大小 (50%-100%)。",
"pill-size-label": "胶囊大小",
"reverse-scrolling-description": "滚动时反转工作区切换方向。",
"reverse-scrolling-label": "反转滚动",
"show-applications-description": "在每个工作区内显示应用程序图标。",
@@ -387,6 +393,7 @@
"disconnecting": "正在断开连接...",
"download": "下载",
"duration": "时长",
"dysfunctional": "功能失调",
"edit": "编辑",
"enabled": "已启用",
"events": "事件",
@@ -807,6 +814,7 @@
"download-title": "下载配色方案",
"method-description": {
"content": "具有高保真色彩提取的 Material Design 方案,可与源内容的实际颜色紧密匹配。",
"dysfunctional": "与 Faithful 类似,但选择第二主导的颜色家族作为主要颜色。",
"faithful": "尝试在生成和谐调色板的同时,尽可能接近源颜色。",
"fruit-salad": "Material Design 方案,产生一个俏皮、充满活力的调色板,具有多样和变化的色调。",
"monochrome": "使用单色调灰阶和最少色彩内容的 Material Design 方案。",
@@ -1302,6 +1310,7 @@
"sources-placeholder": "我的酷仓库",
"sources-remove-tooltip": "移除插件源",
"title": "插件",
"translations-reloaded": "已重新加载翻译:{name}",
"uninstall-dialog-description": "你确定要卸载 {plugin} 吗? 这将移除所有插件数据。",
"uninstall-dialog-title": "卸载插件",
"uninstall-error": "卸载失败:{error}",
@@ -1349,6 +1358,7 @@
"system-monitor": {
"critical-color-label": "严重颜色",
"custom-highlight-colors-title-label": "自定义高亮颜色",
"disk-available-label": "可用磁盘空间",
"disk-section-label": "磁盘使用率",
"enable-dgpu-monitoring-description": "警告:这将唤醒您的独立显卡 (NVIDIA/AMD),这可能会显著影响配备混合显卡的笔记本电脑的电池续航时间。",
"enable-dgpu-monitoring-label": "启用独立显卡监控",
+10
View File
@@ -211,6 +211,10 @@
"memory-usage-label": "記憶體使用量",
"network-traffic-description": "顯示網路上傳及下載的速度",
"network-traffic-label": "網路流量",
"storage-as-percentage-description": "以百分比而非絕對值顯示磁碟空間。",
"storage-as-percentage-label": "磁碟百分比",
"storage-available-description": "顯示可用磁碟空間,而非已用空間。",
"storage-available-label": "可用磁碟空間",
"storage-usage-description": "顯示磁碟空間的使用量",
"storage-usage-label": "磁碟使用量",
"swap-usage-description": "顯示交換記憶體使用量",
@@ -272,6 +276,8 @@
"label-mode-label": "標籤樣式",
"occupied-color-description": "設定已佔用Workspace的背景顏色。",
"occupied-color-label": "已佔用工作區顏色",
"pill-size-description": "調整工作區指示器的大小 (50%-100%)。",
"pill-size-label": "膠囊大小",
"reverse-scrolling-description": "捲動時反轉工作區切換方向。",
"reverse-scrolling-label": "反轉捲動",
"show-applications-description": "顯示各個工作區的程式圖示",
@@ -387,6 +393,7 @@
"disconnecting": "正在斷線...",
"download": "下載",
"duration": "持續時間",
"dysfunctional": "功能失調",
"edit": "編輯",
"enabled": "已啟用",
"events": "事件",
@@ -807,6 +814,7 @@
"download-title": "下載配色",
"method-description": {
"content": "具有高保真色彩提取的 Material Design 方案,可與來源內容的實際色彩緊密匹配。",
"dysfunctional": "與 Faithful 類似,但選擇第二主導的顏色家族作為主要顏色。",
"faithful": "嘗試在生成和諧調色盤的同時,盡可能接近來源顏色。",
"fruit-salad": "Material Design 配色方案,可產生一個俏皮、充滿活力的調色盤,具有多樣且多變的色調。",
"monochrome": "使用單一色調灰階和最少色彩內容的 Material Design 方案。",
@@ -1302,6 +1310,7 @@
"sources-placeholder": "我的超酷儲存庫",
"sources-remove-tooltip": "移除外掛模組來源",
"title": "外掛模組 (Plugins)",
"translations-reloaded": "已重新載入翻譯:{name}",
"uninstall-dialog-description": "你確定想要移除 {plugin}? 這樣會移除所有模組的資料",
"uninstall-dialog-title": "移除外掛模組",
"uninstall-error": "移除失敗: {error}",
@@ -1349,6 +1358,7 @@
"system-monitor": {
"critical-color-label": "危急顏色",
"custom-highlight-colors-title-label": "自訂突出色",
"disk-available-label": "可用磁碟空間",
"disk-section-label": "磁碟使用量",
"enable-dgpu-monitoring-description": "注意: 這個選項會喚醒你的 GPU (NVIDIA/AMD), 在混合顯卡的筆電上可能會嚴重影響電池續航力",
"enable-dgpu-monitoring-label": "啟用監視獨立 GPU",
+2
View File
@@ -270,6 +270,8 @@
"swapCriticalThreshold": 90,
"diskWarningThreshold": 80,
"diskCriticalThreshold": 90,
"diskAvailWarningThreshold": 20,
"diskAvailCriticalThreshold": 10,
"cpuPollingInterval": 1000,
"gpuPollingInterval": 3000,
"enableDgpuMonitoring": false,
+2 -1
View File
@@ -138,7 +138,8 @@
"showSwapUsage": false,
"showNetworkStats": false,
"showDiskUsage": false,
"showDiskAsFree": false,
"showDiskUsageAsPercent": false,
"showDiskAvailable": false,
"diskPath": "/"
},
"Taskbar": {
+28
View File
@@ -0,0 +1,28 @@
import QtQuick
import Quickshell
QtObject {
id: root
function migrate(adapter, logger, rawJson) {
logger.i("Migration47", "Removing network_stats.json cache and updating polling intervals");
// Remove the network_stats.json cache file (no longer used - autoscaling from history now)
const shellName = "noctalia";
const cacheDir = Quickshell.env("NOCTALIA_CACHE_DIR") || (Quickshell.env("XDG_CACHE_HOME") || Quickshell.env("HOME") + "/.cache") + "/" + shellName + "/";
const networkStatsFile = cacheDir + "network_stats.json";
Quickshell.execDetached(["rm", "-f", networkStatsFile]);
// Update polling intervals to 1000ms for smoother graphs (only if currently slower)
if (adapter.systemMonitor.cpuPollingInterval > 1000)
adapter.systemMonitor.cpuPollingInterval = 1000;
if (adapter.systemMonitor.memPollingInterval > 1000)
adapter.systemMonitor.memPollingInterval = 1000;
if (adapter.systemMonitor.networkPollingInterval > 1000)
adapter.systemMonitor.networkPollingInterval = 1000;
logger.d("Migration47", "Removed network_stats.json and adjusted polling intervals");
return true;
}
}
+3 -1
View File
@@ -20,7 +20,8 @@ QtObject {
43: migration43Component,
44: migration44Component,
45: migration45Component,
46: migration46Component
46: migration46Component,
47: migration47Component
})
// Migration components
@@ -38,4 +39,5 @@ QtObject {
property Component migration44Component: Migration44 {}
property Component migration45Component: Migration45 {}
property Component migration46Component: Migration46 {}
property Component migration47Component: Migration47 {}
}
+4 -3
View File
@@ -25,7 +25,7 @@ Singleton {
- Default cache directory: ~/.cache/noctalia
*/
readonly property alias data: adapter // Used to access via Settings.data.xxx.yyy
readonly property int settingsVersion: 46
readonly property int settingsVersion: 47
readonly property bool isDebug: Quickshell.env("NOCTALIA_DEBUG") === "1"
readonly property string shellName: "noctalia"
readonly property string configDir: Quickshell.env("NOCTALIA_CONFIG_DIR") || (Quickshell.env("XDG_CONFIG_HOME") || Quickshell.env("HOME") + "/.config") + "/" + shellName + "/"
@@ -482,8 +482,9 @@ Singleton {
property int swapCriticalThreshold: 90
property int diskWarningThreshold: 80
property int diskCriticalThreshold: 90
property int cpuPollingInterval: 3000
property int tempPollingInterval: 3000
property int diskAvailWarningThreshold: 20
property int diskAvailCriticalThreshold: 10
property int cpuPollingInterval: 1000
property int gpuPollingInterval: 3000
property bool enableDgpuMonitoring: false // Opt-in: reading dGPU sysfs/nvidia-smi wakes it from D3cold, draining battery
property int memPollingInterval: 1000
+79
View File
@@ -293,6 +293,85 @@ PopupWindow {
anchors.rightMargin: Style.marginM
spacing: Style.marginS
// Indicator Container
Item {
visible: (modelData?.buttonType ?? QsMenuButtonType.None) !== QsMenuButtonType.None
implicitWidth: Math.round(Style.baseWidgetSize * 0.5)
implicitHeight: Math.round(Style.baseWidgetSize * 0.5)
Layout.alignment: Qt.AlignVCenter
// Helper properties
readonly property int type: modelData?.buttonType ?? QsMenuButtonType.None
readonly property bool isRadio: type === QsMenuButtonType.RadioButton
readonly property bool isChecked: modelData?.checkState === Qt.Checked || (modelData?.checked ?? false)
// Color Logic
readonly property color activeColor: mouseArea.containsMouse ? Color.mOnHover : Color.mPrimary
readonly property color checkMarkColor: mouseArea.containsMouse ? Color.mHover : Color.mOnPrimary
readonly property color borderColor: isChecked ? activeColor : (mouseArea.containsMouse ? Color.mOnHover : Color.mOnSurface)
// Checkbox Visuals
Rectangle {
visible: !parent.isRadio
anchors.centerIn: parent
width: Math.round(Style.baseWidgetSize * 0.5)
height: Math.round(Style.baseWidgetSize * 0.5)
radius: Style.iRadiusXS
color: "transparent" // Transparent to match RadioButton style
border.color: parent.borderColor
border.width: Style.borderM
Behavior on border.color {
ColorAnimation {
duration: Style.animationFast
}
}
NIcon {
visible: parent.parent.isChecked
anchors.centerIn: parent
anchors.horizontalCenterOffset: -1
icon: "check"
color: parent.parent.activeColor
pointSize: Math.max(Style.fontSizeXXS, parent.width * 0.6)
}
}
// RadioButton Visuals
Rectangle {
visible: parent.isRadio
anchors.centerIn: parent
width: Style.toOdd(Style.baseWidgetSize * 0.5)
height: Style.toOdd(Style.baseWidgetSize * 0.5)
radius: width / 2
color: "transparent"
border.color: parent.borderColor
border.width: Style.borderM // Slightly thicker for radio look
Behavior on border.color {
ColorAnimation {
duration: Style.animationFast
}
}
Rectangle {
visible: parent.parent.isChecked
anchors.centerIn: parent
width: Style.toOdd(parent.width * 0.5)
height: Style.toOdd(parent.height * 0.5)
radius: width / 2
color: parent.parent.activeColor
Behavior on color {
ColorAnimation {
duration: Style.animationFast
}
}
}
}
}
NText {
id: text
Layout.fillWidth: true
+2 -2
View File
@@ -117,7 +117,7 @@ Item {
if (action === "toggle-mute") {
AudioService.setInputMuted(!AudioService.inputMuted);
} else if (action === "custom-command") {
Quickshell.execDetached(["sh", "-lc", middleClickCommand]);
Quickshell.execDetached(["sh", "-c", middleClickCommand]);
} else if (action === "widget-settings") {
BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings);
}
@@ -167,7 +167,7 @@ Item {
PanelService.showContextMenu(contextMenu, pill, screen);
}
onMiddleClicked: {
Quickshell.execDetached(["sh", "-lc", middleClickCommand]);
Quickshell.execDetached(["sh", "-c", middleClickCommand]);
}
}
}
+14 -19
View File
@@ -51,7 +51,8 @@ Item {
readonly property bool showSwapUsage: (widgetSettings.showSwapUsage !== undefined) ? widgetSettings.showSwapUsage : widgetMetadata.showSwapUsage
readonly property bool showNetworkStats: (widgetSettings.showNetworkStats !== undefined) ? widgetSettings.showNetworkStats : widgetMetadata.showNetworkStats
readonly property bool showDiskUsage: (widgetSettings.showDiskUsage !== undefined) ? widgetSettings.showDiskUsage : widgetMetadata.showDiskUsage
readonly property bool showDiskAsFree: (widgetSettings.showDiskAsFree !== undefined) ? widgetSettings.showDiskAsFree : widgetMetadata.showDiskAsFree
readonly property bool showDiskUsageAsPercent: (widgetSettings.showDiskUsageAsPercent !== undefined) ? widgetSettings.showDiskUsageAsPercent : widgetMetadata.showDiskUsageAsPercent
readonly property bool showDiskAvailable: (widgetSettings.showDiskAvailable !== undefined) ? widgetSettings.showDiskAvailable : widgetMetadata.showDiskAvailable
readonly property bool showLoadAverage: (widgetSettings.showLoadAverage !== undefined) ? widgetSettings.showLoadAverage : widgetMetadata.showLoadAverage
readonly property string diskPath: (widgetSettings.diskPath !== undefined) ? widgetSettings.diskPath : widgetMetadata.diskPath
readonly property string fontFamily: useMonospaceFont ? Settings.data.ui.fontFixed : Settings.data.ui.fontDefault
@@ -94,11 +95,11 @@ Item {
}
// Memory
rows.push([I18n.tr("common.memory"), `${Math.round(SystemStatService.memPercent)}% (${SystemStatService.formatMemoryGb(SystemStatService.memGb).replace(/[^0-9.]/g, "") + " GB"})`]);
rows.push([I18n.tr("common.memory"), `${Math.round(SystemStatService.memPercent)}% (${SystemStatService.formatGigabytes(SystemStatService.memGb).replace(/[^0-9.]/g, "") + " GB"})`]);
// Swap (if available)
if (SystemStatService.swapTotalGb > 0) {
rows.push([I18n.tr("bar.system-monitor.swap-usage-label"), `${Math.round(SystemStatService.swapPercent)}% (${SystemStatService.formatMemoryGb(SystemStatService.swapGb).replace(/[^0-9.]/g, "") + " GB"})`]);
rows.push([I18n.tr("bar.system-monitor.swap-usage-label"), `${Math.round(SystemStatService.swapPercent)}% (${SystemStatService.formatGigabytes(SystemStatService.swapGb).replace(/[^0-9.]/g, "") + " GB"})`]);
}
// Network
@@ -110,11 +111,9 @@ Item {
if (diskPercent !== undefined) {
const usedGb = SystemStatService.diskUsedGb[diskPath] || 0;
const sizeGb = SystemStatService.diskSizeGb[diskPath] || 0;
const availGb = SystemStatService.diskAvailGb[diskPath] || 0;
rows.push([I18n.tr("system-monitor.disk"), `${usedGb.toFixed(1)}GB/${sizeGb.toFixed(1)}GB (${diskPercent}%)`]);
// TODO i18n
rows.push(["Available", `${availGb.toFixed(1)}G`]);
const availGb = SystemStatService.diskAvailableGb[diskPath] || 0;
rows.push([I18n.tr("system-monitor.disk"), `${diskPercent}% (${usedGb.toFixed(1)} / ${sizeGb.toFixed(1)} GB)`]);
rows.push([I18n.tr("common.available"), `${availGb.toFixed(1)} GB`]);
}
return rows;
@@ -597,7 +596,7 @@ Item {
// Text mode
NText {
visible: !compactMode
text: showMemoryAsPercent ? `${Math.round(SystemStatService.memPercent)}%` : SystemStatService.formatMemoryGb(SystemStatService.memGb)
text: showMemoryAsPercent ? `${Math.round(SystemStatService.memPercent)}%` : SystemStatService.formatGigabytes(SystemStatService.memGb)
family: fontFamily
pointSize: barFontSize
applyUiScale: false
@@ -863,14 +862,10 @@ Item {
// Text mode
NText {
visible: !compactMode
text: {
if (showDiskAsFree && !isVertical) {
let avail = SystemStatService.diskAvailGb[diskPath] || 0;
return avail.toFixed(1) + "G";
} else {
return SystemStatService.diskPercents[diskPath] ? `${SystemStatService.diskPercents[diskPath]}%` : "n/a";
}
}
text: SystemStatService.formatDiskDisplay(diskPath, {
percent: showDiskUsageAsPercent,
available: showDiskAvailable
})
family: fontFamily
pointSize: barFontSize
applyUiScale: false
@@ -892,8 +887,8 @@ Item {
Layout.column: 1
onLoaded: {
item.ratio = Qt.binding(() => (SystemStatService.diskPercents[diskPath] ?? 0) / 100);
item.statColor = Qt.binding(() => SystemStatService.getDiskColor(diskPath));
item.ratio = Qt.binding(() => (showDiskAvailable ? SystemStatService.diskAvailPercents[diskPath] : SystemStatService.diskPercents[diskPath] ?? 0) / 100);
item.statColor = Qt.binding(() => SystemStatService.getDiskColor(diskPath, showDiskAvailable));
}
}
}
+2 -2
View File
@@ -99,7 +99,7 @@ Item {
if (action === "toggle-mute") {
AudioService.setOutputMuted(!AudioService.muted);
} else if (action === "custom-command") {
Quickshell.execDetached(["sh", "-lc", middleClickCommand]);
Quickshell.execDetached(["sh", "-c", middleClickCommand]);
} else if (action === "widget-settings") {
BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings);
}
@@ -149,7 +149,7 @@ Item {
PanelService.showContextMenu(contextMenu, pill, screen);
}
onMiddleClicked: {
Quickshell.execDetached(["sh", "-lc", middleClickCommand]);
Quickshell.execDetached(["sh", "-c", middleClickCommand]);
}
}
}
+6 -1
View File
@@ -41,7 +41,6 @@ Item {
readonly property real barHeight: Style.getBarHeightForScreen(screenName)
readonly property real capsuleHeight: Style.getCapsuleHeightForScreen(screenName)
readonly property real barFontSize: Style.getBarFontSizeForScreen(screenName)
readonly property real baseDimensionRatio: 0.65
readonly property string labelMode: (widgetSettings.labelMode !== undefined) ? widgetSettings.labelMode : widgetMetadata.labelMode
readonly property bool hasLabel: (labelMode !== "none")
@@ -49,6 +48,12 @@ Item {
readonly property bool followFocusedScreen: (widgetSettings.followFocusedScreen !== undefined) ? widgetSettings.followFocusedScreen : widgetMetadata.followFocusedScreen
readonly property int characterCount: isVertical ? 2 : ((widgetSettings.characterCount !== undefined) ? widgetSettings.characterCount : widgetMetadata.characterCount)
// Pill size setting (0.5-1.0 range)
readonly property real pillSize: (widgetSettings.pillSize !== undefined) ? widgetSettings.pillSize : widgetMetadata.pillSize
// When no label the pills are smaller
readonly property real baseDimensionRatio: pillSize
// Grouped mode (show applications) settings
readonly property bool showApplications: (widgetSettings.showApplications !== undefined) ? widgetSettings.showApplications : widgetMetadata.showApplications
readonly property bool showLabelsOnlyWhenOccupied: (widgetSettings.showLabelsOnlyWhenOccupied !== undefined) ? widgetSettings.showLabelsOnlyWhenOccupied : widgetMetadata.showLabelsOnlyWhenOccupied
+25 -13
View File
@@ -649,8 +649,8 @@ Loader {
Flickable {
id: dock
// Use parent dimensions more directly to avoid clipping
width: isVertical ? parent.width - Style.marginS * 2 : Math.min(dockLayout.implicitWidth, parent.width - Style.marginXL)
height: !isVertical ? parent.height - Style.marginS * 2 : Math.min(dockLayout.implicitHeight, parent.height - Style.marginXL)
width: isVertical ? parent.width : Math.min(dockLayout.implicitWidth, parent.width - Style.marginXL)
height: !isVertical ? parent.height : Math.min(dockLayout.implicitHeight, parent.height - Style.marginXL)
contentWidth: dockLayout.implicitWidth
contentHeight: dockLayout.implicitHeight
anchors.centerIn: parent
@@ -662,10 +662,8 @@ Loader {
interactive: isVertical ? contentHeight > height : contentWidth > width
// Centering margins
leftMargin: contentWidth < width ? (width - contentWidth) / 2 : 0
rightMargin: leftMargin
topMargin: contentHeight < height ? (height - contentHeight) / 2 : 0
bottomMargin: topMargin
contentX: isVertical && contentWidth < width ? (contentWidth - width) / 2 : 0
contentY: !isVertical && contentHeight < height ? (contentHeight - height) / 2 : 0
WheelHandler {
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
@@ -714,8 +712,9 @@ Loader {
delegate: Item {
id: appButton
Layout.preferredWidth: iconSize
Layout.preferredHeight: iconSize
readonly property real indicatorMargin: Math.max(3, Math.round(iconSize * 0.18))
Layout.preferredWidth: isVertical ? iconSize + indicatorMargin * 2 : iconSize
Layout.preferredHeight: isVertical ? iconSize : iconSize + indicatorMargin * 2
Layout.alignment: Qt.AlignCenter
property bool isActive: modelData.toplevel && ToplevelManager.activeToplevel && ToplevelManager.activeToplevel === modelData.toplevel
@@ -1072,15 +1071,28 @@ Loader {
}
}
// Active indicator - always below the icon
// Active indicator - positioned at the edge of the delegate area
Rectangle {
visible: Settings.data.dock.inactiveIndicators ? isRunning : isActive
width: iconSize * 0.2
height: iconSize * 0.1
width: isVertical ? indicatorMargin * 0.6 : iconSize * 0.2
height: isVertical ? iconSize * 0.2 : indicatorMargin * 0.6
color: Color.mPrimary
radius: Style.radiusXS
anchors.top: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
// Anchor to the edge facing the screen center
anchors.bottom: !isVertical && dockPosition === "bottom" ? parent.bottom : undefined
anchors.top: !isVertical && dockPosition === "top" ? parent.top : undefined
anchors.left: isVertical && dockPosition === "left" ? parent.left : undefined
anchors.right: isVertical && dockPosition === "right" ? parent.right : undefined
anchors.horizontalCenter: isVertical ? undefined : parent.horizontalCenter
anchors.verticalCenter: isVertical ? parent.verticalCenter : undefined
// Offset slightly from the edge
anchors.bottomMargin: !isVertical && dockPosition === "bottom" ? 2 : 0
anchors.topMargin: !isVertical && dockPosition === "top" ? 2 : 0
anchors.leftMargin: isVertical && dockPosition === "left" ? 2 : 0
anchors.rightMargin: isVertical && dockPosition === "right" ? 2 : 0
}
}
}
+5 -2
View File
@@ -102,12 +102,15 @@ Variants {
}
// BarExclusionZone - created after MainScreen has fully loaded
// Disabled when bar is hidden or not configured for this screen
// Note: Exclusion zone should NOT be affected by hideOnOverview setting.
// When bar is hidden during overview, the exclusion zone should remain to prevent
// windows from moving into the bar area. Auto-hide is handled by the component
// itself via ExclusionMode.Ignore/Auto.
Repeater {
model: Settings.data.bar.barType === "framed" ? ["top", "bottom", "left", "right"] : [Settings.getBarPositionForScreen(windowItem.modelData?.name)]
delegate: Loader {
active: {
if (!windowItem.windowLoaded || !windowItem.shouldBeActive || !BarService.effectivelyVisible)
if (!windowItem.windowLoaded || !windowItem.shouldBeActive)
return false;
// Check if bar is configured for this screen
@@ -15,15 +15,6 @@ SmartPanel {
preferredWidth: Math.round(440 * Style.uiScaleRatio)
preferredHeight: Math.round(420 * Style.uiScaleRatio)
onOpened: {
// Refresh DDC brightness from monitors (one-time on panel open)
BrightnessService.monitors.forEach(m => {
if (m.isDdc) {
m.refreshBrightnessFromSystem();
}
});
}
panelContent: Item {
id: panelContent
property real contentPreferredHeight: mainColumn.implicitHeight + Style.marginL * 2
+18
View File
@@ -1585,6 +1585,24 @@ SmartPanel {
font.weight: Style.fontWeightBold
color: modelData.displayString ? Color.mOnSurface : Color.mOnPrimary
}
// Badge icon overlay (generic indicator for any provider)
Rectangle {
visible: !!modelData.badgeIcon
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.margins: 2
width: height
height: Style.fontSizeM + Style.marginXS
color: Color.mSurfaceVariant
radius: Style.radiusXXS
NIcon {
anchors.centerIn: parent
icon: modelData.badgeIcon || ""
pointSize: Style.fontSizeS
color: Color.mOnSurfaceVariant
}
}
}
// Text content (hidden when hideLabel is true)
@@ -41,7 +41,7 @@ Item {
"onActivate": function () {
launcher.closeImmediately();
Qt.callLater(() => {
Quickshell.execDetached(["sh", "-lc", expression]);
Quickshell.execDetached(["sh", "-c", expression]);
});
}
}
@@ -150,7 +150,7 @@ Item {
// If custom command is defined, execute it
if (command && command.trim() !== "") {
Logger.i("SessionProvider", "Executing custom command for action:", action, "Command:", command);
Quickshell.execDetached(["sh", "-lc", command]);
Quickshell.execDetached(["sh", "-c", command]);
return;
}
+1 -1
View File
@@ -222,7 +222,7 @@ SmartPanel {
// If custom command is defined, execute it
if (option && option.command && option.command.trim() !== "") {
Logger.i("SessionMenu", "Executing custom command for action:", action, "Command:", option.command);
Quickshell.execDetached(["sh", "-lc", option.command]);
Quickshell.execDetached(["sh", "-c", option.command]);
cancelTimer();
root.close();
return;
@@ -14,6 +14,11 @@ NBox {
required property var screen
readonly property string screenName: screen?.name || ""
// Determine if the bar on a per screen basis is vertical
readonly property bool barIsVertical: {
var pos = Settings.getBarPositionForScreen(screenName);
return pos === "left" || pos === "right";
}
color: Color.mSurfaceVariant
Layout.fillWidth: true
@@ -153,7 +158,7 @@ NBox {
// Left Section
NSectionEditor {
sectionName: I18n.tr("positions.left")
sectionName: root.barIsVertical ? I18n.tr("positions.top") : I18n.tr("positions.left")
sectionId: "left"
screen: root.screen
settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/Bar/BarWidgetSettingsDialog.qml")
@@ -187,7 +192,7 @@ NBox {
// Right Section
NSectionEditor {
sectionName: I18n.tr("positions.right")
sectionName: root.barIsVertical ? I18n.tr("positions.bottom") : I18n.tr("positions.right")
sectionId: "right"
screen: root.screen
settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/Bar/BarWidgetSettingsDialog.qml")
@@ -29,7 +29,8 @@ ColumnLayout {
property bool valueShowSwapUsage: widgetData.showSwapUsage !== undefined ? widgetData.showSwapUsage : widgetMetadata.showSwapUsage
property bool valueShowNetworkStats: widgetData.showNetworkStats !== undefined ? widgetData.showNetworkStats : widgetMetadata.showNetworkStats
property bool valueShowDiskUsage: widgetData.showDiskUsage !== undefined ? widgetData.showDiskUsage : widgetMetadata.showDiskUsage
property bool valueShowDiskAsFree: widgetData.showDiskAsFree !== undefined ? widgetData.showDiskAsFree : widgetMetadata.showDiskAsFree
property bool valueShowDiskUsageAsPercent: widgetData.showDiskUsageAsPercent !== undefined ? widgetData.showDiskUsageAsPercent : widgetMetadata.showDiskUsageAsPercent
property bool valueShowDiskAvailable: widgetData.showDiskAvailable !== undefined ? widgetData.showDiskAvailable : widgetMetadata.showDiskAvailable
property string valueDiskPath: widgetData.diskPath !== undefined ? widgetData.diskPath : widgetMetadata.diskPath
function saveSettings() {
@@ -47,7 +48,8 @@ ColumnLayout {
settings.showSwapUsage = valueShowSwapUsage;
settings.showNetworkStats = valueShowNetworkStats;
settings.showDiskUsage = valueShowDiskUsage;
settings.showDiskAsFree = valueShowDiskAsFree;
settings.showDiskUsageAsPercent = valueShowDiskUsageAsPercent;
settings.showDiskAvailable = valueShowDiskAvailable;
settings.diskPath = valueDiskPath;
return settings;
@@ -211,16 +213,27 @@ ColumnLayout {
}
NToggle {
id: showDiskAsFree
id: showDiskUsageAsPercent
Layout.fillWidth: true
label: "Show Free Space" // TODO: use I18n.tr
description: "Display available space (GB) instead of percentage" // TODO: use I18n.tr
checked: valueShowDiskAsFree
label: I18n.tr("bar.system-monitor.storage-as-percentage-label")
description: I18n.tr("bar.system-monitor.storage-as-percentage-description")
checked: valueShowDiskUsageAsPercent
onToggled: checked => {
valueShowDiskAsFree = checked;
valueShowDiskUsageAsPercent = checked;
settingsChanged(saveSettings());
}
}
NToggle {
id: showDiskAvailable
Layout.fillWidth: true
label: I18n.tr("bar.system-monitor.storage-available-label")
description: I18n.tr("bar.system-monitor.storage-available-description")
checked: valueShowDiskAvailable
onToggled: checked => {
valueShowDiskAvailable = checked;
settingsChanged(saveSettings());
}
visible: valueShowDiskUsage
}
NComboBox {
@@ -32,6 +32,7 @@ ColumnLayout {
property string valueOccupiedColor: widgetData.occupiedColor !== undefined ? widgetData.occupiedColor : widgetMetadata.occupiedColor
property string valueEmptyColor: widgetData.emptyColor !== undefined ? widgetData.emptyColor : widgetMetadata.emptyColor
property bool valueShowBadge: widgetData.showBadge !== undefined ? widgetData.showBadge : widgetMetadata.showBadge
property real valuePillSize: widgetData.pillSize !== undefined ? widgetData.pillSize : widgetMetadata.pillSize
function saveSettings() {
var settings = Object.assign({}, widgetData || {});
@@ -51,6 +52,7 @@ ColumnLayout {
settings.occupiedColor = valueOccupiedColor;
settings.emptyColor = valueEmptyColor;
settings.showBadge = valueShowBadge;
settings.pillSize = valuePillSize;
return settings;
}
@@ -97,6 +99,21 @@ ColumnLayout {
visible: valueLabelMode === "name"
}
NValueSlider {
label: I18n.tr("bar.workspace.pill-size-label")
description: I18n.tr("bar.workspace.pill-size-description")
from: 0.4
to: 1.0
stepSize: 0.01
value: valuePillSize
onMoved: value => {
valuePillSize = value;
settingsChanged(saveSettings());
}
text: Math.round(valuePillSize * 100) + "%"
visible: !valueShowApplications
}
NToggle {
label: I18n.tr("bar.workspace.hide-unoccupied-label")
description: I18n.tr("bar.workspace.hide-unoccupied-description")
@@ -140,7 +140,7 @@ ColumnLayout {
info += "GPU: " + gpu.result.map(g => g.name || "Unknown").join(", ") + "\n";
}
if (mem?.result) {
info += "Memory: " + SystemStatService.formatMemoryGb(mem.result.total / root.giga) + "\n";
info += "Memory: " + SystemStatService.formatGigabytes(mem.result.total / root.giga) + "\n";
}
if (wm?.result) {
info += "WM: " + (wm.result.prettyName || wm.result.processName || "N/A") + "\n";
@@ -476,17 +476,22 @@ ColumnLayout {
}
}
// Action buttons row
RowLayout {
GridLayout {
id: actionsGrid
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: Style.marginM
Layout.bottomMargin: Style.marginM
spacing: Style.marginM
rowSpacing: Style.marginM
columnSpacing: Style.marginM
columns: (changelogBtn.implicitWidth + copyBtn.implicitWidth + supportBtn.implicitWidth + 2 * columnSpacing) < root.width ? 3 : 1
NButton {
id: changelogBtn
icon: "sparkles"
text: I18n.tr("panels.about.changelog")
outlined: true
Layout.alignment: Qt.AlignHCenter
onClicked: {
var screen = PanelService.openedPanel?.screen || Quickshell.screens[0];
UpdateService.viewChangelog(screen);
@@ -494,16 +499,20 @@ ColumnLayout {
}
NButton {
id: copyBtn
icon: "copy"
text: I18n.tr("panels.about.copy-info")
outlined: true
Layout.alignment: Qt.AlignHCenter
onClicked: root.copyInfoToClipboard()
}
NButton {
id: supportBtn
icon: "heart"
text: I18n.tr("panels.about.support")
outlined: true
Layout.alignment: Qt.AlignHCenter
onClicked: {
Quickshell.execDetached(["xdg-open", "https://buymeacoffee.com/noctalia"]);
ToastService.showNotice(I18n.tr("panels.about.support"), I18n.tr("toast.kofi-opened"));
@@ -688,8 +697,8 @@ ColumnLayout {
const mem = root.getModule("Memory");
if (!mem?.result)
return "N/A";
const used = SystemStatService.formatMemoryGb(mem.result.used / root.giga);
const total = SystemStatService.formatMemoryGb(mem.result.total / root.giga);
const used = SystemStatService.formatGigabytes(mem.result.used / root.giga);
const total = SystemStatService.formatGigabytes(mem.result.total / root.giga);
return used + " / " + total;
}
color: Color.mOnSurface
@@ -712,8 +721,8 @@ ColumnLayout {
const rootDisk = disk.result.find(d => d.mountpoint === "/");
if (!rootDisk?.bytes)
return "N/A";
const used = SystemStatService.formatMemoryGb(rootDisk.bytes.used / root.giga);
const total = SystemStatService.formatMemoryGb(rootDisk.bytes.total / root.giga);
const used = SystemStatService.formatGigabytes(rootDisk.bytes.used / root.giga);
const total = SystemStatService.formatGigabytes(rootDisk.bytes.total / root.giga);
return used + " / " + total + " (" + rootDisk.filesystem + ")";
}
color: Color.mOnSurface
@@ -19,6 +19,8 @@ ColumnLayout {
property var moveWidgetBetweenSections
signal openPluginSettings(var manifest)
// determine if the bar is vertical
readonly property bool barIsVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
function getSectionIcons() {
return {
@@ -36,7 +38,7 @@ ColumnLayout {
// Left Section
NSectionEditor {
sectionName: I18n.tr("positions.left")
sectionName: root.barIsVertical ? I18n.tr("positions.top") : I18n.tr("positions.left")
sectionId: "left"
settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/Bar/BarWidgetSettingsDialog.qml")
widgetRegistry: BarWidgetRegistry
@@ -70,7 +72,7 @@ ColumnLayout {
// Right Section
NSectionEditor {
sectionName: I18n.tr("positions.right")
sectionName: root.barIsVertical ? I18n.tr("positions.bottom") : I18n.tr("positions.right")
sectionId: "right"
settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/Bar/BarWidgetSettingsDialog.qml")
widgetRegistry: BarWidgetRegistry
@@ -56,11 +56,19 @@ ColumnLayout {
if (app.id === "discord") {
include = TemplateProcessor.isDiscordClientEnabled(client.name);
} else if (app.id === "code") {
include = TemplateProcessor.isCodeClientEnabled(client.name);
// For code clients, resolve all theme paths dynamically (version-independent)
if (TemplateProcessor.isCodeClientEnabled(client.name)) {
var resolvedPaths = TemplateRegistry.resolvedCodeClientPaths(client.name);
for (var p = 0; p < resolvedPaths.length; p++) {
validClients.push(resolvedPaths[p]);
}
}
continue;
}
if (include) {
validClients.push(client.path);
if (client.path)
validClients.push(client.path);
}
}
@@ -243,5 +243,38 @@ ColumnLayout {
suffix: "%"
onValueChanged: Settings.data.systemMonitor.diskCriticalThreshold = value
}
// Disk Available
NText {
text: I18n.tr("panels.system-monitor.disk-available-label")
pointSize: Style.fontSizeM
}
NSpinBox {
Layout.alignment: Qt.AlignHCenter
from: 0
to: 100
stepSize: 5
value: Settings.data.systemMonitor.diskAvailWarningThreshold
defaultValue: Settings.getDefaultValue("systemMonitor.diskAvailWarningThreshold")
suffix: "%"
onValueChanged: {
Settings.data.systemMonitor.diskAvailWarningThreshold = value;
if (Settings.data.systemMonitor.diskAvailCriticalThreshold > value) {
Settings.data.systemMonitor.diskAvailCriticalThreshold = value;
}
}
}
NSpinBox {
Layout.alignment: Qt.AlignHCenter
from: 0
to: 20
stepSize: 5
value: Settings.data.systemMonitor.diskAvailCriticalThreshold
defaultValue: Settings.getDefaultValue("systemMonitor.diskAvailCriticalThreshold")
suffix: "%"
onValueChanged: Settings.data.systemMonitor.diskAvailCriticalThreshold = value
}
}
}
+37 -32
View File
@@ -138,13 +138,15 @@ SmartPanel {
values: SystemStatService.cpuHistory
values2: SystemStatService.cpuTempHistory
minValue: 0
maxValue: Math.max(SystemStatService.cpuHistoryMax, 1)
maxValue: 100
minValue2: Math.max(SystemStatService.cpuTempHistoryMin - 5, 0)
maxValue2: Math.max(SystemStatService.cpuTempHistoryMax + 5, 1)
autoScale: false
color: Color.mPrimary
color2: Color.mError
fill: true
fillOpacity: 0.15
updateInterval: Settings.data.systemMonitor.cpuPollingInterval
}
}
}
@@ -170,7 +172,7 @@ SmartPanel {
}
NText {
text: `${Math.round(SystemStatService.memPercent)}% ${SystemStatService.formatMemoryGb(SystemStatService.memGb).replace(/[^0-9.]/g, "")} GB`
text: `${Math.round(SystemStatService.memPercent)}% ${SystemStatService.formatGigabytes(SystemStatService.memGb).replace(/[^0-9.]/g, "")} GB`
pointSize: Style.fontSizeXS
color: Color.mPrimary
}
@@ -205,10 +207,12 @@ SmartPanel {
Layout.fillHeight: true
values: SystemStatService.memHistory
minValue: 0
maxValue: Math.max(SystemStatService.memHistoryMax, 1)
maxValue: 100
autoScale: false
color: Color.mPrimary
fill: true
fillOpacity: 0.15
updateInterval: Settings.data.systemMonitor.memPollingInterval
}
}
}
@@ -276,6 +280,7 @@ SmartPanel {
color2: Color.mError
fill: true
fillOpacity: 0.15
updateInterval: Settings.data.systemMonitor.networkPollingInterval
}
}
}
@@ -291,7 +296,7 @@ SmartPanel {
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: Style.marginM
spacing: Style.marginS
spacing: Style.marginXS
// Load Average
RowLayout {
@@ -320,6 +325,33 @@ SmartPanel {
}
}
// GPU Temperature (only if available)
RowLayout {
Layout.fillWidth: true
spacing: Style.marginS
visible: SystemStatService.gpuAvailable
NIcon {
icon: "gpu-temperature"
pointSize: Style.fontSizeM
color: Color.mPrimary
}
NText {
text: I18n.tr("system-monitor.gpu-temp") + ":"
pointSize: Style.fontSizeXS
color: Color.mOnSurfaceVariant
}
NText {
text: `${Math.round(SystemStatService.gpuTemp)}°C`
pointSize: Style.fontSizeXS
color: Color.mOnSurface
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
}
}
// Disk usage
RowLayout {
Layout.fillWidth: true
@@ -352,33 +384,6 @@ SmartPanel {
}
}
// GPU Temperature (only if available)
RowLayout {
Layout.fillWidth: true
spacing: Style.marginS
visible: SystemStatService.gpuAvailable
NIcon {
icon: "gpu-temperature"
pointSize: Style.fontSizeM
color: Color.mPrimary
}
NText {
text: I18n.tr("system-monitor.gpu-temp") + ":"
pointSize: Style.fontSizeXS
color: Color.mOnSurfaceVariant
}
NText {
text: `${Math.round(SystemStatService.gpuTemp)}°C`
pointSize: Style.fontSizeXS
color: Color.mOnSurface
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
}
}
// Swap details (only visible if swap is enabled)
RowLayout {
Layout.fillWidth: true
@@ -398,7 +403,7 @@ SmartPanel {
}
NText {
text: `${SystemStatService.formatMemoryGb(SystemStatService.swapGb).replace(/[^0-9.]/g, "")} / ${SystemStatService.formatMemoryGb(SystemStatService.swapTotalGb).replace(/[^0-9.]/g, "")} GB`
text: `${SystemStatService.formatGigabytes(SystemStatService.swapGb).replace(/[^0-9.]/g, "")} / ${SystemStatService.formatGigabytes(SystemStatService.swapTotalGb).replace(/[^0-9.]/g, "")} GB`
pointSize: Style.fontSizeXS
color: Color.mOnSurface
Layout.fillWidth: true
+4 -2
View File
@@ -34,7 +34,9 @@ Item {
onHoverCountChanged: {
if (hoverCount > 0) {
resumeTimer.stop();
progressAnimation.pause();
if (progressAnimation.running && !progressAnimation.paused) {
progressAnimation.pause();
}
} else {
resumeTimer.start();
}
@@ -45,7 +47,7 @@ Item {
interval: 50
repeat: false
onTriggered: {
if (hoverCount === 0) {
if (hoverCount === 0 && progressAnimation.paused) {
progressAnimation.resume();
}
}
+1 -1
View File
@@ -17,7 +17,7 @@ PopupWindow {
property int gridPaddingVertical: Style.marginXS // extra vertical padding for grid mode
property int delay: 0
property int hideDelay: 0
property int maxWidth: 320
property int maxWidth: 340
property int animationDuration: Style.animationFast
property real animationScale: 0.85
+27 -14
View File
@@ -12,6 +12,7 @@ Scheme types:
- M3 schemes (tonal-spot, fruit-salad, rainbow, content): Compared with matugen
- vibrant: Prioritizes the most saturated colors regardless of area
- faithful: Prioritizes dominant colors by area coverage
- dysfunctional: Like faithful but picks the 2nd most dominant color family
- muted: Preserves hue but caps saturation low (for monochrome wallpapers)
"""
@@ -115,20 +116,21 @@ def run_matugen(image_path: Path, scheme: str) -> dict | None:
def analyze_vibrant_faithful_muted(image_path: Path) -> None:
"""Analyze vibrant, faithful, and muted mode outputs."""
"""Analyze vibrant, faithful, dysfunctional, and muted mode outputs."""
print("\n" + "=" * 78)
print("VIBRANT vs FAITHFUL vs MUTED COMPARISON")
print("VIBRANT vs FAITHFUL vs DYSFUNCTIONAL vs MUTED COMPARISON")
print("=" * 78)
print()
print("Vibrant: Prioritizes the most saturated colors regardless of area")
print("Faithful: Prioritizes dominant colors by area coverage")
print("Muted: Preserves hue but caps saturation low (monochrome wallpapers)")
print("Vibrant: Prioritizes the most saturated colors regardless of area")
print("Faithful: Prioritizes dominant colors by area coverage")
print("Dysfunctional: Like faithful but picks 2nd most dominant color family")
print("Muted: Preserves hue but caps saturation low (monochrome wallpapers)")
print()
print("-" * 78)
print(f"{'Mode':<12} {'Color':<12} {'Hex':<10} {'Hue':>8} {'Chroma':>8} {'Name':<10}")
print(f"{'Mode':<14} {'Color':<12} {'Hex':<10} {'Hue':>8} {'Chroma':>8} {'Name':<10}")
print("-" * 78)
for scheme in ["vibrant", "faithful", "muted"]:
for scheme in ["vibrant", "faithful", "dysfunctional", "muted"]:
colors = run_our_processor(image_path, scheme)
if not colors:
print(f"{scheme}: Failed to get colors")
@@ -142,40 +144,51 @@ def analyze_vibrant_faithful_muted(image_path: Path) -> None:
try:
hct = get_hct(hex_color)
name = hue_to_name(hct.hue)
print(f"{scheme:<12} {key:<12} {hex_color:<10} {hct.hue:>7.1f}° {hct.chroma:>7.1f} {name:<10}")
print(f"{scheme:<14} {key:<12} {hex_color:<10} {hct.hue:>7.1f}° {hct.chroma:>7.1f} {name:<10}")
except Exception as e:
print(f"{scheme:<12} {key:<12} Error: {e}")
print(f"{scheme:<14} {key:<12} Error: {e}")
print("-" * 78)
# Summary comparison
vibrant = run_our_processor(image_path, "vibrant")
faithful = run_our_processor(image_path, "faithful")
dysfunctional = run_our_processor(image_path, "dysfunctional")
muted = run_our_processor(image_path, "muted")
if vibrant and faithful and muted:
if vibrant and faithful and dysfunctional and muted:
print()
print("Summary:")
v_hct = get_hct(vibrant.get("primary", "#000000"))
f_hct = get_hct(faithful.get("primary", "#000000"))
d_hct = get_hct(dysfunctional.get("primary", "#000000"))
m_hct = get_hct(muted.get("primary", "#000000"))
v_name = hue_to_name(v_hct.hue)
f_name = hue_to_name(f_hct.hue)
d_name = hue_to_name(d_hct.hue)
m_name = hue_to_name(m_hct.hue)
vf_diff = hue_diff(v_hct.hue, f_hct.hue)
fd_diff = hue_diff(f_hct.hue, d_hct.hue)
print(f" Vibrant primary: {vibrant.get('primary')} ({v_name}, hue {v_hct.hue:.0f}°, chroma {v_hct.chroma:.1f})")
print(f" Faithful primary: {faithful.get('primary')} ({f_name}, hue {f_hct.hue:.0f}°, chroma {f_hct.chroma:.1f})")
print(f" Muted primary: {muted.get('primary')} ({m_name}, hue {m_hct.hue:.0f}°, chroma {m_hct.chroma:.1f})")
print(f" V-F hue diff: {vf_diff:.1f}°")
print(f" Vibrant primary: {vibrant.get('primary')} ({v_name}, hue {v_hct.hue:.0f}°, chroma {v_hct.chroma:.1f})")
print(f" Faithful primary: {faithful.get('primary')} ({f_name}, hue {f_hct.hue:.0f}°, chroma {f_hct.chroma:.1f})")
print(f" Dysfunctional primary:{dysfunctional.get('primary')} ({d_name}, hue {d_hct.hue:.0f}°, chroma {d_hct.chroma:.1f})")
print(f" Muted primary: {muted.get('primary')} ({m_name}, hue {m_hct.hue:.0f}°, chroma {m_hct.chroma:.1f})")
print(f" V-F hue diff: {vf_diff:.1f}°")
print(f" F-D hue diff: {fd_diff:.1f}°")
if vf_diff > 60:
print(f" → Vibrant/Faithful picked DIFFERENT color families ({v_name} vs {f_name})")
else:
print(f" → Vibrant/Faithful picked SIMILAR colors")
if fd_diff > 30:
print(f" → Faithful/Dysfunctional picked DIFFERENT color families ({f_name} vs {d_name})")
else:
print(f" → Faithful/Dysfunctional picked SIMILAR colors (may only have 1 dominant family)")
# Note the muted chroma reduction
if m_hct.chroma < 20:
print(f" → Muted successfully reduced chroma to {m_hct.chroma:.1f}")
@@ -31,11 +31,35 @@ from wayland import protocol as wp
from wayland.client import MakeDisplay, ServerDisconnected, NoXDGRuntimeDir
# Protocol XML paths
WAYLAND_XML = '/usr/share/wayland/wayland.xml'
PROTOCOLS_DIR = os.path.join(VENDOR_DIR, 'wayland', 'protocols')
EXT_WORKSPACE_XML = os.path.join(PROTOCOLS_DIR, 'ext-workspace-v1.xml')
def find_wayland_xml():
"""Find wayland.xml using XDG_DATA_DIRS"""
# Get XDG_DATA_DIRS, falling back to standard paths
xdg_data_dirs = os.environ.get('XDG_DATA_DIRS', '/usr/local/share:/usr/share')
for data_dir in xdg_data_dirs.split(':'):
wayland_xml = os.path.join(data_dir, 'wayland', 'wayland.xml')
if os.path.exists(wayland_xml):
return wayland_xml
# Fallback to common paths if not found in XDG_DATA_DIRS
fallback_paths = [
'/usr/share/wayland/wayland.xml',
'/usr/local/share/wayland/wayland.xml',
]
for path in fallback_paths:
if os.path.exists(path):
return path
return None
WAYLAND_XML = find_wayland_xml()
class WorkspaceState:
"""Tracks the current state of all workspaces"""
@@ -349,10 +373,10 @@ def main():
args = parser.parse_args()
# Check for required protocol files
if not os.path.exists(WAYLAND_XML):
if not WAYLAND_XML or not os.path.exists(WAYLAND_XML):
print(json.dumps({
'type': 'error',
'message': f'Wayland protocol file not found: {WAYLAND_XML}'
'message': 'Wayland protocol file not found. Check XDG_DATA_DIRS or install wayland-devel.'
}), flush=True)
return 1
+6 -3
View File
@@ -250,19 +250,22 @@ def _read_image_imagemagick(path: Path) -> list[RGB]:
# Resize to 112x112 to match matugen's color extraction
# Use -filter Box for consistent results across ImageMagick versions
# Use -depth 8 -colorspace sRGB -strip to reduce variance between HDRI/non-HDRI builds
resize_spec = "112x112!"
try:
# Try 'magick convert' first (ImageMagick 7+), fallback to 'convert' (ImageMagick 6)
# Try 'magick' first (ImageMagick 7+), fallback to 'convert' (ImageMagick 6)
try:
result = subprocess.run(
['magick', 'convert', str(path), '-filter', 'Box', '-resize', resize_spec, '-depth', '8', 'ppm:-'],
['magick', str(path), '-filter', 'Box', '-resize', resize_spec,
'-depth', '8', '-colorspace', 'sRGB', '-strip', 'ppm:-'],
capture_output=True,
check=True
)
except FileNotFoundError:
result = subprocess.run(
['convert', str(path), '-filter', 'Box', '-resize', resize_spec, '-depth', '8', 'ppm:-'],
['convert', str(path), '-filter', 'Box', '-resize', resize_spec,
'-depth', '8', '-colorspace', 'sRGB', '-strip', 'ppm:-'],
capture_output=True,
check=True
)
+132
View File
@@ -262,6 +262,129 @@ def _score_colors_count(
return result_colors
def _family_center_hue(family: int) -> float:
"""Get the center hue for a family index."""
# Family centers based on _hue_to_family ranges:
# 0: RED (330-30°, wraps) -> center 0°
# 1: ORANGE (30-60°) -> center 45°
# 2: YELLOW (60-105°) -> center 82.5°
# 3: GREEN (105-190°) -> center 147.5°
# 4: BLUE (190-270°) -> center 230°
# 5: PURPLE (270-330°) -> center 300°
centers = [0.0, 45.0, 82.5, 147.5, 230.0, 300.0]
return centers[family]
def _circular_hue_diff(h1: float, h2: float) -> float:
"""Calculate circular hue difference (0-180)."""
diff = abs(h1 - h2)
return min(diff, 360.0 - diff)
def _score_colors_dysfunctional(
colors_with_counts: list[tuple[RGB, int]],
) -> list[tuple[Color, float]]:
"""
Score colors prioritizing the 2nd most dominant hue family.
Like count scoring but skips the dominant family (and any families
too close to it) to pick a visually distinct secondary color.
Args:
colors_with_counts: List of (RGB, count) tuples from clustering
Returns:
List of (Color, score) tuples, sorted by family dominance then count
"""
MIN_CHROMA = 10.0 # Filter out near-gray colors
MIN_HUE_DISTANCE = 45.0 # Minimum hue distance from dominant family
MIN_COUNT_RATIO = 0.02 # Distant family must have at least 2% of total colorful pixels
# First pass: collect colorful colors and group by hue family
hue_families: dict[int, list[tuple[Color, float, float, int]]] = {} # family -> [(color, hue, chroma, count), ...]
for rgb, count in colors_with_counts:
color = Color.from_rgb(rgb)
try:
hct = color.to_hct()
if hct.chroma >= MIN_CHROMA:
family = _hue_to_family(hct.hue)
if family not in hue_families:
hue_families[family] = []
hue_families[family].append((color, hct.hue, hct.chroma, count))
except (ValueError, ZeroDivisionError):
pass
# If no colorful colors found, fall back to all colors
if not hue_families:
result = []
for rgb, count in colors_with_counts:
color = Color.from_rgb(rgb)
result.append((color, float(count)))
result.sort(key=lambda x: -x[1])
return result
# Calculate total count per hue family
family_totals: list[tuple[int, int]] = []
for family, colors in hue_families.items():
total = sum(c[3] for c in colors)
family_totals.append((family, total))
# Sort families by total count (dominant family first)
family_totals.sort(key=lambda x: -x[1])
# Find the dominant family and its center hue
dominant_family, dominant_count = family_totals[0]
dominant_center = _family_center_hue(dominant_family)
total_colorful_pixels = sum(count for _, count in family_totals)
min_count = total_colorful_pixels * MIN_COUNT_RATIO
# Find families that are far enough from the dominant one AND have enough pixels
distant_families = []
close_families = [dominant_family]
for family, count in family_totals[1:]:
family_center = _family_center_hue(family)
hue_diff = _circular_hue_diff(dominant_center, family_center)
if hue_diff >= MIN_HUE_DISTANCE and count >= min_count:
# Get max chroma in this family - we want families with vibrant colors
max_chroma = max(c[2] for c in hue_families[family])
distant_families.append((family, count, hue_diff, max_chroma))
else:
close_families.append(family)
# Build result: colors from distant families first
result_colors = []
# Sort distant families by weighted score: hue_distance * max_chroma
# This balances visual distinctness (hue distance) with color quality (chroma)
# A family that's far away AND has good colors beats one that's close with great colors
distant_families.sort(key=lambda x: -(x[2] * x[3]))
for family, _, _, _ in distant_families:
family_colors = hue_families[family]
# Sort by chroma descending - we want the most vibrant color from this family
# Count is tiebreaker to avoid picking tiny noise clusters
family_colors.sort(key=lambda x: (-x[2], -x[3]))
for color, hue, chroma, count in family_colors:
# Score encodes family rank + chroma for proper ordering
# Chroma is primary (we want vibrant), count is tiebreaker
family_rank = next(i for i, (f, _, _, _) in enumerate(distant_families) if f == family)
score = (len(distant_families) - family_rank) * 1000000 + chroma * 1000 + count
result_colors.append((color, score))
# Add colors from close families (including dominant) at lower priority
for family in close_families:
family_colors = hue_families[family]
family_colors.sort(key=lambda x: (-x[3], -x[2]))
for color, hue, chroma, count in family_colors:
# Lower score than all distant-family colors
score = count * 1000 + chroma
result_colors.append((color, score))
result_colors.sort(key=lambda x: -x[1])
return result_colors
def _score_colors_muted(
colors_with_counts: list[tuple[RGB, int]],
) -> list[tuple[Color, float]]:
@@ -434,6 +557,7 @@ def extract_palette(
- "population": matugen-like, representative colors (M3 schemes)
- "chroma": vibrant, chroma-prioritized with centroid averaging
- "count": area-dominant, picks by pixel count (faithful mode)
- "dysfunctional": picks 2nd most dominant color family
- "muted": like count but without chroma filtering (monochrome wallpapers)
Returns:
@@ -456,6 +580,10 @@ def extract_palette(
# Scoring will filter to colorful colors and pick by count
cluster_count = 48
filtered = sampled
elif scoring == "dysfunctional":
# Dysfunctional mode: same as count but picks 2nd dominant family
cluster_count = 48
filtered = sampled
elif scoring == "muted":
# Muted mode: similar to count but accepts low-chroma colors
# For monochrome/monotonal wallpapers
@@ -494,6 +622,10 @@ def extract_palette(
# Use representative colors with count scoring (faithful mode)
colors_for_scoring = [(c[1], c[2]) for c in clusters]
scored = _score_colors_count(colors_for_scoring)
elif scoring == "dysfunctional":
# Use representative colors with dysfunctional scoring (2nd dominant family)
colors_for_scoring = [(c[1], c[2]) for c in clusters]
scored = _score_colors_dysfunctional(colors_for_scoring)
elif scoring == "muted":
# Use representative colors with muted scoring (no chroma filter)
colors_for_scoring = [(c[1], c[2]) for c in clusters]
+4 -4
View File
@@ -853,14 +853,14 @@ def generate_theme(
Args:
palette: List of extracted colors
mode: "dark" or "light"
scheme_type: One of "tonal-spot", "fruit-salad", "rainbow", "vibrant", "faithful", "muted"
scheme_type: One of "tonal-spot", "fruit-salad", "rainbow", "vibrant", "faithful", "dysfunctional", "muted"
Returns:
Dictionary of color token names to hex values
"""
# Handle vibrant/faithful modes (use generate_normal_* functions)
# Both use same theme generation, but different color extraction (handled in palette.py)
if scheme_type in ("vibrant", "faithful"):
# Handle vibrant/faithful/dysfunctional modes (use generate_normal_* functions)
# All three use same theme generation, but different color extraction (handled in palette.py)
if scheme_type in ("vibrant", "faithful", "dysfunctional"):
if mode == "dark":
return generate_normal_dark(palette)
return generate_normal_light(palette)
@@ -12,13 +12,14 @@ Supported scheme types:
- monochrome: Pure grayscale M3 scheme (chroma = 0, only error has color)
- vibrant: Prioritizes the most saturated colors regardless of area coverage
- faithful: Prioritizes dominant colors by area, what you see is what you get
- dysfunctional: Like faithful but picks the 2nd most dominant color family
- muted: Preserves hue but caps saturation low (for monochrome/monotonal wallpapers)
Usage:
python3 template-processor.py IMAGE_OR_JSON [OPTIONS]
Options:
--scheme-type Scheme type: tonal-spot (default), content, fruit-salad, rainbow, monochrome, vibrant, faithful, muted
--scheme-type Scheme type: tonal-spot (default), content, fruit-salad, rainbow, monochrome, vibrant, faithful, dysfunctional, muted
--dark Generate dark theme only
--light Generate light theme only
--both Generate both themes (default)
@@ -83,7 +84,7 @@ Examples:
# Scheme type selection
parser.add_argument(
'--scheme-type',
choices=['tonal-spot', 'content', 'fruit-salad', 'rainbow', 'monochrome', 'vibrant', 'faithful', 'muted'],
choices=['tonal-spot', 'content', 'fruit-salad', 'rainbow', 'monochrome', 'vibrant', 'faithful', 'dysfunctional', 'muted'],
default='tonal-spot',
help='Color scheme type (default: tonal-spot)'
)
@@ -267,6 +268,7 @@ def main() -> int:
# This matches matugen's color extraction exactly
# - vibrant: Use k-means clustering for colorful/blended colors
# - faithful: Use Wu quantizer for primary (dominant by area), k-means for accents
# - dysfunctional: Like faithful but picks 2nd most dominant color family
# - muted: Like count but without chroma filtering (for monochrome wallpapers)
if scheme_type == "vibrant":
# K-means with chroma scoring for vibrant, blended colors
@@ -275,6 +277,10 @@ def main() -> int:
# K-means with count scoring - picks dominant color by area coverage
# This ensures primary reflects what you actually see in the image
palette = extract_palette(pixels, k=5, scoring="count")
elif scheme_type == "dysfunctional":
# K-means with dysfunctional scoring - picks 2nd most dominant color family
# For when the dominant color is not what you want as primary
palette = extract_palette(pixels, k=5, scoring="dysfunctional")
elif scheme_type == "muted":
# K-means with muted scoring - accepts low/zero chroma colors
# For monochrome/monotonal wallpapers where dominant color has low saturation
@@ -0,0 +1,30 @@
#!/usr/bin/env python3
# Finds all installed Noctalia theme extensions for VSCode/VSCodium.
import sys
from pathlib import Path
def find_all_noctalia_themes(extensions_dir: Path, prefix: str) -> list[str]:
# Bail early if the extensions directory doesn't exist
if not extensions_dir.is_dir():
return []
# Collect all directories matching the extension prefix
candidates = [d for d in extensions_dir.iterdir() if d.is_dir() and d.name.startswith(prefix)]
# Return theme file paths for all matching extensions
return [str(d / "themes" / "NoctaliaTheme-color-theme.json") for d in candidates]
if __name__ == "__main__":
# Resolve ~ in the provided extensions directory path
extensions_dir = Path(sys.argv[1]).expanduser()
prefix = sys.argv[2] if len(sys.argv) > 2 else "noctalia.noctaliatheme-"
# Print the resolved paths to stdout for the QML Process to capture
results = find_all_noctalia_themes(extensions_dir, prefix)
if results:
for path in results:
print(path)
else:
print(f"No matching extension found in {extensions_dir}", file=sys.stderr)
sys.exit(1)
+6 -1
View File
@@ -178,8 +178,9 @@ Item {
for (var i = 0; i < hlWorkspaces.length; i++) {
const ws = hlWorkspaces[i];
if (!ws || ws.id < 1)
if (ws.name && ws.name.startsWith("special:"))
continue;
const wsData = {
"id": ws.id,
"idx": ws.id,
@@ -428,6 +429,10 @@ Item {
// Public functions
function switchToWorkspace(workspace) {
try {
if (workspace.name) {
Hyprland.dispatch(`workspace ${workspace.name}`);
return;
}
Hyprland.dispatch(`workspace ${workspace.idx}`);
} catch (e) {
Logger.e("HyprlandService", "Failed to switch workspace:", e);
+1 -1
View File
@@ -692,7 +692,7 @@ Item {
function spawn(command) {
try {
const cmdStr = Array.isArray(command) ? command.join(" ") : command;
Quickshell.execDetached(["mmsg", "-d", "'spawn," + cmdStr + "'"]);
Quickshell.execDetached(["sh", "-c", "mmsg -d 'spawn," + cmdStr + "'"]);
} catch (e) {
Logger.e("MangoService", "Failed to spawn command:", e);
}
+1 -1
View File
@@ -241,7 +241,7 @@ Singleton {
function runPowerHook(script, callback) {
pendingPowerCallback = callback;
powerHookProcess.command = ["sh", "-c", script];
powerHookProcess.command = ["sh", "-lc", script];
powerHookProcess.running = true;
}
+14 -9
View File
@@ -227,7 +227,7 @@ Singleton {
// For internal displays, query the system directly
refreshProc.command = ["sh", "-c", "cat " + monitor.brightnessPath + " && " + "cat " + monitor.maxBrightnessPath];
refreshProc.running = true;
} else if (monitor.isDdc) {
} else if (monitor.isDdc && monitor.busNum !== "") {
// For DDC displays, get the current value
refreshProc.command = ["ddcutil", "-b", monitor.busNum, "--sleep-multiplier=0.05", "getvcp", "10", "--brief"];
refreshProc.running = true;
@@ -362,17 +362,20 @@ Singleton {
}
// Execute the brightness change command
monitor.commandRunning = true;
monitor.ignoreNextChange = true;
if (isAppleDisplay) {
monitor.commandRunning = true;
monitor.ignoreNextChange = true;
setBrightnessProc.command = ["asdbctl", "set", rounded];
setBrightnessProc.running = true;
} else if (isDdc) {
} else if (isDdc && busNum !== "") {
monitor.commandRunning = true;
monitor.ignoreNextChange = true;
var ddcValue = Math.round(value * monitor.maxBrightness);
setBrightnessProc.command = ["ddcutil", "-b", busNum, "--noverify", "--async", "--sleep-multiplier=0.05", "setvcp", "10", ddcValue];
setBrightnessProc.running = true;
} else {
} else if (!isDdc) {
monitor.commandRunning = true;
monitor.ignoreNextChange = true;
setBrightnessProc.command = ["brightnessctl", "s", rounded + "%"];
setBrightnessProc.running = true;
}
@@ -381,14 +384,16 @@ Singleton {
function initBrightness(): void {
if (isAppleDisplay) {
initProc.command = ["asdbctl", "get"];
} else if (isDdc) {
initProc.running = true;
} else if (isDdc && busNum !== "") {
initProc.command = ["ddcutil", "-b", busNum, "--sleep-multiplier=0.05", "getvcp", "10", "--brief"];
} else {
initProc.running = true;
} else if (!isDdc) {
// Internal backlight - find the first available backlight device and get its info
// This now returns: device_path, current_brightness, max_brightness (on separate lines)
initProc.command = ["sh", "-c", "for dev in /sys/class/backlight/*; do " + " if [ -f \"$dev/brightness\" ] && [ -f \"$dev/max_brightness\" ]; then " + " echo \"$dev\"; " + " cat \"$dev/brightness\"; " + " cat \"$dev/max_brightness\"; " + " break; " + " fi; " + "done"];
initProc.running = true;
}
initProc.running = true;
}
onBusNumChanged: initBrightness()
+6 -6
View File
@@ -291,11 +291,11 @@ Singleton {
watchersStarted = true;
// Text watcher
watchText.command = ["sh", "-lc", Settings.data.appLauncher.clipboardWatchTextCommand];
watchText.command = ["sh", "-c", Settings.data.appLauncher.clipboardWatchTextCommand];
watchText.running = true;
// Image watcher
watchImage.command = ["sh", "-lc", Settings.data.appLauncher.clipboardWatchImageCommand];
watchImage.command = ["sh", "-c", Settings.data.appLauncher.clipboardWatchImageCommand];
watchImage.running = true;
}
@@ -407,7 +407,7 @@ Singleton {
root._b64CurrentCb = job.cb;
root._b64CurrentMime = job.mime;
root._b64CurrentId = job.id;
decodeB64Proc.command = ["sh", "-lc", `cliphist decode ${job.id} | base64 -w 0`];
decodeB64Proc.command = ["sh", "-c", `cliphist decode ${job.id} | base64 -w 0`];
decodeB64Proc.running = true;
}
@@ -415,7 +415,7 @@ Singleton {
if (!root.cliphistAvailable) {
return;
}
copyProc.command = ["sh", "-lc", `cliphist decode ${id} | wl-copy`];
copyProc.command = ["sh", "-c", `cliphist decode ${id} | wl-copy`];
copyProc.running = true;
}
@@ -427,7 +427,7 @@ Singleton {
const typeArg = isImage ? ` --type ${mime}` : "";
const pasteKeys = isImage ? "wtype -M ctrl -k v" : "wtype -M ctrl -M shift v";
const cmd = `cliphist decode ${id} | wl-copy${typeArg} && ${pasteKeys}`;
pasteProc.command = ["sh", "-lc", cmd];
pasteProc.command = ["sh", "-c", cmd];
pasteProc.running = true;
}
@@ -436,7 +436,7 @@ Singleton {
return;
const escaped = text.replace(/'/g, "'\\''");
const cmd = `printf '%s' '${escaped}' | wl-copy && wtype -M ctrl -M shift v`;
pasteProc.command = ["sh", "-lc", cmd];
pasteProc.command = ["sh", "-c", cmd];
pasteProc.running = true;
}
+24 -6
View File
@@ -1446,7 +1446,9 @@ Singleton {
}
// Try to find the plugin panel slot (pluginPanel1 or pluginPanel2)
// Try slot 1 first, then slot 2
// Priority: 1) toggle same plugin, 2) empty slot, 3) closed slot, 4) replace open slot
var closedSlot = null;
for (var slotNum = 1; slotNum <= 2; slotNum++) {
var panelName = "pluginPanel" + slotNum;
var panel = PanelService.getPanel(panelName, screen);
@@ -1460,22 +1462,38 @@ Singleton {
// If this slot is empty, use it
if (panel.currentPluginId === "") {
// Set the pluginId first - when panel opens and panelContent loads,
// Component.onCompleted will call loadPluginPanel automatically
panel.currentPluginId = pluginId;
panel.open(buttonItem);
return true;
}
// Track first closed slot (panel assigned but not showing)
if (!closedSlot && !panel.isPanelOpen) {
closedSlot = panel;
}
}
}
// If both slots are occupied, use slot 1 (replace existing)
// Prefer reusing a closed slot over replacing an open one
if (closedSlot) {
closedSlot.currentPluginId = pluginId;
closedSlot.open(buttonItem);
return true;
}
// If both slots are occupied and open, use slot 1 (replace existing)
var panel1 = PanelService.getPanel("pluginPanel1", screen);
if (panel1) {
var wasAlreadyOpen = panel1.isPanelOpen;
panel1.unloadPluginPanel();
// Set the pluginId first - when panel opens and panelContent loads,
// Component.onCompleted will call loadPluginPanel automatically
panel1.currentPluginId = pluginId;
// If panel was already open, Component.onCompleted won't fire again
// since panelContent is already loaded. We need to load the plugin manually.
if (wasAlreadyOpen && panel1.contentLoader) {
panel1.loadPluginPanel(pluginId);
}
panel1.open(buttonItem);
return true;
}
+1 -1
View File
@@ -11,7 +11,7 @@ Singleton {
id: root
// Version properties
readonly property string baseVersion: "4.3.1"
readonly property string baseVersion: "4.3.2"
readonly property bool isDevelopment: true
readonly property string developmentSuffix: "-git"
readonly property string currentVersion: `v${!isDevelopment ? baseVersion : baseVersion + developmentSuffix}`
+41 -85
View File
@@ -32,7 +32,9 @@ Singleton {
property real swapPercent: 0
property real swapTotalGb: 0
property var diskPercents: ({})
property var diskAvailPercents: ({}) // available disk space in percent
property var diskUsedGb: ({}) // Used space in GB per mount point
property var diskAvailableGb: ({}) // available space in GB per mount point
property var diskSizeGb: ({}) // Total size in GB per mount point
property var diskAvailGb: ({})
property real rxSpeed: 0
@@ -70,7 +72,7 @@ Singleton {
property real gpuTempHistoryMin: 100
property real gpuTempHistoryMax: 0
property real memHistoryMax: 0
// Network uses existing rxMaxSpeed/txMaxSpeed (7-day learned peaks)
// Network uses autoscaling from current history window
// Disk is always 0-100%
// History management - called from update functions, not change handlers
@@ -150,17 +152,17 @@ Singleton {
txSpeedHistory = txH;
}
// Network max speed tracking (learned over time, cached for 7 days)
// Network max speed tracking (autoscales from current history window)
readonly property real rxMaxSpeed: {
const peaks = networkStatsAdapter.rxPeaks || [];
return peaks.length > 0 ? Math.max(...peaks.map(p => p.speed)) : 0;
const max = Math.max(...rxSpeedHistory);
return max > 0 ? max : 1024; // Minimum 1 KB/s floor
}
readonly property real txMaxSpeed: {
const peaks = networkStatsAdapter.txPeaks || [];
return peaks.length > 0 ? Math.max(...peaks.map(p => p.speed)) : 0;
const max = Math.max(...txSpeedHistory);
return max > 0 ? max : 1024; // Minimum 1 KB/s floor
}
// Ready-to-use ratios based on learned maximums (0..1 range)
// Ready-to-use ratios based on current maximums (0..1 range)
readonly property real rxRatio: rxMaxSpeed > 0 ? Math.min(1, rxSpeed / rxMaxSpeed) : 0
readonly property real txRatio: txMaxSpeed > 0 ? Math.min(1, txSpeed / txMaxSpeed) : 0
@@ -181,6 +183,8 @@ Singleton {
readonly property int swapCriticalThreshold: Settings.data.systemMonitor.swapCriticalThreshold
readonly property int diskWarningThreshold: Settings.data.systemMonitor.diskWarningThreshold
readonly property int diskCriticalThreshold: Settings.data.systemMonitor.diskCriticalThreshold
readonly property int diskAvailWarningThreshold: Settings.data.systemMonitor.diskAvailWarningThreshold
readonly property int diskAvailCriticalThreshold: Settings.data.systemMonitor.diskAvailCriticalThreshold
// Computed warning/critical states (uses >= inclusive comparison)
readonly property bool cpuWarning: cpuUsage >= cpuWarningThreshold
@@ -195,12 +199,12 @@ Singleton {
readonly property bool swapCritical: swapPercent >= swapCriticalThreshold
// Helper functions for disk (disk path is dynamic)
function isDiskWarning(diskPath) {
return (diskPercents[diskPath] || 0) >= diskWarningThreshold;
function isDiskWarning(diskPath, available = false) {
return available ? (diskAvailPercents[diskPath] || 0) <= diskAvailWarningThreshold : (diskPercents[diskPath] || 0) >= diskWarningThreshold;
}
function isDiskCritical(diskPath) {
return (diskPercents[diskPath] || 0) >= diskCriticalThreshold;
function isDiskCritical(diskPath, available = false) {
return available ? (diskAvailPercents[diskPath] || 0) <= diskAvailCriticalThreshold : (diskPercents[diskPath] || 0) >= diskCriticalThreshold;
}
// Ready-to-use stat colors (for gauges, panels, icons)
@@ -210,8 +214,8 @@ Singleton {
readonly property color memColor: memCritical ? criticalColor : (memWarning ? warningColor : Color.mPrimary)
readonly property color swapColor: swapCritical ? criticalColor : (swapWarning ? warningColor : Color.mPrimary)
function getDiskColor(diskPath) {
return isDiskCritical(diskPath) ? criticalColor : (isDiskWarning(diskPath) ? warningColor : Color.mPrimary);
function getDiskColor(diskPath, available = false) {
return isDiskCritical(diskPath, available) ? criticalColor : (isDiskWarning(diskPath, available) ? warningColor : Color.mPrimary);
}
// Helper function for color resolution based on value and thresholds
@@ -251,52 +255,6 @@ Singleton {
property var foundGpuSensors: [] // [{hwmonPath, type, hasDedicatedVram}]
property int gpuVramCheckIndex: 0
// --------------------------------------------
// Network speed stats cache (7-day rolling window)
property string networkStatsFile: Settings.cacheDir + "network_stats.json"
FileView {
id: networkStatsView
path: root.networkStatsFile
printErrors: false
JsonAdapter {
id: networkStatsAdapter
property var rxPeaks: []
property var txPeaks: []
}
onLoadFailed: {
networkStatsAdapter.rxPeaks = [];
networkStatsAdapter.txPeaks = [];
}
onLoaded: {
root.pruneExpiredPeaks();
}
}
Timer {
id: networkStatsSaveDebounce
interval: 1000
onTriggered: networkStatsView.writeAdapter()
}
function pruneExpiredPeaks() {
const sevenDaysMs = 7 * 24 * 60 * 60 * 1000;
const cutoff = Date.now() - sevenDaysMs;
const rxBefore = (networkStatsAdapter.rxPeaks || []).length;
const txBefore = (networkStatsAdapter.txPeaks || []).length;
networkStatsAdapter.rxPeaks = (networkStatsAdapter.rxPeaks || []).filter(p => p.timestamp > cutoff);
networkStatsAdapter.txPeaks = (networkStatsAdapter.txPeaks || []).filter(p => p.timestamp > cutoff);
// Save if any were pruned
if (networkStatsAdapter.rxPeaks.length !== rxBefore || networkStatsAdapter.txPeaks.length !== txBefore) {
networkStatsSaveDebounce.restart();
}
}
// --------------------------------------------
Component.onCompleted: {
Logger.i("SystemStat", "Service started with custom polling intervals");
@@ -490,9 +448,10 @@ Singleton {
onStreamFinished: {
const lines = text.trim().split('\n');
const newPercents = {};
const newAvailPercents = {};
const newUsedGb = {};
const newSizeGb = {};
const newAvailGb = {};
const newAvailableGb = {};
const bytesPerGb = 1024 * 1024 * 1024;
// Start from line 1 (skip header)
for (var i = 1; i < lines.length; i++) {
@@ -503,16 +462,19 @@ Singleton {
const usedBytes = parseFloat(parts[2]) || 0;
const sizeBytes = parseFloat(parts[3]) || 0;
const availBytes = parseFloat(parts[4]) || 0;
const availPercent = sizeBytes > 0 ? (availBytes / sizeBytes) * 100 : 0;
newPercents[target] = percent;
newAvailPercents[target] = Math.round(availPercent);
newUsedGb[target] = usedBytes / bytesPerGb;
newSizeGb[target] = sizeBytes / bytesPerGb;
newAvailGb[target] = availBytes / bytesPerGb;
newAvailableGb[target] = availBytes / bytesPerGb;
}
}
root.diskPercents = newPercents;
root.diskAvailPercents = newAvailPercents;
root.diskUsedGb = newUsedGb;
root.diskSizeGb = newSizeGb;
root.diskAvailGb = newAvailGb;
root.diskAvailableGb = newAvailableGb;
root.pushDiskHistory();
}
}
@@ -571,7 +533,6 @@ Singleton {
if (!isNaN(maxKHz) && maxKHz > 0) {
let newMaxFreq = maxKHz / 1000000.0;
if (Math.abs(root.cpuGlobalMaxFreq - newMaxFreq) > 0.01) {
Logger.i("SystemStat", `CPU Max Freq changed: ${root.cpuGlobalMaxFreq} -> ${newMaxFreq} GHz`);
root.cpuGlobalMaxFreq = newMaxFreq;
}
}
@@ -1006,27 +967,6 @@ Singleton {
root.rxSpeed = Math.round(rxDiff / timeDiff); // Speed in Bytes/s
root.txSpeed = Math.round(txDiff / timeDiff);
// Record new peaks if higher than current max (for adaptive ratio calculation)
const now = Date.now();
if (root.rxSpeed > root.rxMaxSpeed) {
networkStatsAdapter.rxPeaks = [...(networkStatsAdapter.rxPeaks || []),
{
speed: root.rxSpeed,
timestamp: now
}
];
networkStatsSaveDebounce.restart();
}
if (root.txSpeed > root.txMaxSpeed) {
networkStatsAdapter.txPeaks = [...(networkStatsAdapter.txPeaks || []),
{
speed: root.txSpeed,
timestamp: now
}
];
networkStatsSaveDebounce.restart();
}
}
}
@@ -1081,7 +1021,7 @@ Singleton {
// -------------------------------------------------------
// Smart formatter for memory values (GB) - max 4 chars
// Uses decimal for < 10GB, integer otherwise
function formatMemoryGb(memGb) {
function formatGigabytes(memGb) {
const value = parseFloat(memGb);
if (isNaN(value))
return "0G";
@@ -1091,6 +1031,22 @@ Singleton {
return Math.round(value) + "G"; // "10G" to "999G"
}
// -------------------------------------------------------
// Formatting disk usage
function formatDiskDisplay(diskPath, {
percent = false,
available = false
} = {}) {
if (percent) {
const raw = available ? root.diskAvailPercents[diskPath] : root.diskPercents[diskPath];
const value = (raw === null) ? 0 : raw;
return `${value}%`;
} else {
const rawGb = available ? root.diskAvailableGb[diskPath] : root.diskUsedGb[diskPath];
return formatGigabytes(rawGb === null ? 0 : rawGb);
}
}
// -------------------------------------------------------
// Function to start fetching and computing the cpu temperature
function updateCpuTemperature() {
+16 -9
View File
@@ -51,6 +51,10 @@ Singleton {
"key": "faithful",
"name": I18n.tr("common.faithful")
},
{
"key": "dysfunctional",
"name": I18n.tr("common.dysfunctional")
},
{
"key": "muted",
"name": I18n.tr("common.color-muted")
@@ -102,7 +106,7 @@ Singleton {
const script = buildGenerationScript(content, wp, mode);
generateProcess.command = ["sh", "-lc", script];
generateProcess.command = ["sh", "-c", script];
generateProcess.running = true;
}
@@ -162,7 +166,7 @@ Singleton {
// Add user templates if enabled
script += buildUserTemplateCommandForPredefined(schemeData, mode);
generateProcess.command = ["sh", "-lc", script];
generateProcess.command = ["sh", "-c", script];
generateProcess.running = true;
}
@@ -245,11 +249,14 @@ Singleton {
if (isTemplateEnabled("code")) {
app.clients.forEach(client => {
// Check if this specific client is detected
if (isCodeClientEnabled(client.name)) {
lines.push(`\n[templates.code_${client.name}]`);
lines.push(`input_path = "${Quickshell.shellDir}/Assets/Templates/${app.input}"`);
const expandedPath = client.path.replace("~", homeDir);
lines.push(`output_path = "${expandedPath}"`);
var resolvedPaths = TemplateRegistry.resolvedCodeClientPaths(client.name);
if (isCodeClientEnabled(client.name) && resolvedPaths.length > 0) {
resolvedPaths.forEach((resolvedPath, pathIndex) => {
var suffix = resolvedPaths.length > 1 ? `_${pathIndex}` : "";
lines.push(`\n[templates.code_${client.name}${suffix}]`);
lines.push(`input_path = "${Quickshell.shellDir}/Assets/Templates/${app.input}"`);
lines.push(`output_path = "${resolvedPath}"`);
});
}
});
}
@@ -399,7 +406,7 @@ Singleton {
}
});
copyProcess.command = ["sh", "-lc", script];
copyProcess.command = ["sh", "-c", script];
copyProcess.running = true;
}
@@ -428,7 +435,7 @@ Singleton {
});
if (commands.length > 0) {
copyProcess.command = ["sh", "-lc", commands.join('; ')];
copyProcess.command = ["sh", "-c", commands.join('; ')];
copyProcess.running = true;
}
}
+49 -1
View File
@@ -10,6 +10,11 @@ Singleton {
readonly property string templateApplyScript: Quickshell.shellDir + '/Scripts/bash/template-apply.sh'
readonly property string gtkRefreshScript: Quickshell.shellDir + '/Scripts/python/src/theming/gtk-refresh.py'
readonly property string vscodeHelperScript: Quickshell.shellDir + '/Scripts/python/src/theming/vscode-helper.py'
// Dynamically resolved VSCode extension theme paths (all matching noctalia extensions)
property var resolvedCodePaths: []
property var resolvedCodiumPaths: []
// Terminal configurations (for wallpaper-based templates)
// Each terminal must define a postHook that sets up config includes and triggers reload
@@ -398,6 +403,15 @@ Singleton {
return clients;
}
// Get resolved theme paths for a code client (returns array of all matching paths)
function resolvedCodeClientPaths(clientName) {
if (clientName === "code")
return resolvedCodePaths;
if (clientName === "codium")
return resolvedCodiumPaths;
return [];
}
// Extract Code clients for ProgramCheckerService compatibility
readonly property var codeClients: {
var clients = [];
@@ -417,13 +431,47 @@ Singleton {
clients.push({
"name": client.name,
"configPath": baseConfigDir,
"themePath": themePath
"themePath": "" // resolved dynamically via resolvedCodeClientPaths()
});
});
}
return clients;
}
// Resolve VSCode extension paths dynamically
Process {
id: codeResolverProcess
command: ["python3", vscodeHelperScript, "~/.vscode/extensions"]
running: true
property var paths: []
stdout: SplitParser {
onRead: data => {
var line = data.trim();
if (line)
codeResolverProcess.paths.push(line);
}
}
onExited: {
root.resolvedCodePaths = paths;
}
}
Process {
id: codiumResolverProcess
command: ["python3", vscodeHelperScript, "~/.vscode-oss/extensions"]
running: true
property var paths: []
stdout: SplitParser {
onRead: data => {
var line = data.trim();
if (line)
codiumResolverProcess.paths.push(line);
}
}
onExited: {
root.resolvedCodiumPaths = paths;
}
}
// Build user templates TOML content
function buildUserTemplatesToml() {
var lines = [];
+4 -2
View File
@@ -208,7 +208,8 @@ Singleton {
"showSwapUsage": false,
"showNetworkStats": false,
"showDiskUsage": false,
"showDiskAsFree": false,
"showDiskUsageAsPercent": false,
"showDiskAvailable": false,
"diskPath": "/"
},
"Taskbar": {
@@ -255,7 +256,8 @@ Singleton {
"occupiedColor": "secondary",
"emptyColor": "secondary",
"showBadge": true,
"reverseScroll": false
"reverseScroll": false,
"pillSize": 0.6
},
"Volume": {
"displayMode": "onhover",
+2 -2
View File
@@ -17,7 +17,7 @@ Item {
// Reactive path that updates when values change
readonly property string svgPath: {
if (!values || !Array.isArray(values) || values.length === 0) {
return "";
return "M 0 0"; // Valid no-op path to avoid Qt triangulator crash on empty path
}
// Apply minimum signal if enabled
@@ -28,7 +28,7 @@ Item {
const mirroredValues = partToMirror.concat(processedValues);
if (mirroredValues.length < 2) {
return "";
return "M 0 0"; // Valid no-op path to avoid Qt triangulator crash on empty path
}
const count = mirroredValues.length;
+182 -138
View File
@@ -1,10 +1,9 @@
import QtQuick
import QtQuick.Shapes
import qs.Commons
Item {
id: root
clip: true // Clip bezier overshoot
clip: true
// Primary line
property var values: []
@@ -27,12 +26,91 @@ Item {
property bool fill: true
property real fillOpacity: 0.15
// Smooth scrolling interval (how often data updates)
property int updateInterval: 1000
// Auto-scale: when false, use minValue/maxValue directly (e.g., for 0-100% graphs)
property bool autoScale: true
property bool autoScale2: true
// Padding for bezier overshoot (percentage of range)
readonly property real curvePadding: 0.08
readonly property bool hasData: values.length >= 2
readonly property bool hasData2: values2.length >= 2
// Animation state
property real _t: 1.0
property bool _ready: false
property real _pred: 0
property real _pred2: 0
onValuesChanged: {
if (values.length < 2)
return;
const last = values[values.length - 1];
const prev = values.length > 1 ? values[values.length - 2] : last;
_pred = Math.max(minValue, last + (last - prev));
if (!_ready) {
_ready = true;
_t = 0;
_animTimer.start();
return;
}
// Maintain continuity: new_t = old_t - 1
_t = _t - 1.0;
_animTimer.start();
}
onValues2Changed: {
if (values2.length < 2)
return;
const last = values2[values2.length - 1];
const prev = values2.length > 1 ? values2[values2.length - 2] : last;
_pred2 = Math.max(minValue2, last + (last - prev));
// Let animation timer handle repaints - don't trigger here
}
Timer {
id: _animTimer
interval: 16
repeat: true
onTriggered: {
root._t = Math.min(1.0, root._t + (16 / root.updateInterval));
canvas.requestPaint();
if (root._t >= 1.0)
stop();
}
}
// Effective max values that include current data and predictions (when autoScale is true)
readonly property real _effectiveMax: {
if (!autoScale || !hasData)
return maxValue;
let m = maxValue;
for (let i = 0; i < values.length; i++) {
if (values[i] > m)
m = values[i];
}
if (_pred > m)
m = _pred;
return m;
}
readonly property real _effectiveMax2: {
if (!autoScale2 || !hasData2)
return maxValue2;
let m = maxValue2;
for (let i = 0; i < values2.length; i++) {
if (values2[i] > m)
m = values2[i];
}
if (_pred2 > m)
m = _pred2;
return m;
}
// Convert a value to Y coordinate (with padding for bezier curves)
function valueToY(val, minVal, maxVal) {
let range = maxVal - minVal;
@@ -46,151 +124,117 @@ Item {
return height - normalized * height;
}
// Generate SVG path for a given values array using monotone cubic interpolation
function generateCurvePath(vals, minVal, maxVal) {
if (!vals || vals.length < 2 || width <= 0 || height <= 0)
return "";
const n = vals.length;
// Build array of points
let points = [];
for (let i = 0; i < n; i++) {
points.push({
x: (i / (n - 1)) * width,
y: valueToY(vals[i], minVal, maxVal)
});
}
// For only 2 points, draw a line
if (points.length === 2) {
return `M ${points[0].x.toFixed(2)} ${points[0].y.toFixed(2)} L ${points[1].x.toFixed(2)} ${points[1].y.toFixed(2)}`;
}
// Calculate tangents using finite differences (monotone cubic)
let tangents = [];
for (let i = 0; i < points.length; i++) {
if (i === 0) {
tangents.push((points[1].y - points[0].y) / (points[1].x - points[0].x));
} else if (i === points.length - 1) {
tangents.push((points[i].y - points[i - 1].y) / (points[i].x - points[i - 1].x));
} else {
// Average of left and right slopes
const left = (points[i].y - points[i - 1].y) / (points[i].x - points[i - 1].x);
const right = (points[i + 1].y - points[i].y) / (points[i + 1].x - points[i].x);
tangents.push((left + right) / 2);
}
}
// Build the path
let path = `M ${points[0].x.toFixed(2)} ${points[0].y.toFixed(2)}`;
for (let i = 0; i < points.length - 1; i++) {
const p0 = points[i];
const p1 = points[i + 1];
const dx = p1.x - p0.x;
// Control points for cubic bezier
const cp1x = p0.x + dx / 3;
const cp1y = p0.y + tangents[i] * dx / 3;
const cp2x = p1.x - dx / 3;
const cp2y = p1.y - tangents[i + 1] * dx / 3;
path += ` C ${cp1x.toFixed(2)},${cp1y.toFixed(2)} ${cp2x.toFixed(2)},${cp2y.toFixed(2)} ${p1.x.toFixed(2)},${p1.y.toFixed(2)}`;
}
return path;
}
// Generate fill path (curve + bottom edge)
function generateFillPath(curvePath) {
if (!curvePath || width <= 0 || height <= 0)
return "";
return curvePath + ` L ${width.toFixed(2)} ${height.toFixed(2)} L 0 ${height.toFixed(2)} Z`;
}
// Computed paths for primary line
readonly property string curvePath: generateCurvePath(values, minValue, maxValue)
readonly property string fillPath: generateFillPath(curvePath)
// Computed paths for secondary line
readonly property string curvePath2: generateCurvePath(values2, minValue2, maxValue2)
readonly property string fillPath2: generateFillPath(curvePath2)
Shape {
Canvas {
id: canvas
anchors.fill: parent
layer.enabled: true
preferredRendererType: Shape.CurveRenderer
visible: root.hasData || root.hasData2
renderStrategy: Canvas.Cooperative
// Primary line fill
ShapePath {
strokeColor: "transparent"
strokeWidth: 0
fillGradient: LinearGradient {
x1: 0
y1: 0
x2: 0
y2: root.height
GradientStop {
position: 0.0
color: Qt.rgba(root.color.r, root.color.g, root.color.b, root.fill ? root.fillOpacity : 0)
}
GradientStop {
position: 1.0
color: "transparent"
}
onPaint: {
var ctx = getContext("2d");
ctx.clearRect(0, 0, width, height);
if (width <= 0 || height <= 0)
return;
// Use primary values length for consistent step size
const baseLen = root.hasData ? root.values.length : (root.hasData2 ? root.values2.length : 0);
if (baseLen < 2)
return;
const step = width / (baseLen - 1);
if (root.hasData) {
drawGraph(ctx, root.values, root._pred, root.minValue, root._effectiveMax, root.color, root._t, step);
}
PathSvg {
path: root.fillPath
if (root.hasData2) {
drawGraph(ctx, root.values2, root._pred2, root.minValue2, root._effectiveMax2, root.color2, root._t, step);
}
}
// Secondary line fill
ShapePath {
strokeColor: "transparent"
strokeWidth: 0
fillGradient: LinearGradient {
x1: 0
y1: 0
x2: 0
y2: root.height
GradientStop {
position: 0.0
color: Qt.rgba(root.color2.r, root.color2.g, root.color2.b, root.fill && root.hasData2 ? root.fillOpacity : 0)
}
GradientStop {
position: 1.0
color: "transparent"
}
}
PathSvg {
path: root.fillPath2
}
}
function drawGraph(ctx, vals, pred, minVal, maxVal, lineColor, t, step) {
if (!vals || vals.length < 2)
return;
// Primary line stroke
ShapePath {
strokeColor: root.hasData ? root.color : "transparent"
strokeWidth: root.strokeWidth
fillColor: "transparent"
joinStyle: ShapePath.RoundJoin
capStyle: ShapePath.RoundCap
PathSvg {
path: root.curvePath
}
}
const n = vals.length;
// Secondary line stroke
ShapePath {
strokeColor: root.hasData2 ? root.color2 : "transparent"
strokeWidth: root.strokeWidth
fillColor: "transparent"
joinStyle: ShapePath.RoundJoin
capStyle: ShapePath.RoundCap
PathSvg {
path: root.curvePath2
// Build points with interpolated X positions for smooth scrolling
let pts = [];
for (let i = 0; i < n; i++) {
const x = (i + 1 - t) * step;
const y = root.valueToY(vals[i], minVal, maxVal);
pts.push({
x: x,
y: y
});
}
// Prediction point enters from right
pts.push({
x: (n + 1 - t) * step,
y: root.valueToY(pred, minVal, maxVal)
});
if (pts.length < 2)
return;
// Calculate tangents for smooth bezier curves
let tan = [];
for (let i = 0; i < pts.length; i++) {
let tg = 0;
if (i === 0 && pts[1].x !== pts[0].x) {
tg = (pts[1].y - pts[0].y) / (pts[1].x - pts[0].x);
} else if (i === pts.length - 1 && pts[i].x !== pts[i - 1].x) {
tg = (pts[i].y - pts[i - 1].y) / (pts[i].x - pts[i - 1].x);
} else if (i > 0 && i < pts.length - 1) {
const dxL = pts[i].x - pts[i - 1].x;
const dxR = pts[i + 1].x - pts[i].x;
const l = dxL !== 0 ? (pts[i].y - pts[i - 1].y) / dxL : 0;
const r = dxR !== 0 ? (pts[i + 1].y - pts[i].y) / dxR : 0;
tg = (l + r) / 2;
}
tan.push(tg);
}
// Draw fill gradient
if (root.fill) {
let grad = ctx.createLinearGradient(0, 0, 0, height);
grad.addColorStop(0, Qt.rgba(lineColor.r, lineColor.g, lineColor.b, root.fillOpacity));
grad.addColorStop(1, "transparent");
ctx.beginPath();
ctx.moveTo(pts[0].x, pts[0].y);
for (let i = 0; i < pts.length - 1; i++) {
const dx = pts[i + 1].x - pts[i].x;
if (Math.abs(dx) < 0.1) {
ctx.lineTo(pts[i + 1].x, pts[i + 1].y);
continue;
}
const c1x = pts[i].x + dx / 3, c1y = pts[i].y + tan[i] * dx / 3;
const c2x = pts[i + 1].x - dx / 3, c2y = pts[i + 1].y - tan[i + 1] * dx / 3;
ctx.bezierCurveTo(c1x, c1y, c2x, c2y, pts[i + 1].x, pts[i + 1].y);
}
ctx.lineTo(pts[pts.length - 1].x, height);
ctx.lineTo(pts[0].x, height);
ctx.closePath();
ctx.fillStyle = grad;
ctx.fill();
}
// Draw stroke
ctx.beginPath();
ctx.moveTo(pts[0].x, pts[0].y);
for (let i = 0; i < pts.length - 1; i++) {
const dx = pts[i + 1].x - pts[i].x;
if (Math.abs(dx) < 0.1) {
ctx.lineTo(pts[i + 1].x, pts[i + 1].y);
continue;
}
const c1x = pts[i].x + dx / 3, c1y = pts[i].y + tan[i] * dx / 3;
const c2x = pts[i + 1].x - dx / 3, c2y = pts[i + 1].y - tan[i + 1] * dx / 3;
ctx.bezierCurveTo(c1x, c1y, c2x, c2y, pts[i + 1].x, pts[i + 1].y);
}
ctx.strokeStyle = lineColor;
ctx.lineWidth = root.strokeWidth;
ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.stroke();
}
}
}
+7
View File
@@ -20,6 +20,10 @@ Rectangle {
Component.onCompleted: _applyDistribution()
function _updateFirstLast() {
// Defensive check for QML initialization timing
if (!tabRow || !tabRow.children) {
return;
}
var kids = tabRow.children;
var len = kids.length;
var firstVisible = -1;
@@ -43,6 +47,9 @@ Rectangle {
}
function _applyDistribution() {
if (!tabRow || !tabRow.children) {
return;
}
if (!distributeEvenly) {
for (var i = 0; i < tabRow.children.length; i++) {
var child = tabRow.children[i];
+2
View File
@@ -28,6 +28,7 @@
imagemagick,
wget,
python3,
wayland-scanner,
# calendar support
calendarSupport ? false,
evolution-data-server,
@@ -90,6 +91,7 @@ stdenvNoCC.mkDerivation {
preFixup = ''
qtWrapperArgs+=(
--prefix PATH : ${lib.makeBinPath (runtimeDeps ++ extraPackages)}
--prefix XDG_DATA_DIRS : ${wayland-scanner}/share
--add-flags "-p $out/share/noctalia-shell"
${lib.optionalString calendarSupport "--prefix GI_TYPELIB_PATH : ${giTypelibPath}"}
)