mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
Merge branch 'main' into media_manager_toggle_artist_first
This commit is contained in:
@@ -314,13 +314,9 @@
|
||||
}
|
||||
},
|
||||
"taskbar-grouped": {
|
||||
"show-numbers-only-when-occupied": {
|
||||
"show-labels-only-when-occupied": {
|
||||
"description": "Zeige nur die Workspace-Bezeichnung für Workspaces an, die offene Fenster haben.",
|
||||
"label": "Zeige Label nur bei Belegung"
|
||||
},
|
||||
"show-workspace-numbers": {
|
||||
"description": "Arbeitsbereichsbezeichnung in der oberen linken Ecke von Aufgabengruppen anzeigen.",
|
||||
"label": "Arbeitsbereichsbezeichnung anzeigen"
|
||||
}
|
||||
},
|
||||
"tray": {
|
||||
@@ -363,13 +359,6 @@
|
||||
"health": "Zustand: {percent}%",
|
||||
"idle": "Leerlauf.",
|
||||
"no-battery-detected": "Keine Batterie erkannt.",
|
||||
"panel": {
|
||||
"balanced": "Ausgeglichen ({percent}%)",
|
||||
"disabled": "Batteriemanager deaktiviert",
|
||||
"full": "Volle Kapazität ({percent}%)",
|
||||
"lifespan": "Verlängerte Lebensdauer ({percent}%)",
|
||||
"title": "Ladeschwelle"
|
||||
},
|
||||
"plugged-in": "Angeschlossen.",
|
||||
"time-left": "Verbleibende Zeit: {time}.",
|
||||
"time-until-full": "Zeit bis vollständig geladen: {time}."
|
||||
@@ -564,6 +553,7 @@
|
||||
},
|
||||
"workspace-labels": {
|
||||
"index": "Index",
|
||||
"index+name": "Index und Name",
|
||||
"name": "Name",
|
||||
"none": "Keine"
|
||||
}
|
||||
@@ -1663,6 +1653,10 @@
|
||||
}
|
||||
},
|
||||
"user-interface": {
|
||||
"allow-panels-without-bar": {
|
||||
"description": "Wenn aktiviert, können sich Panels auf jedem Bildschirm öffnen. Wenn deaktiviert, öffnen sich Panels nur auf Bildschirmen mit einer Leiste, was den Speicherverbrauch reduzieren kann.",
|
||||
"label": "Paneele auf Bildschirmen ohne Leiste zulassen"
|
||||
},
|
||||
"animation-disable": {
|
||||
"description": "Deaktivieren Sie alle Animationen für eine schnellere und reaktionsfreudigere Erfahrung.",
|
||||
"label": "UI-Animationen deaktivieren"
|
||||
@@ -1870,19 +1864,6 @@
|
||||
"low": "Niedriger Batteriestand",
|
||||
"low-desc": "Batterie ist bei {percent}%. Bitte schließen Sie das Ladegerät an."
|
||||
},
|
||||
"battery-manager": {
|
||||
"initial-setup": "Ersteinrichtung erforderlich",
|
||||
"install-failed": "Installation fehlgeschlagen",
|
||||
"install-missing": "Erforderliche Dateien fehlen",
|
||||
"install-success": "Erfolgreich installiert",
|
||||
"install-unsupported": "System wird nicht unterstützt",
|
||||
"set-failed": "Fehler beim Setzen der Batterieschwelle",
|
||||
"set-success-desc": "Batterieschwelle auf {percent}% gesetzt",
|
||||
"title": "Batterieschwelle",
|
||||
"uninstall-failed": "Deinstallation fehlgeschlagen",
|
||||
"uninstall-setup": "Deinstallation, Authentifizierung erforderlich",
|
||||
"uninstall-success": "Erfolgreich deinstalliert"
|
||||
},
|
||||
"bluetooth": {
|
||||
"disabled": "Deaktiviert",
|
||||
"enabled": "Aktiviert"
|
||||
|
||||
@@ -318,13 +318,9 @@
|
||||
}
|
||||
},
|
||||
"taskbar-grouped": {
|
||||
"show-numbers-only-when-occupied": {
|
||||
"show-labels-only-when-occupied": {
|
||||
"description": "Only show workspace label for workspaces that have open windows.",
|
||||
"label": "Show label only when occupied"
|
||||
},
|
||||
"show-workspace-numbers": {
|
||||
"description": "Display workspace label in the top-left corner of task groups.",
|
||||
"label": "Show workspace label"
|
||||
}
|
||||
},
|
||||
"tray": {
|
||||
@@ -367,13 +363,6 @@
|
||||
"health": "Health: {percent}%",
|
||||
"idle": "Idle.",
|
||||
"no-battery-detected": "No battery detected.",
|
||||
"panel": {
|
||||
"balanced": "Balanced ({percent}%)",
|
||||
"disabled": "Battery manager disabled",
|
||||
"full": "Full capacity ({percent}%)",
|
||||
"lifespan": "Extended lifespan ({percent}%)",
|
||||
"title": "Charge threshold"
|
||||
},
|
||||
"plugged-in": "Plugged in.",
|
||||
"time-left": "Time left: {time}.",
|
||||
"time-until-full": "Time until full: {time}."
|
||||
@@ -568,6 +557,7 @@
|
||||
},
|
||||
"workspace-labels": {
|
||||
"index": "Index",
|
||||
"index+name": "Index and Name",
|
||||
"name": "Name",
|
||||
"none": "None"
|
||||
}
|
||||
@@ -1667,6 +1657,10 @@
|
||||
}
|
||||
},
|
||||
"user-interface": {
|
||||
"allow-panels-without-bar": {
|
||||
"description": "When enabled, panels can open on any screen. When disabled, panels will only open on screens that have a bar, which can reduce memory usage.",
|
||||
"label": "Allow panels on screens without a bar"
|
||||
},
|
||||
"animation-disable": {
|
||||
"description": "Disable all animations for a faster, more responsive experience.",
|
||||
"label": "Disable UI Animations"
|
||||
@@ -1874,19 +1868,6 @@
|
||||
"low": "Low battery",
|
||||
"low-desc": "Battery is at {percent}%. Please connect the charger."
|
||||
},
|
||||
"battery-manager": {
|
||||
"initial-setup": "Initial setup required",
|
||||
"install-failed": "Installation failed",
|
||||
"install-missing": "Required files are missing",
|
||||
"install-success": "Installed successfully",
|
||||
"install-unsupported": "System is not supported",
|
||||
"set-failed": "Failed to set battery threshold",
|
||||
"set-success-desc": "Battery threshold set to {percent}%",
|
||||
"title": "Battery threshold",
|
||||
"uninstall-failed": "Uninstallation failed",
|
||||
"uninstall-setup": "Uninstalling, authentication required",
|
||||
"uninstall-success": "Uninstalled successfully"
|
||||
},
|
||||
"bluetooth": {
|
||||
"disabled": "Disabled",
|
||||
"enabled": "Enabled"
|
||||
|
||||
@@ -314,13 +314,9 @@
|
||||
}
|
||||
},
|
||||
"taskbar-grouped": {
|
||||
"show-numbers-only-when-occupied": {
|
||||
"show-labels-only-when-occupied": {
|
||||
"description": "Mostrar solo la etiqueta del espacio de trabajo para los espacios de trabajo que tienen ventanas abiertas.",
|
||||
"label": "Mostrar etiqueta solo cuando esté ocupado."
|
||||
},
|
||||
"show-workspace-numbers": {
|
||||
"description": "Mostrar la etiqueta del espacio de trabajo en la esquina superior izquierda de los grupos de tareas.",
|
||||
"label": "Mostrar etiqueta del espacio de trabajo"
|
||||
}
|
||||
},
|
||||
"tray": {
|
||||
@@ -363,13 +359,6 @@
|
||||
"health": "Salud: {percent}%",
|
||||
"idle": "Inactivo.",
|
||||
"no-battery-detected": "No se detectó ninguna batería.",
|
||||
"panel": {
|
||||
"balanced": "Equilibrado ({percent}%)",
|
||||
"disabled": "Administrador de batería deshabilitado",
|
||||
"full": "Capacidad total ({percent}%)",
|
||||
"lifespan": "Vida útil prolongada ({percent}%)",
|
||||
"title": "Umbral de carga"
|
||||
},
|
||||
"plugged-in": "Conectado.",
|
||||
"time-left": "Tiempo restante: {time}.",
|
||||
"time-until-full": "Tiempo hasta carga completa: {time}."
|
||||
@@ -564,6 +553,7 @@
|
||||
},
|
||||
"workspace-labels": {
|
||||
"index": "Índice",
|
||||
"index+name": "Índice y Nombre",
|
||||
"name": "Nombre",
|
||||
"none": "Ninguno"
|
||||
}
|
||||
@@ -1663,6 +1653,10 @@
|
||||
}
|
||||
},
|
||||
"user-interface": {
|
||||
"allow-panels-without-bar": {
|
||||
"description": "Cuando está activado, los paneles se pueden abrir en cualquier pantalla. Cuando está desactivado, los paneles solo se abrirán en las pantallas que tengan una barra, lo que puede reducir el uso de memoria.",
|
||||
"label": "Permitir paneles en pantallas sin barra."
|
||||
},
|
||||
"animation-disable": {
|
||||
"description": "Desactiva todas las animaciones para una experiencia más rápida y con mayor capacidad de respuesta.",
|
||||
"label": "Desactivar animaciones de la interfaz de usuario"
|
||||
@@ -1870,19 +1864,6 @@
|
||||
"low": "Batería baja",
|
||||
"low-desc": "La batería está al {percent}%. Por favor, conecta el cargador."
|
||||
},
|
||||
"battery-manager": {
|
||||
"initial-setup": "Configuración inicial requerida",
|
||||
"install-failed": "Error en la instalación",
|
||||
"install-missing": "Faltan archivos requeridos",
|
||||
"install-success": "Instalado correctamente",
|
||||
"install-unsupported": "El sistema no es compatible",
|
||||
"set-failed": "No se pudo establecer el umbral de batería",
|
||||
"set-success-desc": "Umbral de batería establecido en {percent}%",
|
||||
"title": "Umbral de batería",
|
||||
"uninstall-failed": "Error en la desinstalación",
|
||||
"uninstall-setup": "Desinstalando, se requiere autenticación",
|
||||
"uninstall-success": "Desinstalado correctamente"
|
||||
},
|
||||
"bluetooth": {
|
||||
"disabled": "Desactivado",
|
||||
"enabled": "Activado"
|
||||
|
||||
@@ -314,13 +314,9 @@
|
||||
}
|
||||
},
|
||||
"taskbar-grouped": {
|
||||
"show-numbers-only-when-occupied": {
|
||||
"show-labels-only-when-occupied": {
|
||||
"description": "Afficher uniquement l'étiquette de l'espace de travail pour les espaces de travail qui ont des fenêtres ouvertes.",
|
||||
"label": "Afficher l'étiquette uniquement lorsque c'est occupé."
|
||||
},
|
||||
"show-workspace-numbers": {
|
||||
"description": "Afficher l'étiquette de l'espace de travail dans le coin supérieur gauche des groupes de tâches.",
|
||||
"label": "Afficher l'étiquette de l'espace de travail"
|
||||
}
|
||||
},
|
||||
"tray": {
|
||||
@@ -363,13 +359,6 @@
|
||||
"health": "État : {percent}%",
|
||||
"idle": "Inactif.",
|
||||
"no-battery-detected": "Aucune batterie détectée.",
|
||||
"panel": {
|
||||
"balanced": "Équilibré ({percent}%)",
|
||||
"disabled": "Gestionnaire de batterie désactivé",
|
||||
"full": "Capacité totale ({percent}%)",
|
||||
"lifespan": "Durée de vie prolongée ({percent}%)",
|
||||
"title": "Seuil de charge"
|
||||
},
|
||||
"plugged-in": "Branché.",
|
||||
"time-left": "Temps restant : {time}.",
|
||||
"time-until-full": "Temps jusqu'à charge complète : {time}."
|
||||
@@ -564,6 +553,7 @@
|
||||
},
|
||||
"workspace-labels": {
|
||||
"index": "Index",
|
||||
"index+name": "Index et Nom",
|
||||
"name": "Nom",
|
||||
"none": "Aucun"
|
||||
}
|
||||
@@ -1663,6 +1653,10 @@
|
||||
}
|
||||
},
|
||||
"user-interface": {
|
||||
"allow-panels-without-bar": {
|
||||
"description": "Quand cette option est activée, les panneaux peuvent s'ouvrir sur n'importe quel écran. Quand elle est désactivée, les panneaux s'ouvriront uniquement sur les écrans qui ont une barre, ce qui peut réduire l'utilisation de la mémoire.",
|
||||
"label": "Autoriser les panneaux sur les écrans sans barre."
|
||||
},
|
||||
"animation-disable": {
|
||||
"description": "Désactiver toutes les animations pour une expérience plus rapide et plus réactive.",
|
||||
"label": "Désactiver les animations de l'interface utilisateur"
|
||||
@@ -1870,19 +1864,6 @@
|
||||
"low": "Batterie faible",
|
||||
"low-desc": "La batterie est à {percent}%. Veuillez brancher le chargeur."
|
||||
},
|
||||
"battery-manager": {
|
||||
"initial-setup": "Configuration initiale requise",
|
||||
"install-failed": "Échec de l'installation",
|
||||
"install-missing": "Fichiers requis manquants",
|
||||
"install-success": "Installation réussie",
|
||||
"install-unsupported": "Système non pris en charge",
|
||||
"set-failed": "Échec de la définition du seuil de batterie",
|
||||
"set-success-desc": "Seuil de batterie défini à {percent}%",
|
||||
"title": "Seuil de batterie",
|
||||
"uninstall-failed": "Échec de la désinstallation",
|
||||
"uninstall-setup": "Désinstallation, authentification requise",
|
||||
"uninstall-success": "Désinstallation réussie"
|
||||
},
|
||||
"bluetooth": {
|
||||
"disabled": "Désactivé",
|
||||
"enabled": "Activé"
|
||||
|
||||
@@ -314,13 +314,9 @@
|
||||
}
|
||||
},
|
||||
"taskbar-grouped": {
|
||||
"show-numbers-only-when-occupied": {
|
||||
"show-labels-only-when-occupied": {
|
||||
"description": "Toon alleen de werkomgevinglabels voor werkomgevingen met open vensters.",
|
||||
"label": "Label alleen tonen wanneer bezet"
|
||||
},
|
||||
"show-workspace-numbers": {
|
||||
"description": "Toon het label van de werkruimte in de linkerbovenhoek van taakgroepen.",
|
||||
"label": "Werkruimtelabel weergeven"
|
||||
}
|
||||
},
|
||||
"tray": {
|
||||
@@ -363,13 +359,6 @@
|
||||
"health": "Gezondheid: {percent}%",
|
||||
"idle": "In rust.",
|
||||
"no-battery-detected": "Geen batterij gedetecteerd.",
|
||||
"panel": {
|
||||
"balanced": "Gebalanceerd ({percent}%)",
|
||||
"disabled": "Batterijbeheer uitgeschakeld",
|
||||
"full": "Volledige capaciteit ({percent}%)",
|
||||
"lifespan": "Verlengde levensduur ({percent}%)",
|
||||
"title": "Batterijdrempel"
|
||||
},
|
||||
"plugged-in": "Op netstroom.",
|
||||
"time-left": "Resterende tijd: {time}.",
|
||||
"time-until-full": "Tijd tot vol: {time}."
|
||||
@@ -564,6 +553,7 @@
|
||||
},
|
||||
"workspace-labels": {
|
||||
"index": "Index",
|
||||
"index+name": "Index en Naam",
|
||||
"name": "Naam",
|
||||
"none": "Geen"
|
||||
}
|
||||
@@ -1663,6 +1653,10 @@
|
||||
}
|
||||
},
|
||||
"user-interface": {
|
||||
"allow-panels-without-bar": {
|
||||
"description": "Indien ingeschakeld, kunnen panelen op elk scherm worden geopend. Indien uitgeschakeld, worden panelen alleen geopend op schermen met een balk, wat het geheugengebruik kan verminderen.",
|
||||
"label": "Sta panelen toe op schermen zonder balk."
|
||||
},
|
||||
"animation-disable": {
|
||||
"description": "Schakel alle animaties uit voor een snellere, responsievere ervaring.",
|
||||
"label": "UI-animaties uitschakelen"
|
||||
@@ -1870,19 +1864,6 @@
|
||||
"low": "Batterij bijna leeg",
|
||||
"low-desc": "De batterij is op {percent}%. Sluit de oplader aan."
|
||||
},
|
||||
"battery-manager": {
|
||||
"initial-setup": "Initiële configuratie vereist",
|
||||
"install-failed": "Installatie mislukt",
|
||||
"install-missing": "Vereiste bestanden ontbreken",
|
||||
"install-success": "Succesvol geïnstalleerd",
|
||||
"install-unsupported": "Systeem wordt niet ondersteund",
|
||||
"set-failed": "Instellen van batterijdrempel mislukt",
|
||||
"set-success-desc": "Batterijdrempel ingesteld op {percent}%",
|
||||
"title": "Batterijdrempel",
|
||||
"uninstall-failed": "Deïnstallatie mislukt",
|
||||
"uninstall-setup": "Verwijderen, authenticatie vereist",
|
||||
"uninstall-success": "Succesvol verwijderd"
|
||||
},
|
||||
"bluetooth": {
|
||||
"disabled": "Uitgeschakeld",
|
||||
"enabled": "Ingeschakeld"
|
||||
|
||||
@@ -314,13 +314,9 @@
|
||||
}
|
||||
},
|
||||
"taskbar-grouped": {
|
||||
"show-numbers-only-when-occupied": {
|
||||
"show-labels-only-when-occupied": {
|
||||
"description": "Mostrar apenas o rótulo da área de trabalho para áreas de trabalho que têm janelas abertas.",
|
||||
"label": "Mostrar rótulo somente quando ocupado."
|
||||
},
|
||||
"show-workspace-numbers": {
|
||||
"description": "Exibir o rótulo do espaço de trabalho no canto superior esquerdo dos grupos de tarefas.",
|
||||
"label": "Mostrar rótulo do espaço de trabalho"
|
||||
}
|
||||
},
|
||||
"tray": {
|
||||
@@ -363,13 +359,6 @@
|
||||
"health": "Saúde: {percent}%",
|
||||
"idle": "Ocioso.",
|
||||
"no-battery-detected": "Nenhuma bateria detectada.",
|
||||
"panel": {
|
||||
"balanced": "Balanceado ({percent}%)",
|
||||
"disabled": "Gerenciador de bateria desativado",
|
||||
"full": "Capacidade máxima ({percent}%)",
|
||||
"lifespan": "Vida útil prolongada ({percent}%)",
|
||||
"title": "Limite de carga"
|
||||
},
|
||||
"plugged-in": "Conectado.",
|
||||
"time-left": "Tempo restante: {time}.",
|
||||
"time-until-full": "Tempo até carga completa: {time}."
|
||||
@@ -564,6 +553,7 @@
|
||||
},
|
||||
"workspace-labels": {
|
||||
"index": "Índice",
|
||||
"index+name": "Índice e Nome",
|
||||
"name": "Nome",
|
||||
"none": "Nenhum"
|
||||
}
|
||||
@@ -1663,6 +1653,10 @@
|
||||
}
|
||||
},
|
||||
"user-interface": {
|
||||
"allow-panels-without-bar": {
|
||||
"description": "Quando ativado, os painéis podem abrir em qualquer tela. Quando desativado, os painéis só abrirão em telas que tenham uma barra, o que pode reduzir o uso de memória.",
|
||||
"label": "Permitir painéis em telas sem uma barra."
|
||||
},
|
||||
"animation-disable": {
|
||||
"description": "Desative todas as animações para uma experiência mais rápida e responsiva.",
|
||||
"label": "Desativar animações da interface do usuário"
|
||||
@@ -1870,19 +1864,6 @@
|
||||
"low": "Bateria Fraca",
|
||||
"low-desc": "A bateria está em {percent}%. Por favor, conecte o carregador."
|
||||
},
|
||||
"battery-manager": {
|
||||
"initial-setup": "Configuração inicial necessária",
|
||||
"install-failed": "Falha na instalação",
|
||||
"install-missing": "Arquivos necessários ausentes",
|
||||
"install-success": "Instalado com sucesso",
|
||||
"install-unsupported": "Sistema não suportado",
|
||||
"set-failed": "Falha ao definir o limite da bateria",
|
||||
"set-success-desc": "Limite da bateria definido para {percent}%",
|
||||
"title": "Limite da bateria",
|
||||
"uninstall-failed": "Falha na desinstalação",
|
||||
"uninstall-setup": "Desinstalando, autenticação necessária",
|
||||
"uninstall-success": "Desinstalado com sucesso"
|
||||
},
|
||||
"bluetooth": {
|
||||
"disabled": "Desativado",
|
||||
"enabled": "Ativado"
|
||||
|
||||
@@ -314,13 +314,9 @@
|
||||
}
|
||||
},
|
||||
"taskbar-grouped": {
|
||||
"show-numbers-only-when-occupied": {
|
||||
"show-labels-only-when-occupied": {
|
||||
"description": "Отображать метку рабочего пространства только для тех рабочих пространств, в которых есть открытые окна.",
|
||||
"label": "Показывать метку только когда занято"
|
||||
},
|
||||
"show-workspace-numbers": {
|
||||
"description": "Отображать метку рабочего пространства в верхнем левом углу групп задач.",
|
||||
"label": "Показать метку рабочей области"
|
||||
}
|
||||
},
|
||||
"tray": {
|
||||
@@ -363,13 +359,6 @@
|
||||
"health": "Здоровье: {percent}%",
|
||||
"idle": "Простой.",
|
||||
"no-battery-detected": "Батарея не обнаружена.",
|
||||
"panel": {
|
||||
"balanced": "Сбалансированный ({percent}%)",
|
||||
"disabled": "Диспетчер батареи отключен",
|
||||
"full": "Полная емкость ({percent}%)",
|
||||
"lifespan": "Увеличенный срок службы ({percent}%)",
|
||||
"title": "Порог зарядки"
|
||||
},
|
||||
"plugged-in": "Подключено.",
|
||||
"time-left": "Осталось времени: {time}.",
|
||||
"time-until-full": "Время до полной зарядки: {time}."
|
||||
@@ -564,6 +553,7 @@
|
||||
},
|
||||
"workspace-labels": {
|
||||
"index": "Индекс",
|
||||
"index+name": "Индекс и название",
|
||||
"name": "Имя",
|
||||
"none": "Нет"
|
||||
}
|
||||
@@ -1663,6 +1653,10 @@
|
||||
}
|
||||
},
|
||||
"user-interface": {
|
||||
"allow-panels-without-bar": {
|
||||
"description": "Когда включено, панели могут открываться на любом экране. Когда выключено, панели будут открываться только на экранах с панелью, что может снизить использование памяти.",
|
||||
"label": "Разрешить панели на экранах без панели задач."
|
||||
},
|
||||
"animation-disable": {
|
||||
"description": "Отключить все анимации для более быстрого и отзывчивого взаимодействия.",
|
||||
"label": "Отключить анимации интерфейса"
|
||||
@@ -1870,19 +1864,6 @@
|
||||
"low": "Низкий заряд батареи",
|
||||
"low-desc": "Заряд батареи {percent}%. Пожалуйста, подключите зарядное устройство."
|
||||
},
|
||||
"battery-manager": {
|
||||
"initial-setup": "Требуется начальная настройка",
|
||||
"install-failed": "Сбой установки",
|
||||
"install-missing": "Отсутствуют необходимые файлы",
|
||||
"install-success": "Успешно установлено",
|
||||
"install-unsupported": "Система не поддерживается",
|
||||
"set-failed": "Не удалось установить порог зарядки батареи",
|
||||
"set-success-desc": "Порог зарядки батареи установлен на {percent}%",
|
||||
"title": "Порог зарядки батареи",
|
||||
"uninstall-failed": "Сбой удаления",
|
||||
"uninstall-setup": "Удаление, требуется аутентификация",
|
||||
"uninstall-success": "Успешно удалено"
|
||||
},
|
||||
"bluetooth": {
|
||||
"disabled": "Отключен",
|
||||
"enabled": "Включен"
|
||||
|
||||
@@ -314,13 +314,9 @@
|
||||
}
|
||||
},
|
||||
"taskbar-grouped": {
|
||||
"show-numbers-only-when-occupied": {
|
||||
"show-labels-only-when-occupied": {
|
||||
"description": "Sadece açık pencereleri olan çalışma alanları için çalışma alanı etiketini göster.",
|
||||
"label": "Sadece dolu olduğunda etiketi göster."
|
||||
},
|
||||
"show-workspace-numbers": {
|
||||
"description": "Görev gruplarının sol üst köşesinde çalışma alanı etiketini görüntüle.",
|
||||
"label": "Çalışma alanı etiketini göster"
|
||||
}
|
||||
},
|
||||
"tray": {
|
||||
@@ -363,13 +359,6 @@
|
||||
"health": "Sağlık: {percent}%",
|
||||
"idle": "Boşta.",
|
||||
"no-battery-detected": "Pil tespit edilmedi.",
|
||||
"panel": {
|
||||
"balanced": "Dengeli ({percent}%)",
|
||||
"disabled": "Pil yöneticisi devre dışı",
|
||||
"full": "Tam kapasite ({percent}%)",
|
||||
"lifespan": "Uzatılmış ömür ({percent}%)",
|
||||
"title": "Şarj eşiği"
|
||||
},
|
||||
"plugged-in": "Prize takılı.",
|
||||
"time-left": "Kalan süre: {time}.",
|
||||
"time-until-full": "Dolma süresi: {time}."
|
||||
@@ -564,6 +553,7 @@
|
||||
},
|
||||
"workspace-labels": {
|
||||
"index": "İndeks",
|
||||
"index+name": "Dizin ve Ad",
|
||||
"name": "İsim",
|
||||
"none": "Hiçbiri"
|
||||
}
|
||||
@@ -1663,6 +1653,10 @@
|
||||
}
|
||||
},
|
||||
"user-interface": {
|
||||
"allow-panels-without-bar": {
|
||||
"description": "Etkinleştirildiğinde, paneller herhangi bir ekranda açılabilir. Devre dışı bırakıldığında, paneller yalnızca bir çubuğu olan ekranlarda açılır, bu da bellek kullanımını azaltabilir.",
|
||||
"label": "Çubuksuz ekranlardaki panellere izin ver."
|
||||
},
|
||||
"animation-disable": {
|
||||
"description": "Daha hızlı ve daha duyarlı bir deneyim için tüm animasyonları devre dışı bırakın.",
|
||||
"label": "UI Animasyonlarını Devre Dışı Bırak"
|
||||
@@ -1870,19 +1864,6 @@
|
||||
"low": "Düşük batarya",
|
||||
"low-desc": "Batarya % {percent}. Lütfen şarj bağlantısını yapın."
|
||||
},
|
||||
"battery-manager": {
|
||||
"initial-setup": "Başlangıç kurulumu gerekli",
|
||||
"install-failed": "Kurulum başarısız oldu",
|
||||
"install-missing": "Gerekli dosyalar eksik",
|
||||
"install-success": "Başarıyla kuruldu",
|
||||
"install-unsupported": "Sistem desteklenmiyor",
|
||||
"set-failed": "Batarya eşiği ayarlanamadı",
|
||||
"set-success-desc": "Batarya eşiği % {percent} olarak ayarlandı",
|
||||
"title": "Batarya eşiği",
|
||||
"uninstall-failed": "Kaldırma başarısız oldu",
|
||||
"uninstall-setup": "Kaldırılıyor, kimlik doğrulama gerekli",
|
||||
"uninstall-success": "Başarıyla kaldırıldı"
|
||||
},
|
||||
"bluetooth": {
|
||||
"disabled": "Devre dışı",
|
||||
"enabled": "Etkin"
|
||||
|
||||
@@ -314,13 +314,9 @@
|
||||
}
|
||||
},
|
||||
"taskbar-grouped": {
|
||||
"show-numbers-only-when-occupied": {
|
||||
"show-labels-only-when-occupied": {
|
||||
"description": "Показувати мітку робочого простору лише для робочих просторів, які мають відкриті вікна.",
|
||||
"label": "Показувати мітку лише коли зайнято"
|
||||
},
|
||||
"show-workspace-numbers": {
|
||||
"description": "Відображати мітку робочої області у верхньому лівому куті груп завдань.",
|
||||
"label": "Показати мітку робочої області"
|
||||
}
|
||||
},
|
||||
"tray": {
|
||||
@@ -363,13 +359,6 @@
|
||||
"health": "Здоров'я: {percent}%",
|
||||
"idle": "Бездіяльність.",
|
||||
"no-battery-detected": "Батарею не виявлено.",
|
||||
"panel": {
|
||||
"balanced": "Збалансований ({percent}%)",
|
||||
"disabled": "Менеджер батареї вимкнено",
|
||||
"full": "Повна ємність ({percent}%)",
|
||||
"lifespan": "Подовжений термін служби ({percent}%)",
|
||||
"title": "Поріг зарядки"
|
||||
},
|
||||
"plugged-in": "Підключено.",
|
||||
"time-left": "Залишилось часу: {time}.",
|
||||
"time-until-full": "Час до повного заряду: {time}."
|
||||
@@ -564,6 +553,7 @@
|
||||
},
|
||||
"workspace-labels": {
|
||||
"index": "Індекс",
|
||||
"index+name": "Індекс та назва",
|
||||
"name": "Назва",
|
||||
"none": "Немає"
|
||||
}
|
||||
@@ -1663,6 +1653,10 @@
|
||||
}
|
||||
},
|
||||
"user-interface": {
|
||||
"allow-panels-without-bar": {
|
||||
"description": "Якщо ввімкнено, панелі можуть відкриватися на будь-якому екрані. Якщо вимкнено, панелі відкриватимуться лише на екранах, де є панель завдань, що може зменшити використання пам'яті.",
|
||||
"label": "Дозволити панелі на екранах без панелі завдань."
|
||||
},
|
||||
"animation-disable": {
|
||||
"description": "Вимкнути всі анімації для швидшого, більш відгукового досвіду.",
|
||||
"label": "Вимкнути анімації інтерфейсу"
|
||||
@@ -1870,19 +1864,6 @@
|
||||
"low": "Низький заряд батареї",
|
||||
"low-desc": "Батарея на {percent}%. Будь ласка, підключіть зарядний пристрій."
|
||||
},
|
||||
"battery-manager": {
|
||||
"initial-setup": "Потрібне початкове налаштування",
|
||||
"install-failed": "Помилка встановлення",
|
||||
"install-missing": "Необхідні файли відсутні",
|
||||
"install-success": "Встановлено успішно",
|
||||
"install-unsupported": "Система не підтримується",
|
||||
"set-failed": "Не вдалося встановити поріг батареї",
|
||||
"set-success-desc": "Поріг батареї встановлено на {percent}%",
|
||||
"title": "Поріг батареї",
|
||||
"uninstall-failed": "Помилка видалення",
|
||||
"uninstall-setup": "Видалення, потрібна автентифікація",
|
||||
"uninstall-success": "Видалено успішно"
|
||||
},
|
||||
"bluetooth": {
|
||||
"disabled": "Вимкнено",
|
||||
"enabled": "Увімкнено"
|
||||
|
||||
@@ -314,13 +314,9 @@
|
||||
}
|
||||
},
|
||||
"taskbar-grouped": {
|
||||
"show-numbers-only-when-occupied": {
|
||||
"show-labels-only-when-occupied": {
|
||||
"description": "只显示有打开窗口的工作区标签。",
|
||||
"label": "仅在被占用时显示标签"
|
||||
},
|
||||
"show-workspace-numbers": {
|
||||
"description": "在任务组左上角显示工作区标签。",
|
||||
"label": "显示工作区标签"
|
||||
}
|
||||
},
|
||||
"tray": {
|
||||
@@ -363,13 +359,6 @@
|
||||
"health": "健康状况:{percent}%",
|
||||
"idle": "空闲。",
|
||||
"no-battery-detected": "未检测到电池。",
|
||||
"panel": {
|
||||
"balanced": "平衡 ({percent}%)",
|
||||
"disabled": "电池管理器已禁用",
|
||||
"full": "完全容量 ({percent}%)",
|
||||
"lifespan": "延长寿命 ({percent}%)",
|
||||
"title": "充电阈值"
|
||||
},
|
||||
"plugged-in": "已接通电源。",
|
||||
"time-left": "剩余时间:{time}。",
|
||||
"time-until-full": "充满所需时间:{time}。"
|
||||
@@ -564,6 +553,7 @@
|
||||
},
|
||||
"workspace-labels": {
|
||||
"index": "索引",
|
||||
"index+name": "索引和名称",
|
||||
"name": "名称",
|
||||
"none": "无"
|
||||
}
|
||||
@@ -1663,6 +1653,10 @@
|
||||
}
|
||||
},
|
||||
"user-interface": {
|
||||
"allow-panels-without-bar": {
|
||||
"description": "启用后,面板可以在任何屏幕上打开。禁用后,面板将仅在有栏的屏幕上打开,这可以减少内存使用。",
|
||||
"label": "允许在没有栏的屏幕上显示面板"
|
||||
},
|
||||
"animation-disable": {
|
||||
"description": "禁用所有动画以获得更快、更流畅的体验。",
|
||||
"label": "禁用 UI 动画"
|
||||
@@ -1870,19 +1864,6 @@
|
||||
"low": "电量低",
|
||||
"low-desc": "电量为 {percent}%。请连接充电器。"
|
||||
},
|
||||
"battery-manager": {
|
||||
"initial-setup": "需要初始设置",
|
||||
"install-failed": "安装失败",
|
||||
"install-missing": "缺少必要文件",
|
||||
"install-success": "安装成功",
|
||||
"install-unsupported": "系统不受支持",
|
||||
"set-failed": "设置电池阈值失败",
|
||||
"set-success-desc": "电池阈值已设置为 {percent}%",
|
||||
"title": "电池阈值",
|
||||
"uninstall-failed": "卸载失败",
|
||||
"uninstall-setup": "正在卸载,需要身份验证",
|
||||
"uninstall-success": "卸载成功"
|
||||
},
|
||||
"bluetooth": {
|
||||
"disabled": "已禁用",
|
||||
"enabled": "已启用"
|
||||
|
||||
@@ -73,7 +73,8 @@
|
||||
"shadowDirection": "bottom_right",
|
||||
"shadowOffsetX": 2,
|
||||
"shadowOffsetY": 3,
|
||||
"language": ""
|
||||
"language": "",
|
||||
"allowPanelsOnScreenWithoutBar": true
|
||||
},
|
||||
"ui": {
|
||||
"fontDefault": "Roboto",
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
# Battery charge control paths
|
||||
# Add one path per line
|
||||
/sys/class/power_supply/BAT0/charge_control_end_threshold
|
||||
/sys/class/power_supply/BAT1/charge_control_end_threshold
|
||||
/sys/class/power_supply/BAT0/charge_stop_threshold
|
||||
@@ -1,178 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
SUCCESS=0
|
||||
FAILURE=1
|
||||
MISSING_FILES=2
|
||||
UNSUPPORTED=3
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
print_error() {
|
||||
echo -e "$1" >&2
|
||||
}
|
||||
|
||||
print_info() {
|
||||
echo -e "$1"
|
||||
}
|
||||
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
print_error "This script must be run with root privileges"
|
||||
exit $FAILURE
|
||||
fi
|
||||
|
||||
print_info "Installing Battery Manager..."
|
||||
echo
|
||||
|
||||
if [ -n "$PKEXEC_UID" ]; then
|
||||
ACTUAL_USER=$(getent passwd "$PKEXEC_UID" | cut -d: -f1)
|
||||
else
|
||||
ACTUAL_USER="$SUDO_USER"
|
||||
fi
|
||||
|
||||
if [ -z "$ACTUAL_USER" ]; then
|
||||
print_error "Could not determine the actual user"
|
||||
exit $FAILURE
|
||||
fi
|
||||
|
||||
print_info "Installing for user: $ACTUAL_USER"
|
||||
echo
|
||||
|
||||
print_info "Checking required files..."
|
||||
|
||||
MISSING_FILES_LIST=()
|
||||
|
||||
if [ ! -f "$SCRIPT_DIR/battery-paths.conf" ]; then
|
||||
MISSING_FILES_LIST+=("battery-paths.conf")
|
||||
fi
|
||||
|
||||
if [ ! -f "$SCRIPT_DIR/templates/battery-manager.sh" ]; then
|
||||
MISSING_FILES_LIST+=("battery-manager.sh")
|
||||
fi
|
||||
|
||||
if [ ! -f "$SCRIPT_DIR/templates/battery-manager.policy" ]; then
|
||||
MISSING_FILES_LIST+=("battery-manager.policy")
|
||||
fi
|
||||
|
||||
if [ ! -f "$SCRIPT_DIR/templates/battery-manager.rules" ]; then
|
||||
MISSING_FILES_LIST+=("battery-manager.rules")
|
||||
fi
|
||||
|
||||
if [ ${#MISSING_FILES_LIST[@]} -gt 0 ]; then
|
||||
print_error "Missing required files in $SCRIPT_DIR:"
|
||||
for file in "${MISSING_FILES_LIST[@]}"; do
|
||||
print_error " - $file"
|
||||
done
|
||||
exit $MISSING_FILES
|
||||
fi
|
||||
|
||||
print_info "All required files found"
|
||||
|
||||
print_info "Checking battery paths..."
|
||||
BATTERY_PATHS=($(grep -v '^#' "$SCRIPT_DIR/battery-paths.conf" | grep -v '^$'))
|
||||
EXISTING_PATHS=()
|
||||
|
||||
for path in "${BATTERY_PATHS[@]}"; do
|
||||
if [ -f "$path" ]; then
|
||||
EXISTING_PATHS+=("$path")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#EXISTING_PATHS[@]} -eq 0 ]; then
|
||||
print_error "None of the battery control files exist. Please check your hardware compatibility."
|
||||
exit $UNSUPPORTED
|
||||
fi
|
||||
|
||||
print_info "Found ${#EXISTING_PATHS[@]} compatible battery control file(s)"
|
||||
|
||||
print_info "Installing battery manager script..."
|
||||
BATTERY_MANAGER_SCRIPT="/usr/bin/battery-manager-$ACTUAL_USER"
|
||||
|
||||
SHEBANG=$(head -n 1 "$SCRIPT_DIR/templates/battery-manager.sh")
|
||||
echo "$SHEBANG" > "$BATTERY_MANAGER_SCRIPT"
|
||||
echo "" >> "$BATTERY_MANAGER_SCRIPT"
|
||||
|
||||
echo "BATTERY_PATHS=(" >> "$BATTERY_MANAGER_SCRIPT"
|
||||
for path in "${EXISTING_PATHS[@]}"; do
|
||||
echo " \"$path\"" >> "$BATTERY_MANAGER_SCRIPT"
|
||||
done
|
||||
echo ")" >> "$BATTERY_MANAGER_SCRIPT"
|
||||
|
||||
echo "" >> "$BATTERY_MANAGER_SCRIPT"
|
||||
|
||||
tail -n +2 "$SCRIPT_DIR/templates/battery-manager.sh" >> "$BATTERY_MANAGER_SCRIPT"
|
||||
|
||||
chmod +x "$BATTERY_MANAGER_SCRIPT"
|
||||
|
||||
print_info "Battery manager script created from $SCRIPT_DIR/templates/battery-manager.sh with compatible paths"
|
||||
print_info "Script installed at $BATTERY_MANAGER_SCRIPT"
|
||||
|
||||
print_info "Creating log file..."
|
||||
touch /var/log/battery-manager.log
|
||||
chmod 644 /var/log/battery-manager.log
|
||||
print_info "Log file created at /var/log/battery-manager.log"
|
||||
|
||||
print_info "Creating polkit policy..."
|
||||
|
||||
POLICY_FILE="/usr/share/polkit-1/actions/com.local.battery-manager.$ACTUAL_USER.policy"
|
||||
|
||||
sed -e "s/ACTUAL_USER_PLACEHOLDER/$ACTUAL_USER/g" \
|
||||
"$SCRIPT_DIR/templates/battery-manager.policy" > "$POLICY_FILE"
|
||||
|
||||
print_info "Polkit policy copied from $SCRIPT_DIR/templates/battery-manager.policy"
|
||||
print_info "Polkit policy created at $POLICY_FILE"
|
||||
print_info "Creating polkit rule..."
|
||||
|
||||
RULES_FILE="/etc/polkit-1/rules.d/50-battery-manager-$ACTUAL_USER.rules"
|
||||
|
||||
sed "s/ACTUAL_USER_PLACEHOLDER/$ACTUAL_USER/g" \
|
||||
"$SCRIPT_DIR/templates/battery-manager.rules" > "$RULES_FILE"
|
||||
|
||||
print_info "Polkit rule copied from $SCRIPT_DIR/templates/battery-manager.rules"
|
||||
print_info "Polkit rule created for user: $ACTUAL_USER at $RULES_FILE"
|
||||
|
||||
print_info "Restarting polkit..."
|
||||
if systemctl restart polkit 2>/dev/null; then
|
||||
print_info "Polkit restarted"
|
||||
else
|
||||
print_info "Could not restart polkit automatically, you may need to reboot"
|
||||
fi
|
||||
|
||||
print_info "Creating uninstall script..."
|
||||
UNINSTALL_SCRIPT="$SCRIPT_DIR/uninstall-battery-manager.sh"
|
||||
|
||||
|
||||
if [ -f "$SCRIPT_DIR/templates/uninstall-template" ]; then
|
||||
SHEBANG=$(head -n 1 "$SCRIPT_DIR/templates/uninstall-template")
|
||||
else
|
||||
SHEBANG="#!/usr/bin/env bash"
|
||||
fi
|
||||
echo "$SHEBANG" > "$UNINSTALL_SCRIPT"
|
||||
echo "" >> "$UNINSTALL_SCRIPT"
|
||||
|
||||
cat >> "$UNINSTALL_SCRIPT" << EOF
|
||||
SCRIPT_PATH="$BATTERY_MANAGER_SCRIPT"
|
||||
POLICY_PATH="$POLICY_FILE"
|
||||
RULE_PATH="$RULES_FILE"
|
||||
LOG_PATH="/var/log/battery-manager.log"
|
||||
|
||||
EOF
|
||||
|
||||
if [ -f "$SCRIPT_DIR/templates/uninstall-template" ]; then
|
||||
tail -n +2 "$SCRIPT_DIR/templates/uninstall-template" >> "$UNINSTALL_SCRIPT"
|
||||
fi
|
||||
|
||||
chmod 744 "$UNINSTALL_SCRIPT"
|
||||
chown root:root "$UNINSTALL_SCRIPT"
|
||||
|
||||
print_info "Uninstall script created at $UNINSTALL_SCRIPT"
|
||||
|
||||
|
||||
echo
|
||||
print_info "Installation complete!"
|
||||
echo
|
||||
print_info "Log file: /var/log/battery-manager.log"
|
||||
print_info "User-specific script: $BATTERY_MANAGER_SCRIPT"
|
||||
print_info "User-specific policy: $POLICY_FILE"
|
||||
print_info "User-specific rules: $RULES_FILE"
|
||||
print_info "User-specific uninstall script: $UNINSTALL_SCRIPT"
|
||||
@@ -1,86 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SUPPRESS_NOTIFICATIONS=false
|
||||
|
||||
print_error() {
|
||||
echo -e "$1" >&2
|
||||
}
|
||||
|
||||
print_info() {
|
||||
echo -e "$1"
|
||||
}
|
||||
|
||||
send_notification() {
|
||||
local urgency="$1"
|
||||
local title="$2"
|
||||
local message="$3"
|
||||
|
||||
if [ "$SUPPRESS_NOTIFICATIONS" = false ] && command -v notify-send >/dev/null 2>&1; then
|
||||
notify-send -u "$urgency" "$title" "$message"
|
||||
fi
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-q|--quiet)
|
||||
SUPPRESS_NOTIFICATIONS=true
|
||||
shift
|
||||
;;
|
||||
-*)
|
||||
print_error "Unknown option: $1"
|
||||
echo "Usage: $0 [OPTIONS] <number>" >&2
|
||||
echo "Options:" >&2
|
||||
echo " -q, --quiet Suppress notifications" >&2
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
BATTERY_LEVEL="$1"
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$BATTERY_LEVEL" ]; then
|
||||
print_error "Battery level not specified"
|
||||
echo "Usage: $0 [OPTIONS] <number>" >&2
|
||||
echo "Options:" >&2
|
||||
echo " -q, --quiet Suppress notifications" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! [[ "$BATTERY_LEVEL" =~ ^[0-9]+$ ]] || [ "$BATTERY_LEVEL" -gt 100 ] || [ "$BATTERY_LEVEL" -lt 0 ]; then
|
||||
print_error "Battery level must be a number between 0-100"
|
||||
echo "Usage: $0 [OPTIONS] <number>" >&2
|
||||
echo "Options:" >&2
|
||||
echo " -q, --quiet Suppress notifications" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CURRENT_USER="$USER"
|
||||
if [ -z "$CURRENT_USER" ]; then
|
||||
CURRENT_USER="$(whoami)"
|
||||
fi
|
||||
|
||||
BATTERY_MANAGER_PATH="/usr/bin/battery-manager-$CURRENT_USER"
|
||||
|
||||
SUCCESS=0
|
||||
MISSING_FILES=2
|
||||
|
||||
if [ ! -f "$BATTERY_MANAGER_PATH" ]; then
|
||||
print_error "Battery manager components missing for user $CURRENT_USER!"
|
||||
exit $MISSING_FILES
|
||||
fi
|
||||
|
||||
print_info "Setting battery charging threshold to $BATTERY_LEVEL% for user $CURRENT_USER..."
|
||||
|
||||
if pkexec "$BATTERY_MANAGER_PATH" "$BATTERY_LEVEL"; then
|
||||
print_info "Battery charging threshold set to $BATTERY_LEVEL%"
|
||||
send_notification "normal" "Battery Threshold Updated" \
|
||||
"Battery charging threshold has been set to $BATTERY_LEVEL%"
|
||||
else
|
||||
print_error "Failed to set battery charging threshold"
|
||||
send_notification "critical" "Battery Threshold Failed" \
|
||||
"Failed to set battery charging threshold to $BATTERY_LEVEL%"
|
||||
exit 1
|
||||
fi
|
||||
@@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE policyconfig PUBLIC
|
||||
"-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
|
||||
<policyconfig>
|
||||
<action id="com.local.battery-manager.ACTUAL_USER_PLACEHOLDER">
|
||||
<description>Manage battery settings for ACTUAL_USER_PLACEHOLDER</description>
|
||||
<message>Authentication is required to manage battery settings</message>
|
||||
<defaults>
|
||||
<allow_any>no</allow_any>
|
||||
<allow_inactive>no</allow_inactive>
|
||||
<allow_active>yes</allow_active>
|
||||
</defaults>
|
||||
<annotate key="org.freedesktop.policykit.exec.path">/usr/bin/battery-manager-ACTUAL_USER_PLACEHOLDER</annotate>
|
||||
<annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
|
||||
</action>
|
||||
</policyconfig>
|
||||
@@ -1,14 +0,0 @@
|
||||
polkit.addRule(function(action, subject) {
|
||||
if (action.id == "com.local.battery-manager.ACTUAL_USER_PLACEHOLDER" &&
|
||||
subject.user == "ACTUAL_USER_PLACEHOLDER") {
|
||||
|
||||
// Check if the parent process is quickshell or set-battery-threshold
|
||||
var pid = subject.pid;
|
||||
var ppid = polkit.spawn(["ps", "-o", "ppid=", "-p", pid.toString()]).trim();
|
||||
var parentCmd = polkit.spawn(["ps", "-o", "comm=", "-p", ppid]).trim();
|
||||
|
||||
if (parentCmd.indexOf("quickshell") !== -1 || parentCmd.indexOf("set-battery-treshold") !== -1) {
|
||||
return polkit.Result.YES;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1,53 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
LOG_FILE="/var/log/battery-manager.log"
|
||||
|
||||
log_message() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo "Error: No battery level provided" >&2
|
||||
log_message "ERROR: No battery level provided"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BATTERY_LEVEL="$1"
|
||||
|
||||
if ! [[ "$BATTERY_LEVEL" =~ ^[0-9]+$ ]] || [ "$BATTERY_LEVEL" -gt 100 ] || [ "$BATTERY_LEVEL" -lt 0 ]; then
|
||||
echo "Error: Invalid battery level. Must be 0-100" >&2
|
||||
log_message "ERROR: Invalid battery level: $BATTERY_LEVEL"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SUCCESS_COUNT=0
|
||||
FAIL_COUNT=0
|
||||
|
||||
for path in "${BATTERY_PATHS[@]}"; do
|
||||
[[ -z "$path" || "$path" =~ ^# ]] && continue
|
||||
|
||||
if [ -f "$path" ] && [ -w "$path" ]; then
|
||||
if echo "$BATTERY_LEVEL" > "$path" 2>/dev/null; then
|
||||
echo "Updated: $path"
|
||||
log_message "SUCCESS: Updated $path to $BATTERY_LEVEL"
|
||||
((SUCCESS_COUNT++))
|
||||
else
|
||||
echo "Failed to write: $path" >&2
|
||||
log_message "ERROR: Failed to write to $path"
|
||||
((FAIL_COUNT++))
|
||||
fi
|
||||
else
|
||||
echo "Skipped (not found/writable): $path"
|
||||
log_message "INFO: Skipped $path (not found or not writable)"
|
||||
fi
|
||||
done
|
||||
|
||||
log_message "SUMMARY: Updated $SUCCESS_COUNT file(s), failed $FAIL_COUNT, battery level: $BATTERY_LEVEL"
|
||||
|
||||
if [ "$SUCCESS_COUNT" -eq 0 ]; then
|
||||
echo "Error: No battery files were updated" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Successfully updated $SUCCESS_COUNT battery file(s)"
|
||||
exit 0
|
||||
@@ -1,30 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "This script must be run as root"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Uninstalling battery manager..."
|
||||
|
||||
if [ -f "$SCRIPT_PATH" ]; then
|
||||
rm -f "$SCRIPT_PATH"
|
||||
echo "Removed script from $SCRIPT_PATH"
|
||||
fi
|
||||
|
||||
if [ -f "$POLICY_PATH" ]; then
|
||||
rm -f "$POLICY_PATH"
|
||||
echo "Removed policy file from $POLICY_PATH"
|
||||
fi
|
||||
|
||||
if [ -f "$RULE_PATH" ]; then
|
||||
rm -f "$RULE_PATH"
|
||||
echo "Removed udev rule from $RULE_PATH"
|
||||
fi
|
||||
|
||||
if [ -f "$LOG_PATH" ]; then
|
||||
rm -f "$LOG_PATH"
|
||||
echo "Removed log file from $LOG_PATH"
|
||||
fi
|
||||
|
||||
echo "Uninstallation completed successfully"
|
||||
+12
-22
@@ -2,38 +2,28 @@
|
||||
set -euo pipefail
|
||||
|
||||
# QML Formatter Script
|
||||
# Uses: https://github.com/jesperhh/qmlfmt
|
||||
# Install: AUR package "qmlfmt-git" (requires qt6-5compat)
|
||||
|
||||
if command -v qmlfmt &>/dev/null; then
|
||||
echo "Using 'qmlfmt' for formatting."
|
||||
format_file() { qmlfmt -e -b 360 -t 2 -i 2 -w "$1" || { echo "Failed: $1" >&2; return 1; }; }
|
||||
# Find qmlformat binary
|
||||
if command -v qmlformat &>/dev/null; then
|
||||
QMLFORMAT="qmlformat"
|
||||
elif [ -x "/usr/lib/qt6/bin/qmlformat" ]; then
|
||||
QMLFORMAT="/usr/lib/qt6/bin/qmlformat"
|
||||
else
|
||||
echo "No 'qmlfmt' found in PATH." >&2
|
||||
echo "No 'qmlformat' found in PATH or /usr/lib/qt6/bin." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Using 'qmlformat' for formatting: $QMLFORMAT"
|
||||
export QMLFORMAT
|
||||
format_file() { "$QMLFORMAT" -w 2 -W 360 -S --semicolon-rule always -i "$1" || { echo "Failed: $1" >&2; return 1; }; }
|
||||
|
||||
export -f format_file
|
||||
|
||||
# Find all .qml files
|
||||
mapfile -t all_files < <(find "${1:-.}" -name "*.qml" -type f)
|
||||
[ ${#all_files[@]} -eq 0 ] && { echo "No QML files found"; exit 0; }
|
||||
|
||||
echo "Scanning ${#all_files[@]} files for array destructuring..."
|
||||
safe_files=()
|
||||
for file in "${all_files[@]}"; do
|
||||
# Checks for a comma inside brackets followed by an equals sign aka "array destructuring"
|
||||
# as this ES6 syntax is not supported by qmlfmt and will result in breakage.
|
||||
if grep -qE '\[.*,.*\]\s*=' "$file"; then
|
||||
echo "-> Skipping (Array destructuring detected): $file" >&2
|
||||
else
|
||||
safe_files+=("$file")
|
||||
fi
|
||||
done
|
||||
|
||||
[ ${#safe_files[@]} -eq 0 ] && { echo "No safe files to format after filtering."; exit 0; }
|
||||
|
||||
echo "Formatting ${#safe_files[@]} files..."
|
||||
printf '%s\0' "${safe_files[@]}" | \
|
||||
echo "Formatting ${#all_files[@]} files..."
|
||||
printf '%s\0' "${all_files[@]}" | \
|
||||
xargs -0 -P "${QMLFMT_JOBS:-$(nproc)}" -I {} bash -c 'format_file "$@"' _ {} \
|
||||
&& echo "Done" || { echo "Errors occurred" >&2; exit 1; }
|
||||
+49
-79
@@ -5,111 +5,80 @@ import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
|
||||
|
||||
/*
|
||||
Noctalia is not strictly a Material Design project, it supports both some predefined
|
||||
color schemes and dynamic color generation from the wallpaper (using Matugen).
|
||||
Noctalia is not strictly a Material Design project, it supports both some predefined
|
||||
color schemes and dynamic color generation from the wallpaper (using Matugen).
|
||||
|
||||
We ultimately decided to use a restricted set of colors that follows the
|
||||
Material Design 3 naming convention.
|
||||
We ultimately decided to use a restricted set of colors that follows the
|
||||
Material Design 3 naming convention.
|
||||
|
||||
NOTE: All color names are prefixed with 'm' (e.g., mPrimary) to prevent QML from
|
||||
misinterpreting them as signals (e.g., the 'onPrimary' property name).
|
||||
NOTE: All color names are prefixed with 'm' (e.g., mPrimary) to prevent QML from
|
||||
misinterpreting them as signals (e.g., the 'onPrimary' property name).
|
||||
*/
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
// --- Key Colors: These are the main accent colors that define your app's style
|
||||
property color mPrimary: customColors.mPrimary
|
||||
property color mOnPrimary: customColors.mOnPrimary
|
||||
property color mSecondary: customColors.mSecondary
|
||||
property color mOnSecondary: customColors.mOnSecondary
|
||||
property color mTertiary: customColors.mTertiary
|
||||
property color mOnTertiary: customColors.mOnTertiary
|
||||
readonly property color mPrimary: customColorsData.mPrimary
|
||||
readonly property color mOnPrimary: customColorsData.mOnPrimary
|
||||
readonly property color mSecondary: customColorsData.mSecondary
|
||||
readonly property color mOnSecondary: customColorsData.mOnSecondary
|
||||
readonly property color mTertiary: customColorsData.mTertiary
|
||||
readonly property color mOnTertiary: customColorsData.mOnTertiary
|
||||
|
||||
// --- Utility Colors: These colors serve specific, universal purposes like indicating errors
|
||||
property color mError: customColors.mError
|
||||
property color mOnError: customColors.mOnError
|
||||
readonly property color mError: customColorsData.mError
|
||||
readonly property color mOnError: customColorsData.mOnError
|
||||
|
||||
// --- Surface and Variant Colors: These provide additional options for surfaces and their contents, creating visual hierarchy
|
||||
property color mSurface: customColors.mSurface
|
||||
property color mOnSurface: customColors.mOnSurface
|
||||
readonly property color mSurface: customColorsData.mSurface
|
||||
readonly property color mOnSurface: customColorsData.mOnSurface
|
||||
|
||||
property color mSurfaceVariant: customColors.mSurfaceVariant
|
||||
property color mOnSurfaceVariant: customColors.mOnSurfaceVariant
|
||||
readonly property color mSurfaceVariant: customColorsData.mSurfaceVariant
|
||||
readonly property color mOnSurfaceVariant: customColorsData.mOnSurfaceVariant
|
||||
|
||||
property color mOutline: customColors.mOutline
|
||||
property color mShadow: customColors.mShadow
|
||||
readonly property color mOutline: customColorsData.mOutline
|
||||
readonly property color mShadow: customColorsData.mShadow
|
||||
|
||||
property color mHover: customColors.mHover
|
||||
property color mOnHover: customColors.mOnHover
|
||||
readonly property color mHover: customColorsData.mHover
|
||||
readonly property color mOnHover: customColorsData.mOnHover
|
||||
|
||||
// --- Absolute Colors
|
||||
property color transparent: "transparent"
|
||||
property color black: "#000000"
|
||||
property color white: "#ffffff"
|
||||
readonly property color transparent: "transparent"
|
||||
readonly property color black: "#000000"
|
||||
readonly property color white: "#ffffff"
|
||||
|
||||
// --------------------------------
|
||||
// Default colors: RosePine
|
||||
QtObject {
|
||||
id: defaultColors
|
||||
|
||||
property color mPrimary: "#c7a1d8"
|
||||
property color mOnPrimary: "#1a151f"
|
||||
readonly property color mPrimary: "#c7a1d8"
|
||||
readonly property color mOnPrimary: "#1a151f"
|
||||
|
||||
property color mSecondary: "#a984c4"
|
||||
property color mOnSecondary: "#f3edf7"
|
||||
readonly property color mSecondary: "#a984c4"
|
||||
readonly property color mOnSecondary: "#f3edf7"
|
||||
|
||||
property color mTertiary: "#e0b7c9"
|
||||
property color mOnTertiary: "#20161f"
|
||||
readonly property color mTertiary: "#e0b7c9"
|
||||
readonly property color mOnTertiary: "#20161f"
|
||||
|
||||
property color mError: "#e9899d"
|
||||
property color mOnError: "#1e1418"
|
||||
readonly property color mError: "#e9899d"
|
||||
readonly property color mOnError: "#1e1418"
|
||||
|
||||
property color mSurface: "#1c1822"
|
||||
property color mOnSurface: "#e9e4f0"
|
||||
readonly property color mSurface: "#1c1822"
|
||||
readonly property color mOnSurface: "#e9e4f0"
|
||||
|
||||
property color mSurfaceVariant: "#262130"
|
||||
property color mOnSurfaceVariant: "#a79ab0"
|
||||
readonly property color mSurfaceVariant: "#262130"
|
||||
readonly property color mOnSurfaceVariant: "#a79ab0"
|
||||
|
||||
property color mOutline: "#342c42"
|
||||
property color mShadow: "#120f18"
|
||||
readonly property color mOutline: "#342c42"
|
||||
readonly property color mShadow: "#120f18"
|
||||
|
||||
property color mHover: "#e0b7c9"
|
||||
property color mOnHover: "#20161f"
|
||||
readonly property color mHover: "#e0b7c9"
|
||||
readonly property color mOnHover: "#20161f"
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// Custom colors loaded from colors.json
|
||||
// These can be generated by matugen or simply come from a well know color scheme (Dracula, Gruvbox, Nord, ...)
|
||||
QtObject {
|
||||
id: customColors
|
||||
|
||||
property color mPrimary: customColorsData.mPrimary
|
||||
property color mOnPrimary: customColorsData.mOnPrimary
|
||||
|
||||
property color mSecondary: customColorsData.mSecondary
|
||||
property color mOnSecondary: customColorsData.mOnSecondary
|
||||
|
||||
property color mTertiary: customColorsData.mTertiary
|
||||
property color mOnTertiary: customColorsData.mOnTertiary
|
||||
|
||||
property color mError: customColorsData.mError
|
||||
property color mOnError: customColorsData.mOnError
|
||||
|
||||
property color mSurface: customColorsData.mSurface
|
||||
property color mOnSurface: customColorsData.mOnSurface
|
||||
|
||||
property color mSurfaceVariant: customColorsData.mSurfaceVariant
|
||||
property color mOnSurfaceVariant: customColorsData.mOnSurfaceVariant
|
||||
|
||||
property color mOutline: customColorsData.mOutline
|
||||
property color mShadow: customColorsData.mShadow
|
||||
|
||||
property color mHover: customColorsData.mHover
|
||||
property color mOnHover: customColorsData.mOnHover
|
||||
}
|
||||
|
||||
// FileView to load custom colors data from colors.json
|
||||
FileView {
|
||||
id: customColorsFile
|
||||
@@ -117,24 +86,25 @@ Singleton {
|
||||
printErrors: false
|
||||
watchChanges: true
|
||||
onFileChanged: {
|
||||
Logger.i("Color", "Reloading colors from disk")
|
||||
reload()
|
||||
Logger.i("Color", "Reloading colors from disk");
|
||||
reload();
|
||||
}
|
||||
onAdapterUpdated: {
|
||||
Logger.i("Color", "Writing colors to disk")
|
||||
writeAdapter()
|
||||
Logger.i("Color", "Writing colors to disk");
|
||||
writeAdapter();
|
||||
}
|
||||
|
||||
// Trigger initial load when path changes from empty to actual path
|
||||
onPathChanged: {
|
||||
if (path !== undefined) {
|
||||
reload()
|
||||
reload();
|
||||
}
|
||||
}
|
||||
onLoadFailed: function (error) {
|
||||
if (error.toString().includes("No such file") || error === 2) {
|
||||
// Error code 2 = ENOENT (No such file or directory)
|
||||
if (error === 2 || error.toString().includes("No such file")) {
|
||||
// File doesn't exist, create it with default values
|
||||
writeAdapter()
|
||||
writeAdapter();
|
||||
}
|
||||
}
|
||||
JsonAdapter {
|
||||
|
||||
+126
-127
@@ -33,13 +33,13 @@ Singleton {
|
||||
|
||||
onExited: function (exitCode, exitStatus) {
|
||||
if (exitCode === 0) {
|
||||
var output = stdoutCollector.text || ""
|
||||
parseDirectoryListing(output)
|
||||
var output = stdoutCollector.text || "";
|
||||
parseDirectoryListing(output);
|
||||
} else {
|
||||
Logger.e("I18n", `Failed to scan translation directory`)
|
||||
Logger.e("I18n", `Failed to scan translation directory`);
|
||||
// Fallback to default languages
|
||||
availableLanguages = ["en"]
|
||||
detectLanguage()
|
||||
availableLanguages = ["en"];
|
||||
detectLanguage();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,21 +51,21 @@ Singleton {
|
||||
onFileChanged: reload()
|
||||
onLoaded: {
|
||||
try {
|
||||
var data = JSON.parse(text())
|
||||
root.translations = data
|
||||
Logger.i("I18n", `Loaded translations for "${root.langCode}"`)
|
||||
Logger.d("I18n", `Available root keys: ${Object.keys(data).join(", ")}`)
|
||||
var data = JSON.parse(text());
|
||||
root.translations = data;
|
||||
Logger.i("I18n", `Loaded translations for "${root.langCode}"`);
|
||||
Logger.d("I18n", `Available root keys: ${Object.keys(data).join(", ")}`);
|
||||
|
||||
root.isLoaded = true
|
||||
root.translationsLoaded()
|
||||
root.isLoaded = true;
|
||||
root.translationsLoaded();
|
||||
} catch (e) {
|
||||
Logger.e("I18n", `Failed to parse translation file: ${e}`)
|
||||
setLanguage("en")
|
||||
Logger.e("I18n", `Failed to parse translation file: ${e}`);
|
||||
setLanguage("en");
|
||||
}
|
||||
}
|
||||
onLoadFailed: function (error) {
|
||||
setLanguage("en")
|
||||
Logger.e("I18n", `Failed to load translation file: ${error}`)
|
||||
setLanguage("en");
|
||||
Logger.e("I18n", `Failed to load translation file: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,162 +76,161 @@ Singleton {
|
||||
onFileChanged: reload()
|
||||
onLoaded: {
|
||||
try {
|
||||
var data = JSON.parse(text())
|
||||
root.fallbackTranslations = data
|
||||
Logger.d("I18n", `Loaded english fallback translations`)
|
||||
var data = JSON.parse(text());
|
||||
root.fallbackTranslations = data;
|
||||
Logger.d("I18n", `Loaded english fallback translations`);
|
||||
} catch (e) {
|
||||
Logger.e("I18n", `Failed to parse fallback translation file: ${e}`)
|
||||
Logger.e("I18n", `Failed to parse fallback translation file: ${e}`);
|
||||
}
|
||||
}
|
||||
onLoadFailed: function (error) {
|
||||
Logger.e("I18n", `Failed to load fallback translation file: ${error}`)
|
||||
Logger.e("I18n", `Failed to load fallback translation file: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
Logger.i("I18n", "Service started")
|
||||
scanAvailableLanguages()
|
||||
Logger.i("I18n", "Service started");
|
||||
scanAvailableLanguages();
|
||||
}
|
||||
|
||||
// -------------------------------------------
|
||||
function scanAvailableLanguages() {
|
||||
Logger.d("I18n", "Scanning for available translation files...")
|
||||
directoryScanner.running = true
|
||||
Logger.d("I18n", "Scanning for available translation files...");
|
||||
directoryScanner.running = true;
|
||||
}
|
||||
|
||||
// -------------------------------------------
|
||||
function parseDirectoryListing(output) {
|
||||
var languages = []
|
||||
var languages = [];
|
||||
|
||||
try {
|
||||
if (!output || output.trim() === "") {
|
||||
Logger.w("I18n", "Empty directory listing output")
|
||||
availableLanguages = ["en"]
|
||||
detectLanguage()
|
||||
return
|
||||
Logger.w("I18n", "Empty directory listing output");
|
||||
availableLanguages = ["en"];
|
||||
detectLanguage();
|
||||
return;
|
||||
}
|
||||
|
||||
const entries = output.trim().split('\n')
|
||||
const entries = output.trim().split('\n');
|
||||
|
||||
for (var i = 0; i < entries.length; i++) {
|
||||
const entry = entries[i].trim()
|
||||
const entry = entries[i].trim();
|
||||
if (entry && entry.endsWith('.json')) {
|
||||
// Extract language code from filename (e.g., "en.json" -> "en")
|
||||
const langCode = entry.substring(0, entry.lastIndexOf('.json'))
|
||||
const langCode = entry.substring(0, entry.lastIndexOf('.json'));
|
||||
if (langCode.length >= 2 && langCode.length <= 5) {
|
||||
// Basic validation for language codes
|
||||
languages.push(langCode)
|
||||
languages.push(langCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort languages alphabetically, but ensure "en" comes first if available
|
||||
languages.sort()
|
||||
const enIndex = languages.indexOf("en")
|
||||
languages.sort();
|
||||
const enIndex = languages.indexOf("en");
|
||||
if (enIndex > 0) {
|
||||
languages.splice(enIndex, 1)
|
||||
languages.unshift("en")
|
||||
languages.splice(enIndex, 1);
|
||||
languages.unshift("en");
|
||||
}
|
||||
|
||||
if (languages.length === 0) {
|
||||
Logger.w("I18n", "No translation files found, using fallback")
|
||||
languages = ["en"] // Fallback
|
||||
Logger.w("I18n", "No translation files found, using fallback");
|
||||
languages = ["en"]; // Fallback
|
||||
}
|
||||
|
||||
availableLanguages = languages
|
||||
Logger.d("I18n", `Found ${languages.length} available languages: ${languages.join(', ')}`)
|
||||
availableLanguages = languages;
|
||||
Logger.d("I18n", `Found ${languages.length} available languages: ${languages.join(', ')}`);
|
||||
|
||||
// Detect language after scanning
|
||||
detectLanguage()
|
||||
detectLanguage();
|
||||
} catch (e) {
|
||||
Logger.e("I18n", `Failed to parse directory listing: ${e}`)
|
||||
Logger.e("I18n", `Failed to parse directory listing: ${e}`);
|
||||
// Fallback to default languages
|
||||
availableLanguages = ["en"]
|
||||
detectLanguage()
|
||||
availableLanguages = ["en"];
|
||||
detectLanguage();
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------
|
||||
function detectLanguage() {
|
||||
Logger.d("I18n", `detectLanguage() called. Available languages: [${availableLanguages.join(', ')}]`)
|
||||
Logger.d("I18n", `detectLanguage() called. Available languages: [${availableLanguages.join(', ')}]`);
|
||||
|
||||
if (availableLanguages.length === 0) {
|
||||
Logger.w("I18n", "No available languages found")
|
||||
return
|
||||
Logger.w("I18n", "No available languages found");
|
||||
return;
|
||||
}
|
||||
|
||||
var detectedLang = ""
|
||||
var detectedFullLocale = ""
|
||||
var detectedLang = "";
|
||||
var detectedFullLocale = "";
|
||||
|
||||
// First, determine the system's preferred language
|
||||
for (var i = 0; i < Qt.locale().uiLanguages.length; i++) {
|
||||
const fullUserLang = Qt.locale().uiLanguages[i]
|
||||
const fullUserLang = Qt.locale().uiLanguages[i];
|
||||
|
||||
if (availableLanguages.includes(fullUserLang)) {
|
||||
detectedLang = fullUserLang
|
||||
detectedFullLocale = fullUserLang
|
||||
break
|
||||
detectedLang = fullUserLang;
|
||||
detectedFullLocale = fullUserLang;
|
||||
break;
|
||||
}
|
||||
|
||||
const shortUserLang = fullUserLang.substring(0, 2)
|
||||
const shortUserLang = fullUserLang.substring(0, 2);
|
||||
if (availableLanguages.includes(shortUserLang)) {
|
||||
detectedLang = shortUserLang
|
||||
detectedFullLocale = fullUserLang
|
||||
break
|
||||
detectedLang = shortUserLang;
|
||||
detectedFullLocale = fullUserLang;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If no system language is found among available languages, fallback
|
||||
if (detectedLang === "") {
|
||||
detectedLang = availableLanguages.includes("en") ? "en" : availableLanguages[0]
|
||||
detectedFullLocale = detectedLang
|
||||
detectedLang = availableLanguages.includes("en") ? "en" : availableLanguages[0];
|
||||
detectedFullLocale = detectedLang;
|
||||
}
|
||||
|
||||
root.systemDetectedLangCode = detectedLang
|
||||
root.fullLocaleCode = detectedFullLocale
|
||||
Logger.d("I18n", `System detected language: "${root.systemDetectedLangCode}" (full locale: "${root.fullLocaleCode}")`)
|
||||
root.systemDetectedLangCode = detectedLang;
|
||||
root.fullLocaleCode = detectedFullLocale;
|
||||
Logger.d("I18n", `System detected language: "${root.systemDetectedLangCode}" (full locale: "${root.fullLocaleCode}")`);
|
||||
|
||||
// Now, apply the language: user-defined, then system-detected
|
||||
if (Settings.data.general.language !== "" && availableLanguages.includes(Settings.data.general.language)) {
|
||||
Logger.d("I18n", `User-defined language found: "${Settings.data.general.language}"`)
|
||||
setLanguage(Settings.data.general.language)
|
||||
Logger.d("I18n", `User-defined language found: "${Settings.data.general.language}"`);
|
||||
setLanguage(Settings.data.general.language);
|
||||
} else {
|
||||
Logger.d("I18n", `No user-defined language, using system detected: "${root.systemDetectedLangCode}"`)
|
||||
setLanguage(root.systemDetectedLangCode, root.fullLocaleCode)
|
||||
Logger.d("I18n", `No user-defined language, using system detected: "${root.systemDetectedLangCode}"`);
|
||||
setLanguage(root.systemDetectedLangCode, root.fullLocaleCode);
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------
|
||||
function setLanguage(newLangCode, fullLocale) {
|
||||
if (typeof fullLocale === "undefined") {
|
||||
fullLocale = newLangCode
|
||||
fullLocale = newLangCode;
|
||||
}
|
||||
|
||||
if (newLangCode !== langCode && availableLanguages.includes(newLangCode)) {
|
||||
langCode = newLangCode
|
||||
fullLocaleCode = fullLocale
|
||||
locale = Qt.locale(fullLocale)
|
||||
Logger.i("I18n", `Language set to "${langCode}" with locale "${fullLocale}"`)
|
||||
languageChanged(langCode)
|
||||
loadTranslations()
|
||||
langCode = newLangCode;
|
||||
fullLocaleCode = fullLocale;
|
||||
locale = Qt.locale(fullLocale);
|
||||
Logger.i("I18n", `Language set to "${langCode}" with locale "${fullLocale}"`);
|
||||
languageChanged(langCode);
|
||||
loadTranslations();
|
||||
} else if (!availableLanguages.includes(newLangCode)) {
|
||||
Logger.w("I18n", `Language "${newLangCode}" is not available`)
|
||||
Logger.w("I18n", `Language "${newLangCode}" is not available`);
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------
|
||||
function loadTranslations() {
|
||||
if (langCode === "")
|
||||
return
|
||||
|
||||
const filePath = `file://${Quickshell.shellDir}/Assets/Translations/${langCode}.json`
|
||||
fileView.path = filePath
|
||||
isLoaded = false
|
||||
Logger.d("I18n", `Loading translations: ${langCode}`)
|
||||
return;
|
||||
const filePath = `file://${Quickshell.shellDir}/Assets/Translations/${langCode}.json`;
|
||||
fileView.path = filePath;
|
||||
isLoaded = false;
|
||||
Logger.d("I18n", `Loading translations: ${langCode}`);
|
||||
|
||||
// Only load fallback translations if we are not using english and english is available
|
||||
if (langCode !== "en" && availableLanguages.includes("en")) {
|
||||
fallbackFileView.path = `file://${Quickshell.shellDir}/Assets/Translations/en.json`
|
||||
fallbackFileView.path = `file://${Quickshell.shellDir}/Assets/Translations/en.json`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,133 +238,133 @@ Singleton {
|
||||
// Check if a translation exists
|
||||
function hasTranslation(key) {
|
||||
if (!isLoaded)
|
||||
return false
|
||||
return false;
|
||||
|
||||
const keys = key.split(".")
|
||||
var value = translations
|
||||
const keys = key.split(".");
|
||||
var value = translations;
|
||||
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
if (value && typeof value === "object" && keys[i] in value) {
|
||||
value = value[keys[i]]
|
||||
value = value[keys[i]];
|
||||
} else {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return typeof value === "string"
|
||||
return typeof value === "string";
|
||||
}
|
||||
|
||||
// -------------------------------------------
|
||||
// Get all translation keys (useful for debugging)
|
||||
function getAllKeys(obj, prefix) {
|
||||
if (typeof obj === "undefined")
|
||||
obj = translations
|
||||
obj = translations;
|
||||
if (typeof prefix === "undefined")
|
||||
prefix = ""
|
||||
prefix = "";
|
||||
|
||||
var keys = []
|
||||
var keys = [];
|
||||
for (var key in (obj || {})) {
|
||||
const value = obj[key]
|
||||
const fullKey = prefix ? `${prefix}.${key}` : key
|
||||
const value = obj[key];
|
||||
const fullKey = prefix ? `${prefix}.${key}` : key;
|
||||
if (typeof value === "object" && value !== null) {
|
||||
keys = keys.concat(getAllKeys(value, fullKey))
|
||||
keys = keys.concat(getAllKeys(value, fullKey));
|
||||
} else if (typeof value === "string") {
|
||||
keys.push(fullKey)
|
||||
keys.push(fullKey);
|
||||
}
|
||||
}
|
||||
return keys
|
||||
return keys;
|
||||
}
|
||||
|
||||
// -------------------------------------------
|
||||
// Reload translations (useful for development)
|
||||
function reload() {
|
||||
Logger.d("I18n", "Reloading translations")
|
||||
loadTranslations()
|
||||
Logger.d("I18n", "Reloading translations");
|
||||
loadTranslations();
|
||||
}
|
||||
|
||||
// -------------------------------------------
|
||||
// Main translation function
|
||||
function tr(key, interpolations) {
|
||||
if (typeof interpolations === "undefined")
|
||||
interpolations = {}
|
||||
interpolations = {};
|
||||
|
||||
if (!isLoaded) {
|
||||
//Logger.d("I18n", "Translations not loaded yet")
|
||||
return key
|
||||
return key;
|
||||
}
|
||||
|
||||
// Navigate nested keys (e.g., "menu.file.open")
|
||||
const keys = key.split(".")
|
||||
const keys = key.split(".");
|
||||
|
||||
// Look-up translation in the active language
|
||||
var value = translations
|
||||
var notFound = false
|
||||
var value = translations;
|
||||
var notFound = false;
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
if (value && typeof value === "object" && keys[i] in value) {
|
||||
value = value[keys[i]]
|
||||
value = value[keys[i]];
|
||||
} else {
|
||||
Logger.d("I18n", `Translation key "${key}" not found at part "${keys[i]}"`)
|
||||
Logger.d("I18n", `Available keys: ${Object.keys(value || {}).join(", ")}`)
|
||||
notFound = true
|
||||
break
|
||||
Logger.d("I18n", `Translation key "${key}" not found at part "${keys[i]}"`);
|
||||
Logger.d("I18n", `Available keys: ${Object.keys(value || {}).join(", ")}`);
|
||||
notFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to english if not found
|
||||
if (notFound && availableLanguages.includes("en") && langCode !== "en") {
|
||||
value = fallbackTranslations
|
||||
value = fallbackTranslations;
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
if (value && typeof value === "object" && keys[i] in value) {
|
||||
value = value[keys[i]]
|
||||
value = value[keys[i]];
|
||||
} else {
|
||||
// Indicate this key does not even exists in the english fallback
|
||||
return `## ${key} ##`
|
||||
return `## ${key} ##`;
|
||||
}
|
||||
}
|
||||
|
||||
// Make untranslated string easy to spot
|
||||
value = `<i>${value}</i>`
|
||||
value = `<i>${value}</i>`;
|
||||
} else if (notFound) {
|
||||
// No fallback available
|
||||
return `## ${key} ##`
|
||||
return `## ${key} ##`;
|
||||
}
|
||||
|
||||
if (typeof value !== "string") {
|
||||
Logger.d("I18n", `Translation key "${key}" is not a string`)
|
||||
return key
|
||||
Logger.d("I18n", `Translation key "${key}" is not a string`);
|
||||
return key;
|
||||
}
|
||||
|
||||
// Handle interpolations (e.g., "Hello {name}!")
|
||||
var result = value
|
||||
var result = value;
|
||||
for (var placeholder in interpolations) {
|
||||
const regex = new RegExp(`\\{${placeholder}\\}`, 'g')
|
||||
result = result.replace(regex, interpolations[placeholder])
|
||||
const regex = new RegExp(`\\{${placeholder}\\}`, 'g');
|
||||
result = result.replace(regex, interpolations[placeholder]);
|
||||
}
|
||||
|
||||
return result
|
||||
return result;
|
||||
}
|
||||
|
||||
// -------------------------------------------
|
||||
// Plural translation function
|
||||
function trp(key, count, defaultSingular, defaultPlural, interpolations) {
|
||||
if (typeof defaultSingular === "undefined")
|
||||
defaultSingular = ""
|
||||
defaultSingular = "";
|
||||
if (typeof defaultPlural === "undefined")
|
||||
defaultPlural = ""
|
||||
defaultPlural = "";
|
||||
if (typeof interpolations === "undefined")
|
||||
interpolations = {}
|
||||
interpolations = {};
|
||||
|
||||
const pluralKey = count === 1 ? key : `${key}_plural`
|
||||
const defaultValue = count === 1 ? defaultSingular : defaultPlural
|
||||
const pluralKey = count === 1 ? key : `${key}_plural`;
|
||||
const defaultValue = count === 1 ? defaultSingular : defaultPlural;
|
||||
|
||||
// Merge interpolations with count (QML doesn't support spread operator)
|
||||
var finalInterpolations = {
|
||||
"count": count
|
||||
}
|
||||
};
|
||||
for (var prop in interpolations) {
|
||||
finalInterpolations[prop] = interpolations[prop]
|
||||
finalInterpolations[prop] = interpolations[prop];
|
||||
}
|
||||
|
||||
return tr(pluralKey, finalInterpolations)
|
||||
return tr(pluralKey, finalInterpolations);
|
||||
}
|
||||
}
|
||||
|
||||
+17
-17
@@ -26,15 +26,15 @@ Singleton {
|
||||
signal fontReloaded
|
||||
|
||||
Component.onCompleted: {
|
||||
Logger.i("Icons", "Service started")
|
||||
loadFontWithCacheBusting()
|
||||
Logger.i("Icons", "Service started");
|
||||
loadFontWithCacheBusting();
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Quickshell
|
||||
function onReloadCompleted() {
|
||||
Logger.d("Icons", "Quickshell reload completed - forcing font reload")
|
||||
reloadFont()
|
||||
Logger.d("Icons", "Quickshell reload completed - forcing font reload");
|
||||
reloadFont();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,20 +42,20 @@ Singleton {
|
||||
function get(iconName) {
|
||||
// Check in aliases first
|
||||
if (aliases[iconName] !== undefined) {
|
||||
iconName = aliases[iconName]
|
||||
iconName = aliases[iconName];
|
||||
}
|
||||
|
||||
// Find the appropriate codepoint
|
||||
return icons[iconName]
|
||||
return icons[iconName];
|
||||
}
|
||||
|
||||
function loadFontWithCacheBusting() {
|
||||
Logger.d("Icons", "Loading font with cache busting")
|
||||
Logger.d("Icons", "Loading font with cache busting");
|
||||
|
||||
// Destroy old loader first
|
||||
if (currentFontLoader) {
|
||||
currentFontLoader.destroy()
|
||||
currentFontLoader = null
|
||||
currentFontLoader.destroy();
|
||||
currentFontLoader = null;
|
||||
}
|
||||
|
||||
// Create new loader with cache-busting URL
|
||||
@@ -64,22 +64,22 @@ Singleton {
|
||||
FontLoader {
|
||||
source: "${cacheBustingPath}"
|
||||
}
|
||||
`, root, "dynamicFontLoader_" + fontVersion)
|
||||
`, root, "dynamicFontLoader_" + fontVersion);
|
||||
|
||||
// Connect to the new loader's status changes
|
||||
currentFontLoader.statusChanged.connect(function () {
|
||||
if (currentFontLoader.status === FontLoader.Ready) {
|
||||
Logger.d("Icons", "Font loaded successfully:", currentFontLoader.name, "(version " + fontVersion + ")")
|
||||
fontReloaded()
|
||||
Logger.d("Icons", "Font loaded successfully:", currentFontLoader.name, "(version " + fontVersion + ")");
|
||||
fontReloaded();
|
||||
} else if (currentFontLoader.status === FontLoader.Error) {
|
||||
Logger.e("Icons", "Font failed to load (version " + fontVersion + ")")
|
||||
Logger.e("Icons", "Font failed to load (version " + fontVersion + ")");
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function reloadFont() {
|
||||
Logger.d("Icons", "Forcing font reload...")
|
||||
fontVersion++
|
||||
loadFontWithCacheBusting()
|
||||
Logger.d("Icons", "Forcing font reload...");
|
||||
fontVersion++;
|
||||
loadFontWithCacheBusting();
|
||||
}
|
||||
}
|
||||
|
||||
+79
-79
@@ -310,8 +310,8 @@ Singleton {
|
||||
"align-left": "\u{ea09}",
|
||||
"align-left-2": "\u{ff00}",
|
||||
"align-right": "\u{ea0a}",
|
||||
"alpha"//"align-right-2": "\u{feff}",
|
||||
: "\u{f543}",
|
||||
//"align-right-2": "\u{feff}",
|
||||
"alpha": "\u{f543}",
|
||||
"alphabet-arabic": "\u{ff2f}",
|
||||
"alphabet-bangla": "\u{ff2e}",
|
||||
"alphabet-cyrillic": "\u{f1df}",
|
||||
@@ -3143,8 +3143,8 @@ Singleton {
|
||||
"friends": "\u{eab0}",
|
||||
"friends-off": "\u{f136}",
|
||||
"frustum": "\u{fa9f}",
|
||||
"frustum-plus"//"frustum-off": "\u{fa9d}",
|
||||
: "\u{fa9e}",
|
||||
//"frustum-off": "\u{fa9d}",
|
||||
"frustum-plus": "\u{fa9e}",
|
||||
"function": "\u{f225}",
|
||||
"function-filled": "\u{fc2b}",
|
||||
"function-off": "\u{f3f0}",
|
||||
@@ -3403,13 +3403,13 @@ Singleton {
|
||||
"hexagon-letter-x": "\u{f479}",
|
||||
"hexagon-letter-x-filled": "\u{fe30}",
|
||||
"hexagon-letter-y": "\u{f47a}",
|
||||
"hexagon-letter-z"//"hexagon-letter-y-filled": "\u{fe2f}",
|
||||
: "\u{f47b}",
|
||||
"hexagon-minus"//"hexagon-letter-z-filled": "\u{fe2e}",
|
||||
: "\u{fc8f}",
|
||||
//"hexagon-letter-y-filled": "\u{fe2f}",
|
||||
"hexagon-letter-z": "\u{f47b}",
|
||||
//"hexagon-letter-z-filled": "\u{fe2e}",
|
||||
"hexagon-minus": "\u{fc8f}",
|
||||
"hexagon-minus-2": "\u{fc8e}",
|
||||
"hexagon-number-0"//"hexagon-minus-filled": "\u{fe2d}",
|
||||
: "\u{f459}",
|
||||
//"hexagon-minus-filled": "\u{fe2d}",
|
||||
"hexagon-number-0": "\u{f459}",
|
||||
"hexagon-number-0-filled": "\u{f74c}",
|
||||
"hexagon-number-1": "\u{f45a}",
|
||||
"hexagon-number-1-filled": "\u{f74d}",
|
||||
@@ -3432,8 +3432,8 @@ Singleton {
|
||||
"hexagon-off": "\u{ee9c}",
|
||||
"hexagon-plus": "\u{fc45}",
|
||||
"hexagon-plus-2": "\u{fc90}",
|
||||
"hexagonal-prism"//"hexagon-plus-filled": "\u{fe2c}",
|
||||
: "\u{faa5}",
|
||||
//"hexagon-plus-filled": "\u{fe2c}",
|
||||
"hexagonal-prism": "\u{faa5}",
|
||||
"hexagonal-prism-off": "\u{faa3}",
|
||||
"hexagonal-prism-plus": "\u{faa4}",
|
||||
"hexagonal-pyramid": "\u{faa8}",
|
||||
@@ -3463,8 +3463,8 @@ Singleton {
|
||||
"home-eco": "\u{f351}",
|
||||
"home-edit": "\u{f352}",
|
||||
"home-exclamation": "\u{f33c}",
|
||||
"home-hand"//"home-filled": "\u{fe2b}",
|
||||
: "\u{f504}",
|
||||
//"home-filled": "\u{fe2b}",
|
||||
"home-hand": "\u{f504}",
|
||||
"home-heart": "\u{f353}",
|
||||
"home-infinity": "\u{f505}",
|
||||
"home-link": "\u{f354}",
|
||||
@@ -3582,8 +3582,8 @@ Singleton {
|
||||
"ironing-2-filled": "\u{1006e}",
|
||||
"ironing-3": "\u{f2f6}",
|
||||
"ironing-3-filled": "\u{1006d}",
|
||||
"ironing-off"//"ironing-filled": "\u{fe2a}",
|
||||
: "\u{f2f7}",
|
||||
//"ironing-filled": "\u{fe2a}",
|
||||
"ironing-off": "\u{f2f7}",
|
||||
"ironing-steam": "\u{f2f9}",
|
||||
"ironing-steam-filled": "\u{1006c}",
|
||||
"ironing-steam-off": "\u{f2f8}",
|
||||
@@ -3593,8 +3593,8 @@ Singleton {
|
||||
"italic": "\u{eb93}",
|
||||
"jacket": "\u{f661}",
|
||||
"jetpack": "\u{f581}",
|
||||
"jewish-star"//"jetpack-filled": "\u{fe29}",
|
||||
: "\u{f3ff}",
|
||||
//"jetpack-filled": "\u{fe29}",
|
||||
"jewish-star": "\u{f3ff}",
|
||||
"jewish-star-filled": "\u{f67e}",
|
||||
"join-bevel": "\u{ff4c}",
|
||||
"join-round": "\u{ff4b}",
|
||||
@@ -3608,8 +3608,8 @@ Singleton {
|
||||
"kering": "\u{efb8}",
|
||||
"kerning": "\u{efb8}",
|
||||
"key": "\u{eac7}",
|
||||
"key-off"//"key-filled": "\u{fe28}",
|
||||
: "\u{f14b}",
|
||||
//"key-filled": "\u{fe28}",
|
||||
"key-off": "\u{f14b}",
|
||||
"keyboard": "\u{ebd6}",
|
||||
"keyboard-filled": "\u{100a2}",
|
||||
"keyboard-hide": "\u{ec7e}",
|
||||
@@ -3665,20 +3665,20 @@ Singleton {
|
||||
"layers-union": "\u{eacb}",
|
||||
"layout": "\u{eadb}",
|
||||
"layout-2": "\u{eacc}",
|
||||
"layout-align-left"//"layout-2-filled": "\u{fe27}",
|
||||
// "layout-align-bottom": "\u{eacd}",
|
||||
//"layout-2-filled": "\u{fe27}",
|
||||
//"layout-align-bottom": "\u{eacd}",
|
||||
//"layout-align-bottom-filled": "\u{fe26}",
|
||||
// "layout-align-center": "\u{eace}",
|
||||
//"layout-align-center": "\u{eace}",
|
||||
//"layout-align-center-filled": "\u{fe25}",
|
||||
: "\u{eacf}",
|
||||
"layout-align-middle"// "layout-align-left-filled": "\u{fe24}",
|
||||
: "\u{ead0}",
|
||||
"layout-align-right"//"layout-align-middle-filled": "\u{fe23}",
|
||||
: "\u{ead1}",
|
||||
"layout-align-top"//"layout-align-right-filled": "\u{fe22}",
|
||||
: "\u{ead2}",
|
||||
"layout-board"//"layout-align-top-filled": "\u{fe21}",
|
||||
: "\u{ef95}",
|
||||
"layout-align-left": "\u{eacf}",
|
||||
//"layout-align-left-filled": "\u{fe24}",
|
||||
"layout-align-middle": "\u{ead0}",
|
||||
//"layout-align-middle-filled": "\u{fe23}",
|
||||
"layout-align-right": "\u{ead1}",
|
||||
//"layout-align-right-filled": "\u{fe22}",
|
||||
"layout-align-top": "\u{ead2}",
|
||||
//"layout-align-top-filled": "\u{fe21}",
|
||||
"layout-board": "\u{ef95}",
|
||||
"layout-board-filled": "\u{10182}",
|
||||
"layout-board-split": "\u{ef94}",
|
||||
"layout-board-split-filled": "\u{10183}",
|
||||
@@ -3690,8 +3690,8 @@ Singleton {
|
||||
"layout-bottombar-filled": "\u{fc37}",
|
||||
"layout-bottombar-inactive": "\u{fd45}",
|
||||
"layout-cards": "\u{ec13}",
|
||||
"layout-collage"// "layout-cards-filled": "\u{fe20}",
|
||||
: "\u{f389}",
|
||||
//"layout-cards-filled": "\u{fe20}",
|
||||
"layout-collage": "\u{f389}",
|
||||
"layout-columns": "\u{ead4}",
|
||||
"layout-dashboard": "\u{f02c}",
|
||||
"layout-dashboard-filled": "\u{fe1f}",
|
||||
@@ -4172,14 +4172,14 @@ Singleton {
|
||||
"microphone": "\u{eaf0}",
|
||||
"microphone-2": "\u{ef2c}",
|
||||
"microphone-2-off": "\u{f40d}",
|
||||
"microphone-off"//"microphone-filled": "\u{fe0f}",
|
||||
: "\u{ed16}",
|
||||
//"microphone-filled": "\u{fe0f}",
|
||||
"microphone-off": "\u{ed16}",
|
||||
"microscope": "\u{ef64}",
|
||||
"microscope-filled": "\u{10166}",
|
||||
"microscope-off": "\u{f40e}",
|
||||
"microwave": "\u{f248}",
|
||||
"microwave-off"//"microwave-filled": "\u{fe0e}",
|
||||
: "\u{f264}",
|
||||
//"microwave-filled": "\u{fe0e}",
|
||||
"microwave-off": "\u{f264}",
|
||||
"military-award": "\u{f079}",
|
||||
"military-rank": "\u{efcf}",
|
||||
"military-rank-filled": "\u{ff5e}",
|
||||
@@ -4413,18 +4413,18 @@ Singleton {
|
||||
"number-4-small": "\u{fcf9}",
|
||||
"number-40-small": "\u{fffa}",
|
||||
"number-41-small": "\u{fff9}",
|
||||
"number-5"//"number-42-small": "\u{fff8}",
|
||||
// "number-43-small": "\u{fff7}",
|
||||
// "number-44-small": "\u{fff6}",
|
||||
// "number-45-small": "\u{fff5}",
|
||||
// "number-46-small": "\u{fff4}",
|
||||
// "number-47-small": "\u{fff3}",
|
||||
// "number-48-small": "\u{fff2}",
|
||||
// "number-49-small": "\u{fff1}",
|
||||
: "\u{edf5}",
|
||||
//"number-42-small": "\u{fff8}",
|
||||
//"number-43-small": "\u{fff7}",
|
||||
//"number-44-small": "\u{fff6}",
|
||||
//"number-45-small": "\u{fff5}",
|
||||
//"number-46-small": "\u{fff4}",
|
||||
//"number-47-small": "\u{fff3}",
|
||||
//"number-48-small": "\u{fff2}",
|
||||
//"number-49-small": "\u{fff1}",
|
||||
"number-5": "\u{edf5}",
|
||||
"number-5-small": "\u{fcfa}",
|
||||
"number-51-small"// "number-50-small": "\u{fff0}",
|
||||
: "\u{ffef}",
|
||||
//"number-50-small": "\u{fff0}",
|
||||
"number-51-small": "\u{ffef}",
|
||||
"number-52-small": "\u{ffee}",
|
||||
"number-53-small": "\u{ffed}",
|
||||
"number-54-small": "\u{ffec}",
|
||||
@@ -4864,11 +4864,11 @@ Singleton {
|
||||
"quote": "\u{efbe}",
|
||||
"quote-filled": "\u{1009c}",
|
||||
"quote-off": "\u{f188}",
|
||||
"radar"//"quotes": "\u{fb1e}",
|
||||
: "\u{f017}",
|
||||
//"quotes": "\u{fb1e}",
|
||||
"radar": "\u{f017}",
|
||||
"radar-2": "\u{f016}",
|
||||
"radar-off"//"radar-filled": "\u{fe0d}",
|
||||
: "\u{f41f}",
|
||||
//"radar-filled": "\u{fe0d}",
|
||||
"radar-off": "\u{f41f}",
|
||||
"radio": "\u{ef2d}",
|
||||
"radio-off": "\u{f420}",
|
||||
"radioactive": "\u{ecc0}",
|
||||
@@ -4928,12 +4928,12 @@ Singleton {
|
||||
"regex-off": "\u{f421}",
|
||||
"registered": "\u{eb14}",
|
||||
"relation-many-to-many": "\u{ed7f}",
|
||||
"relation-one-to-many"//"relation-many-to-many-filled": "\u{fe0c}",
|
||||
: "\u{ed80}",
|
||||
"relation-one-to-one"//"relation-one-to-many-filled": "\u{fe0b}",
|
||||
: "\u{ed81}",
|
||||
"reload"//"relation-one-to-one-filled": "\u{fe0a}",
|
||||
: "\u{f3ae}",
|
||||
//"relation-many-to-many-filled": "\u{fe0c}",
|
||||
"relation-one-to-many": "\u{ed80}",
|
||||
//"relation-one-to-many-filled": "\u{fe0b}",
|
||||
"relation-one-to-one": "\u{ed81}",
|
||||
//"relation-one-to-one-filled": "\u{fe0a}",
|
||||
"reload": "\u{f3ae}",
|
||||
"reorder": "\u{fc15}",
|
||||
"repeat": "\u{eb72}",
|
||||
"repeat-off": "\u{f18e}",
|
||||
@@ -5085,8 +5085,8 @@ Singleton {
|
||||
"search": "\u{eb1c}",
|
||||
"search-off": "\u{f19c}",
|
||||
"section": "\u{eed5}",
|
||||
"section-sign"//"section-filled": "\u{fe09}",
|
||||
: "\u{f019}",
|
||||
//"section-filled": "\u{fe09}",
|
||||
"section-sign": "\u{f019}",
|
||||
"seeding": "\u{ed51}",
|
||||
"seeding-filled": "\u{10006}",
|
||||
"seeding-off": "\u{f19d}",
|
||||
@@ -5294,8 +5294,8 @@ Singleton {
|
||||
"sort-z-a": "\u{f550}",
|
||||
"sos": "\u{f24a}",
|
||||
"soup": "\u{ef2e}",
|
||||
"soup-off"//"soup-filled": "\u{fe08}",
|
||||
: "\u{f42d}",
|
||||
//"soup-filled": "\u{fe08}",
|
||||
"soup-off": "\u{f42d}",
|
||||
"source-code": "\u{f4a2}",
|
||||
"space": "\u{ec0c}",
|
||||
"space-off": "\u{f1aa}",
|
||||
@@ -5388,22 +5388,22 @@ Singleton {
|
||||
"square-half": "\u{effb}",
|
||||
"square-key": "\u{f638}",
|
||||
"square-letter-a": "\u{f47c}",
|
||||
"square-letter-b"//"square-letter-a-filled": "\u{fe07}",
|
||||
: "\u{f47d}",
|
||||
"square-letter-c"//"square-letter-b-filled": "\u{fe06}",
|
||||
: "\u{f47e}",
|
||||
"square-letter-d"//"square-letter-c-filled": "\u{fe05}",
|
||||
: "\u{f47f}",
|
||||
"square-letter-e"//"square-letter-d-filled": "\u{fe04}",
|
||||
: "\u{f480}",
|
||||
"square-letter-f"//"square-letter-e-filled": "\u{fe03}",
|
||||
: "\u{f481}",
|
||||
"square-letter-g"//"square-letter-f-filled": "\u{fe02}",
|
||||
: "\u{f482}",
|
||||
"square-letter-h"//"square-letter-g-filled": "\u{fe01}",
|
||||
: "\u{f483}",
|
||||
"square-letter-i"//"square-letter-h-filled": "\u{fe00}",
|
||||
: "\u{f484}",
|
||||
//"square-letter-a-filled": "\u{fe07}",
|
||||
"square-letter-b": "\u{f47d}",
|
||||
//"square-letter-b-filled": "\u{fe06}",
|
||||
"square-letter-c": "\u{f47e}",
|
||||
//"square-letter-c-filled": "\u{fe05}",
|
||||
"square-letter-d": "\u{f47f}",
|
||||
//"square-letter-d-filled": "\u{fe04}",
|
||||
"square-letter-e": "\u{f480}",
|
||||
//"square-letter-e-filled": "\u{fe03}",
|
||||
"square-letter-f": "\u{f481}",
|
||||
//"square-letter-f-filled": "\u{fe02}",
|
||||
"square-letter-g": "\u{f482}",
|
||||
//"square-letter-g-filled": "\u{fe01}",
|
||||
"square-letter-h": "\u{f483}",
|
||||
//"square-letter-h-filled": "\u{fe00}",
|
||||
"square-letter-i": "\u{f484}",
|
||||
"square-letter-i-filled": "\u{fdff}",
|
||||
"square-letter-j": "\u{f485}",
|
||||
"square-letter-j-filled": "\u{fdfe}",
|
||||
|
||||
+23
-23
@@ -7,63 +7,63 @@ Singleton {
|
||||
id: root
|
||||
|
||||
function _formatMessage(...args) {
|
||||
var t = Time.getFormattedTimestamp()
|
||||
var t = Time.getFormattedTimestamp();
|
||||
if (args.length > 1) {
|
||||
const maxLength = 14
|
||||
var module = args.shift().substring(0, maxLength).padStart(maxLength, " ")
|
||||
return `\x1b[36m[${t}]\x1b[0m \x1b[35m${module}\x1b[0m ` + args.join(" ")
|
||||
const maxLength = 14;
|
||||
var module = args.shift().substring(0, maxLength).padStart(maxLength, " ");
|
||||
return `\x1b[36m[${t}]\x1b[0m \x1b[35m${module}\x1b[0m ` + args.join(" ");
|
||||
} else {
|
||||
return `[\x1b[36m[${t}]\x1b[0m ` + args.join(" ")
|
||||
return `[\x1b[36m[${t}]\x1b[0m ` + args.join(" ");
|
||||
}
|
||||
}
|
||||
|
||||
function _getStackTrace() {
|
||||
try {
|
||||
throw new Error("Stack trace")
|
||||
throw new Error("Stack trace");
|
||||
} catch (e) {
|
||||
return e.stack
|
||||
return e.stack;
|
||||
}
|
||||
}
|
||||
|
||||
// Debug log (only when Settings.isDebug is true)
|
||||
function d(...args) {
|
||||
if (Settings && Settings.isDebug) {
|
||||
var msg = _formatMessage(...args)
|
||||
console.debug(msg)
|
||||
if (Settings?.isDebug) {
|
||||
var msg = _formatMessage(...args);
|
||||
console.debug(msg);
|
||||
}
|
||||
}
|
||||
|
||||
// Info log (always visible)
|
||||
function i(...args) {
|
||||
var msg = _formatMessage(...args)
|
||||
console.info(msg)
|
||||
var msg = _formatMessage(...args);
|
||||
console.info(msg);
|
||||
}
|
||||
|
||||
// Warning log (always visible)
|
||||
function w(...args) {
|
||||
var msg = _formatMessage(...args)
|
||||
console.warn(msg)
|
||||
var msg = _formatMessage(...args);
|
||||
console.warn(msg);
|
||||
}
|
||||
|
||||
// Error log (always visible)
|
||||
function e(...args) {
|
||||
var msg = _formatMessage(...args)
|
||||
console.error(msg)
|
||||
var msg = _formatMessage(...args);
|
||||
console.error(msg);
|
||||
}
|
||||
|
||||
function callStack() {
|
||||
var stack = _getStackTrace()
|
||||
Logger.i("Debug", "--------------------------")
|
||||
Logger.i("Debug", "Current call stack")
|
||||
var stack = _getStackTrace();
|
||||
Logger.i("Debug", "--------------------------");
|
||||
Logger.i("Debug", "Current call stack");
|
||||
// Split the stack into lines and log each one
|
||||
var stackLines = stack.split('\n')
|
||||
var stackLines = stack.split('\n');
|
||||
for (var i = 0; i < stackLines.length; i++) {
|
||||
var line = stackLines[i].trim() // Remove leading/trailing whitespace
|
||||
var line = stackLines[i].trim(); // Remove leading/trailing whitespace
|
||||
if (line.length > 0) {
|
||||
// Only log non-empty lines
|
||||
Logger.i("Debug", `- ${line}`)
|
||||
Logger.i("Debug", `- ${line}`);
|
||||
}
|
||||
}
|
||||
Logger.i("Debug", "--------------------------")
|
||||
Logger.i("Debug", "--------------------------");
|
||||
}
|
||||
}
|
||||
|
||||
+189
-150
@@ -3,9 +3,9 @@ pragma Singleton
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import "../Helpers/QtObj2JS.js" as QtObj2JS
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import "../Helpers/QtObj2JS.js" as QtObj2JS
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
@@ -42,30 +42,30 @@ Singleton {
|
||||
// Ensure directories exist before FileView tries to read files
|
||||
Component.onCompleted: {
|
||||
// ensure settings dir exists
|
||||
Quickshell.execDetached(["mkdir", "-p", configDir])
|
||||
Quickshell.execDetached(["mkdir", "-p", cacheDir])
|
||||
Quickshell.execDetached(["mkdir", "-p", configDir]);
|
||||
Quickshell.execDetached(["mkdir", "-p", cacheDir]);
|
||||
|
||||
Quickshell.execDetached(["mkdir", "-p", cacheDirImagesWallpapers])
|
||||
Quickshell.execDetached(["mkdir", "-p", cacheDirImagesNotifications])
|
||||
Quickshell.execDetached(["mkdir", "-p", cacheDirImagesWallpapers]);
|
||||
Quickshell.execDetached(["mkdir", "-p", cacheDirImagesNotifications]);
|
||||
|
||||
// Mark directories as created and trigger file loading
|
||||
directoriesCreated = true
|
||||
directoriesCreated = true;
|
||||
|
||||
// This should only be activated once when the settings structure has changed
|
||||
// Then it should be commented out again, regular users don't need to generate
|
||||
// default settings on every start
|
||||
if (isDebug) {
|
||||
generateDefaultSettings()
|
||||
generateDefaultSettings();
|
||||
}
|
||||
|
||||
// Patch-in the local default, resolved to user's home
|
||||
adapter.general.avatarImage = defaultAvatar
|
||||
adapter.screenRecorder.directory = defaultVideosDirectory
|
||||
adapter.wallpaper.directory = defaultWallpapersDirectory
|
||||
adapter.wallpaper.defaultWallpaper = Quickshell.shellDir + "/Assets/Wallpaper/noctalia.png"
|
||||
adapter.general.avatarImage = defaultAvatar;
|
||||
adapter.screenRecorder.directory = defaultVideosDirectory;
|
||||
adapter.wallpaper.directory = defaultWallpapersDirectory;
|
||||
adapter.wallpaper.defaultWallpaper = Quickshell.shellDir + "/Assets/Wallpaper/noctalia.png";
|
||||
|
||||
// Set the adapter to the settingsFileView to trigger the real settings load
|
||||
settingsFileView.adapter = adapter
|
||||
settingsFileView.adapter = adapter;
|
||||
}
|
||||
|
||||
// Don't write settings to disk immediately
|
||||
@@ -75,7 +75,7 @@ Singleton {
|
||||
running: false
|
||||
interval: 1000
|
||||
onTriggered: {
|
||||
root.saveImmediate()
|
||||
root.saveImmediate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,31 +90,31 @@ Singleton {
|
||||
// Trigger initial load when path changes from empty to actual path
|
||||
onPathChanged: {
|
||||
if (path !== undefined) {
|
||||
reload()
|
||||
reload();
|
||||
}
|
||||
}
|
||||
onLoaded: function () {
|
||||
if (!isLoaded) {
|
||||
Logger.i("Settings", "Settings loaded")
|
||||
Logger.i("Settings", "Settings loaded");
|
||||
|
||||
upgradeSettingsData()
|
||||
validateMonitorConfigurations()
|
||||
isLoaded = true
|
||||
upgradeSettingsData();
|
||||
validateMonitorConfigurations();
|
||||
isLoaded = true;
|
||||
|
||||
// Emit the signal
|
||||
root.settingsLoaded()
|
||||
root.settingsLoaded();
|
||||
|
||||
// Finally, update our local settings version
|
||||
adapter.settingsVersion = settingsVersion
|
||||
adapter.settingsVersion = settingsVersion;
|
||||
}
|
||||
}
|
||||
onLoadFailed: function (error) {
|
||||
if (error.toString().includes("No such file") || error === 2) {
|
||||
// File doesn't exist, create it with default values
|
||||
writeAdapter()
|
||||
writeAdapter();
|
||||
// Also write to fallback if set
|
||||
if (Quickshell.env("NOCTALIA_SETTINGS_FALLBACK")) {
|
||||
settingsFallbackFileView.writeAdapter()
|
||||
settingsFallbackFileView.writeAdapter();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -156,33 +156,48 @@ Singleton {
|
||||
// Widget configuration for modular bar system
|
||||
property JsonObject widgets
|
||||
widgets: JsonObject {
|
||||
property list<var> left: [{
|
||||
property list<var> left: [
|
||||
{
|
||||
"id": "ControlCenter"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "SystemMonitor"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "ActiveWindow"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "MediaMini"
|
||||
}]
|
||||
property list<var> center: [{
|
||||
}
|
||||
]
|
||||
property list<var> center: [
|
||||
{
|
||||
"id": "Workspace"
|
||||
}]
|
||||
property list<var> right: [{
|
||||
}
|
||||
]
|
||||
property list<var> right: [
|
||||
{
|
||||
"id": "ScreenRecorder"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "Tray"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "NotificationHistory"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "Battery"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "Volume"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "Brightness"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "Clock"
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,6 +219,7 @@ Singleton {
|
||||
property int shadowOffsetX: 2
|
||||
property int shadowOffsetY: 3
|
||||
property string language: ""
|
||||
property bool allowPanelsOnScreenWithoutBar: true
|
||||
}
|
||||
|
||||
// ui
|
||||
@@ -294,41 +310,57 @@ Singleton {
|
||||
property string position: "close_to_bar_button"
|
||||
property JsonObject shortcuts
|
||||
shortcuts: JsonObject {
|
||||
property list<var> left: [{
|
||||
property list<var> left: [
|
||||
{
|
||||
"id": "WiFi"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "Bluetooth"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "ScreenRecorder"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "WallpaperSelector"
|
||||
}]
|
||||
property list<var> right: [{
|
||||
}
|
||||
]
|
||||
property list<var> right: [
|
||||
{
|
||||
"id": "Notifications"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "PowerProfile"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "KeepAwake"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "NightLight"
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
property list<var> cards: [{
|
||||
property list<var> cards: [
|
||||
{
|
||||
"id": "profile-card",
|
||||
"enabled": true
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "shortcuts-card",
|
||||
"enabled": true
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "audio-card",
|
||||
"enabled": true
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "weather-card",
|
||||
"enabled": true
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "media-sysmon-card",
|
||||
"enabled": true
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// system monitor
|
||||
@@ -371,25 +403,32 @@ Singleton {
|
||||
property int countdownDuration: 10000
|
||||
property string position: "center"
|
||||
property bool showHeader: true
|
||||
property list<var> powerOptions: [{
|
||||
property list<var> powerOptions: [
|
||||
{
|
||||
"action": "lock",
|
||||
"enabled": true
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"action": "suspend",
|
||||
"enabled": true
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"action": "hibernate",
|
||||
"enabled": true
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"action": "reboot",
|
||||
"enabled": true
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"action": "logout",
|
||||
"enabled": true
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"action": "shutdown",
|
||||
"enabled": true
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// notifications
|
||||
@@ -493,81 +532,81 @@ Singleton {
|
||||
// Function to preprocess paths by expanding "~" to user's home directory
|
||||
function preprocessPath(path) {
|
||||
if (typeof path !== "string" || path === "") {
|
||||
return path
|
||||
return path;
|
||||
}
|
||||
|
||||
// Expand "~" to user's home directory
|
||||
if (path.startsWith("~/")) {
|
||||
return Quickshell.env("HOME") + path.substring(1)
|
||||
return Quickshell.env("HOME") + path.substring(1);
|
||||
} else if (path === "~") {
|
||||
return Quickshell.env("HOME")
|
||||
return Quickshell.env("HOME");
|
||||
}
|
||||
|
||||
return path
|
||||
return path;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------
|
||||
// Public function to trigger immediate settings saving
|
||||
function saveImmediate() {
|
||||
settingsFileView.writeAdapter()
|
||||
settingsFileView.writeAdapter();
|
||||
// Write to fallback location if set
|
||||
if (Quickshell.env("NOCTALIA_SETTINGS_FALLBACK")) {
|
||||
settingsFallbackFileView.writeAdapter()
|
||||
settingsFallbackFileView.writeAdapter();
|
||||
}
|
||||
root.settingsSaved() // Emit signal after saving
|
||||
root.settingsSaved(); // Emit signal after saving
|
||||
}
|
||||
|
||||
// -----------------------------------------------------
|
||||
// Generate default settings at the root of the repo
|
||||
function generateDefaultSettings() {
|
||||
try {
|
||||
Logger.d("Settings", "Generating settings-default.json")
|
||||
Logger.d("Settings", "Generating settings-default.json");
|
||||
|
||||
// Prepare a clean JSON
|
||||
var plainAdapter = QtObj2JS.qtObjectToPlainObject(adapter)
|
||||
var jsonData = JSON.stringify(plainAdapter, null, 2)
|
||||
var plainAdapter = QtObj2JS.qtObjectToPlainObject(adapter);
|
||||
var jsonData = JSON.stringify(plainAdapter, null, 2);
|
||||
|
||||
var defaultPath = Quickshell.shellDir + "/Assets/settings-default.json"
|
||||
var defaultPath = Quickshell.shellDir + "/Assets/settings-default.json";
|
||||
|
||||
// Encode transfer it has base64 to avoid any escaping issue
|
||||
var base64Data = Qt.btoa(jsonData)
|
||||
Quickshell.execDetached(["sh", "-c", `echo "${base64Data}" | base64 -d > "${defaultPath}"`])
|
||||
var base64Data = Qt.btoa(jsonData);
|
||||
Quickshell.execDetached(["sh", "-c", `echo "${base64Data}" | base64 -d > "${defaultPath}"`]);
|
||||
} catch (error) {
|
||||
Logger.e("Settings", "Failed to generate default settings file: " + error)
|
||||
Logger.e("Settings", "Failed to generate default settings file: " + error);
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------
|
||||
// Function to validate monitor configurations
|
||||
function validateMonitorConfigurations() {
|
||||
var availableScreenNames = []
|
||||
var availableScreenNames = [];
|
||||
for (var i = 0; i < Quickshell.screens.length; i++) {
|
||||
availableScreenNames.push(Quickshell.screens[i].name)
|
||||
availableScreenNames.push(Quickshell.screens[i].name);
|
||||
}
|
||||
|
||||
Logger.d("Settings", "Available monitors: [" + availableScreenNames.join(", ") + "]")
|
||||
Logger.d("Settings", "Configured bar monitors: [" + adapter.bar.monitors.join(", ") + "]")
|
||||
Logger.d("Settings", "Available monitors: [" + availableScreenNames.join(", ") + "]");
|
||||
Logger.d("Settings", "Configured bar monitors: [" + adapter.bar.monitors.join(", ") + "]");
|
||||
|
||||
// Check bar monitors
|
||||
if (adapter.bar.monitors.length > 0) {
|
||||
var hasValidBarMonitor = false
|
||||
var hasValidBarMonitor = false;
|
||||
for (var j = 0; j < adapter.bar.monitors.length; j++) {
|
||||
if (availableScreenNames.includes(adapter.bar.monitors[j])) {
|
||||
hasValidBarMonitor = true
|
||||
break
|
||||
hasValidBarMonitor = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasValidBarMonitor) {
|
||||
Logger.w("Settings", "No configured bar monitors found on system, clearing bar monitor list to show on all screens")
|
||||
adapter.bar.monitors = []
|
||||
} else {
|
||||
Logger.w("Settings", "No configured bar monitors found on system, clearing bar monitor list to show on all screens");
|
||||
adapter.bar.monitors = [];
|
||||
} else
|
||||
|
||||
//Logger.i("Settings", "Found valid bar monitors, keeping configuration")
|
||||
}
|
||||
} else {
|
||||
{}
|
||||
} else
|
||||
|
||||
//Logger.i("Settings", "Bar monitor list is empty, will show on all available screens")
|
||||
}
|
||||
{}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------
|
||||
@@ -576,50 +615,50 @@ Singleton {
|
||||
function upgradeSettingsData() {
|
||||
// Wait for BarWidgetRegistry to be ready
|
||||
if (!BarWidgetRegistry.widgets || Object.keys(BarWidgetRegistry.widgets).length === 0) {
|
||||
Logger.w("Settings", "BarWidgetRegistry not ready, deferring upgrade")
|
||||
Qt.callLater(upgradeSettingsData)
|
||||
return
|
||||
Logger.w("Settings", "BarWidgetRegistry not ready, deferring upgrade");
|
||||
Qt.callLater(upgradeSettingsData);
|
||||
return;
|
||||
}
|
||||
|
||||
const sections = ["left", "center", "right"]
|
||||
const sections = ["left", "center", "right"];
|
||||
|
||||
// -----------------
|
||||
// 1st. convert old widget id to new id
|
||||
for (var s = 0; s < sections.length; s++) {
|
||||
const sectionName = sections[s]
|
||||
const sectionName = sections[s];
|
||||
for (var i = 0; i < adapter.bar.widgets[sectionName].length; i++) {
|
||||
var widget = adapter.bar.widgets[sectionName][i]
|
||||
var widget = adapter.bar.widgets[sectionName][i];
|
||||
|
||||
switch (widget.id) {
|
||||
case "DarkModeToggle":
|
||||
widget.id = "DarkMode"
|
||||
break
|
||||
widget.id = "DarkMode";
|
||||
break;
|
||||
case "PowerToggle":
|
||||
widget.id = "SessionMenu"
|
||||
break
|
||||
widget.id = "SessionMenu";
|
||||
break;
|
||||
case "ScreenRecorderIndicator":
|
||||
widget.id = "ScreenRecorder"
|
||||
break
|
||||
widget.id = "ScreenRecorder";
|
||||
break;
|
||||
case "SidePanelToggle":
|
||||
widget.id = "ControlCenter"
|
||||
break
|
||||
widget.id = "ControlCenter";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------
|
||||
// 2nd. remove any non existing widget type
|
||||
var removedWidget = false
|
||||
var removedWidget = false;
|
||||
for (var s = 0; s < sections.length; s++) {
|
||||
const sectionName = sections[s]
|
||||
const widgets = adapter.bar.widgets[sectionName]
|
||||
const sectionName = sections[s];
|
||||
const widgets = adapter.bar.widgets[sectionName];
|
||||
// Iterate backward through the widgets array, so it does not break when removing a widget
|
||||
for (var i = widgets.length - 1; i >= 0; i--) {
|
||||
var widget = widgets[i]
|
||||
var widget = widgets[i];
|
||||
if (!BarWidgetRegistry.hasWidget(widget.id)) {
|
||||
Logger.w(`Settings`, `Deleted invalid widget ${widget.id}`)
|
||||
widgets.splice(i, 1)
|
||||
removedWidget = true
|
||||
Logger.w(`Settings`, `Deleted invalid widget ${widget.id}`);
|
||||
widgets.splice(i, 1);
|
||||
removedWidget = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -627,18 +666,18 @@ Singleton {
|
||||
// -----------------
|
||||
// 3nd. upgrade widget settings
|
||||
for (var s = 0; s < sections.length; s++) {
|
||||
const sectionName = sections[s]
|
||||
const sectionName = sections[s];
|
||||
for (var i = 0; i < adapter.bar.widgets[sectionName].length; i++) {
|
||||
var widget = adapter.bar.widgets[sectionName][i]
|
||||
var widget = adapter.bar.widgets[sectionName][i];
|
||||
|
||||
// Check if widget registry supports user settings, if it does not, then there is nothing to do
|
||||
const reg = BarWidgetRegistry.widgetMetadata[widget.id]
|
||||
const reg = BarWidgetRegistry.widgetMetadata[widget.id];
|
||||
if ((reg === undefined) || (reg.allowUserSettings === undefined) || !reg.allowUserSettings) {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
|
||||
if (upgradeWidget(widget)) {
|
||||
Logger.d("Settings", `Upgraded ${widget.id} widget:`, JSON.stringify(widget))
|
||||
Logger.d("Settings", `Upgraded ${widget.id} widget:`, JSON.stringify(widget));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -647,14 +686,14 @@ Singleton {
|
||||
// 4th. safety check
|
||||
// if a widget was deleted, ensure we still have a control center
|
||||
if (removedWidget) {
|
||||
var gotControlCenter = false
|
||||
var gotControlCenter = false;
|
||||
for (var s = 0; s < sections.length; s++) {
|
||||
const sectionName = sections[s]
|
||||
const sectionName = sections[s];
|
||||
for (var i = 0; i < adapter.bar.widgets[sectionName].length; i++) {
|
||||
var widget = adapter.bar.widgets[sectionName][i]
|
||||
var widget = adapter.bar.widgets[sectionName][i];
|
||||
if (widget.id === "ControlCenter") {
|
||||
gotControlCenter = true
|
||||
break
|
||||
gotControlCenter = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -663,8 +702,8 @@ Singleton {
|
||||
//const obj = JSON.parse('{"id": "ControlCenter"}');
|
||||
adapter.bar.widgets["right"].push(({
|
||||
"id": "ControlCenter"
|
||||
}))
|
||||
Logger.w("Settings", "Added a ControlCenter widget to the right section")
|
||||
}));
|
||||
Logger.w("Settings", "Added a ControlCenter widget to the right section");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -674,31 +713,31 @@ Singleton {
|
||||
if (adapter.settingsVersion < 21) {
|
||||
// Read raw JSON file to access properties not in adapter schema
|
||||
try {
|
||||
var rawJson = settingsFileView.text()
|
||||
var rawJson = settingsFileView.text();
|
||||
|
||||
if (rawJson) {
|
||||
var parsed = JSON.parse(rawJson)
|
||||
var anyDiscordEnabled = false
|
||||
var parsed = JSON.parse(rawJson);
|
||||
var anyDiscordEnabled = false;
|
||||
|
||||
// Check if any Discord client was enabled
|
||||
const discordClients = ["discord_vesktop", "discord_webcord", "discord_armcord", "discord_equibop", "discord_lightcord", "discord_dorion", "discord_vencord"]
|
||||
const discordClients = ["discord_vesktop", "discord_webcord", "discord_armcord", "discord_equibop", "discord_lightcord", "discord_dorion", "discord_vencord"];
|
||||
|
||||
if (parsed.templates) {
|
||||
for (var i = 0; i < discordClients.length; i++) {
|
||||
if (parsed.templates[discordClients[i]]) {
|
||||
anyDiscordEnabled = true
|
||||
break
|
||||
anyDiscordEnabled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set unified discord property
|
||||
adapter.templates.discord = anyDiscordEnabled
|
||||
adapter.templates.discord = anyDiscordEnabled;
|
||||
|
||||
Logger.i("Settings", "Migrated Discord templates to unified 'discord' property (enabled:", anyDiscordEnabled + ")")
|
||||
Logger.i("Settings", "Migrated Discord templates to unified 'discord' property (enabled:", anyDiscordEnabled + ")");
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.w("Settings", "Failed to read raw JSON for Discord migration:", error)
|
||||
Logger.w("Settings", "Failed to read raw JSON for Discord migration:", error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -708,20 +747,20 @@ Singleton {
|
||||
if (adapter.settingsVersion < 22) {
|
||||
// Read raw JSON file to access properties not in adapter schema
|
||||
try {
|
||||
var rawJson = settingsFileView.text()
|
||||
var rawJson = settingsFileView.text();
|
||||
|
||||
if (rawJson) {
|
||||
var parsed = JSON.parse(rawJson)
|
||||
var parsed = JSON.parse(rawJson);
|
||||
if (parsed.appLauncher && parsed.appLauncher.backgroundOpacity !== undefined) {
|
||||
var oldOpacity = parsed.appLauncher.backgroundOpacity
|
||||
var oldOpacity = parsed.appLauncher.backgroundOpacity;
|
||||
if (adapter.ui) {
|
||||
adapter.ui.panelBackgroundOpacity = oldOpacity
|
||||
Logger.i("Settings", "Migrated appLauncher.backgroundOpacity to ui.panelBackgroundOpacity (value:", oldOpacity + ")")
|
||||
adapter.ui.panelBackgroundOpacity = oldOpacity;
|
||||
Logger.i("Settings", "Migrated appLauncher.backgroundOpacity to ui.panelBackgroundOpacity (value:", oldOpacity + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.w("Settings", "Failed to read raw JSON for migration:", error)
|
||||
Logger.w("Settings", "Failed to read raw JSON for migration:", error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -732,23 +771,23 @@ Singleton {
|
||||
if (adapter.settingsVersion < 23) {
|
||||
// Read raw JSON file to access dimDesktop property
|
||||
try {
|
||||
var rawJson = settingsFileView.text()
|
||||
var rawJson = settingsFileView.text();
|
||||
|
||||
if (rawJson) {
|
||||
var parsed = JSON.parse(rawJson)
|
||||
var parsed = JSON.parse(rawJson);
|
||||
if (parsed.general && parsed.general.dimDesktop === true) {
|
||||
// Check if dimmerOpacity exists in raw JSON (not adapter default)
|
||||
var dimmerOpacityInJson = parsed.general.dimmerOpacity
|
||||
var dimmerOpacityInJson = parsed.general.dimmerOpacity;
|
||||
|
||||
// If dimmerOpacity wasn't explicitly set in JSON or was 0, set it to 0.8 (80% dimming)
|
||||
if (dimmerOpacityInJson === undefined || dimmerOpacityInJson === 0) {
|
||||
adapter.general.dimmerOpacity = 0.8
|
||||
Logger.i("Settings", "Migrated dimDesktop=true: set dimmerOpacity to 0.8 (80% dimming)")
|
||||
adapter.general.dimmerOpacity = 0.8;
|
||||
Logger.i("Settings", "Migrated dimDesktop=true: set dimmerOpacity to 0.8 (80% dimming)");
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.w("Settings", "Failed to read raw JSON for dimDesktop migration:", error)
|
||||
Logger.w("Settings", "Failed to read raw JSON for dimDesktop migration:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -756,35 +795,35 @@ Singleton {
|
||||
// -----------------------------------------------------
|
||||
function upgradeWidget(widget) {
|
||||
// Backup the widget definition before altering
|
||||
const widgetBefore = JSON.stringify(widget)
|
||||
const widgetBefore = JSON.stringify(widget);
|
||||
|
||||
// Get all existing custom settings keys
|
||||
const keys = Object.keys(BarWidgetRegistry.widgetMetadata[widget.id])
|
||||
const keys = Object.keys(BarWidgetRegistry.widgetMetadata[widget.id]);
|
||||
|
||||
// Delete deprecated user settings from the wiget
|
||||
for (const k of Object.keys(widget)) {
|
||||
if (k === "id" || k === "allowUserSettings") {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
if (!keys.includes(k)) {
|
||||
delete widget[k]
|
||||
delete widget[k];
|
||||
}
|
||||
}
|
||||
|
||||
// Inject missing default setting (metaData) from BarWidgetRegistry
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
const k = keys[i]
|
||||
const k = keys[i];
|
||||
if (k === "id" || k === "allowUserSettings") {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
|
||||
if (widget[k] === undefined) {
|
||||
widget[k] = BarWidgetRegistry.widgetMetadata[widget.id][k]
|
||||
widget[k] = BarWidgetRegistry.widgetMetadata[widget.id][k];
|
||||
}
|
||||
}
|
||||
|
||||
// Compare settings, to detect if something has been upgraded
|
||||
const widgetAfter = JSON.stringify(widget)
|
||||
return (widgetAfter !== widgetBefore)
|
||||
const widgetAfter = JSON.stringify(widget);
|
||||
return (widgetAfter !== widgetBefore);
|
||||
}
|
||||
}
|
||||
|
||||
+59
-66
@@ -7,108 +7,101 @@ import qs.Services.Power
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
|
||||
/*
|
||||
Preset sizes for font, radii, ?
|
||||
*/
|
||||
|
||||
// Font size
|
||||
property real fontSizeXXS: 8
|
||||
property real fontSizeXS: 9
|
||||
property real fontSizeS: 10
|
||||
property real fontSizeM: 11
|
||||
property real fontSizeL: 13
|
||||
property real fontSizeXL: 16
|
||||
property real fontSizeXXL: 18
|
||||
property real fontSizeXXXL: 24
|
||||
readonly property real fontSizeXXS: 8
|
||||
readonly property real fontSizeXS: 9
|
||||
readonly property real fontSizeS: 10
|
||||
readonly property real fontSizeM: 11
|
||||
readonly property real fontSizeL: 13
|
||||
readonly property real fontSizeXL: 16
|
||||
readonly property real fontSizeXXL: 18
|
||||
readonly property real fontSizeXXXL: 24
|
||||
|
||||
// Font weight
|
||||
property int fontWeightRegular: 400
|
||||
property int fontWeightMedium: 500
|
||||
property int fontWeightSemiBold: 600
|
||||
property int fontWeightBold: 700
|
||||
readonly property int fontWeightRegular: 400
|
||||
readonly property int fontWeightMedium: 500
|
||||
readonly property int fontWeightSemiBold: 600
|
||||
readonly property int fontWeightBold: 700
|
||||
|
||||
// Radii
|
||||
property int radiusXXS: Math.round(4 * Settings.data.general.radiusRatio)
|
||||
property int radiusXS: Math.round(8 * Settings.data.general.radiusRatio)
|
||||
property int radiusS: Math.round(12 * Settings.data.general.radiusRatio)
|
||||
property int radiusM: Math.round(16 * Settings.data.general.radiusRatio)
|
||||
property int radiusL: Math.round(20 * Settings.data.general.radiusRatio)
|
||||
property int screenRadius: Math.round(20 * Settings.data.general.screenRadiusRatio)
|
||||
readonly property int radiusXXS: Math.round(4 * Settings.data.general.radiusRatio)
|
||||
readonly property int radiusXS: Math.round(8 * Settings.data.general.radiusRatio)
|
||||
readonly property int radiusS: Math.round(12 * Settings.data.general.radiusRatio)
|
||||
readonly property int radiusM: Math.round(16 * Settings.data.general.radiusRatio)
|
||||
readonly property int radiusL: Math.round(20 * Settings.data.general.radiusRatio)
|
||||
readonly property int screenRadius: Math.round(20 * Settings.data.general.screenRadiusRatio)
|
||||
|
||||
// Border
|
||||
property int borderS: Math.max(1, Math.round(1 * uiScaleRatio))
|
||||
property int borderM: Math.max(1, Math.round(2 * uiScaleRatio))
|
||||
property int borderL: Math.max(1, Math.round(3 * uiScaleRatio))
|
||||
readonly property int borderS: Math.max(1, Math.round(1 * uiScaleRatio))
|
||||
readonly property int borderM: Math.max(1, Math.round(2 * uiScaleRatio))
|
||||
readonly property int borderL: Math.max(1, Math.round(3 * uiScaleRatio))
|
||||
|
||||
// Margins (for margins and spacing)
|
||||
property int marginXXS: Math.round(2 * uiScaleRatio)
|
||||
property int marginXS: Math.round(4 * uiScaleRatio)
|
||||
property int marginS: Math.round(6 * uiScaleRatio)
|
||||
property int marginM: Math.round(9 * uiScaleRatio)
|
||||
property int marginL: Math.round(13 * uiScaleRatio)
|
||||
property int marginXL: Math.round(18 * uiScaleRatio)
|
||||
readonly property int marginXXS: Math.round(2 * uiScaleRatio)
|
||||
readonly property int marginXS: Math.round(4 * uiScaleRatio)
|
||||
readonly property int marginS: Math.round(6 * uiScaleRatio)
|
||||
readonly property int marginM: Math.round(9 * uiScaleRatio)
|
||||
readonly property int marginL: Math.round(13 * uiScaleRatio)
|
||||
readonly property int marginXL: Math.round(18 * uiScaleRatio)
|
||||
|
||||
// Opacity
|
||||
property real opacityNone: 0.0
|
||||
property real opacityLight: 0.25
|
||||
property real opacityMedium: 0.5
|
||||
property real opacityHeavy: 0.75
|
||||
property real opacityAlmost: 0.95
|
||||
property real opacityFull: 1.0
|
||||
readonly property real opacityNone: 0.0
|
||||
readonly property real opacityLight: 0.25
|
||||
readonly property real opacityMedium: 0.5
|
||||
readonly property real opacityHeavy: 0.75
|
||||
readonly property real opacityAlmost: 0.95
|
||||
readonly property real opacityFull: 1.0
|
||||
|
||||
// Shadows
|
||||
property real shadowOpacity: 0.85
|
||||
property real shadowBlur: 1.0
|
||||
property int shadowBlurMax: 22
|
||||
property real shadowHorizontalOffset: Settings.data.general.shadowOffsetX
|
||||
property real shadowVerticalOffset: Settings.data.general.shadowOffsetY
|
||||
readonly property real shadowOpacity: 0.85
|
||||
readonly property real shadowBlur: 1.0
|
||||
readonly property int shadowBlurMax: 22
|
||||
readonly property real shadowHorizontalOffset: Settings.data.general.shadowOffsetX
|
||||
readonly property real shadowVerticalOffset: Settings.data.general.shadowOffsetY
|
||||
|
||||
// Animation duration (ms)
|
||||
property int animationFaster: (Settings.data.general.animationDisabled || PowerProfileService.noctaliaPerformanceMode) ? 0 : Math.round(75 / Settings.data.general.animationSpeed)
|
||||
property int animationFast: (Settings.data.general.animationDisabled || PowerProfileService.noctaliaPerformanceMode) ? 0 : Math.round(150 / Settings.data.general.animationSpeed)
|
||||
property int animationNormal: (Settings.data.general.animationDisabled || PowerProfileService.noctaliaPerformanceMode) ? 0 : Math.round(300 / Settings.data.general.animationSpeed)
|
||||
property int animationSlow: (Settings.data.general.animationDisabled || PowerProfileService.noctaliaPerformanceMode) ? 0 : Math.round(450 / Settings.data.general.animationSpeed)
|
||||
property int animationSlowest: (Settings.data.general.animationDisabled || PowerProfileService.noctaliaPerformanceMode) ? 0 : Math.round(750 / Settings.data.general.animationSpeed)
|
||||
readonly property int animationFaster: (Settings.data.general.animationDisabled || PowerProfileService.noctaliaPerformanceMode) ? 0 : Math.round(75 / Settings.data.general.animationSpeed)
|
||||
readonly property int animationFast: (Settings.data.general.animationDisabled || PowerProfileService.noctaliaPerformanceMode) ? 0 : Math.round(150 / Settings.data.general.animationSpeed)
|
||||
readonly property int animationNormal: (Settings.data.general.animationDisabled || PowerProfileService.noctaliaPerformanceMode) ? 0 : Math.round(300 / Settings.data.general.animationSpeed)
|
||||
readonly property int animationSlow: (Settings.data.general.animationDisabled || PowerProfileService.noctaliaPerformanceMode) ? 0 : Math.round(450 / Settings.data.general.animationSpeed)
|
||||
readonly property int animationSlowest: (Settings.data.general.animationDisabled || PowerProfileService.noctaliaPerformanceMode) ? 0 : Math.round(750 / Settings.data.general.animationSpeed)
|
||||
|
||||
// Delays
|
||||
property int tooltipDelay: 300
|
||||
property int tooltipDelayLong: 1200
|
||||
property int pillDelay: 500
|
||||
readonly property int tooltipDelay: 300
|
||||
readonly property int tooltipDelayLong: 1200
|
||||
readonly property int pillDelay: 500
|
||||
|
||||
// Widgets base size
|
||||
property real baseWidgetSize: 33
|
||||
property real sliderWidth: 200
|
||||
readonly property real baseWidgetSize: 33
|
||||
readonly property real sliderWidth: 200
|
||||
|
||||
property real uiScaleRatio: Settings.data.general.scaleRatio
|
||||
readonly property real uiScaleRatio: Settings.data.general.scaleRatio
|
||||
|
||||
// Bar Dimensions
|
||||
property real barHeight: {
|
||||
readonly property real barHeight: {
|
||||
switch (Settings.data.bar.density) {
|
||||
case "mini":
|
||||
return (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 22 : 20
|
||||
return (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 22 : 20;
|
||||
case "compact":
|
||||
return (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 27 : 25
|
||||
return (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 27 : 25;
|
||||
case "comfortable":
|
||||
return (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 39 : 37
|
||||
return (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 39 : 37;
|
||||
default:
|
||||
|
||||
case "default":
|
||||
return (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 33 : 31
|
||||
return (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 33 : 31;
|
||||
}
|
||||
}
|
||||
property real capsuleHeight: {
|
||||
readonly property real capsuleHeight: {
|
||||
switch (Settings.data.bar.density) {
|
||||
case "mini":
|
||||
return Math.round(barHeight * 1.0)
|
||||
return Math.round(barHeight * 1.0);
|
||||
case "compact":
|
||||
return Math.round(barHeight * 0.85)
|
||||
return Math.round(barHeight * 0.85);
|
||||
case "comfortable":
|
||||
return Math.round(barHeight * 0.73)
|
||||
return Math.round(barHeight * 0.73);
|
||||
default:
|
||||
|
||||
case "default":
|
||||
return Math.round(barHeight * 0.82)
|
||||
return Math.round(barHeight * 0.82);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+16
-16
@@ -7,46 +7,46 @@ Singleton {
|
||||
id: root
|
||||
|
||||
function iconFromName(iconName, fallbackName) {
|
||||
const fallback = fallbackName || "application-x-executable"
|
||||
const fallback = fallbackName || "application-x-executable";
|
||||
try {
|
||||
if (iconName && typeof Quickshell !== 'undefined' && Quickshell.iconPath) {
|
||||
const p = Quickshell.iconPath(iconName, fallback)
|
||||
const p = Quickshell.iconPath(iconName, fallback);
|
||||
if (p && p !== "")
|
||||
return p
|
||||
return p;
|
||||
}
|
||||
} catch (e) {
|
||||
} catch (e)
|
||||
|
||||
// ignore and fall back
|
||||
}
|
||||
{}
|
||||
try {
|
||||
return Quickshell.iconPath ? (Quickshell.iconPath(fallback, true) || "") : ""
|
||||
return Quickshell.iconPath ? (Quickshell.iconPath(fallback, true) || "") : "";
|
||||
} catch (e2) {
|
||||
return ""
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve icon path for a DesktopEntries appId - safe on missing entries
|
||||
function iconForAppId(appId, fallbackName) {
|
||||
const fallback = fallbackName || "application-x-executable"
|
||||
const fallback = fallbackName || "application-x-executable";
|
||||
if (!appId)
|
||||
return iconFromName(fallback, fallback)
|
||||
return iconFromName(fallback, fallback);
|
||||
try {
|
||||
if (typeof DesktopEntries === 'undefined' || !DesktopEntries.byId)
|
||||
return iconFromName(fallback, fallback)
|
||||
const entry = (DesktopEntries.heuristicLookup) ? DesktopEntries.heuristicLookup(appId) : DesktopEntries.byId(appId)
|
||||
const name = entry && entry.icon ? entry.icon : ""
|
||||
return iconFromName(name || fallback, fallback)
|
||||
return iconFromName(fallback, fallback);
|
||||
const entry = (DesktopEntries.heuristicLookup) ? DesktopEntries.heuristicLookup(appId) : DesktopEntries.byId(appId);
|
||||
const name = entry && entry.icon ? entry.icon : "";
|
||||
return iconFromName(name || fallback, fallback);
|
||||
} catch (e) {
|
||||
return iconFromName(fallback, fallback)
|
||||
return iconFromName(fallback, fallback);
|
||||
}
|
||||
}
|
||||
|
||||
// Distro logo helper (absolute path or empty string)
|
||||
function distroLogoPath() {
|
||||
try {
|
||||
return (typeof OSInfo !== 'undefined' && OSInfo.distroIconPath) ? OSInfo.distroIconPath : ""
|
||||
return (typeof OSInfo !== 'undefined' && OSInfo.distroIconPath) ? OSInfo.distroIconPath : "";
|
||||
} catch (e) {
|
||||
return ""
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+38
-38
@@ -1,7 +1,7 @@
|
||||
pragma Singleton
|
||||
import QtQuick
|
||||
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import qs.Commons
|
||||
|
||||
Singleton {
|
||||
@@ -12,7 +12,7 @@ Singleton {
|
||||
|
||||
// Returns a Unix Timestamp (in seconds)
|
||||
readonly property int timestamp: {
|
||||
return Math.floor(root.now / 1000)
|
||||
return Math.floor(root.now / 1000);
|
||||
}
|
||||
|
||||
Timer {
|
||||
@@ -22,89 +22,89 @@ Singleton {
|
||||
running: true
|
||||
triggeredOnStart: false
|
||||
onTriggered: {
|
||||
var newTime = new Date()
|
||||
root.now = newTime
|
||||
var newTime = new Date();
|
||||
root.now = newTime;
|
||||
|
||||
// Adjust next interval to sync with the start of the next second
|
||||
var msIntoSecond = newTime.getMilliseconds()
|
||||
var msIntoSecond = newTime.getMilliseconds();
|
||||
if (msIntoSecond > 100) {
|
||||
// If we're more than 100ms into the second, adjust for next time
|
||||
updateTimer.interval = 1000 - msIntoSecond + 10 // +10ms buffer
|
||||
updateTimer.restart()
|
||||
updateTimer.interval = 1000 - msIntoSecond + 10; // +10ms buffer
|
||||
updateTimer.restart();
|
||||
} else {
|
||||
updateTimer.interval = 1000
|
||||
updateTimer.interval = 1000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
// Start by syncing to the next second boundary
|
||||
var now = new Date()
|
||||
var msUntilNextSecond = 1000 - now.getMilliseconds()
|
||||
updateTimer.interval = msUntilNextSecond + 10 // +10ms buffer
|
||||
updateTimer.restart()
|
||||
var now = new Date();
|
||||
var msUntilNextSecond = 1000 - now.getMilliseconds();
|
||||
updateTimer.interval = msUntilNextSecond + 10; // +10ms buffer
|
||||
updateTimer.restart();
|
||||
}
|
||||
|
||||
// Formats a Date object into a YYYYMMDD-HHMMSS string.
|
||||
function getFormattedTimestamp(date) {
|
||||
if (!date) {
|
||||
date = new Date()
|
||||
date = new Date();
|
||||
}
|
||||
const year = date.getFullYear()
|
||||
const year = date.getFullYear();
|
||||
|
||||
// getMonth() is zero-based, so we add 1
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0');
|
||||
|
||||
return `${year}${month}${day}-${hours}${minutes}${seconds}`
|
||||
return `${year}${month}${day}-${hours}${minutes}${seconds}`;
|
||||
}
|
||||
|
||||
// Format an easy to read approximate duration ex: 4h 32m
|
||||
// Used to display the time remaining on the Battery widget, computer uptime, etc..
|
||||
function formatVagueHumanReadableDuration(totalSeconds) {
|
||||
if (typeof totalSeconds !== 'number' || totalSeconds < 0) {
|
||||
return '0s'
|
||||
return '0s';
|
||||
}
|
||||
|
||||
// Floor the input to handle decimal seconds
|
||||
totalSeconds = Math.floor(totalSeconds)
|
||||
totalSeconds = Math.floor(totalSeconds);
|
||||
|
||||
const days = Math.floor(totalSeconds / 86400)
|
||||
const hours = Math.floor((totalSeconds % 86400) / 3600)
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60)
|
||||
const seconds = totalSeconds % 60
|
||||
const days = Math.floor(totalSeconds / 86400);
|
||||
const hours = Math.floor((totalSeconds % 86400) / 3600);
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
||||
const seconds = totalSeconds % 60;
|
||||
|
||||
const parts = []
|
||||
const parts = [];
|
||||
if (days)
|
||||
parts.push(`${days}d`)
|
||||
parts.push(`${days}d`);
|
||||
if (hours)
|
||||
parts.push(`${hours}h`)
|
||||
parts.push(`${hours}h`);
|
||||
if (minutes)
|
||||
parts.push(`${minutes}m`)
|
||||
parts.push(`${minutes}m`);
|
||||
|
||||
// Only show seconds if no hours and no minutes
|
||||
if (!hours && !minutes) {
|
||||
parts.push(`${seconds}s`)
|
||||
parts.push(`${seconds}s`);
|
||||
}
|
||||
|
||||
return parts.join(' ')
|
||||
return parts.join(' ');
|
||||
}
|
||||
|
||||
// Format a date into
|
||||
function formatRelativeTime(date) {
|
||||
if (!date)
|
||||
return ""
|
||||
const diff = Date.now() - date.getTime()
|
||||
return "";
|
||||
const diff = Date.now() - date.getTime();
|
||||
if (diff < 60000)
|
||||
return "now"
|
||||
return "now";
|
||||
if (diff < 3600000)
|
||||
return `${Math.floor(diff / 60000)}m ago`
|
||||
return `${Math.floor(diff / 60000)}m ago`;
|
||||
if (diff < 86400000)
|
||||
return `${Math.floor(diff / 3600000)}h ago`
|
||||
return `${Math.floor(diff / 86400000)}d ago`
|
||||
return `${Math.floor(diff / 3600000)}h ago`;
|
||||
return `${Math.floor(diff / 86400000)}d ago`;
|
||||
}
|
||||
}
|
||||
|
||||
+120
-120
@@ -48,17 +48,17 @@ Variants {
|
||||
Component.onCompleted: setWallpaperInitial()
|
||||
|
||||
Component.onDestruction: {
|
||||
transitionAnimation.stop()
|
||||
debounceTimer.stop()
|
||||
shaderLoader.active = false
|
||||
currentWallpaper.source = ""
|
||||
nextWallpaper.source = ""
|
||||
transitionAnimation.stop();
|
||||
debounceTimer.stop();
|
||||
shaderLoader.active = false;
|
||||
currentWallpaper.source = "";
|
||||
nextWallpaper.source = "";
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Settings.data.wallpaper
|
||||
function onFillModeChanged() {
|
||||
fillMode = WallpaperService.getFillModeUniform()
|
||||
fillMode = WallpaperService.getFillModeUniform();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,8 +69,8 @@ Variants {
|
||||
if (screenName === modelData.name) {
|
||||
// Update wallpaper display
|
||||
// Set wallpaper immediately on startup
|
||||
futureWallpaper = path
|
||||
debounceTimer.restart()
|
||||
futureWallpaper = path;
|
||||
debounceTimer.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,9 +80,9 @@ Variants {
|
||||
function onDisplayScalesChanged() {
|
||||
// Recalculate image sizes without interrupting startup transition
|
||||
if (isStartupTransition) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
recalculateImageSizes()
|
||||
recalculateImageSizes();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ Variants {
|
||||
running: false
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
changeWallpaper()
|
||||
changeWallpaper();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,18 +123,18 @@ Variants {
|
||||
sourceSize: undefined
|
||||
onStatusChanged: {
|
||||
if (status === Image.Error) {
|
||||
Logger.w("Current wallpaper failed to load:", source)
|
||||
Logger.w("Current wallpaper failed to load:", source);
|
||||
} else if (status === Image.Ready && !dimensionsCalculated) {
|
||||
dimensionsCalculated = true
|
||||
const optimalSize = calculateOptimalWallpaperSize(implicitWidth, implicitHeight)
|
||||
dimensionsCalculated = true;
|
||||
const optimalSize = calculateOptimalWallpaperSize(implicitWidth, implicitHeight);
|
||||
if (optimalSize !== false) {
|
||||
sourceSize = optimalSize
|
||||
sourceSize = optimalSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
onSourceChanged: {
|
||||
dimensionsCalculated = false
|
||||
sourceSize = undefined
|
||||
dimensionsCalculated = false;
|
||||
sourceSize = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,18 +152,18 @@ Variants {
|
||||
sourceSize: undefined
|
||||
onStatusChanged: {
|
||||
if (status === Image.Error) {
|
||||
Logger.w("Next wallpaper failed to load:", source)
|
||||
Logger.w("Next wallpaper failed to load:", source);
|
||||
} else if (status === Image.Ready && !dimensionsCalculated) {
|
||||
dimensionsCalculated = true
|
||||
const optimalSize = calculateOptimalWallpaperSize(implicitWidth, implicitHeight)
|
||||
dimensionsCalculated = true;
|
||||
const optimalSize = calculateOptimalWallpaperSize(implicitWidth, implicitHeight);
|
||||
if (optimalSize !== false) {
|
||||
sourceSize = optimalSize
|
||||
sourceSize = optimalSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
onSourceChanged: {
|
||||
dimensionsCalculated = false
|
||||
sourceSize = undefined
|
||||
dimensionsCalculated = false;
|
||||
sourceSize = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,15 +176,15 @@ Variants {
|
||||
sourceComponent: {
|
||||
switch (transitionType) {
|
||||
case "wipe":
|
||||
return wipeShaderComponent
|
||||
return wipeShaderComponent;
|
||||
case "disc":
|
||||
return discShaderComponent
|
||||
return discShaderComponent;
|
||||
case "stripes":
|
||||
return stripesShaderComponent
|
||||
return stripesShaderComponent;
|
||||
case "fade":
|
||||
case "none":
|
||||
default:
|
||||
return fadeShaderComponent
|
||||
return fadeShaderComponent;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -307,64 +307,64 @@ Variants {
|
||||
easing.type: Easing.InOutCubic
|
||||
onFinished: {
|
||||
// Assign new image to current BEFORE clearing to prevent flicker
|
||||
const tempSource = nextWallpaper.source
|
||||
currentWallpaper.source = tempSource
|
||||
transitionProgress = 0.0
|
||||
const tempSource = nextWallpaper.source;
|
||||
currentWallpaper.source = tempSource;
|
||||
transitionProgress = 0.0;
|
||||
|
||||
// Now clear nextWallpaper after currentWallpaper has the new source
|
||||
// Force complete cleanup to free texture memory (~18-25MB per monitor)
|
||||
Qt.callLater(() => {
|
||||
nextWallpaper.source = ""
|
||||
nextWallpaper.sourceSize = undefined
|
||||
nextWallpaper.source = "";
|
||||
nextWallpaper.sourceSize = undefined;
|
||||
Qt.callLater(() => {
|
||||
currentWallpaper.asynchronous = true
|
||||
})
|
||||
})
|
||||
currentWallpaper.asynchronous = true;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------
|
||||
function calculateOptimalWallpaperSize(wpWidth, wpHeight) {
|
||||
const compositorScale = CompositorService.getDisplayScale(modelData.name)
|
||||
const screenWidth = modelData.width * compositorScale
|
||||
const screenHeight = modelData.height * compositorScale
|
||||
const compositorScale = CompositorService.getDisplayScale(modelData.name);
|
||||
const screenWidth = modelData.width * compositorScale;
|
||||
const screenHeight = modelData.height * compositorScale;
|
||||
if (wpWidth <= screenWidth || wpHeight <= screenHeight || wpWidth <= 0 || wpHeight <= 0) {
|
||||
// Do not resize if wallpaper is smaller than one of the screen dimension
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
const imageAspectRatio = wpWidth / wpHeight
|
||||
var dim = Qt.size(0, 0)
|
||||
const imageAspectRatio = wpWidth / wpHeight;
|
||||
var dim = Qt.size(0, 0);
|
||||
if (screenWidth >= screenHeight) {
|
||||
const w = Math.min(screenWidth, wpWidth)
|
||||
dim = Qt.size(Math.round(w), Math.round(w / imageAspectRatio))
|
||||
const w = Math.min(screenWidth, wpWidth);
|
||||
dim = Qt.size(Math.round(w), Math.round(w / imageAspectRatio));
|
||||
} else {
|
||||
const h = Math.min(screenHeight, wpHeight)
|
||||
dim = Qt.size(Math.round(h * imageAspectRatio), Math.round(h))
|
||||
const h = Math.min(screenHeight, wpHeight);
|
||||
dim = Qt.size(Math.round(h * imageAspectRatio), Math.round(h));
|
||||
}
|
||||
|
||||
Logger.d("Background", `Wallpaper resized on ${modelData.name} ${screenWidth}x${screenHeight} @ ${compositorScale}x`, "src:", wpWidth, wpHeight, "dst:", dim.width, dim.height)
|
||||
return dim
|
||||
Logger.d("Background", `Wallpaper resized on ${modelData.name} ${screenWidth}x${screenHeight} @ ${compositorScale}x`, "src:", wpWidth, wpHeight, "dst:", dim.width, dim.height);
|
||||
return dim;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------
|
||||
function recalculateImageSizes() {
|
||||
// Re-evaluate and apply optimal sourceSize for both images when ready
|
||||
if (currentWallpaper.status === Image.Ready) {
|
||||
const optimal = calculateOptimalWallpaperSize(currentWallpaper.implicitWidth, currentWallpaper.implicitHeight)
|
||||
const optimal = calculateOptimalWallpaperSize(currentWallpaper.implicitWidth, currentWallpaper.implicitHeight);
|
||||
if (optimal !== undefined && optimal !== false) {
|
||||
currentWallpaper.sourceSize = optimal
|
||||
currentWallpaper.sourceSize = optimal;
|
||||
} else {
|
||||
currentWallpaper.sourceSize = undefined
|
||||
currentWallpaper.sourceSize = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
if (nextWallpaper.status === Image.Ready) {
|
||||
const optimal2 = calculateOptimalWallpaperSize(nextWallpaper.implicitWidth, nextWallpaper.implicitHeight)
|
||||
const optimal2 = calculateOptimalWallpaperSize(nextWallpaper.implicitWidth, nextWallpaper.implicitHeight);
|
||||
if (optimal2 !== undefined && optimal2 !== false) {
|
||||
nextWallpaper.sourceSize = optimal2
|
||||
nextWallpaper.sourceSize = optimal2;
|
||||
} else {
|
||||
nextWallpaper.sourceSize = undefined
|
||||
nextWallpaper.sourceSize = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -373,104 +373,104 @@ Variants {
|
||||
function setWallpaperInitial() {
|
||||
// On startup, defer assigning wallpaper until the service cache is ready, retries every tick
|
||||
if (!WallpaperService || !WallpaperService.isInitialized) {
|
||||
Qt.callLater(setWallpaperInitial)
|
||||
return
|
||||
Qt.callLater(setWallpaperInitial);
|
||||
return;
|
||||
}
|
||||
|
||||
const wallpaperPath = WallpaperService.getWallpaper(modelData.name)
|
||||
const wallpaperPath = WallpaperService.getWallpaper(modelData.name);
|
||||
|
||||
futureWallpaper = wallpaperPath
|
||||
performStartupTransition()
|
||||
futureWallpaper = wallpaperPath;
|
||||
performStartupTransition();
|
||||
}
|
||||
|
||||
// ------------------------------------------------------
|
||||
function setWallpaperImmediate(source) {
|
||||
transitionAnimation.stop()
|
||||
transitionProgress = 0.0
|
||||
transitionAnimation.stop();
|
||||
transitionProgress = 0.0;
|
||||
|
||||
// Clear nextWallpaper completely to free texture memory
|
||||
nextWallpaper.source = ""
|
||||
nextWallpaper.sourceSize = undefined
|
||||
nextWallpaper.source = "";
|
||||
nextWallpaper.sourceSize = undefined;
|
||||
|
||||
currentWallpaper.source = ""
|
||||
currentWallpaper.source = "";
|
||||
|
||||
Qt.callLater(() => {
|
||||
currentWallpaper.source = source
|
||||
})
|
||||
currentWallpaper.source = source;
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------
|
||||
function setWallpaperWithTransition(source) {
|
||||
if (source === currentWallpaper.source) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
if (transitioning) {
|
||||
// We are interrupting a transition - handle cleanup properly
|
||||
transitionAnimation.stop()
|
||||
transitionProgress = 0
|
||||
transitionAnimation.stop();
|
||||
transitionProgress = 0;
|
||||
|
||||
// Assign nextWallpaper to currentWallpaper BEFORE clearing to prevent flicker
|
||||
const newCurrentSource = nextWallpaper.source
|
||||
currentWallpaper.source = newCurrentSource
|
||||
const newCurrentSource = nextWallpaper.source;
|
||||
currentWallpaper.source = newCurrentSource;
|
||||
|
||||
// Now clear nextWallpaper after current has the new source
|
||||
Qt.callLater(() => {
|
||||
nextWallpaper.source = ""
|
||||
nextWallpaper.source = "";
|
||||
|
||||
// Now set the next wallpaper after a brief delay
|
||||
Qt.callLater(() => {
|
||||
nextWallpaper.source = source
|
||||
currentWallpaper.asynchronous = false
|
||||
transitionAnimation.start()
|
||||
})
|
||||
})
|
||||
return
|
||||
nextWallpaper.source = source;
|
||||
currentWallpaper.asynchronous = false;
|
||||
transitionAnimation.start();
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
nextWallpaper.source = source
|
||||
currentWallpaper.asynchronous = false
|
||||
transitionAnimation.start()
|
||||
nextWallpaper.source = source;
|
||||
currentWallpaper.asynchronous = false;
|
||||
transitionAnimation.start();
|
||||
}
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Main method that actually trigger the wallpaper change
|
||||
function changeWallpaper() {
|
||||
// Get the transitionType from the settings
|
||||
transitionType = Settings.data.wallpaper.transitionType
|
||||
transitionType = Settings.data.wallpaper.transitionType;
|
||||
|
||||
if (transitionType == "random") {
|
||||
var index = Math.floor(Math.random() * allTransitions.length)
|
||||
transitionType = allTransitions[index]
|
||||
var index = Math.floor(Math.random() * allTransitions.length);
|
||||
transitionType = allTransitions[index];
|
||||
}
|
||||
|
||||
// Ensure the transition type really exists
|
||||
if (transitionType !== "none" && !allTransitions.includes(transitionType)) {
|
||||
transitionType = "fade"
|
||||
transitionType = "fade";
|
||||
}
|
||||
|
||||
//Logger.i("Background", "New wallpaper: ", futureWallpaper, "On:", modelData.name, "Transition:", transitionType)
|
||||
switch (transitionType) {
|
||||
case "none":
|
||||
setWallpaperImmediate(futureWallpaper)
|
||||
break
|
||||
setWallpaperImmediate(futureWallpaper);
|
||||
break;
|
||||
case "wipe":
|
||||
wipeDirection = Math.random() * 4
|
||||
setWallpaperWithTransition(futureWallpaper)
|
||||
break
|
||||
wipeDirection = Math.random() * 4;
|
||||
setWallpaperWithTransition(futureWallpaper);
|
||||
break;
|
||||
case "disc":
|
||||
discCenterX = Math.random()
|
||||
discCenterY = Math.random()
|
||||
setWallpaperWithTransition(futureWallpaper)
|
||||
break
|
||||
discCenterX = Math.random();
|
||||
discCenterY = Math.random();
|
||||
setWallpaperWithTransition(futureWallpaper);
|
||||
break;
|
||||
case "stripes":
|
||||
stripesCount = Math.round(Math.random() * 20 + 4)
|
||||
stripesAngle = Math.random() * 360
|
||||
setWallpaperWithTransition(futureWallpaper)
|
||||
break
|
||||
stripesCount = Math.round(Math.random() * 20 + 4);
|
||||
stripesAngle = Math.random() * 360;
|
||||
setWallpaperWithTransition(futureWallpaper);
|
||||
break;
|
||||
default:
|
||||
setWallpaperWithTransition(futureWallpaper)
|
||||
break
|
||||
setWallpaperWithTransition(futureWallpaper);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -478,46 +478,46 @@ Variants {
|
||||
// Dedicated function for startup animation
|
||||
function performStartupTransition() {
|
||||
// Get the transitionType from the settings
|
||||
transitionType = Settings.data.wallpaper.transitionType
|
||||
transitionType = Settings.data.wallpaper.transitionType;
|
||||
|
||||
if (transitionType == "random") {
|
||||
var index = Math.floor(Math.random() * allTransitions.length)
|
||||
transitionType = allTransitions[index]
|
||||
var index = Math.floor(Math.random() * allTransitions.length);
|
||||
transitionType = allTransitions[index];
|
||||
}
|
||||
|
||||
// Ensure the transition type really exists
|
||||
if (transitionType !== "none" && !allTransitions.includes(transitionType)) {
|
||||
transitionType = "fade"
|
||||
transitionType = "fade";
|
||||
}
|
||||
|
||||
// Apply transitionType so the shader loader picks the correct shader
|
||||
this.transitionType = transitionType
|
||||
this.transitionType = transitionType;
|
||||
|
||||
switch (transitionType) {
|
||||
case "none":
|
||||
setWallpaperImmediate(futureWallpaper)
|
||||
break
|
||||
setWallpaperImmediate(futureWallpaper);
|
||||
break;
|
||||
case "wipe":
|
||||
wipeDirection = Math.random() * 4
|
||||
setWallpaperWithTransition(futureWallpaper)
|
||||
break
|
||||
wipeDirection = Math.random() * 4;
|
||||
setWallpaperWithTransition(futureWallpaper);
|
||||
break;
|
||||
case "disc":
|
||||
// Force center origin for elegant startup animation
|
||||
discCenterX = 0.5
|
||||
discCenterY = 0.5
|
||||
setWallpaperWithTransition(futureWallpaper)
|
||||
break
|
||||
discCenterX = 0.5;
|
||||
discCenterY = 0.5;
|
||||
setWallpaperWithTransition(futureWallpaper);
|
||||
break;
|
||||
case "stripes":
|
||||
stripesCount = Math.round(Math.random() * 20 + 4)
|
||||
stripesAngle = Math.random() * 360
|
||||
setWallpaperWithTransition(futureWallpaper)
|
||||
break
|
||||
stripesCount = Math.round(Math.random() * 20 + 4);
|
||||
stripesAngle = Math.random() * 360;
|
||||
setWallpaperWithTransition(futureWallpaper);
|
||||
break;
|
||||
default:
|
||||
setWallpaperWithTransition(futureWallpaper)
|
||||
break
|
||||
setWallpaperWithTransition(futureWallpaper);
|
||||
break;
|
||||
}
|
||||
// Mark startup transition complete
|
||||
isStartupTransition = false
|
||||
isStartupTransition = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
import qs.Services.Compositor
|
||||
@@ -20,16 +20,16 @@ Loader {
|
||||
|
||||
Component.onCompleted: {
|
||||
if (modelData) {
|
||||
Logger.d("Overview", "Loading overview for Niri on", modelData.name)
|
||||
Logger.d("Overview", "Loading overview for Niri on", modelData.name);
|
||||
}
|
||||
setWallpaperInitial()
|
||||
setWallpaperInitial();
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
// Clean up resources to prevent memory leak when overviewEnabled is toggled off
|
||||
timerDisableFx.stop()
|
||||
bgImage.layer.enabled = false
|
||||
bgImage.source = ""
|
||||
timerDisableFx.stop();
|
||||
bgImage.layer.enabled = false;
|
||||
bgImage.source = "";
|
||||
}
|
||||
|
||||
// External state management
|
||||
@@ -37,19 +37,19 @@ Loader {
|
||||
target: WallpaperService
|
||||
function onWallpaperChanged(screenName, path) {
|
||||
if (screenName === modelData.name) {
|
||||
wallpaper = path
|
||||
wallpaper = path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setWallpaperInitial() {
|
||||
if (!WallpaperService || !WallpaperService.isInitialized) {
|
||||
Qt.callLater(setWallpaperInitial)
|
||||
return
|
||||
Qt.callLater(setWallpaperInitial);
|
||||
return;
|
||||
}
|
||||
const wallpaperPath = WallpaperService.getWallpaper(modelData.name)
|
||||
const wallpaperPath = WallpaperService.getWallpaper(modelData.name);
|
||||
if (wallpaperPath && wallpaperPath !== wallpaper) {
|
||||
wallpaper = wallpaperPath
|
||||
wallpaper = wallpaperPath;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+34
-34
@@ -2,13 +2,13 @@ import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Services.UPower
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
import qs.Modules.Bar.Extras
|
||||
import qs.Modules.Notification
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
import qs.Modules.Notification
|
||||
import qs.Modules.Bar.Extras
|
||||
|
||||
// Bar Component
|
||||
Item {
|
||||
@@ -34,9 +34,9 @@ Item {
|
||||
// Register bar when screen becomes available
|
||||
onScreenChanged: {
|
||||
if (screen && screen.name) {
|
||||
Logger.d("Bar", "Bar screen set to:", screen.name)
|
||||
Logger.d("Bar", " Position:", barPosition, "Floating:", barFloating)
|
||||
BarService.registerBar(screen.name)
|
||||
Logger.d("Bar", "Bar screen set to:", screen.name);
|
||||
Logger.d("Bar", " Position:", barPosition, "Floating:", barFloating);
|
||||
BarService.registerBar(screen.name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,12 +46,12 @@ Item {
|
||||
anchors.fill: parent
|
||||
active: {
|
||||
if (root.screen === null || root.screen === undefined) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
var monitors = Settings.data.bar.monitors || []
|
||||
var result = monitors.length === 0 || monitors.includes(root.screen.name)
|
||||
return result
|
||||
var monitors = Settings.data.bar.monitors || [];
|
||||
var result = monitors.length === 0 || monitors.includes(root.screen.name);
|
||||
return result;
|
||||
}
|
||||
|
||||
sourceComponent: Item {
|
||||
@@ -75,73 +75,73 @@ Item {
|
||||
readonly property int topLeftCornerState: {
|
||||
// Floating bar: always simple rounded corners
|
||||
if (barFloating)
|
||||
return 0
|
||||
return 0;
|
||||
// Top bar: top corners against screen edge = no radius
|
||||
if (barPosition === "top")
|
||||
return -1
|
||||
return -1;
|
||||
// Left bar: top-left against screen edge = no radius
|
||||
if (barPosition === "left")
|
||||
return -1
|
||||
return -1;
|
||||
// Bottom/Right bar with outerCorners: inverted corner
|
||||
if (Settings.data.bar.outerCorners && (barPosition === "bottom" || barPosition === "right")) {
|
||||
return barIsVertical ? 1 : 2 // horizontal invert for vertical bars, vertical invert for horizontal
|
||||
return barIsVertical ? 1 : 2; // horizontal invert for vertical bars, vertical invert for horizontal
|
||||
}
|
||||
// No outerCorners = square
|
||||
return -1
|
||||
return -1;
|
||||
}
|
||||
|
||||
readonly property int topRightCornerState: {
|
||||
// Floating bar: always simple rounded corners
|
||||
if (barFloating)
|
||||
return 0
|
||||
return 0;
|
||||
// Top bar: top corners against screen edge = no radius
|
||||
if (barPosition === "top")
|
||||
return -1
|
||||
return -1;
|
||||
// Right bar: top-right against screen edge = no radius
|
||||
if (barPosition === "right")
|
||||
return -1
|
||||
return -1;
|
||||
// Bottom/Left bar with outerCorners: inverted corner
|
||||
if (Settings.data.bar.outerCorners && (barPosition === "bottom" || barPosition === "left")) {
|
||||
return barIsVertical ? 1 : 2
|
||||
return barIsVertical ? 1 : 2;
|
||||
}
|
||||
// No outerCorners = square
|
||||
return -1
|
||||
return -1;
|
||||
}
|
||||
|
||||
readonly property int bottomLeftCornerState: {
|
||||
// Floating bar: always simple rounded corners
|
||||
if (barFloating)
|
||||
return 0
|
||||
return 0;
|
||||
// Bottom bar: bottom corners against screen edge = no radius
|
||||
if (barPosition === "bottom")
|
||||
return -1
|
||||
return -1;
|
||||
// Left bar: bottom-left against screen edge = no radius
|
||||
if (barPosition === "left")
|
||||
return -1
|
||||
return -1;
|
||||
// Top/Right bar with outerCorners: inverted corner
|
||||
if (Settings.data.bar.outerCorners && (barPosition === "top" || barPosition === "right")) {
|
||||
return barIsVertical ? 1 : 2
|
||||
return barIsVertical ? 1 : 2;
|
||||
}
|
||||
// No outerCorners = square
|
||||
return -1
|
||||
return -1;
|
||||
}
|
||||
|
||||
readonly property int bottomRightCornerState: {
|
||||
// Floating bar: always simple rounded corners
|
||||
if (barFloating)
|
||||
return 0
|
||||
return 0;
|
||||
// Bottom bar: bottom corners against screen edge = no radius
|
||||
if (barPosition === "bottom")
|
||||
return -1
|
||||
return -1;
|
||||
// Right bar: bottom-right against screen edge = no radius
|
||||
if (barPosition === "right")
|
||||
return -1
|
||||
return -1;
|
||||
// Top/Left bar with outerCorners: inverted corner
|
||||
if (Settings.data.bar.outerCorners && (barPosition === "top" || barPosition === "left")) {
|
||||
return barIsVertical ? 1 : 2
|
||||
return barIsVertical ? 1 : 2;
|
||||
}
|
||||
// No outerCorners = square
|
||||
return -1
|
||||
return -1;
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
@@ -151,14 +151,14 @@ Item {
|
||||
preventStealing: true
|
||||
onClicked: function (mouse) {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
var controlCenterPanel = PanelService.getPanel("controlCenterPanel", screen)
|
||||
var controlCenterPanel = PanelService.getPanel("controlCenterPanel", screen);
|
||||
if (Settings.data.controlCenter.position === "close_to_bar_button") {
|
||||
// Will attempt to open the panel next to the bar button if any.
|
||||
controlCenterPanel?.toggle(null, "ControlCenter")
|
||||
controlCenterPanel?.toggle(null, "ControlCenter");
|
||||
} else {
|
||||
controlCenterPanel?.toggle()
|
||||
controlCenterPanel?.toggle();
|
||||
}
|
||||
mouse.accepted = true
|
||||
mouse.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,12 @@ import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
|
||||
|
||||
/**
|
||||
* BarExclusionZone - Invisible PanelWindow that reserves exclusive space for the bar
|
||||
*
|
||||
* This is a minimal window that works with the compositor to reserve space,
|
||||
* while the actual bar UI is rendered in NFullScreenWindow.
|
||||
*/
|
||||
* BarExclusionZone - Invisible PanelWindow that reserves exclusive space for the bar
|
||||
*
|
||||
* This is a minimal window that works with the compositor to reserve space,
|
||||
* while the actual bar UI is rendered in NFullScreenWindow.
|
||||
*/
|
||||
PanelWindow {
|
||||
id: root
|
||||
|
||||
@@ -46,11 +45,11 @@ PanelWindow {
|
||||
// Vertical bar: reserve bar height + margin on the anchored edge only
|
||||
if (barFloating) {
|
||||
// For left bar, reserve left margin; for right bar, reserve right margin
|
||||
return Style.barHeight + barMarginH
|
||||
return Style.barHeight + barMarginH;
|
||||
}
|
||||
return Style.barHeight
|
||||
return Style.barHeight;
|
||||
}
|
||||
return 0 // Auto-width when left/right anchors are true
|
||||
return 0; // Auto-width when left/right anchors are true
|
||||
}
|
||||
|
||||
implicitHeight: {
|
||||
@@ -58,17 +57,17 @@ PanelWindow {
|
||||
// Horizontal bar: reserve bar height + margin on the anchored edge only
|
||||
if (barFloating) {
|
||||
// For top bar, reserve top margin; for bottom bar, reserve bottom margin
|
||||
return Style.barHeight + barMarginV
|
||||
return Style.barHeight + barMarginV;
|
||||
}
|
||||
return Style.barHeight
|
||||
return Style.barHeight;
|
||||
}
|
||||
return 0 // Auto-height when top/bottom anchors are true
|
||||
return 0; // Auto-height when top/bottom anchors are true
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
Logger.d("BarExclusionZone", "Created for screen:", screen?.name)
|
||||
Logger.d("BarExclusionZone", " Position:", barPosition, "Exclusive:", exclusive, "Floating:", barFloating)
|
||||
Logger.d("BarExclusionZone", " Anchors - top:", anchors.top, "bottom:", anchors.bottom, "left:", anchors.left, "right:", anchors.right)
|
||||
Logger.d("BarExclusionZone", " Size:", width, "x", height, "implicitWidth:", implicitWidth, "implicitHeight:", implicitHeight)
|
||||
Logger.d("BarExclusionZone", "Created for screen:", screen?.name);
|
||||
Logger.d("BarExclusionZone", " Position:", barPosition, "Exclusive:", exclusive, "Floating:", barFloating);
|
||||
Logger.d("BarExclusionZone", " Anchors - top:", anchors.top, "bottom:", anchors.bottom, "left:", anchors.left, "right:", anchors.right);
|
||||
Logger.d("BarExclusionZone", " Size:", width, "x", height, "implicitWidth:", implicitWidth, "implicitHeight:", implicitHeight);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,19 +92,19 @@ Item {
|
||||
|
||||
function show() {
|
||||
if (pillLoader.item && pillLoader.item.show) {
|
||||
pillLoader.item.show()
|
||||
pillLoader.item.show();
|
||||
}
|
||||
}
|
||||
|
||||
function hide() {
|
||||
if (pillLoader.item && pillLoader.item.hide) {
|
||||
pillLoader.item.hide()
|
||||
pillLoader.item.hide();
|
||||
}
|
||||
}
|
||||
|
||||
function showDelayed() {
|
||||
if (pillLoader.item && pillLoader.item.showDelayed) {
|
||||
pillLoader.item.showDelayed()
|
||||
pillLoader.item.showDelayed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,18 +45,18 @@ Item {
|
||||
readonly property real iconSize: {
|
||||
switch (root.density) {
|
||||
case "compact":
|
||||
return Math.max(1, Math.round(pillHeight * 0.65))
|
||||
return Math.max(1, Math.round(pillHeight * 0.65));
|
||||
default:
|
||||
return Math.max(1, Math.round(pillHeight * 0.48))
|
||||
return Math.max(1, Math.round(pillHeight * 0.48));
|
||||
}
|
||||
}
|
||||
|
||||
readonly property real textSize: {
|
||||
switch (root.density) {
|
||||
case "compact":
|
||||
return Math.max(1, Math.round(pillHeight * 0.45))
|
||||
return Math.max(1, Math.round(pillHeight * 0.45));
|
||||
default:
|
||||
return Math.max(1, Math.round(pillHeight * 0.33))
|
||||
return Math.max(1, Math.round(pillHeight * 0.33));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ Item {
|
||||
target: root
|
||||
function onTooltipTextChanged() {
|
||||
if (hovered) {
|
||||
TooltipService.updateText(root.tooltipText)
|
||||
TooltipService.updateText(root.tooltipText);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,13 +97,13 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
x: {
|
||||
// Better text horizontal centering
|
||||
var centerX = (parent.width - width) / 2
|
||||
var offset = oppositeDirection ? Style.marginXS : -Style.marginXS
|
||||
var centerX = (parent.width - width) / 2;
|
||||
var offset = oppositeDirection ? Style.marginXS : -Style.marginXS;
|
||||
if (forceOpen) {
|
||||
// If its force open, the icon disc background is the same color as the bg pill move text slightly
|
||||
offset += oppositeDirection ? -Style.marginXXS : Style.marginXXS
|
||||
offset += oppositeDirection ? -Style.marginXXS : Style.marginXXS;
|
||||
}
|
||||
return centerX + offset
|
||||
return centerX + offset;
|
||||
}
|
||||
text: root.text + root.suffix
|
||||
family: Settings.data.ui.fontFixed
|
||||
@@ -179,11 +179,11 @@ Item {
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
onStarted: {
|
||||
showPill = true
|
||||
showPill = true;
|
||||
}
|
||||
onStopped: {
|
||||
delayedHideAnim.start()
|
||||
root.shown()
|
||||
delayedHideAnim.start();
|
||||
root.shown();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,7 +195,7 @@ Item {
|
||||
}
|
||||
ScriptAction {
|
||||
script: if (shouldAnimateHide) {
|
||||
hideAnim.start()
|
||||
hideAnim.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -220,9 +220,9 @@ Item {
|
||||
easing.type: Easing.InCubic
|
||||
}
|
||||
onStopped: {
|
||||
showPill = false
|
||||
shouldAnimateHide = false
|
||||
root.hidden()
|
||||
showPill = false;
|
||||
shouldAnimateHide = false;
|
||||
root.hidden();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,7 +231,7 @@ Item {
|
||||
interval: Style.pillDelay
|
||||
onTriggered: {
|
||||
if (!showPill) {
|
||||
showAnim.start()
|
||||
showAnim.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -241,31 +241,31 @@ Item {
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
onEntered: {
|
||||
hovered = true
|
||||
root.entered()
|
||||
TooltipService.show(Screen, pill, root.tooltipText, BarService.getTooltipDirection(), Style.tooltipDelayLong)
|
||||
hovered = true;
|
||||
root.entered();
|
||||
TooltipService.show(Screen, pill, root.tooltipText, BarService.getTooltipDirection(), Style.tooltipDelayLong);
|
||||
if (forceClose) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
if (!forceOpen) {
|
||||
showDelayed()
|
||||
showDelayed();
|
||||
}
|
||||
}
|
||||
onExited: {
|
||||
hovered = false
|
||||
root.exited()
|
||||
hovered = false;
|
||||
root.exited();
|
||||
if (!forceOpen && !forceClose) {
|
||||
hide()
|
||||
hide();
|
||||
}
|
||||
TooltipService.hide()
|
||||
TooltipService.hide();
|
||||
}
|
||||
onClicked: function (mouse) {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
root.clicked()
|
||||
root.clicked();
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
root.rightClicked()
|
||||
root.rightClicked();
|
||||
} else if (mouse.button === Qt.MiddleButton) {
|
||||
root.middleClicked()
|
||||
root.middleClicked();
|
||||
}
|
||||
}
|
||||
onWheel: wheel => root.wheel(wheel.angleDelta.y)
|
||||
@@ -273,43 +273,43 @@ Item {
|
||||
|
||||
function show() {
|
||||
if (!showPill) {
|
||||
shouldAnimateHide = autoHide
|
||||
showAnim.start()
|
||||
shouldAnimateHide = autoHide;
|
||||
showAnim.start();
|
||||
} else {
|
||||
hideAnim.stop()
|
||||
delayedHideAnim.restart()
|
||||
hideAnim.stop();
|
||||
delayedHideAnim.restart();
|
||||
}
|
||||
}
|
||||
|
||||
function hide() {
|
||||
if (forceOpen) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
if (showPill) {
|
||||
hideAnim.start()
|
||||
hideAnim.start();
|
||||
}
|
||||
showTimer.stop()
|
||||
showTimer.stop();
|
||||
}
|
||||
|
||||
function showDelayed() {
|
||||
if (!showPill) {
|
||||
shouldAnimateHide = autoHide
|
||||
showTimer.start()
|
||||
shouldAnimateHide = autoHide;
|
||||
showTimer.start();
|
||||
} else {
|
||||
hideAnim.stop()
|
||||
delayedHideAnim.restart()
|
||||
hideAnim.stop();
|
||||
delayedHideAnim.restart();
|
||||
}
|
||||
}
|
||||
|
||||
onForceOpenChanged: {
|
||||
if (forceOpen) {
|
||||
// Immediately lock open without animations
|
||||
showAnim.stop()
|
||||
hideAnim.stop()
|
||||
delayedHideAnim.stop()
|
||||
showPill = true
|
||||
showAnim.stop();
|
||||
hideAnim.stop();
|
||||
delayedHideAnim.stop();
|
||||
showPill = true;
|
||||
} else {
|
||||
hide()
|
||||
hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,18 +55,18 @@ Item {
|
||||
readonly property real iconSize: {
|
||||
switch (root.density) {
|
||||
case "compact":
|
||||
return Math.max(1, Math.round(pillHeight * 0.65))
|
||||
return Math.max(1, Math.round(pillHeight * 0.65));
|
||||
default:
|
||||
return Math.max(1, Math.round(pillHeight * 0.48))
|
||||
return Math.max(1, Math.round(pillHeight * 0.48));
|
||||
}
|
||||
}
|
||||
|
||||
readonly property real textSize: {
|
||||
switch (root.density) {
|
||||
case "compact":
|
||||
return Math.max(1, Math.round(pillHeight * 0.38))
|
||||
return Math.max(1, Math.round(pillHeight * 0.38));
|
||||
default:
|
||||
return Math.max(1, Math.round(pillHeight * 0.33))
|
||||
return Math.max(1, Math.round(pillHeight * 0.33));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ Item {
|
||||
target: root
|
||||
function onTooltipTextChanged() {
|
||||
if (hovered) {
|
||||
TooltipService.updateText(root.tooltipText)
|
||||
TooltipService.updateText(root.tooltipText);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,11 +123,11 @@ Item {
|
||||
visible: revealed
|
||||
|
||||
function getVerticalCenterOffset() {
|
||||
var offset = openDownward ? Math.round(pillPaddingVertical * 0.75) : -Math.round(pillPaddingVertical * 0.75)
|
||||
var offset = openDownward ? Math.round(pillPaddingVertical * 0.75) : -Math.round(pillPaddingVertical * 0.75);
|
||||
if (forceOpen) {
|
||||
offset += oppositeDirection ? -Style.marginXXS : Style.marginXXS
|
||||
offset += oppositeDirection ? -Style.marginXXS : Style.marginXXS;
|
||||
}
|
||||
return offset
|
||||
return offset;
|
||||
}
|
||||
}
|
||||
Behavior on width {
|
||||
@@ -212,11 +212,11 @@ Item {
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
onStarted: {
|
||||
showPill = true
|
||||
showPill = true;
|
||||
}
|
||||
onStopped: {
|
||||
delayedHideAnim.start()
|
||||
root.shown()
|
||||
delayedHideAnim.start();
|
||||
root.shown();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,7 +228,7 @@ Item {
|
||||
}
|
||||
ScriptAction {
|
||||
script: if (shouldAnimateHide) {
|
||||
hideAnim.start()
|
||||
hideAnim.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -261,9 +261,9 @@ Item {
|
||||
easing.type: Easing.InCubic
|
||||
}
|
||||
onStopped: {
|
||||
showPill = false
|
||||
shouldAnimateHide = false
|
||||
root.hidden()
|
||||
showPill = false;
|
||||
shouldAnimateHide = false;
|
||||
root.hidden();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,7 +272,7 @@ Item {
|
||||
interval: Style.pillDelay
|
||||
onTriggered: {
|
||||
if (!showPill) {
|
||||
showAnim.start()
|
||||
showAnim.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -282,31 +282,31 @@ Item {
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
onEntered: {
|
||||
hovered = true
|
||||
root.entered()
|
||||
TooltipService.show(Screen, pill, root.tooltipText, BarService.getTooltipDirection(), Style.tooltipDelayLong)
|
||||
hovered = true;
|
||||
root.entered();
|
||||
TooltipService.show(Screen, pill, root.tooltipText, BarService.getTooltipDirection(), Style.tooltipDelayLong);
|
||||
if (forceClose) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
if (!forceOpen) {
|
||||
showDelayed()
|
||||
showDelayed();
|
||||
}
|
||||
}
|
||||
onExited: {
|
||||
hovered = false
|
||||
root.exited()
|
||||
hovered = false;
|
||||
root.exited();
|
||||
if (!forceOpen && !forceClose) {
|
||||
hide()
|
||||
hide();
|
||||
}
|
||||
TooltipService.hide()
|
||||
TooltipService.hide();
|
||||
}
|
||||
onClicked: function (mouse) {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
root.clicked()
|
||||
root.clicked();
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
root.rightClicked()
|
||||
root.rightClicked();
|
||||
} else if (mouse.button === Qt.MiddleButton) {
|
||||
root.middleClicked()
|
||||
root.middleClicked();
|
||||
}
|
||||
}
|
||||
onWheel: wheel => root.wheel(wheel.angleDelta.y)
|
||||
@@ -314,43 +314,43 @@ Item {
|
||||
|
||||
function show() {
|
||||
if (!showPill) {
|
||||
shouldAnimateHide = autoHide
|
||||
showAnim.start()
|
||||
shouldAnimateHide = autoHide;
|
||||
showAnim.start();
|
||||
} else {
|
||||
hideAnim.stop()
|
||||
delayedHideAnim.restart()
|
||||
hideAnim.stop();
|
||||
delayedHideAnim.restart();
|
||||
}
|
||||
}
|
||||
|
||||
function hide() {
|
||||
if (forceOpen) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
if (showPill) {
|
||||
hideAnim.start()
|
||||
hideAnim.start();
|
||||
}
|
||||
showTimer.stop()
|
||||
showTimer.stop();
|
||||
}
|
||||
|
||||
function showDelayed() {
|
||||
if (!showPill) {
|
||||
shouldAnimateHide = autoHide
|
||||
showTimer.start()
|
||||
shouldAnimateHide = autoHide;
|
||||
showTimer.start();
|
||||
} else {
|
||||
hideAnim.stop()
|
||||
delayedHideAnim.restart()
|
||||
hideAnim.stop();
|
||||
delayedHideAnim.restart();
|
||||
}
|
||||
}
|
||||
|
||||
onForceOpenChanged: {
|
||||
if (forceOpen) {
|
||||
// Immediately lock open without animations
|
||||
showAnim.stop()
|
||||
hideAnim.stop()
|
||||
delayedHideAnim.stop()
|
||||
showPill = true
|
||||
showAnim.stop();
|
||||
hideAnim.stop();
|
||||
delayedHideAnim.stop();
|
||||
showPill = true;
|
||||
} else {
|
||||
hide()
|
||||
hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Services.UI
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@@ -25,7 +25,7 @@ Item {
|
||||
visible: loader.item ? ((loader.item.opacity > 0.0) || (loader.item.hasOwnProperty("hideMode") && loader.item.hideMode === "transparent")) : false
|
||||
|
||||
function getImplicitSize(item, prop) {
|
||||
return (item && item.visible) ? Math.round(item[prop]) : 0
|
||||
return (item && item.visible) ? Math.round(item[prop]) : 0;
|
||||
}
|
||||
|
||||
Loader {
|
||||
@@ -36,42 +36,41 @@ Item {
|
||||
|
||||
onLoaded: {
|
||||
if (!item)
|
||||
return
|
||||
|
||||
Logger.d("BarWidgetLoader", "Loading widget", widgetId, "on screen:", widgetScreen.name)
|
||||
return;
|
||||
Logger.d("BarWidgetLoader", "Loading widget", widgetId, "on screen:", widgetScreen.name);
|
||||
|
||||
// Apply properties to loaded widget
|
||||
for (var prop in widgetProps) {
|
||||
if (item.hasOwnProperty(prop)) {
|
||||
item[prop] = widgetProps[prop]
|
||||
item[prop] = widgetProps[prop];
|
||||
}
|
||||
}
|
||||
|
||||
// Set screen property
|
||||
if (item.hasOwnProperty("screen")) {
|
||||
item.screen = widgetScreen
|
||||
item.screen = widgetScreen;
|
||||
}
|
||||
|
||||
// Set scaling property
|
||||
if (item.hasOwnProperty("scaling")) {
|
||||
item.scaling = Qt.binding(function () {
|
||||
return root.scaling
|
||||
})
|
||||
return root.scaling;
|
||||
});
|
||||
}
|
||||
|
||||
// Register this widget instance with BarService
|
||||
BarService.registerWidget(widgetScreen.name, section, widgetId, sectionIndex, item)
|
||||
BarService.registerWidget(widgetScreen.name, section, widgetId, sectionIndex, item);
|
||||
|
||||
// Call custom onLoaded if it exists
|
||||
if (item.hasOwnProperty("onLoaded")) {
|
||||
item.onLoaded()
|
||||
item.onLoaded();
|
||||
}
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
// Unregister when destroyed
|
||||
if (widgetScreen && section) {
|
||||
BarService.unregisterWidget(widgetScreen.name, section, widgetId, sectionIndex)
|
||||
BarService.unregisterWidget(widgetScreen.name, section, widgetId, sectionIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,7 +78,7 @@ Item {
|
||||
// Error handling
|
||||
Component.onCompleted: {
|
||||
if (!BarWidgetRegistry.hasWidget(widgetId)) {
|
||||
Logger.w("BarWidgetLoader", "Widget not found in registry:", widgetId)
|
||||
Logger.w("BarWidgetLoader", "Widget not found in registry:", widgetId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+100
-100
@@ -25,20 +25,20 @@ PopupWindow {
|
||||
// Compute if current tray item is pinned
|
||||
readonly property bool isPinned: {
|
||||
if (!trayItem || widgetSection === "" || widgetIndex < 0)
|
||||
return false
|
||||
var widgets = Settings.data.bar.widgets[widgetSection]
|
||||
return false;
|
||||
var widgets = Settings.data.bar.widgets[widgetSection];
|
||||
if (!widgets || widgetIndex >= widgets.length)
|
||||
return false
|
||||
var widgetSettings = widgets[widgetIndex]
|
||||
return false;
|
||||
var widgetSettings = widgets[widgetIndex];
|
||||
if (!widgetSettings || widgetSettings.id !== "Tray")
|
||||
return false
|
||||
var pinnedList = widgetSettings.pinned || []
|
||||
const itemName = trayItem.tooltipTitle || trayItem.name || trayItem.id || ""
|
||||
return false;
|
||||
var pinnedList = widgetSettings.pinned || [];
|
||||
const itemName = trayItem.tooltipTitle || trayItem.name || trayItem.id || "";
|
||||
for (var i = 0; i < pinnedList.length; i++) {
|
||||
if (pinnedList[i] === itemName)
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
readonly property int menuWidth: 220
|
||||
@@ -53,47 +53,47 @@ PopupWindow {
|
||||
anchor.rect.x: anchorX
|
||||
anchor.rect.y: {
|
||||
if (isSubMenu) {
|
||||
const offsetY = Settings.data.bar.position === "bottom" ? -10 : 10
|
||||
return anchorY + offsetY
|
||||
const offsetY = Settings.data.bar.position === "bottom" ? -10 : 10;
|
||||
return anchorY + offsetY;
|
||||
}
|
||||
return anchorY + Settings.data.bar.position === "bottom" ? -implicitHeight : Style.barHeight
|
||||
return anchorY + Settings.data.bar.position === "bottom" ? -implicitHeight : Style.barHeight;
|
||||
}
|
||||
|
||||
function showAt(item, x, y) {
|
||||
if (!item) {
|
||||
Logger.w("TrayMenu", "anchorItem is undefined, won't show menu.")
|
||||
return
|
||||
Logger.w("TrayMenu", "anchorItem is undefined, won't show menu.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!opener.children || opener.children.values.length === 0) {
|
||||
//Logger.w("TrayMenu", "Menu not ready, delaying show")
|
||||
Qt.callLater(() => showAt(item, x, y))
|
||||
return
|
||||
Qt.callLater(() => showAt(item, x, y));
|
||||
return;
|
||||
}
|
||||
|
||||
anchorItem = item
|
||||
anchorX = x
|
||||
anchorY = y
|
||||
anchorItem = item;
|
||||
anchorX = x;
|
||||
anchorY = y;
|
||||
|
||||
visible = true
|
||||
forceActiveFocus()
|
||||
visible = true;
|
||||
forceActiveFocus();
|
||||
|
||||
// Force update after showing.
|
||||
Qt.callLater(() => {
|
||||
root.anchor.updateAnchor()
|
||||
})
|
||||
root.anchor.updateAnchor();
|
||||
});
|
||||
}
|
||||
|
||||
function hideMenu() {
|
||||
visible = false
|
||||
visible = false;
|
||||
|
||||
// Clean up all submenus recursively
|
||||
for (var i = 0; i < columnLayout.children.length; i++) {
|
||||
const child = columnLayout.children[i]
|
||||
const child = columnLayout.children[i];
|
||||
if (child?.subMenu) {
|
||||
child.subMenu.hideMenu()
|
||||
child.subMenu.destroy()
|
||||
child.subMenu = null
|
||||
child.subMenu.hideMenu();
|
||||
child.subMenu.destroy();
|
||||
child.subMenu = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -166,11 +166,11 @@ PopupWindow {
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: {
|
||||
if (modelData?.isSeparator) {
|
||||
return 8
|
||||
return 8;
|
||||
} else {
|
||||
// Calculate based on text content
|
||||
const textHeight = text.contentHeight || (Style.fontSizeS * 1.2)
|
||||
return Math.max(28, textHeight + (Style.marginS * 2))
|
||||
const textHeight = text.contentHeight || (Style.fontSizeS * 1.2);
|
||||
return Math.max(28, textHeight + (Style.marginS * 2));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,31 +237,31 @@ PopupWindow {
|
||||
// Click on items with children toggles submenu
|
||||
if (entry.subMenu) {
|
||||
// Close existing submenu
|
||||
entry.subMenu.hideMenu()
|
||||
entry.subMenu.destroy()
|
||||
entry.subMenu = null
|
||||
entry.subMenu.hideMenu();
|
||||
entry.subMenu.destroy();
|
||||
entry.subMenu = null;
|
||||
} else {
|
||||
// Close any other open submenus first
|
||||
for (var i = 0; i < columnLayout.children.length; i++) {
|
||||
const sibling = columnLayout.children[i]
|
||||
const sibling = columnLayout.children[i];
|
||||
if (sibling !== entry && sibling.subMenu) {
|
||||
sibling.subMenu.hideMenu()
|
||||
sibling.subMenu.destroy()
|
||||
sibling.subMenu = null
|
||||
sibling.subMenu.hideMenu();
|
||||
sibling.subMenu.destroy();
|
||||
sibling.subMenu = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Determine submenu opening direction
|
||||
let openLeft = false
|
||||
const barPosition = Settings.data.bar.position
|
||||
const globalPos = entry.mapToItem(null, 0, 0)
|
||||
let openLeft = false;
|
||||
const barPosition = Settings.data.bar.position;
|
||||
const globalPos = entry.mapToItem(null, 0, 0);
|
||||
|
||||
if (barPosition === "right") {
|
||||
openLeft = true
|
||||
openLeft = true;
|
||||
} else if (barPosition === "left") {
|
||||
openLeft = false
|
||||
openLeft = false;
|
||||
} else {
|
||||
openLeft = (root.widgetSection === "right")
|
||||
openLeft = (root.widgetSection === "right");
|
||||
}
|
||||
|
||||
// Open new submenu
|
||||
@@ -269,30 +269,30 @@ PopupWindow {
|
||||
"menu": modelData,
|
||||
"isSubMenu": true,
|
||||
"screen": root.screen
|
||||
})
|
||||
});
|
||||
|
||||
if (entry.subMenu) {
|
||||
const overlap = 60
|
||||
entry.subMenu.anchorItem = entry
|
||||
entry.subMenu.anchorX = openLeft ? -overlap : overlap
|
||||
entry.subMenu.anchorY = 0
|
||||
entry.subMenu.visible = true
|
||||
const overlap = 60;
|
||||
entry.subMenu.anchorItem = entry;
|
||||
entry.subMenu.anchorX = openLeft ? -overlap : overlap;
|
||||
entry.subMenu.anchorY = 0;
|
||||
entry.subMenu.visible = true;
|
||||
// Force anchor update with new position
|
||||
Qt.callLater(() => {
|
||||
entry.subMenu.anchor.updateAnchor()
|
||||
})
|
||||
entry.subMenu.anchor.updateAnchor();
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Click on regular items triggers them
|
||||
modelData.triggered()
|
||||
root.hideMenu()
|
||||
modelData.triggered();
|
||||
root.hideMenu();
|
||||
|
||||
// Close the drawer if it's open
|
||||
if (root.screen) {
|
||||
const panel = PanelService.getPanel("trayDrawerPanel", root.screen)
|
||||
const panel = PanelService.getPanel("trayDrawerPanel", root.screen);
|
||||
if (panel && panel.visible) {
|
||||
panel.close()
|
||||
panel.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -303,8 +303,8 @@ PopupWindow {
|
||||
|
||||
Component.onDestruction: {
|
||||
if (subMenu) {
|
||||
subMenu.destroy()
|
||||
subMenu = null
|
||||
subMenu.destroy();
|
||||
subMenu = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -351,9 +351,9 @@ PopupWindow {
|
||||
|
||||
onClicked: {
|
||||
if (root.isPinned) {
|
||||
root.removeFromPinned()
|
||||
root.removeFromPinned();
|
||||
} else {
|
||||
root.addToPinned()
|
||||
root.addToPinned();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -363,72 +363,72 @@ PopupWindow {
|
||||
|
||||
function addToPinned() {
|
||||
if (!trayItem || widgetSection === "" || widgetIndex < 0) {
|
||||
Logger.w("TrayMenu", "Cannot pin: missing tray item or widget info")
|
||||
return
|
||||
Logger.w("TrayMenu", "Cannot pin: missing tray item or widget info");
|
||||
return;
|
||||
}
|
||||
const itemName = trayItem.tooltipTitle || trayItem.name || trayItem.id || ""
|
||||
const itemName = trayItem.tooltipTitle || trayItem.name || trayItem.id || "";
|
||||
if (!itemName) {
|
||||
Logger.w("TrayMenu", "Cannot pin: tray item has no name")
|
||||
return
|
||||
Logger.w("TrayMenu", "Cannot pin: tray item has no name");
|
||||
return;
|
||||
}
|
||||
var widgets = Settings.data.bar.widgets[widgetSection]
|
||||
var widgets = Settings.data.bar.widgets[widgetSection];
|
||||
if (!widgets || widgetIndex >= widgets.length) {
|
||||
Logger.w("TrayMenu", "Cannot pin: invalid widget index")
|
||||
return
|
||||
Logger.w("TrayMenu", "Cannot pin: invalid widget index");
|
||||
return;
|
||||
}
|
||||
var widgetSettings = widgets[widgetIndex]
|
||||
var widgetSettings = widgets[widgetIndex];
|
||||
if (!widgetSettings || widgetSettings.id !== "Tray") {
|
||||
Logger.w("TrayMenu", "Cannot pin: widget is not a Tray widget")
|
||||
return
|
||||
Logger.w("TrayMenu", "Cannot pin: widget is not a Tray widget");
|
||||
return;
|
||||
}
|
||||
var pinnedList = widgetSettings.pinned || []
|
||||
var newPinned = pinnedList.slice()
|
||||
newPinned.push(itemName)
|
||||
var newSettings = Object.assign({}, widgetSettings)
|
||||
newSettings.pinned = newPinned
|
||||
widgets[widgetIndex] = newSettings
|
||||
Settings.data.bar.widgets[widgetSection] = widgets
|
||||
Settings.saveImmediate()
|
||||
var pinnedList = widgetSettings.pinned || [];
|
||||
var newPinned = pinnedList.slice();
|
||||
newPinned.push(itemName);
|
||||
var newSettings = Object.assign({}, widgetSettings);
|
||||
newSettings.pinned = newPinned;
|
||||
widgets[widgetIndex] = newSettings;
|
||||
Settings.data.bar.widgets[widgetSection] = widgets;
|
||||
Settings.saveImmediate();
|
||||
|
||||
// Close drawer when pinning (drawer needs to resize)
|
||||
if (screen) {
|
||||
const panel = PanelService.getPanel("trayDrawerPanel", screen)
|
||||
const panel = PanelService.getPanel("trayDrawerPanel", screen);
|
||||
if (panel)
|
||||
panel.close()
|
||||
panel.close();
|
||||
}
|
||||
}
|
||||
|
||||
function removeFromPinned() {
|
||||
if (!trayItem || widgetSection === "" || widgetIndex < 0) {
|
||||
Logger.w("TrayMenu", "Cannot unpin: missing tray item or widget info")
|
||||
return
|
||||
Logger.w("TrayMenu", "Cannot unpin: missing tray item or widget info");
|
||||
return;
|
||||
}
|
||||
const itemName = trayItem.tooltipTitle || trayItem.name || trayItem.id || ""
|
||||
const itemName = trayItem.tooltipTitle || trayItem.name || trayItem.id || "";
|
||||
if (!itemName) {
|
||||
Logger.w("TrayMenu", "Cannot unpin: tray item has no name")
|
||||
return
|
||||
Logger.w("TrayMenu", "Cannot unpin: tray item has no name");
|
||||
return;
|
||||
}
|
||||
var widgets = Settings.data.bar.widgets[widgetSection]
|
||||
var widgets = Settings.data.bar.widgets[widgetSection];
|
||||
if (!widgets || widgetIndex >= widgets.length) {
|
||||
Logger.w("TrayMenu", "Cannot unpin: invalid widget index")
|
||||
return
|
||||
Logger.w("TrayMenu", "Cannot unpin: invalid widget index");
|
||||
return;
|
||||
}
|
||||
var widgetSettings = widgets[widgetIndex]
|
||||
var widgetSettings = widgets[widgetIndex];
|
||||
if (!widgetSettings || widgetSettings.id !== "Tray") {
|
||||
Logger.w("TrayMenu", "Cannot unpin: widget is not a Tray widget")
|
||||
return
|
||||
Logger.w("TrayMenu", "Cannot unpin: widget is not a Tray widget");
|
||||
return;
|
||||
}
|
||||
var pinnedList = widgetSettings.pinned || []
|
||||
var newPinned = []
|
||||
var pinnedList = widgetSettings.pinned || [];
|
||||
var newPinned = [];
|
||||
for (var i = 0; i < pinnedList.length; i++) {
|
||||
if (pinnedList[i] !== itemName) {
|
||||
newPinned.push(pinnedList[i])
|
||||
newPinned.push(pinnedList[i]);
|
||||
}
|
||||
}
|
||||
var newSettings = Object.assign({}, widgetSettings)
|
||||
newSettings.pinned = newPinned
|
||||
widgets[widgetIndex] = newSettings
|
||||
Settings.data.bar.widgets[widgetSection] = widgets
|
||||
Settings.saveImmediate()
|
||||
var newSettings = Object.assign({}, widgetSettings);
|
||||
newSettings.pinned = newPinned;
|
||||
widgets[widgetIndex] = newSettings;
|
||||
Settings.data.bar.widgets[widgetSection] = widgets;
|
||||
Settings.saveImmediate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,12 +23,12 @@ Item {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
// Widget settings - matching MediaMini pattern
|
||||
@@ -73,60 +73,60 @@ Item {
|
||||
}
|
||||
|
||||
function calculatedVerticalDimension() {
|
||||
return Math.round((Style.baseWidgetSize - 5) * scaling)
|
||||
return Math.round((Style.baseWidgetSize - 5) * scaling);
|
||||
}
|
||||
|
||||
function calculateContentWidth() {
|
||||
// Calculate the actual content width based on visible elements
|
||||
var contentWidth = 0
|
||||
var margins = Style.marginS * scaling * 2 // Left and right margins
|
||||
var contentWidth = 0;
|
||||
var margins = Style.marginS * scaling * 2; // Left and right margins
|
||||
|
||||
// Icon width (if visible)
|
||||
if (showIcon) {
|
||||
contentWidth += 18 * scaling
|
||||
contentWidth += Style.marginS * scaling // Spacing after icon
|
||||
contentWidth += 18 * scaling;
|
||||
contentWidth += Style.marginS * scaling; // Spacing after icon
|
||||
}
|
||||
|
||||
// Text width (use the measured width)
|
||||
contentWidth += fullTitleMetrics.contentWidth
|
||||
contentWidth += fullTitleMetrics.contentWidth;
|
||||
|
||||
// Additional small margin for text
|
||||
contentWidth += Style.marginXXS * 2
|
||||
contentWidth += Style.marginXXS * 2;
|
||||
|
||||
// Add container margins
|
||||
contentWidth += margins
|
||||
contentWidth += margins;
|
||||
|
||||
return Math.ceil(contentWidth)
|
||||
return Math.ceil(contentWidth);
|
||||
}
|
||||
|
||||
// Dynamic width: adapt to content but respect maximum width setting
|
||||
readonly property real dynamicWidth: {
|
||||
// If using fixed width mode, always use maxWidth
|
||||
if (useFixedWidth) {
|
||||
return maxWidth
|
||||
return maxWidth;
|
||||
}
|
||||
// Otherwise, adapt to content
|
||||
if (!hasFocusedWindow) {
|
||||
return Math.min(calculateContentWidth(), maxWidth)
|
||||
return Math.min(calculateContentWidth(), maxWidth);
|
||||
}
|
||||
// Use content width but don't exceed user-set maximum width
|
||||
return Math.min(calculateContentWidth(), maxWidth)
|
||||
return Math.min(calculateContentWidth(), maxWidth);
|
||||
}
|
||||
|
||||
function getAppIcon() {
|
||||
try {
|
||||
// Try CompositorService first
|
||||
const focusedWindow = CompositorService.getFocusedWindow()
|
||||
const focusedWindow = CompositorService.getFocusedWindow();
|
||||
if (focusedWindow && focusedWindow.appId) {
|
||||
try {
|
||||
const idValue = focusedWindow.appId
|
||||
const normalizedId = (typeof idValue === 'string') ? idValue : String(idValue)
|
||||
const iconResult = ThemeIcons.iconForAppId(normalizedId.toLowerCase())
|
||||
const idValue = focusedWindow.appId;
|
||||
const normalizedId = (typeof idValue === 'string') ? idValue : String(idValue);
|
||||
const iconResult = ThemeIcons.iconForAppId(normalizedId.toLowerCase());
|
||||
if (iconResult && iconResult !== "") {
|
||||
return iconResult
|
||||
return iconResult;
|
||||
}
|
||||
} catch (iconError) {
|
||||
Logger.w("ActiveWindow", "Error getting icon from CompositorService:", iconError)
|
||||
Logger.w("ActiveWindow", "Error getting icon from CompositorService:", iconError);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,25 +134,25 @@ Item {
|
||||
// Fallback to ToplevelManager
|
||||
if (ToplevelManager && ToplevelManager.activeToplevel) {
|
||||
try {
|
||||
const activeToplevel = ToplevelManager.activeToplevel
|
||||
const activeToplevel = ToplevelManager.activeToplevel;
|
||||
if (activeToplevel.appId) {
|
||||
const idValue2 = activeToplevel.appId
|
||||
const normalizedId2 = (typeof idValue2 === 'string') ? idValue2 : String(idValue2)
|
||||
const iconResult2 = ThemeIcons.iconForAppId(normalizedId2.toLowerCase())
|
||||
const idValue2 = activeToplevel.appId;
|
||||
const normalizedId2 = (typeof idValue2 === 'string') ? idValue2 : String(idValue2);
|
||||
const iconResult2 = ThemeIcons.iconForAppId(normalizedId2.toLowerCase());
|
||||
if (iconResult2 && iconResult2 !== "") {
|
||||
return iconResult2
|
||||
return iconResult2;
|
||||
}
|
||||
}
|
||||
} catch (fallbackError) {
|
||||
Logger.w("ActiveWindow", "Error getting icon from ToplevelManager:", fallbackError)
|
||||
Logger.w("ActiveWindow", "Error getting icon from ToplevelManager:", fallbackError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ThemeIcons.iconFromName(fallbackIcon)
|
||||
return ThemeIcons.iconFromName(fallbackIcon);
|
||||
} catch (e) {
|
||||
Logger.w("ActiveWindow", "Error in getAppIcon:", e)
|
||||
return ThemeIcons.iconFromName(fallbackIcon)
|
||||
Logger.w("ActiveWindow", "Error in getAppIcon:", e);
|
||||
return ThemeIcons.iconFromName(fallbackIcon);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,10 +228,10 @@ Item {
|
||||
id: titleContainer
|
||||
Layout.preferredWidth: {
|
||||
// Calculate available width based on other elements
|
||||
var iconWidth = (showIcon && windowIcon.visible ? (18 + Style.marginS) : 0)
|
||||
var totalMargins = Style.marginXXS * 2
|
||||
var availableWidth = mainContainer.width - iconWidth - totalMargins
|
||||
return Math.max(20, availableWidth)
|
||||
var iconWidth = (showIcon && windowIcon.visible ? (18 + Style.marginS) : 0);
|
||||
var totalMargins = Style.marginXXS * 2;
|
||||
var availableWidth = mainContainer.width - iconWidth - totalMargins;
|
||||
return Math.max(20, availableWidth);
|
||||
}
|
||||
Layout.maximumWidth: Layout.preferredWidth
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
@@ -252,8 +252,8 @@ Item {
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (scrollingMode === "always" && titleContainer.needsScrolling) {
|
||||
titleContainer.isScrolling = true
|
||||
titleContainer.isResetting = false
|
||||
titleContainer.isScrolling = true;
|
||||
titleContainer.isResetting = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -261,29 +261,29 @@ Item {
|
||||
// Update scrolling state based on mode
|
||||
property var updateScrollingState: function () {
|
||||
if (scrollingMode === "never") {
|
||||
isScrolling = false
|
||||
isResetting = false
|
||||
isScrolling = false;
|
||||
isResetting = false;
|
||||
} else if (scrollingMode === "always") {
|
||||
if (needsScrolling) {
|
||||
if (mouseArea.containsMouse) {
|
||||
isScrolling = false
|
||||
isResetting = true
|
||||
isScrolling = false;
|
||||
isResetting = true;
|
||||
} else {
|
||||
scrollStartTimer.restart()
|
||||
scrollStartTimer.restart();
|
||||
}
|
||||
} else {
|
||||
scrollStartTimer.stop()
|
||||
isScrolling = false
|
||||
isResetting = false
|
||||
scrollStartTimer.stop();
|
||||
isScrolling = false;
|
||||
isResetting = false;
|
||||
}
|
||||
} else if (scrollingMode === "hover") {
|
||||
if (mouseArea.containsMouse && needsScrolling) {
|
||||
isScrolling = true
|
||||
isResetting = false
|
||||
isScrolling = true;
|
||||
isResetting = false;
|
||||
} else {
|
||||
isScrolling = false
|
||||
isScrolling = false;
|
||||
if (needsScrolling) {
|
||||
isResetting = true
|
||||
isResetting = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -296,7 +296,7 @@ Item {
|
||||
Connections {
|
||||
target: mouseArea
|
||||
function onContainsMouseChanged() {
|
||||
titleContainer.updateScrollingState()
|
||||
titleContainer.updateScrollingState();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,10 +322,10 @@ Item {
|
||||
color: Color.mOnSurface
|
||||
onTextChanged: {
|
||||
if (root.scrollingMode === "always") {
|
||||
titleContainer.isScrolling = false
|
||||
titleContainer.isResetting = false
|
||||
scrollContainer.scrollX = 0
|
||||
scrollStartTimer.restart()
|
||||
titleContainer.isScrolling = false;
|
||||
titleContainer.isResetting = false;
|
||||
scrollContainer.scrollX = 0;
|
||||
scrollStartTimer.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -349,7 +349,7 @@ Item {
|
||||
duration: 300
|
||||
easing.type: Easing.OutQuad
|
||||
onFinished: {
|
||||
titleContainer.isResetting = false
|
||||
titleContainer.isResetting = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -412,11 +412,11 @@ Item {
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onEntered: {
|
||||
if ((windowTitle !== "") && isVerticalBar || (scrollingMode === "never")) {
|
||||
TooltipService.show(Screen, root, windowTitle, BarService.getTooltipDirection())
|
||||
TooltipService.show(Screen, root, windowTitle, BarService.getTooltipDirection());
|
||||
}
|
||||
}
|
||||
onExited: {
|
||||
TooltipService.hide()
|
||||
TooltipService.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -426,18 +426,18 @@ Item {
|
||||
target: CompositorService
|
||||
function onActiveWindowChanged() {
|
||||
try {
|
||||
windowIcon.source = Qt.binding(getAppIcon)
|
||||
windowIconVertical.source = Qt.binding(getAppIcon)
|
||||
windowIcon.source = Qt.binding(getAppIcon);
|
||||
windowIconVertical.source = Qt.binding(getAppIcon);
|
||||
} catch (e) {
|
||||
Logger.w("ActiveWindow", "Error in onActiveWindowChanged:", e)
|
||||
Logger.w("ActiveWindow", "Error in onActiveWindowChanged:", e);
|
||||
}
|
||||
}
|
||||
function onWindowListChanged() {
|
||||
try {
|
||||
windowIcon.source = Qt.binding(getAppIcon)
|
||||
windowIconVertical.source = Qt.binding(getAppIcon)
|
||||
windowIcon.source = Qt.binding(getAppIcon);
|
||||
windowIconVertical.source = Qt.binding(getAppIcon);
|
||||
} catch (e) {
|
||||
Logger.w("ActiveWindow", "Error in onWindowListChanged:", e)
|
||||
Logger.w("ActiveWindow", "Error in onWindowListChanged:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import qs.Services.Media
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
import qs.Widgets.AudioSpectrum
|
||||
|
||||
@@ -22,12 +22,12 @@ Item {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
// Resolve settings: try user settings or defaults from BarWidgetRegistry
|
||||
@@ -38,16 +38,16 @@ Item {
|
||||
readonly property color fillColor: {
|
||||
switch (colorName) {
|
||||
case "primary":
|
||||
return Color.mPrimary
|
||||
return Color.mPrimary;
|
||||
case "secondary":
|
||||
return Color.mSecondary
|
||||
return Color.mSecondary;
|
||||
case "tertiary":
|
||||
return Color.mTertiary
|
||||
return Color.mTertiary;
|
||||
case "error":
|
||||
return Color.mError
|
||||
return Color.mError;
|
||||
case "onSurface":
|
||||
default:
|
||||
return Color.mOnSurface
|
||||
return Color.mOnSurface;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,13 +92,13 @@ Item {
|
||||
sourceComponent: {
|
||||
switch (currentVisualizerType) {
|
||||
case "linear":
|
||||
return linearComponent
|
||||
return linearComponent;
|
||||
case "mirrored":
|
||||
return mirroredComponent
|
||||
return mirroredComponent;
|
||||
case "wave":
|
||||
return waveComponent
|
||||
return waveComponent;
|
||||
default:
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,13 +112,13 @@ Item {
|
||||
acceptedButtons: Qt.LeftButton
|
||||
|
||||
onClicked: mouse => {
|
||||
const types = ["linear", "mirrored", "wave"]
|
||||
const currentIndex = types.indexOf(currentVisualizerType)
|
||||
const nextIndex = (currentIndex + 1) % types.length
|
||||
const newType = types[nextIndex]
|
||||
const types = ["linear", "mirrored", "wave"];
|
||||
const currentIndex = types.indexOf(currentVisualizerType);
|
||||
const nextIndex = (currentIndex + 1) % types.length;
|
||||
const newType = types[nextIndex];
|
||||
|
||||
// Update settings directly, maybe this should be a widget setting...
|
||||
Settings.data.audio.visualizerType = newType
|
||||
Settings.data.audio.visualizerType = newType;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Services.UPower
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import qs.Services.Hardware
|
||||
import qs.Widgets
|
||||
import qs.Modules.Bar.Extras
|
||||
import qs.Services.Hardware
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@@ -22,12 +22,12 @@ Item {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
|
||||
@@ -53,13 +53,13 @@ Item {
|
||||
function maybeNotify(percent, charging) {
|
||||
// Only notify once we are a below threshold
|
||||
if (!charging && !root.hasNotifiedLowBattery && percent <= warningThreshold) {
|
||||
root.hasNotifiedLowBattery = true
|
||||
root.hasNotifiedLowBattery = true;
|
||||
ToastService.showWarning(I18n.tr("toast.battery.low"), I18n.tr("toast.battery.low-desc", {
|
||||
"percent": Math.round(percent)
|
||||
}))
|
||||
}));
|
||||
} else if (root.hasNotifiedLowBattery && (charging || percent > warningThreshold + 5)) {
|
||||
// Reset when charging starts or when battery recovers 5% above threshold
|
||||
root.hasNotifiedLowBattery = false
|
||||
root.hasNotifiedLowBattery = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,20 +67,20 @@ Item {
|
||||
Connections {
|
||||
target: UPower.displayDevice
|
||||
function onPercentageChanged() {
|
||||
var currentPercent = UPower.displayDevice.percentage * 100
|
||||
var isCharging = UPower.displayDevice.state === UPowerDeviceState.Charging
|
||||
root.maybeNotify(currentPercent, isCharging)
|
||||
var currentPercent = UPower.displayDevice.percentage * 100;
|
||||
var isCharging = UPower.displayDevice.state === UPowerDeviceState.Charging;
|
||||
root.maybeNotify(currentPercent, isCharging);
|
||||
}
|
||||
|
||||
function onStateChanged() {
|
||||
var isCharging = UPower.displayDevice.state === UPowerDeviceState.Charging
|
||||
var isCharging = UPower.displayDevice.state === UPowerDeviceState.Charging;
|
||||
// Reset notification flag when charging starts
|
||||
if (isCharging) {
|
||||
root.hasNotifiedLowBattery = false
|
||||
root.hasNotifiedLowBattery = false;
|
||||
}
|
||||
// Also re-evaluate maybeNotify, as state might have changed
|
||||
var currentPercent = UPower.displayDevice.percentage * 100
|
||||
root.maybeNotify(currentPercent, isCharging)
|
||||
var currentPercent = UPower.displayDevice.percentage * 100;
|
||||
root.maybeNotify(currentPercent, isCharging);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,51 +95,50 @@ Item {
|
||||
autoHide: false
|
||||
forceOpen: isReady && (testMode || battery.isLaptopBattery) && displayMode === "alwaysShow"
|
||||
forceClose: displayMode === "alwaysHide" || !isReady || (!testMode && !battery.isLaptopBattery)
|
||||
onClicked: PanelService.getPanel("batteryPanel", screen)?.toggle(this)
|
||||
tooltipText: {
|
||||
let lines = []
|
||||
let lines = [];
|
||||
if (testMode) {
|
||||
lines.push(`Time left: ${Time.formatVagueHumanReadableDuration(12345)}.`)
|
||||
return lines.join("\n")
|
||||
lines.push(`Time left: ${Time.formatVagueHumanReadableDuration(12345)}.`);
|
||||
return lines.join("\n");
|
||||
}
|
||||
if (!isReady || !battery.isLaptopBattery) {
|
||||
return I18n.tr("battery.no-battery-detected")
|
||||
return I18n.tr("battery.no-battery-detected");
|
||||
}
|
||||
if (battery.timeToEmpty > 0) {
|
||||
lines.push(I18n.tr("battery.time-left", {
|
||||
"time": Time.formatVagueHumanReadableDuration(battery.timeToEmpty)
|
||||
}))
|
||||
}));
|
||||
}
|
||||
if (battery.timeToFull > 0) {
|
||||
lines.push(I18n.tr("battery.time-until-full", {
|
||||
"time": Time.formatVagueHumanReadableDuration(battery.timeToFull)
|
||||
}))
|
||||
}));
|
||||
}
|
||||
if (battery.changeRate !== undefined) {
|
||||
const rate = battery.changeRate
|
||||
const rate = battery.changeRate;
|
||||
if (rate > 0) {
|
||||
lines.push(charging ? I18n.tr("battery.charging-rate", {
|
||||
"rate": rate.toFixed(2)
|
||||
}) : I18n.tr("battery.discharging-rate", {
|
||||
"rate": rate.toFixed(2)
|
||||
}))
|
||||
}));
|
||||
} else if (rate < 0) {
|
||||
lines.push(I18n.tr("battery.discharging-rate", {
|
||||
"rate": Math.abs(rate).toFixed(2)
|
||||
}))
|
||||
}));
|
||||
} else {
|
||||
// Rate is 0 - check if plugged in (charging state) or idle
|
||||
lines.push(charging ? I18n.tr("battery.plugged-in") : I18n.tr("battery.idle"))
|
||||
lines.push(charging ? I18n.tr("battery.plugged-in") : I18n.tr("battery.idle"));
|
||||
}
|
||||
} else {
|
||||
lines.push(charging ? I18n.tr("battery.charging") : I18n.tr("battery.discharging"))
|
||||
lines.push(charging ? I18n.tr("battery.charging") : I18n.tr("battery.discharging"));
|
||||
}
|
||||
if (battery.healthPercentage !== undefined && battery.healthPercentage > 0) {
|
||||
lines.push(I18n.tr("battery.health", {
|
||||
"percent": Math.round(battery.healthPercentage)
|
||||
}))
|
||||
}));
|
||||
}
|
||||
return lines.join("\n")
|
||||
return lines.join("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,12 +19,12 @@ Item {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
|
||||
@@ -41,16 +41,16 @@ Item {
|
||||
icon: BluetoothService.enabled ? "bluetooth" : "bluetooth-off"
|
||||
text: {
|
||||
if (BluetoothService.connectedDevices && BluetoothService.connectedDevices.length > 0) {
|
||||
const firstDevice = BluetoothService.connectedDevices[0]
|
||||
return firstDevice.name || firstDevice.deviceName
|
||||
const firstDevice = BluetoothService.connectedDevices[0];
|
||||
return firstDevice.name || firstDevice.deviceName;
|
||||
}
|
||||
return ""
|
||||
return "";
|
||||
}
|
||||
suffix: {
|
||||
if (BluetoothService.connectedDevices && BluetoothService.connectedDevices.length > 1) {
|
||||
return ` + ${BluetoothService.connectedDevices.length - 1}`
|
||||
return ` + ${BluetoothService.connectedDevices.length - 1}`;
|
||||
}
|
||||
return ""
|
||||
return "";
|
||||
}
|
||||
autoHide: false
|
||||
forceOpen: !isBarVertical && root.displayMode === "alwaysShow"
|
||||
@@ -59,9 +59,9 @@ Item {
|
||||
onRightClicked: BluetoothService.setBluetoothEnabled(!BluetoothService.enabled)
|
||||
tooltipText: {
|
||||
if (pill.text !== "") {
|
||||
return pill.text
|
||||
return pill.text;
|
||||
}
|
||||
return I18n.tr("tooltips.bluetooth-devices")
|
||||
return I18n.tr("tooltips.bluetooth-devices");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,12 +21,12 @@ Item {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
|
||||
@@ -40,13 +40,13 @@ Item {
|
||||
visible: getMonitor() !== null
|
||||
|
||||
function getMonitor() {
|
||||
return BrightnessService.getMonitorForScreen(screen) || null
|
||||
return BrightnessService.getMonitorForScreen(screen) || null;
|
||||
}
|
||||
|
||||
function getIcon() {
|
||||
var monitor = getMonitor()
|
||||
var brightness = monitor ? monitor.brightness : 0
|
||||
return brightness <= 0.5 ? "brightness-low" : "brightness-high"
|
||||
var monitor = getMonitor();
|
||||
var brightness = monitor ? monitor.brightness : 0;
|
||||
return brightness <= 0.5 ? "brightness-low" : "brightness-high";
|
||||
}
|
||||
|
||||
// Connection used to open the pill when brightness changes
|
||||
@@ -57,12 +57,12 @@ Item {
|
||||
// Ignore if this is the first time we receive an update.
|
||||
// Most likely service just kicked off.
|
||||
if (!firstBrightnessReceived) {
|
||||
firstBrightnessReceived = true
|
||||
return
|
||||
firstBrightnessReceived = true;
|
||||
return;
|
||||
}
|
||||
|
||||
pill.show()
|
||||
hideTimerAfterChange.restart()
|
||||
pill.show();
|
||||
hideTimerAfterChange.restart();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,42 +82,42 @@ Item {
|
||||
icon: getIcon()
|
||||
autoHide: false // Important to be false so we can hover as long as we want
|
||||
text: {
|
||||
var monitor = getMonitor()
|
||||
return monitor ? Math.round(monitor.brightness * 100) : ""
|
||||
var monitor = getMonitor();
|
||||
return monitor ? Math.round(monitor.brightness * 100) : "";
|
||||
}
|
||||
suffix: text.length > 0 ? "%" : "-"
|
||||
forceOpen: displayMode === "alwaysShow"
|
||||
forceClose: displayMode === "alwaysHide"
|
||||
tooltipText: {
|
||||
var monitor = getMonitor()
|
||||
var monitor = getMonitor();
|
||||
if (!monitor)
|
||||
return ""
|
||||
return "";
|
||||
return I18n.tr("tooltips.brightness-at", {
|
||||
"brightness": Math.round(monitor.brightness * 100)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
onWheel: function (angle) {
|
||||
var monitor = getMonitor()
|
||||
var monitor = getMonitor();
|
||||
if (!monitor)
|
||||
return
|
||||
return;
|
||||
if (angle > 0) {
|
||||
monitor.increaseBrightness()
|
||||
monitor.increaseBrightness();
|
||||
} else if (angle < 0) {
|
||||
monitor.decreaseBrightness()
|
||||
monitor.decreaseBrightness();
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
var settingsPanel = PanelService.getPanel("settingsPanel", screen)
|
||||
settingsPanel.requestedTab = SettingsPanel.Tab.Display
|
||||
settingsPanel.open()
|
||||
var settingsPanel = PanelService.getPanel("settingsPanel", screen);
|
||||
settingsPanel.requestedTab = SettingsPanel.Tab.Display;
|
||||
settingsPanel.open();
|
||||
}
|
||||
|
||||
onRightClicked: {
|
||||
var settingsPanel = PanelService.getPanel("settingsPanel", screen)
|
||||
settingsPanel.requestedTab = SettingsPanel.Tab.Display
|
||||
settingsPanel.open()
|
||||
var settingsPanel = PanelService.getPanel("settingsPanel", screen);
|
||||
settingsPanel.requestedTab = SettingsPanel.Tab.Display;
|
||||
settingsPanel.open();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,12 +20,12 @@ Rectangle {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
readonly property string barPosition: Settings.data.bar.position
|
||||
@@ -70,9 +70,9 @@ Rectangle {
|
||||
Binding on pointSize {
|
||||
value: {
|
||||
if (repeater.model.length == 1) {
|
||||
return Style.fontSizeS * scaling
|
||||
return Style.fontSizeS * scaling;
|
||||
} else {
|
||||
return (index == 0) ? Style.fontSizeXS * scaling : Style.fontSizeXXS * scaling
|
||||
return (index == 0) ? Style.fontSizeXS * scaling : Style.fontSizeXXS * scaling;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -122,15 +122,15 @@ Rectangle {
|
||||
hoverEnabled: true
|
||||
onEntered: {
|
||||
if (!PanelService.getPanel("calendarPanel", screen)?.active) {
|
||||
TooltipService.show(Screen, root, I18n.tr("clock.tooltip"), BarService.getTooltipDirection())
|
||||
TooltipService.show(Screen, root, I18n.tr("clock.tooltip"), BarService.getTooltipDirection());
|
||||
}
|
||||
}
|
||||
onExited: {
|
||||
TooltipService.hide()
|
||||
TooltipService.hide();
|
||||
}
|
||||
onClicked: {
|
||||
TooltipService.hide()
|
||||
PanelService.getPanel("calendarPanel", screen)?.toggle(this)
|
||||
TooltipService.hide();
|
||||
PanelService.getPanel("calendarPanel", screen)?.toggle(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import QtQuick.Effects
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
import qs.Services.UI
|
||||
import qs.Services.System
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
NIconButton {
|
||||
id: root
|
||||
@@ -21,12 +21,12 @@ NIconButton {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
readonly property string customIcon: widgetSettings.icon || widgetMetadata.icon
|
||||
@@ -34,8 +34,8 @@ NIconButton {
|
||||
readonly property string customIconPath: widgetSettings.customIconPath || ""
|
||||
readonly property bool colorizeDistroLogo: {
|
||||
if (widgetSettings.colorizeDistroLogo !== undefined)
|
||||
return widgetSettings.colorizeDistroLogo
|
||||
return widgetMetadata.colorizeDistroLogo !== undefined ? widgetMetadata.colorizeDistroLogo : false
|
||||
return widgetSettings.colorizeDistroLogo;
|
||||
return widgetMetadata.colorizeDistroLogo !== undefined ? widgetMetadata.colorizeDistroLogo : false;
|
||||
}
|
||||
|
||||
// If we have a custom path or distro logo, don't use the theme icon.
|
||||
@@ -51,12 +51,12 @@ NIconButton {
|
||||
colorBorder: Color.transparent
|
||||
colorBorderHover: useDistroLogo ? Color.mHover : Color.transparent
|
||||
onClicked: {
|
||||
var controlCenterPanel = PanelService.getPanel("controlCenterPanel", screen)
|
||||
var controlCenterPanel = PanelService.getPanel("controlCenterPanel", screen);
|
||||
if (Settings.data.controlCenter.position === "close_to_bar_button") {
|
||||
// Willopen the panel next to the bar button.
|
||||
controlCenterPanel?.toggle(this)
|
||||
controlCenterPanel?.toggle(this);
|
||||
} else {
|
||||
controlCenterPanel?.toggle()
|
||||
controlCenterPanel?.toggle();
|
||||
}
|
||||
}
|
||||
onRightClicked: PanelService.getPanel("settingsPanel", screen)?.toggle()
|
||||
@@ -69,10 +69,10 @@ NIconButton {
|
||||
height: width
|
||||
source: {
|
||||
if (customIconPath !== "")
|
||||
return customIconPath.startsWith("file://") ? customIconPath : "file://" + customIconPath
|
||||
return customIconPath.startsWith("file://") ? customIconPath : "file://" + customIconPath;
|
||||
if (useDistroLogo)
|
||||
return HostService.osLogo
|
||||
return ""
|
||||
return HostService.osLogo;
|
||||
return "";
|
||||
}
|
||||
visible: source !== ""
|
||||
smooth: true
|
||||
|
||||
@@ -3,10 +3,10 @@ import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
import qs.Modules.Bar.Extras
|
||||
import qs.Modules.Panels.Settings
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
import qs.Modules.Panels.Settings
|
||||
import qs.Modules.Bar.Extras
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@@ -22,12 +22,12 @@ Item {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
readonly property bool isVerticalBar: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
|
||||
@@ -63,31 +63,31 @@ Item {
|
||||
autoHide: false
|
||||
forceOpen: _dynamicText !== ""
|
||||
tooltipText: {
|
||||
var tooltipLines = []
|
||||
var tooltipLines = [];
|
||||
|
||||
if (hasExec) {
|
||||
if (leftClickExec !== "") {
|
||||
tooltipLines.push(`Left click: ${leftClickExec}.`)
|
||||
tooltipLines.push(`Left click: ${leftClickExec}.`);
|
||||
}
|
||||
if (rightClickExec !== "") {
|
||||
tooltipLines.push(`Right click: ${rightClickExec}.`)
|
||||
tooltipLines.push(`Right click: ${rightClickExec}.`);
|
||||
}
|
||||
if (middleClickExec !== "") {
|
||||
tooltipLines.push(`Middle click: ${middleClickExec}.`)
|
||||
tooltipLines.push(`Middle click: ${middleClickExec}.`);
|
||||
}
|
||||
}
|
||||
|
||||
if (_dynamicTooltip !== "") {
|
||||
if (tooltipLines.length > 0) {
|
||||
tooltipLines.push("")
|
||||
tooltipLines.push("");
|
||||
}
|
||||
tooltipLines.push(_dynamicTooltip)
|
||||
tooltipLines.push(_dynamicTooltip);
|
||||
}
|
||||
|
||||
if (tooltipLines.length === 0) {
|
||||
return "Custom button, configure in settings."
|
||||
return "Custom button, configure in settings.";
|
||||
} else {
|
||||
return tooltipLines.join("\n")
|
||||
return tooltipLines.join("\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,121 +135,121 @@ Item {
|
||||
stderr: StdioCollector {}
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
if (textStream) {
|
||||
Logger.w("CustomButton", `Streaming text command exited (code: ${exitCode}), restarting...`)
|
||||
return
|
||||
Logger.w("CustomButton", `Streaming text command exited (code: ${exitCode}), restarting...`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parseDynamicContent(content) {
|
||||
var contentStr = String(content || "").trim()
|
||||
var contentStr = String(content || "").trim();
|
||||
|
||||
if (parseJson) {
|
||||
var lineToParse = contentStr
|
||||
var lineToParse = contentStr;
|
||||
|
||||
if (!textStream && contentStr.includes('\n')) {
|
||||
const lines = contentStr.split('\n').filter(line => line.trim() !== '')
|
||||
const lines = contentStr.split('\n').filter(line => line.trim() !== '');
|
||||
if (lines.length > 0) {
|
||||
lineToParse = lines[lines.length - 1]
|
||||
lineToParse = lines[lines.length - 1];
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(lineToParse)
|
||||
const text = parsed.text || ""
|
||||
const icon = parsed.icon || ""
|
||||
const tooltip = parsed.tooltip || ""
|
||||
const parsed = JSON.parse(lineToParse);
|
||||
const text = parsed.text || "";
|
||||
const icon = parsed.icon || "";
|
||||
const tooltip = parsed.tooltip || "";
|
||||
|
||||
if (checkCollapse(text)) {
|
||||
_dynamicText = ""
|
||||
_dynamicIcon = ""
|
||||
_dynamicTooltip = ""
|
||||
return
|
||||
_dynamicText = "";
|
||||
_dynamicIcon = "";
|
||||
_dynamicTooltip = "";
|
||||
return;
|
||||
}
|
||||
|
||||
_dynamicText = text
|
||||
_dynamicIcon = icon
|
||||
_dynamicTooltip = tooltip
|
||||
return
|
||||
_dynamicText = text;
|
||||
_dynamicIcon = icon;
|
||||
_dynamicTooltip = tooltip;
|
||||
return;
|
||||
} catch (e) {
|
||||
Logger.w("CustomButton", `Failed to parse JSON. Content: "${lineToParse}"`)
|
||||
Logger.w("CustomButton", `Failed to parse JSON. Content: "${lineToParse}"`);
|
||||
}
|
||||
}
|
||||
|
||||
if (checkCollapse(contentStr)) {
|
||||
_dynamicText = ""
|
||||
_dynamicIcon = ""
|
||||
_dynamicTooltip = ""
|
||||
return
|
||||
_dynamicText = "";
|
||||
_dynamicIcon = "";
|
||||
_dynamicTooltip = "";
|
||||
return;
|
||||
}
|
||||
|
||||
_dynamicText = contentStr
|
||||
_dynamicIcon = ""
|
||||
_dynamicTooltip = ""
|
||||
_dynamicText = contentStr;
|
||||
_dynamicIcon = "";
|
||||
_dynamicTooltip = "";
|
||||
}
|
||||
|
||||
function checkCollapse(text) {
|
||||
if (!textCollapse || textCollapse.length === 0) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
if (textCollapse.startsWith("/") && textCollapse.endsWith("/") && textCollapse.length > 1) {
|
||||
// Treat as regex
|
||||
var pattern = textCollapse.substring(1, textCollapse.length - 1)
|
||||
var pattern = textCollapse.substring(1, textCollapse.length - 1);
|
||||
try {
|
||||
var regex = new RegExp(pattern)
|
||||
return regex.test(text)
|
||||
var regex = new RegExp(pattern);
|
||||
return regex.test(text);
|
||||
} catch (e) {
|
||||
Logger.w("CustomButton", `Invalid regex for textCollapse: ${textCollapse} - ${e.message}`)
|
||||
return (textCollapse === text) // Fallback to exact match on invalid regex
|
||||
Logger.w("CustomButton", `Invalid regex for textCollapse: ${textCollapse} - ${e.message}`);
|
||||
return (textCollapse === text); // Fallback to exact match on invalid regex
|
||||
}
|
||||
} else {
|
||||
// Treat as plain string
|
||||
return (textCollapse === text)
|
||||
return (textCollapse === text);
|
||||
}
|
||||
}
|
||||
|
||||
function onClicked() {
|
||||
if (leftClickExec) {
|
||||
Quickshell.execDetached(["sh", "-c", leftClickExec])
|
||||
Logger.i("CustomButton", `Executing command: ${leftClickExec}`)
|
||||
Quickshell.execDetached(["sh", "-c", leftClickExec]);
|
||||
Logger.i("CustomButton", `Executing command: ${leftClickExec}`);
|
||||
} else if (!hasExec && !leftClickUpdateText) {
|
||||
// No script was defined, open settings
|
||||
var settingsPanel = PanelService.getPanel("settingsPanel", screen)
|
||||
settingsPanel.requestedTab = SettingsPanel.Tab.Bar
|
||||
settingsPanel.open()
|
||||
var settingsPanel = PanelService.getPanel("settingsPanel", screen);
|
||||
settingsPanel.requestedTab = SettingsPanel.Tab.Bar;
|
||||
settingsPanel.open();
|
||||
}
|
||||
if (!textStream && leftClickUpdateText) {
|
||||
runTextCommand()
|
||||
runTextCommand();
|
||||
}
|
||||
}
|
||||
|
||||
function onRightClicked() {
|
||||
if (rightClickExec) {
|
||||
Quickshell.execDetached(["sh", "-c", rightClickExec])
|
||||
Logger.i("CustomButton", `Executing command: ${rightClickExec}`)
|
||||
Quickshell.execDetached(["sh", "-c", rightClickExec]);
|
||||
Logger.i("CustomButton", `Executing command: ${rightClickExec}`);
|
||||
}
|
||||
if (!textStream && rightClickUpdateText) {
|
||||
runTextCommand()
|
||||
runTextCommand();
|
||||
}
|
||||
}
|
||||
|
||||
function onMiddleClicked() {
|
||||
if (middleClickExec) {
|
||||
Quickshell.execDetached(["sh", "-c", middleClickExec])
|
||||
Logger.i("CustomButton", `Executing command: ${middleClickExec}`)
|
||||
Quickshell.execDetached(["sh", "-c", middleClickExec]);
|
||||
Logger.i("CustomButton", `Executing command: ${middleClickExec}`);
|
||||
}
|
||||
if (!textStream && middleClickUpdateText) {
|
||||
runTextCommand()
|
||||
runTextCommand();
|
||||
}
|
||||
}
|
||||
|
||||
function runTextCommand() {
|
||||
if (!textCommand || textCommand.length === 0)
|
||||
return
|
||||
return;
|
||||
if (textProc.running)
|
||||
return
|
||||
textProc.command = ["sh", "-lc", textCommand]
|
||||
textProc.running = true
|
||||
return;
|
||||
textProc.command = ["sh", "-lc", textCommand];
|
||||
textProc.running = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
NIconButton {
|
||||
id: root
|
||||
|
||||
@@ -2,10 +2,10 @@ import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Modules.Bar.Extras
|
||||
import qs.Services.Power
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
import qs.Modules.Bar.Extras
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@@ -21,12 +21,12 @@ Item {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
|
||||
@@ -47,20 +47,20 @@ Item {
|
||||
forceOpen: IdleInhibitorService.timeout !== null
|
||||
forceClose: IdleInhibitorService.timeout == null
|
||||
onWheel: function (delta) {
|
||||
var sign = delta > 0 ? 1 : -1
|
||||
var sign = delta > 0 ? 1 : -1;
|
||||
// the offset makes scrolling down feel symmetrical to scrolling up
|
||||
var timeout = IdleInhibitorService.timeout - (delta < 0 ? 60 : 0)
|
||||
var timeout = IdleInhibitorService.timeout - (delta < 0 ? 60 : 0);
|
||||
if (timeout == null || timeout < 600) {
|
||||
delta = 60 // <= 10m, increment at 1m interval
|
||||
delta = 60; // <= 10m, increment at 1m interval
|
||||
} else if (timeout >= 600 && timeout < 1800) {
|
||||
delta = 300 // >= 10m, increment at 5m interval
|
||||
delta = 300; // >= 10m, increment at 5m interval
|
||||
} else if (timeout >= 1800 && timeout < 3600) {
|
||||
delta = 600 // >= 30m, increment at 10m interval
|
||||
delta = 600; // >= 30m, increment at 10m interval
|
||||
} else if (timeout >= 3600) {
|
||||
delta = 1800 // > 1h, increment at 30m interval
|
||||
delta = 1800; // > 1h, increment at 30m interval
|
||||
}
|
||||
|
||||
IdleInhibitorService.changeTimeout(delta * sign)
|
||||
IdleInhibitorService.changeTimeout(delta * sign);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import qs.Services.Keyboard
|
||||
import qs.Widgets
|
||||
import qs.Modules.Bar.Extras
|
||||
import qs.Services.Keyboard
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@@ -23,12 +23,12 @@ Item {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
readonly property string displayMode: (widgetSettings.displayMode !== undefined) ? widgetSettings.displayMode : widgetMetadata.displayMode
|
||||
@@ -53,9 +53,9 @@ Item {
|
||||
})
|
||||
forceOpen: root.displayMode === "forceOpen"
|
||||
forceClose: root.displayMode === "alwaysHide"
|
||||
onClicked: {
|
||||
onClicked:
|
||||
|
||||
// You could open keyboard settings here if needed.
|
||||
}
|
||||
{}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,12 +20,12 @@ Rectangle {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
readonly property string barPosition: Settings.data.bar.position
|
||||
|
||||
@@ -2,11 +2,11 @@ import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Widgets.AudioSpectrum
|
||||
import qs.Commons
|
||||
import qs.Services.Media
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
import qs.Widgets.AudioSpectrum
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@@ -23,12 +23,12 @@ Item {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
readonly property bool isVerticalBar: (Settings.data.bar.position === "left" || Settings.data.bar.position === "right")
|
||||
@@ -50,18 +50,18 @@ Item {
|
||||
readonly property string placeholderText: I18n.tr("bar.widget-settings.media-mini.no-active-player")
|
||||
|
||||
readonly property string tooltipText: {
|
||||
var title = getTitle()
|
||||
var controls = ""
|
||||
var title = getTitle();
|
||||
var controls = "";
|
||||
if (MediaService.canGoNext) {
|
||||
controls += "Right click for next.\n"
|
||||
controls += "Right click for next.\n";
|
||||
}
|
||||
if (MediaService.canGoPrevious) {
|
||||
controls += "Middle click for previous."
|
||||
controls += "Middle click for previous.";
|
||||
}
|
||||
if (controls !== "") {
|
||||
return title + "\n\n" + controls
|
||||
return title + "\n\n" + controls;
|
||||
}
|
||||
return title
|
||||
return title;
|
||||
}
|
||||
|
||||
// Hide conditions
|
||||
@@ -103,53 +103,53 @@ Item {
|
||||
}
|
||||
|
||||
function calculatedVerticalDimension() {
|
||||
return Math.round((Style.baseWidgetSize - 5) * scaling)
|
||||
return Math.round((Style.baseWidgetSize - 5) * scaling);
|
||||
}
|
||||
|
||||
function calculateContentWidth() {
|
||||
// Calculate the actual content width based on visible elements
|
||||
var contentWidth = 0
|
||||
var margins = Style.marginS * scaling * 2 // Left and right margins
|
||||
var contentWidth = 0;
|
||||
var margins = Style.marginS * scaling * 2; // Left and right margins
|
||||
|
||||
// Icon or album art width
|
||||
if (!hasActivePlayer || !showAlbumArt) {
|
||||
// Icon width
|
||||
contentWidth += Math.round(18 * scaling)
|
||||
contentWidth += Math.round(18 * scaling);
|
||||
} else if (showAlbumArt && hasActivePlayer) {
|
||||
// Album art width
|
||||
contentWidth += 21 * scaling
|
||||
contentWidth += 21 * scaling;
|
||||
}
|
||||
|
||||
// Spacing between icon/art and text; only if there is text
|
||||
if (fullTitleMetrics.contentWidth > 0) {
|
||||
contentWidth += Style.marginS * scaling
|
||||
contentWidth += Style.marginS * scaling;
|
||||
|
||||
// Text width (use the measured width)
|
||||
contentWidth += fullTitleMetrics.contentWidth
|
||||
contentWidth += fullTitleMetrics.contentWidth;
|
||||
|
||||
// Additional small margin for text
|
||||
contentWidth += Style.marginXXS * 2
|
||||
contentWidth += Style.marginXXS * 2;
|
||||
}
|
||||
|
||||
// Add container margins
|
||||
contentWidth += margins
|
||||
contentWidth += margins;
|
||||
|
||||
return Math.ceil(contentWidth)
|
||||
return Math.ceil(contentWidth);
|
||||
}
|
||||
|
||||
// Dynamic width: adapt to content but respect maximum width setting
|
||||
readonly property real dynamicWidth: {
|
||||
// If using fixed width mode, always use maxWidth
|
||||
if (useFixedWidth) {
|
||||
return maxWidth
|
||||
return maxWidth;
|
||||
}
|
||||
// Otherwise, adapt to content
|
||||
if (!hasActivePlayer) {
|
||||
// Keep compact when no active player
|
||||
return calculateContentWidth()
|
||||
return calculateContentWidth();
|
||||
}
|
||||
// Use content width but don't exceed user-set maximum width
|
||||
return Math.min(calculateContentWidth(), maxWidth)
|
||||
return Math.min(calculateContentWidth(), maxWidth);
|
||||
}
|
||||
|
||||
// A hidden text element to safely measure the full title width
|
||||
@@ -284,11 +284,11 @@ Item {
|
||||
id: titleContainer
|
||||
Layout.preferredWidth: {
|
||||
// Calculate available width based on other elements in the row
|
||||
var iconWidth = (windowIcon.visible ? (18 * scaling + Style.marginS * scaling) : 0)
|
||||
var albumArtWidth = (hasActivePlayer && showAlbumArt ? (21 * scaling + Style.marginS * scaling) : 0)
|
||||
var totalMargins = Style.marginXXS * 2
|
||||
var availableWidth = mainContainer.width - iconWidth - albumArtWidth - totalMargins
|
||||
return Math.max(20, availableWidth)
|
||||
var iconWidth = (windowIcon.visible ? (18 * scaling + Style.marginS * scaling) : 0);
|
||||
var albumArtWidth = (hasActivePlayer && showAlbumArt ? (21 * scaling + Style.marginS * scaling) : 0);
|
||||
var totalMargins = Style.marginXXS * 2;
|
||||
var availableWidth = mainContainer.width - iconWidth - albumArtWidth - totalMargins;
|
||||
return Math.max(20, availableWidth);
|
||||
}
|
||||
Layout.maximumWidth: Layout.preferredWidth
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
@@ -309,8 +309,8 @@ Item {
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (scrollingMode === "always" && titleContainer.needsScrolling) {
|
||||
titleContainer.isScrolling = true
|
||||
titleContainer.isResetting = false
|
||||
titleContainer.isScrolling = true;
|
||||
titleContainer.isResetting = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -318,48 +318,48 @@ Item {
|
||||
// Update scrolling state based on mode
|
||||
property var updateScrollingState: function () {
|
||||
if (scrollingMode === "never") {
|
||||
isScrolling = false
|
||||
isResetting = false
|
||||
isScrolling = false;
|
||||
isResetting = false;
|
||||
} else if (scrollingMode === "always") {
|
||||
if (needsScrolling) {
|
||||
if (mouseArea.containsMouse) {
|
||||
isScrolling = false
|
||||
isResetting = true
|
||||
isScrolling = false;
|
||||
isResetting = true;
|
||||
} else {
|
||||
scrollStartTimer.restart()
|
||||
scrollStartTimer.restart();
|
||||
}
|
||||
} else {
|
||||
scrollStartTimer.stop()
|
||||
isScrolling = false
|
||||
isResetting = false
|
||||
scrollStartTimer.stop();
|
||||
isScrolling = false;
|
||||
isResetting = false;
|
||||
}
|
||||
} else if (scrollingMode === "hover") {
|
||||
if (mouseArea.containsMouse && needsScrolling) {
|
||||
isScrolling = true
|
||||
isResetting = false
|
||||
isScrolling = true;
|
||||
isResetting = false;
|
||||
} else {
|
||||
isScrolling = false
|
||||
isScrolling = false;
|
||||
if (needsScrolling) {
|
||||
isResetting = true
|
||||
isResetting = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onWidthChanged: {
|
||||
containerWidth = width
|
||||
updateScrollingState()
|
||||
containerWidth = width;
|
||||
updateScrollingState();
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
containerWidth = width
|
||||
updateScrollingState()
|
||||
containerWidth = width;
|
||||
updateScrollingState();
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: mouseArea
|
||||
function onContainsMouseChanged() {
|
||||
titleContainer.updateScrollingState()
|
||||
titleContainer.updateScrollingState();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,11 +385,11 @@ Item {
|
||||
horizontalAlignment: hasActivePlayer ? Text.AlignLeft : Text.AlignHCenter
|
||||
color: hasActivePlayer ? Color.mOnSurface : Color.mOnSurfaceVariant
|
||||
onTextChanged: {
|
||||
titleContainer.isScrolling = false
|
||||
titleContainer.isResetting = false
|
||||
scrollContainer.scrollX = 0
|
||||
titleContainer.isScrolling = false;
|
||||
titleContainer.isResetting = false;
|
||||
scrollContainer.scrollX = 0;
|
||||
if (titleContainer.needsScrolling) {
|
||||
scrollStartTimer.restart()
|
||||
scrollStartTimer.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -413,7 +413,7 @@ Item {
|
||||
duration: 300
|
||||
easing.type: Easing.OutQuad
|
||||
onFinished: {
|
||||
titleContainer.isResetting = false
|
||||
titleContainer.isResetting = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -467,28 +467,28 @@ Item {
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
onClicked: mouse => {
|
||||
if (!hasActivePlayer || !MediaService.currentPlayer || !MediaService.canPlay) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
MediaService.playPause()
|
||||
MediaService.playPause();
|
||||
} else if (mouse.button == Qt.RightButton) {
|
||||
MediaService.next()
|
||||
TooltipService.hide()
|
||||
MediaService.next();
|
||||
TooltipService.hide();
|
||||
} else if (mouse.button == Qt.MiddleButton) {
|
||||
MediaService.previous()
|
||||
TooltipService.hide()
|
||||
MediaService.previous();
|
||||
TooltipService.hide();
|
||||
}
|
||||
}
|
||||
|
||||
onEntered: {
|
||||
var textToShow = hasActivePlayer ? tooltipText : placeholderText
|
||||
var textToShow = hasActivePlayer ? tooltipText : placeholderText;
|
||||
if ((textToShow !== "") && isVerticalBar || (scrollingMode === "never")) {
|
||||
TooltipService.show(Screen, root, textToShow, BarService.getTooltipDirection())
|
||||
TooltipService.show(Screen, root, textToShow, BarService.getTooltipDirection());
|
||||
}
|
||||
}
|
||||
onExited: {
|
||||
TooltipService.hide()
|
||||
TooltipService.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,12 +23,12 @@ Item {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
|
||||
@@ -48,13 +48,13 @@ Item {
|
||||
// Logger.i("Bar:Microphone", "onInputVolumeChanged")
|
||||
if (!firstInputVolumeReceived) {
|
||||
// Ignore the first volume change
|
||||
firstInputVolumeReceived = true
|
||||
firstInputVolumeReceived = true;
|
||||
} else {
|
||||
// If a tooltip is visible while we show the pill
|
||||
// hide it so it doesn't overlap the volume slider.
|
||||
TooltipService.hide()
|
||||
pill.show()
|
||||
externalHideTimer.restart()
|
||||
TooltipService.hide();
|
||||
pill.show();
|
||||
externalHideTimer.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,11 +66,11 @@ Item {
|
||||
// Logger.i("Bar:Microphone", "onInputMutedChanged")
|
||||
if (!firstInputVolumeReceived) {
|
||||
// Ignore the first mute change
|
||||
firstInputVolumeReceived = true
|
||||
firstInputVolumeReceived = true;
|
||||
} else {
|
||||
TooltipService.hide()
|
||||
pill.show()
|
||||
externalHideTimer.restart()
|
||||
TooltipService.hide();
|
||||
pill.show();
|
||||
externalHideTimer.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,7 +80,7 @@ Item {
|
||||
running: false
|
||||
interval: 1500
|
||||
onTriggered: {
|
||||
pill.hide()
|
||||
pill.hide();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,25 +101,25 @@ Item {
|
||||
|
||||
onWheel: function (delta) {
|
||||
// As soon as we start scrolling to adjust volume, hide the tooltip
|
||||
TooltipService.hide()
|
||||
TooltipService.hide();
|
||||
|
||||
wheelAccumulator += delta
|
||||
wheelAccumulator += delta;
|
||||
if (wheelAccumulator >= 120) {
|
||||
wheelAccumulator = 0
|
||||
AudioService.setInputVolume(AudioService.inputVolume + AudioService.stepVolume)
|
||||
wheelAccumulator = 0;
|
||||
AudioService.setInputVolume(AudioService.inputVolume + AudioService.stepVolume);
|
||||
} else if (wheelAccumulator <= -120) {
|
||||
wheelAccumulator = 0
|
||||
AudioService.setInputVolume(AudioService.inputVolume - AudioService.stepVolume)
|
||||
wheelAccumulator = 0;
|
||||
AudioService.setInputVolume(AudioService.inputVolume - AudioService.stepVolume);
|
||||
}
|
||||
}
|
||||
onClicked: {
|
||||
PanelService.getPanel("audioPanel", screen)?.toggle(this)
|
||||
PanelService.getPanel("audioPanel", screen)?.toggle(this);
|
||||
}
|
||||
onRightClicked: {
|
||||
AudioService.setInputMuted(!AudioService.inputMuted)
|
||||
AudioService.setInputMuted(!AudioService.inputMuted);
|
||||
}
|
||||
onMiddleClicked: {
|
||||
Quickshell.execDetached(["pwvucontrol"])
|
||||
Quickshell.execDetached(["pwvucontrol"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
@@ -28,24 +28,24 @@ NIconButton {
|
||||
onClicked: {
|
||||
// Check if wlsunset is available before enabling night light
|
||||
if (!ProgramCheckerService.wlsunsetAvailable) {
|
||||
ToastService.showWarning(I18n.tr("settings.display.night-light.section.label"), I18n.tr("toast.night-light.not-installed"))
|
||||
return
|
||||
ToastService.showWarning(I18n.tr("settings.display.night-light.section.label"), I18n.tr("toast.night-light.not-installed"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Settings.data.nightLight.enabled) {
|
||||
Settings.data.nightLight.enabled = true
|
||||
Settings.data.nightLight.forced = false
|
||||
Settings.data.nightLight.enabled = true;
|
||||
Settings.data.nightLight.forced = false;
|
||||
} else if (Settings.data.nightLight.enabled && !Settings.data.nightLight.forced) {
|
||||
Settings.data.nightLight.forced = true
|
||||
Settings.data.nightLight.forced = true;
|
||||
} else {
|
||||
Settings.data.nightLight.enabled = false
|
||||
Settings.data.nightLight.forced = false
|
||||
Settings.data.nightLight.enabled = false;
|
||||
Settings.data.nightLight.forced = false;
|
||||
}
|
||||
}
|
||||
|
||||
onRightClicked: {
|
||||
var settingsPanel = PanelService.getPanel("settingsPanel", screen)
|
||||
settingsPanel.requestedTab = SettingsPanel.Tab.Display
|
||||
settingsPanel.open()
|
||||
var settingsPanel = PanelService.getPanel("settingsPanel", screen);
|
||||
settingsPanel.requestedTab = SettingsPanel.Tab.Display;
|
||||
settingsPanel.open();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
@@ -26,6 +26,6 @@ NIconButton {
|
||||
tooltipText: PowerProfileService.noctaliaPerformanceMode ? I18n.tr("tooltips.noctalia-performance-enabled") : I18n.tr("tooltips.noctalia-performance-disabled")
|
||||
tooltipDirection: BarService.getTooltipDirection()
|
||||
onClicked: {
|
||||
PowerProfileService.toggleNoctaliaPerformance()
|
||||
PowerProfileService.toggleNoctaliaPerformance();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import qs.Services.System
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
NIconButton {
|
||||
@@ -22,27 +22,27 @@ NIconButton {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
readonly property bool showUnreadBadge: (widgetSettings.showUnreadBadge !== undefined) ? widgetSettings.showUnreadBadge : widgetMetadata.showUnreadBadge
|
||||
readonly property bool hideWhenZero: (widgetSettings.hideWhenZero !== undefined) ? widgetSettings.hideWhenZero : widgetMetadata.hideWhenZero
|
||||
|
||||
function computeUnreadCount() {
|
||||
var since = NotificationService.lastSeenTs
|
||||
var count = 0
|
||||
var model = NotificationService.historyList
|
||||
var since = NotificationService.lastSeenTs;
|
||||
var count = 0;
|
||||
var model = NotificationService.historyList;
|
||||
for (var i = 0; i < model.count; i++) {
|
||||
var item = model.get(i)
|
||||
var ts = item.timestamp instanceof Date ? item.timestamp.getTime() : item.timestamp
|
||||
var item = model.get(i);
|
||||
var ts = item.timestamp instanceof Date ? item.timestamp.getTime() : item.timestamp;
|
||||
if (ts > since)
|
||||
count++
|
||||
count++;
|
||||
}
|
||||
return count
|
||||
return count;
|
||||
}
|
||||
|
||||
baseSize: Style.capsuleHeight
|
||||
@@ -57,8 +57,8 @@ NIconButton {
|
||||
colorBorderHover: Color.transparent
|
||||
|
||||
onClicked: {
|
||||
var panel = PanelService.getPanel("notificationHistoryPanel", screen)
|
||||
panel?.toggle(this)
|
||||
var panel = PanelService.getPanel("notificationHistoryPanel", screen);
|
||||
panel?.toggle(this);
|
||||
}
|
||||
|
||||
onRightClicked: NotificationService.doNotDisturb = !NotificationService.doNotDisturb
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import qs.Services.Media
|
||||
import qs.Services.System
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
// Screen Recording Indicator
|
||||
@@ -24,10 +24,10 @@ NIconButton {
|
||||
|
||||
function handleClick() {
|
||||
if (!ScreenRecorderService.isAvailable) {
|
||||
ToastService.showError(I18n.tr("toast.recording.not-installed"), I18n.tr("toast.recording.not-installed-desc"))
|
||||
return
|
||||
ToastService.showError(I18n.tr("toast.recording.not-installed"), I18n.tr("toast.recording.not-installed-desc"));
|
||||
return;
|
||||
}
|
||||
ScreenRecorderService.toggleRecording()
|
||||
ScreenRecorderService.toggleRecording();
|
||||
}
|
||||
|
||||
onClicked: handleClick()
|
||||
|
||||
@@ -19,12 +19,12 @@ NIconButton {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
readonly property string colorName: widgetSettings.colorName !== undefined ? widgetSettings.colorName : widgetMetadata.colorName
|
||||
@@ -32,16 +32,16 @@ NIconButton {
|
||||
readonly property color iconColor: {
|
||||
switch (colorName) {
|
||||
case "primary":
|
||||
return Color.mPrimary
|
||||
return Color.mPrimary;
|
||||
case "secondary":
|
||||
return Color.mSecondary
|
||||
return Color.mSecondary;
|
||||
case "tertiary":
|
||||
return Color.mTertiary
|
||||
return Color.mTertiary;
|
||||
case "error":
|
||||
return Color.mError
|
||||
return Color.mError;
|
||||
case "onSurface":
|
||||
default:
|
||||
return Color.mOnSurface
|
||||
return Color.mOnSurface;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,12 +19,12 @@ Item {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
// Use settings or defaults from BarWidgetRegistry
|
||||
|
||||
@@ -3,8 +3,8 @@ import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Modules.Panels.Settings
|
||||
import qs.Services.UI
|
||||
import qs.Services.System
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
@@ -21,12 +21,12 @@ Rectangle {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
readonly property string barPosition: Settings.data.bar.position
|
||||
@@ -43,8 +43,8 @@ Rectangle {
|
||||
|
||||
readonly property real iconSize: textSize * 1.4
|
||||
readonly property real textSize: {
|
||||
var base = isVertical ? width * 0.82 : height
|
||||
return Math.max(1, (density === "compact") ? base * 0.43 : base * 0.33)
|
||||
var base = isVertical ? width * 0.82 : height;
|
||||
return Math.max(1, (density === "compact") ? base * 0.43 : base * 0.33);
|
||||
}
|
||||
|
||||
// Match Workspace widget pill sizing: base ratio depends on bar density
|
||||
@@ -178,11 +178,11 @@ Rectangle {
|
||||
anchors.centerIn: parent
|
||||
|
||||
onLoaded: {
|
||||
item.warning = Qt.binding(() => cpuWarning)
|
||||
item.critical = Qt.binding(() => cpuCritical)
|
||||
item.indicatorWidth = Qt.binding(() => cpuUsageContainer.width)
|
||||
item.warningColor = Qt.binding(() => root.warningColor)
|
||||
item.criticalColor = Qt.binding(() => root.criticalColor)
|
||||
item.warning = Qt.binding(() => cpuWarning);
|
||||
item.critical = Qt.binding(() => cpuCritical);
|
||||
item.indicatorWidth = Qt.binding(() => cpuUsageContainer.width);
|
||||
item.warningColor = Qt.binding(() => root.warningColor);
|
||||
item.criticalColor = Qt.binding(() => root.criticalColor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,11 +214,11 @@ Rectangle {
|
||||
|
||||
NText {
|
||||
text: {
|
||||
let usage = Math.round(SystemStatService.cpuUsage)
|
||||
let usage = Math.round(SystemStatService.cpuUsage);
|
||||
if (usage < 100) {
|
||||
return `${usage}%`
|
||||
return `${usage}%`;
|
||||
} else {
|
||||
return usage
|
||||
return usage;
|
||||
}
|
||||
}
|
||||
family: Settings.data.ui.fontFixed
|
||||
@@ -252,11 +252,11 @@ Rectangle {
|
||||
anchors.centerIn: parent
|
||||
|
||||
onLoaded: {
|
||||
item.warning = Qt.binding(() => tempWarning)
|
||||
item.critical = Qt.binding(() => tempCritical)
|
||||
item.indicatorWidth = Qt.binding(() => cpuTempContainer.width)
|
||||
item.warningColor = Qt.binding(() => root.warningColor)
|
||||
item.criticalColor = Qt.binding(() => root.criticalColor)
|
||||
item.warning = Qt.binding(() => tempWarning);
|
||||
item.critical = Qt.binding(() => tempCritical);
|
||||
item.indicatorWidth = Qt.binding(() => cpuTempContainer.width);
|
||||
item.warningColor = Qt.binding(() => root.warningColor);
|
||||
item.criticalColor = Qt.binding(() => root.criticalColor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,11 +319,11 @@ Rectangle {
|
||||
anchors.centerIn: parent
|
||||
|
||||
onLoaded: {
|
||||
item.warning = Qt.binding(() => memWarning)
|
||||
item.critical = Qt.binding(() => memCritical)
|
||||
item.indicatorWidth = Qt.binding(() => memoryContainer.width)
|
||||
item.warningColor = Qt.binding(() => root.warningColor)
|
||||
item.criticalColor = Qt.binding(() => root.criticalColor)
|
||||
item.warning = Qt.binding(() => memWarning);
|
||||
item.critical = Qt.binding(() => memCritical);
|
||||
item.indicatorWidth = Qt.binding(() => memoryContainer.width);
|
||||
item.warningColor = Qt.binding(() => root.warningColor);
|
||||
item.criticalColor = Qt.binding(() => root.criticalColor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -472,11 +472,11 @@ Rectangle {
|
||||
anchors.centerIn: parent
|
||||
|
||||
onLoaded: {
|
||||
item.warning = Qt.binding(() => diskWarning)
|
||||
item.critical = Qt.binding(() => diskCritical)
|
||||
item.indicatorWidth = Qt.binding(() => diskContainer.width)
|
||||
item.warningColor = Qt.binding(() => root.warningColor)
|
||||
item.criticalColor = Qt.binding(() => root.criticalColor)
|
||||
item.warning = Qt.binding(() => diskWarning);
|
||||
item.critical = Qt.binding(() => diskCritical);
|
||||
item.indicatorWidth = Qt.binding(() => diskContainer.width);
|
||||
item.warningColor = Qt.binding(() => root.warningColor);
|
||||
item.criticalColor = Qt.binding(() => root.criticalColor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import qs.Commons
|
||||
import qs.Services.Compositor
|
||||
import qs.Services.UI
|
||||
@@ -27,12 +27,12 @@ Rectangle {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
property bool hasWindow: false
|
||||
@@ -42,35 +42,35 @@ Rectangle {
|
||||
|
||||
function updateHasWindow() {
|
||||
try {
|
||||
var total = CompositorService.windows.count || 0
|
||||
var total = CompositorService.windows.count || 0;
|
||||
var activeIds = CompositorService.getActiveWorkspaces().map(function (ws) {
|
||||
return ws.id
|
||||
})
|
||||
var found = false
|
||||
return ws.id;
|
||||
});
|
||||
var found = false;
|
||||
for (var i = 0; i < total; i++) {
|
||||
var w = CompositorService.windows.get(i)
|
||||
var w = CompositorService.windows.get(i);
|
||||
if (!w)
|
||||
continue
|
||||
var passOutput = (!onlySameOutput) || (w.output == screen.name)
|
||||
var passWorkspace = (!onlyActiveWorkspaces) || (activeIds.includes(w.workspaceId))
|
||||
continue;
|
||||
var passOutput = (!onlySameOutput) || (w.output == screen.name);
|
||||
var passWorkspace = (!onlyActiveWorkspaces) || (activeIds.includes(w.workspaceId));
|
||||
if (passOutput && passWorkspace) {
|
||||
found = true
|
||||
break
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
hasWindow = found
|
||||
hasWindow = found;
|
||||
} catch (e) {
|
||||
hasWindow = false
|
||||
hasWindow = false;
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: CompositorService
|
||||
function onWindowListChanged() {
|
||||
updateHasWindow()
|
||||
updateHasWindow();
|
||||
}
|
||||
function onWorkspaceChanged() {
|
||||
updateHasWindow()
|
||||
updateHasWindow();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ Rectangle {
|
||||
property ShellScreen screen: root.screen
|
||||
|
||||
visible: (!onlySameOutput || modelData.output == screen.name) && (!onlyActiveWorkspaces || CompositorService.getActiveWorkspaces().map(function (ws) {
|
||||
return ws.id
|
||||
return ws.id;
|
||||
}).includes(modelData.workspaceId))
|
||||
|
||||
Layout.preferredWidth: root.itemSize
|
||||
@@ -125,7 +125,6 @@ Rectangle {
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
|
||||
IconImage {
|
||||
|
||||
id: appIcon
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
@@ -144,10 +143,10 @@ Rectangle {
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: iconBackground
|
||||
anchors.bottomMargin: -2
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
id: iconBackground
|
||||
width: 4
|
||||
height: 4
|
||||
color: modelData.isFocused ? Color.mPrimary : Color.transparent
|
||||
@@ -163,19 +162,18 @@ Rectangle {
|
||||
|
||||
onPressed: function (mouse) {
|
||||
if (!taskbarItem.modelData)
|
||||
return
|
||||
|
||||
return;
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
try {
|
||||
CompositorService.focusWindow(taskbarItem.modelData)
|
||||
CompositorService.focusWindow(taskbarItem.modelData);
|
||||
} catch (error) {
|
||||
Logger.e("Taskbar", "Failed to activate toplevel: " + error)
|
||||
Logger.e("Taskbar", "Failed to activate toplevel: " + error);
|
||||
}
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
try {
|
||||
CompositorService.closeWindow(taskbarItem.modelData)
|
||||
CompositorService.closeWindow(taskbarItem.modelData);
|
||||
} catch (error) {
|
||||
Logger.e("Taskbar", "Failed to close toplevel: " + error)
|
||||
Logger.e("Taskbar", "Failed to close toplevel: " + error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,18 +26,17 @@ Item {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
readonly property string labelMode: (widgetSettings.labelMode !== undefined) ? widgetSettings.labelMode : widgetMetadata.labelMode
|
||||
readonly property bool hideUnoccupied: (widgetSettings.hideUnoccupied !== undefined) ? widgetSettings.hideUnoccupied : widgetMetadata.hideUnoccupied
|
||||
readonly property int characterCount: 2
|
||||
readonly property bool showWorkspaceNumbers: (widgetSettings.showWorkspaceNumbers !== undefined) ? widgetSettings.showWorkspaceNumbers : true
|
||||
readonly property bool showNumbersOnlyWhenOccupied: (widgetSettings.showNumbersOnlyWhenOccupied !== undefined) ? widgetSettings.showNumbersOnlyWhenOccupied : true
|
||||
readonly property bool showLabelsOnlyWhenOccupied: (widgetSettings.showLabelsOnlyWhenOccupied !== undefined) ? widgetSettings.showLabelsOnlyWhenOccupied : true
|
||||
readonly property bool colorizeIcons: (widgetSettings.colorizeIcons !== undefined) ? widgetSettings.colorizeIcons : widgetMetadata.colorizeIcons
|
||||
property ListModel localWorkspaces: ListModel {}
|
||||
property real masterProgress: 0.0
|
||||
@@ -49,40 +48,39 @@ Item {
|
||||
property bool wheelCooldown: false
|
||||
|
||||
function refreshWorkspaces() {
|
||||
localWorkspaces.clear()
|
||||
localWorkspaces.clear();
|
||||
if (!screen)
|
||||
return
|
||||
|
||||
const screenName = screen.name.toLowerCase()
|
||||
return;
|
||||
const screenName = screen.name.toLowerCase();
|
||||
|
||||
for (var i = 0; i < CompositorService.workspaces.count; i++) {
|
||||
const ws = CompositorService.workspaces.get(i)
|
||||
const ws = CompositorService.workspaces.get(i);
|
||||
|
||||
if (ws.output.toLowerCase() !== screenName)
|
||||
continue
|
||||
continue;
|
||||
if (hideUnoccupied && !ws.isOccupied && !ws.isFocused)
|
||||
continue
|
||||
continue;
|
||||
|
||||
// Copy all properties from ws and add windows
|
||||
var workspaceData = Object.assign({}, ws)
|
||||
workspaceData.windows = CompositorService.getWindowsForWorkspace(ws.id)
|
||||
var workspaceData = Object.assign({}, ws);
|
||||
workspaceData.windows = CompositorService.getWindowsForWorkspace(ws.id);
|
||||
|
||||
localWorkspaces.append(workspaceData)
|
||||
localWorkspaces.append(workspaceData);
|
||||
}
|
||||
updateWorkspaceFocus()
|
||||
updateWorkspaceFocus();
|
||||
}
|
||||
|
||||
function triggerUnifiedWave() {
|
||||
effectColor = Color.mPrimary
|
||||
masterAnimation.restart()
|
||||
effectColor = Color.mPrimary;
|
||||
masterAnimation.restart();
|
||||
}
|
||||
|
||||
function updateWorkspaceFocus() {
|
||||
for (var i = 0; i < localWorkspaces.count; i++) {
|
||||
const ws = localWorkspaces.get(i)
|
||||
const ws = localWorkspaces.get(i);
|
||||
if (ws.isFocused === true) {
|
||||
root.triggerUnifiedWave()
|
||||
break
|
||||
root.triggerUnifiedWave();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,27 +88,27 @@ Item {
|
||||
function getFocusedLocalIndex() {
|
||||
for (var i = 0; i < localWorkspaces.count; i++) {
|
||||
if (localWorkspaces.get(i).isFocused === true)
|
||||
return i
|
||||
return i;
|
||||
}
|
||||
return -1
|
||||
return -1;
|
||||
}
|
||||
|
||||
function switchByOffset(offset) {
|
||||
if (localWorkspaces.count === 0)
|
||||
return
|
||||
var current = getFocusedLocalIndex()
|
||||
return;
|
||||
var current = getFocusedLocalIndex();
|
||||
if (current < 0)
|
||||
current = 0
|
||||
var next = (current + offset) % localWorkspaces.count
|
||||
current = 0;
|
||||
var next = (current + offset) % localWorkspaces.count;
|
||||
if (next < 0)
|
||||
next = localWorkspaces.count - 1
|
||||
const ws = localWorkspaces.get(next)
|
||||
next = localWorkspaces.count - 1;
|
||||
const ws = localWorkspaces.get(next);
|
||||
if (ws && ws.idx !== undefined)
|
||||
CompositorService.switchToWorkspace(ws)
|
||||
CompositorService.switchToWorkspace(ws);
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
refreshWorkspaces()
|
||||
refreshWorkspaces();
|
||||
}
|
||||
|
||||
onScreenChanged: refreshWorkspaces()
|
||||
@@ -123,11 +121,11 @@ Item {
|
||||
target: CompositorService
|
||||
|
||||
function onWorkspacesChanged() {
|
||||
refreshWorkspaces()
|
||||
refreshWorkspaces();
|
||||
}
|
||||
|
||||
function onWindowListChanged() {
|
||||
refreshWorkspaces()
|
||||
refreshWorkspaces();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,8 +162,8 @@ Item {
|
||||
interval: 150
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
root.wheelCooldown = false
|
||||
root.wheelAccumulatedDelta = 0
|
||||
root.wheelCooldown = false;
|
||||
root.wheelAccumulatedDelta = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,24 +174,24 @@ Item {
|
||||
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
||||
onWheel: function (event) {
|
||||
if (root.wheelCooldown)
|
||||
return
|
||||
return;
|
||||
// Prefer vertical delta, fall back to horizontal if needed
|
||||
var dy = event.angleDelta.y
|
||||
var dx = event.angleDelta.x
|
||||
var useDy = Math.abs(dy) >= Math.abs(dx)
|
||||
var delta = useDy ? dy : dx
|
||||
var dy = event.angleDelta.y;
|
||||
var dx = event.angleDelta.x;
|
||||
var useDy = Math.abs(dy) >= Math.abs(dx);
|
||||
var delta = useDy ? dy : dx;
|
||||
// One notch is typically 120
|
||||
root.wheelAccumulatedDelta += delta
|
||||
var step = 120
|
||||
root.wheelAccumulatedDelta += delta;
|
||||
var step = 120;
|
||||
if (Math.abs(root.wheelAccumulatedDelta) >= step) {
|
||||
var direction = root.wheelAccumulatedDelta > 0 ? -1 : 1
|
||||
var direction = root.wheelAccumulatedDelta > 0 ? -1 : 1;
|
||||
// For vertical layout, natural mapping: wheel up -> previous, down -> next (already handled by sign)
|
||||
// For horizontal layout, same mapping using vertical wheel
|
||||
root.switchByOffset(direction)
|
||||
root.wheelCooldown = true
|
||||
wheelDebounce.restart()
|
||||
root.wheelAccumulatedDelta = 0
|
||||
event.accepted = true
|
||||
root.switchByOffset(direction);
|
||||
root.wheelCooldown = true;
|
||||
wheelDebounce.restart();
|
||||
root.wheelAccumulatedDelta = 0;
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -221,7 +219,7 @@ Item {
|
||||
enabled: !hasWindows
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: {
|
||||
CompositorService.switchToWorkspace(workspaceModel)
|
||||
CompositorService.switchToWorkspace(workspaceModel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,22 +295,22 @@ Item {
|
||||
|
||||
onPressed: function (mouse) {
|
||||
if (!model) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
CompositorService.focusWindow(model)
|
||||
CompositorService.focusWindow(model);
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
CompositorService.closeWindow(model)
|
||||
CompositorService.closeWindow(model);
|
||||
}
|
||||
}
|
||||
onEntered: {
|
||||
taskbarItem.itemHovered = true
|
||||
TooltipService.show(Screen, taskbarItem, model.title || model.appId || "Unknown app.", BarService.getTooltipDirection())
|
||||
taskbarItem.itemHovered = true;
|
||||
TooltipService.show(Screen, taskbarItem, model.title || model.appId || "Unknown app.", BarService.getTooltipDirection());
|
||||
}
|
||||
onExited: {
|
||||
taskbarItem.itemHovered = false
|
||||
TooltipService.hide()
|
||||
taskbarItem.itemHovered = false;
|
||||
TooltipService.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -322,7 +320,7 @@ Item {
|
||||
Item {
|
||||
id: workspaceNumberContainer
|
||||
|
||||
visible: root.labelMode !== "none" && root.showWorkspaceNumbers && (!root.showNumbersOnlyWhenOccupied || container.hasWindows)
|
||||
visible: root.labelMode !== "none" && (!root.showLabelsOnlyWhenOccupied || container.hasWindows)
|
||||
|
||||
anchors {
|
||||
left: parent.left
|
||||
@@ -331,7 +329,8 @@ Item {
|
||||
topMargin: -Style.fontSizeXS * 0.5
|
||||
}
|
||||
|
||||
width: Math.max(workspaceNumber.implicitWidth + Style.marginXS, Style.fontSizeXXS * 2)
|
||||
// Doube width margin necessary here for Name or Name+Index, but double height not needed.
|
||||
width: Math.max(workspaceNumber.implicitWidth + (Style.marginXS * 2), Style.fontSizeXXS * 2)
|
||||
height: Math.max(workspaceNumber.implicitHeight + Style.marginXS, Style.fontSizeXXS * 2)
|
||||
|
||||
Rectangle {
|
||||
@@ -342,13 +341,13 @@ Item {
|
||||
|
||||
color: {
|
||||
if (workspaceModel.isFocused)
|
||||
return Color.mPrimary
|
||||
return Color.mPrimary;
|
||||
if (workspaceModel.isUrgent)
|
||||
return Color.mError
|
||||
return Color.mError;
|
||||
if (hasWindows)
|
||||
return Color.mSecondary
|
||||
return Color.mSecondary;
|
||||
|
||||
return Qt.alpha(Color.mOutline, 0.3)
|
||||
return Qt.alpha(Color.mOutline, 0.3);
|
||||
}
|
||||
|
||||
scale: workspaceModel.isActive ? 1.0 : 0.9
|
||||
@@ -389,11 +388,15 @@ Item {
|
||||
anchors.centerIn: parent
|
||||
|
||||
text: {
|
||||
if (root.labelMode === "name" && workspaceModel.name && workspaceModel.name.length > 0) {
|
||||
return workspaceModel.name.substring(0, root.characterCount)
|
||||
} else {
|
||||
return workspaceModel.idx.toString()
|
||||
if (workspaceModel.name && workspaceModel.name.length > 0) {
|
||||
if (root.labelMode === "name") {
|
||||
return workspaceModel.name.substring(0, root.characterCount)
|
||||
}
|
||||
if (root.labelMode === "index+name") {
|
||||
return (workspaceModel.idx.toString() + workspaceModel.name.substring(0, 1))
|
||||
}
|
||||
}
|
||||
return workspaceModel.idx.toString()
|
||||
}
|
||||
|
||||
family: Settings.data.ui.fontFixed
|
||||
@@ -406,24 +409,24 @@ Item {
|
||||
|
||||
color: {
|
||||
if (workspaceModel.isFocused)
|
||||
return Color.mOnPrimary
|
||||
return Color.mOnPrimary;
|
||||
if (workspaceModel.isUrgent)
|
||||
return Color.mOnError
|
||||
return Color.mOnError;
|
||||
if (hasWindows)
|
||||
return Color.mOnSecondary
|
||||
return Color.mOnSecondary;
|
||||
|
||||
return Color.mOnSurface
|
||||
return Color.mOnSurface;
|
||||
}
|
||||
|
||||
opacity: {
|
||||
if (workspaceModel.isFocused)
|
||||
return 1.0
|
||||
return 1.0;
|
||||
if (workspaceModel.isUrgent)
|
||||
return 0.9
|
||||
return 0.9;
|
||||
if (hasWindows)
|
||||
return 0.8
|
||||
return 0.8;
|
||||
|
||||
return 0.6
|
||||
return 0.6;
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Services.SystemTray
|
||||
import Quickshell.Widgets
|
||||
@@ -21,8 +21,8 @@ Rectangle {
|
||||
// Get shared tray menu window from PanelService (reactive to trigger changes)
|
||||
readonly property var trayMenuWindow: {
|
||||
// Reference trigger to force re-evaluation
|
||||
var _ = trayMenuUpdateTrigger
|
||||
return PanelService.getTrayMenuWindow(screen)
|
||||
var _ = trayMenuUpdateTrigger;
|
||||
return PanelService.getTrayMenuWindow(screen);
|
||||
}
|
||||
|
||||
readonly property var trayMenu: trayMenuWindow ? trayMenuWindow.trayMenuLoader : null
|
||||
@@ -31,7 +31,7 @@ Rectangle {
|
||||
target: PanelService
|
||||
function onTrayMenuWindowRegistered(registeredScreen) {
|
||||
if (registeredScreen === screen) {
|
||||
root.trayMenuUpdateTrigger++
|
||||
root.trayMenuUpdateTrigger++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,12 +45,12 @@ Rectangle {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
readonly property string barPosition: Settings.data.bar.position
|
||||
@@ -76,149 +76,149 @@ Rectangle {
|
||||
}
|
||||
|
||||
function _performFilteredItemsUpdate() {
|
||||
let newItems = []
|
||||
let newItems = [];
|
||||
if (SystemTray.items && SystemTray.items.values) {
|
||||
const trayItems = SystemTray.items.values
|
||||
const trayItems = SystemTray.items.values;
|
||||
for (var i = 0; i < trayItems.length; i++) {
|
||||
const item = trayItems[i]
|
||||
const item = trayItems[i];
|
||||
if (!item) {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
|
||||
const title = item.tooltipTitle || item.name || item.id || ""
|
||||
const title = item.tooltipTitle || item.name || item.id || "";
|
||||
|
||||
// Check if blacklisted
|
||||
let isBlacklisted = false
|
||||
let isBlacklisted = false;
|
||||
if (root.blacklist && root.blacklist.length > 0) {
|
||||
for (var j = 0; j < root.blacklist.length; j++) {
|
||||
const rule = root.blacklist[j]
|
||||
const rule = root.blacklist[j];
|
||||
if (wildCardMatch(title, rule)) {
|
||||
isBlacklisted = true
|
||||
break
|
||||
isBlacklisted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isBlacklisted) {
|
||||
newItems.push(item)
|
||||
newItems.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If drawer is disabled, show all items inline
|
||||
if (!root.drawerEnabled) {
|
||||
filteredItems = newItems
|
||||
dropdownItems = []
|
||||
filteredItems = newItems;
|
||||
dropdownItems = [];
|
||||
} else {
|
||||
// Build inline (pinned) and drawer (unpinned) lists
|
||||
// If pinned list is empty, all items go to drawer (none inline)
|
||||
// If pinned list has items, pinned items are inline, rest go to drawer
|
||||
if (pinned && pinned.length > 0) {
|
||||
let pinnedItems = []
|
||||
let pinnedItems = [];
|
||||
for (var k = 0; k < newItems.length; k++) {
|
||||
const item2 = newItems[k]
|
||||
const title2 = item2.tooltipTitle || item2.name || item2.id || ""
|
||||
const item2 = newItems[k];
|
||||
const title2 = item2.tooltipTitle || item2.name || item2.id || "";
|
||||
for (var m = 0; m < pinned.length; m++) {
|
||||
const rule2 = pinned[m]
|
||||
const rule2 = pinned[m];
|
||||
if (wildCardMatch(title2, rule2)) {
|
||||
pinnedItems.push(item2)
|
||||
break
|
||||
pinnedItems.push(item2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
filteredItems = pinnedItems
|
||||
filteredItems = pinnedItems;
|
||||
|
||||
// Unpinned items go to drawer
|
||||
let unpinnedItems = []
|
||||
let unpinnedItems = [];
|
||||
for (var v = 0; v < newItems.length; v++) {
|
||||
const cand = newItems[v]
|
||||
let isPinned = false
|
||||
const cand = newItems[v];
|
||||
let isPinned = false;
|
||||
for (var f = 0; f < filteredItems.length; f++) {
|
||||
if (filteredItems[f] === cand) {
|
||||
isPinned = true
|
||||
break
|
||||
isPinned = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isPinned)
|
||||
unpinnedItems.push(cand)
|
||||
unpinnedItems.push(cand);
|
||||
}
|
||||
dropdownItems = unpinnedItems
|
||||
dropdownItems = unpinnedItems;
|
||||
} else {
|
||||
// No pinned items: all items go to drawer (none inline)
|
||||
filteredItems = []
|
||||
dropdownItems = newItems
|
||||
filteredItems = [];
|
||||
dropdownItems = newItems;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateFilteredItems() {
|
||||
updateDebounceTimer.restart()
|
||||
updateDebounceTimer.restart();
|
||||
}
|
||||
|
||||
function wildCardMatch(str, rule) {
|
||||
if (!str || !rule) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
//Logger.d("Tray", "wildCardMatch - Input str:", str, "rule:", rule)
|
||||
|
||||
// Escape all special regex characters in the rule
|
||||
let escapedRule = rule.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
let escapedRule = rule.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
// Convert '*' to '.*' for wildcard matching
|
||||
let pattern = escapedRule.replace(/\\\*/g, '.*')
|
||||
let pattern = escapedRule.replace(/\\\*/g, '.*');
|
||||
// Add ^ and $ to match the entire string
|
||||
pattern = '^' + pattern + '$'
|
||||
pattern = '^' + pattern + '$';
|
||||
|
||||
//Logger.d("Tray", "wildCardMatch - Generated pattern:", pattern)
|
||||
try {
|
||||
const regex = new RegExp(pattern, 'i')
|
||||
const regex = new RegExp(pattern, 'i');
|
||||
// 'i' for case-insensitive
|
||||
//Logger.d("Tray", "wildCardMatch - Regex test result:", regex.test(str))
|
||||
return regex.test(str)
|
||||
return regex.test(str);
|
||||
} catch (e) {
|
||||
Logger.w("Tray", "Invalid regex pattern for wildcard match:", rule, e.message)
|
||||
return false // If regex is invalid, it won't match
|
||||
Logger.w("Tray", "Invalid regex pattern for wildcard match:", rule, e.message);
|
||||
return false; // If regex is invalid, it won't match
|
||||
}
|
||||
}
|
||||
|
||||
function toggleDrawer(button) {
|
||||
TooltipService.hideImmediately()
|
||||
TooltipService.hideImmediately();
|
||||
|
||||
// Close the tray menu if it's open
|
||||
if (trayMenuWindow && trayMenuWindow.visible) {
|
||||
trayMenuWindow.close()
|
||||
trayMenuWindow.close();
|
||||
}
|
||||
|
||||
const panel = PanelService.getPanel("trayDrawerPanel", root.screen)
|
||||
const panel = PanelService.getPanel("trayDrawerPanel", root.screen);
|
||||
if (panel) {
|
||||
panel.widgetSection = root.section
|
||||
panel.widgetIndex = root.sectionWidgetIndex
|
||||
panel.toggle(this)
|
||||
panel.widgetSection = root.section;
|
||||
panel.widgetIndex = root.sectionWidgetIndex;
|
||||
panel.toggle(this);
|
||||
}
|
||||
}
|
||||
|
||||
function onLoaded() {
|
||||
// When the widget is fully initialized with its props set the screen for the trayMenu
|
||||
if (trayMenu && trayMenu.item) {
|
||||
trayMenu.item.screen = screen
|
||||
trayMenu.item.screen = screen;
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SystemTray.items
|
||||
function onValuesChanged() {
|
||||
root.updateFilteredItems()
|
||||
root.updateFilteredItems();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Settings
|
||||
function onSettingsSaved() {
|
||||
root.updateFilteredItems()
|
||||
root.updateFilteredItems();
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
root.updateFilteredItems() // Initial update
|
||||
root.updateFilteredItems(); // Initial update
|
||||
}
|
||||
|
||||
visible: filteredItems.length > 0 || dropdownItems.length > 0
|
||||
@@ -248,14 +248,14 @@ Rectangle {
|
||||
icon: {
|
||||
switch (barPosition) {
|
||||
case "bottom":
|
||||
return "caret-up"
|
||||
return "caret-up";
|
||||
case "left":
|
||||
return "caret-right"
|
||||
return "caret-right";
|
||||
case "right":
|
||||
return "caret-left"
|
||||
return "caret-left";
|
||||
case "top":
|
||||
default:
|
||||
return "caret-down"
|
||||
return "caret-down";
|
||||
}
|
||||
}
|
||||
onClicked: toggleDrawer(this)
|
||||
@@ -283,20 +283,20 @@ Rectangle {
|
||||
property bool menuJustOpened: false
|
||||
|
||||
source: {
|
||||
let icon = modelData?.icon || ""
|
||||
let icon = modelData?.icon || "";
|
||||
if (!icon) {
|
||||
return ""
|
||||
return "";
|
||||
}
|
||||
|
||||
// Process icon path
|
||||
if (icon.includes("?path=")) {
|
||||
const chunks = icon.split("?path=")
|
||||
const name = chunks[0]
|
||||
const path = chunks[1]
|
||||
const fileName = name.substring(name.lastIndexOf("/") + 1)
|
||||
return `file://${path}/${fileName}`
|
||||
const chunks = icon.split("?path=");
|
||||
const name = chunks[0];
|
||||
const path = chunks[1];
|
||||
const fileName = name.substring(name.lastIndexOf("/") + 1);
|
||||
return `file://${path}/${fileName}`;
|
||||
}
|
||||
return icon
|
||||
return icon;
|
||||
}
|
||||
opacity: status === Image.Ready ? 1 : 0
|
||||
|
||||
@@ -315,71 +315,71 @@ Rectangle {
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
onClicked: mouse => {
|
||||
if (!modelData) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
// Close any open menu first
|
||||
if (trayMenuWindow) {
|
||||
trayMenuWindow.close()
|
||||
trayMenuWindow.close();
|
||||
}
|
||||
|
||||
if (!modelData.onlyMenu) {
|
||||
modelData.activate()
|
||||
modelData.activate();
|
||||
}
|
||||
} else if (mouse.button === Qt.MiddleButton) {
|
||||
// Close the menu if it was visible
|
||||
if (trayMenuWindow && trayMenuWindow.visible) {
|
||||
trayMenuWindow.close()
|
||||
return
|
||||
trayMenuWindow.close();
|
||||
return;
|
||||
}
|
||||
modelData.secondaryActivate && modelData.secondaryActivate()
|
||||
modelData.secondaryActivate && modelData.secondaryActivate();
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
TooltipService.hideImmediately()
|
||||
TooltipService.hideImmediately();
|
||||
|
||||
// Close the menu if it was visible
|
||||
if (trayMenuWindow && trayMenuWindow.visible) {
|
||||
trayMenuWindow.close()
|
||||
return
|
||||
trayMenuWindow.close();
|
||||
return;
|
||||
}
|
||||
|
||||
// Close any opened panel
|
||||
if ((PanelService.openedPanel !== null) && !PanelService.openedPanel.isClosing) {
|
||||
PanelService.openedPanel.close()
|
||||
PanelService.openedPanel.close();
|
||||
}
|
||||
|
||||
if (modelData.hasMenu && modelData.menu && trayMenuWindow && trayMenu && trayMenu.item) {
|
||||
trayMenuWindow.open()
|
||||
trayMenuWindow.open();
|
||||
|
||||
// Position menu based on bar position
|
||||
let menuX, menuY
|
||||
let menuX, menuY;
|
||||
if (barPosition === "left") {
|
||||
// For left bar: position menu to the right of the bar
|
||||
menuX = width + Style.marginM
|
||||
menuY = 0
|
||||
menuX = width + Style.marginM;
|
||||
menuY = 0;
|
||||
} else if (barPosition === "right") {
|
||||
// For right bar: position menu to the left of the bar
|
||||
menuX = -trayMenu.item.width - Style.marginM
|
||||
menuY = 0
|
||||
menuX = -trayMenu.item.width - Style.marginM;
|
||||
menuY = 0;
|
||||
} else {
|
||||
// For horizontal bars: center horizontally and position below
|
||||
menuX = (width / 2) - (trayMenu.item.width / 2)
|
||||
menuY = (barPosition === "top") ? Style.barHeight : -Style.barHeight
|
||||
menuX = (width / 2) - (trayMenu.item.width / 2);
|
||||
menuY = (barPosition === "top") ? Style.barHeight : -Style.barHeight;
|
||||
}
|
||||
trayMenu.item.trayItem = modelData
|
||||
trayMenu.item.widgetSection = root.section
|
||||
trayMenu.item.widgetIndex = root.sectionWidgetIndex
|
||||
trayMenu.item.showAt(parent, menuX, menuY)
|
||||
trayMenu.item.trayItem = modelData;
|
||||
trayMenu.item.widgetSection = root.section;
|
||||
trayMenu.item.widgetIndex = root.sectionWidgetIndex;
|
||||
trayMenu.item.showAt(parent, menuX, menuY);
|
||||
} else {
|
||||
Logger.d("Tray", "No menu available for", modelData.id, "or trayMenu not set")
|
||||
Logger.d("Tray", "No menu available for", modelData.id, "or trayMenu not set");
|
||||
}
|
||||
}
|
||||
}
|
||||
onEntered: {
|
||||
if (trayMenuWindow) {
|
||||
trayMenuWindow.close()
|
||||
trayMenuWindow.close();
|
||||
}
|
||||
TooltipService.show(Screen, trayIcon, modelData.tooltipTitle || modelData.name || modelData.id || "Tray Item", BarService.getTooltipDirection())
|
||||
TooltipService.show(Screen, trayIcon, modelData.tooltipTitle || modelData.name || modelData.id || "Tray Item", BarService.getTooltipDirection());
|
||||
}
|
||||
onExited: TooltipService.hide()
|
||||
}
|
||||
@@ -403,14 +403,14 @@ Rectangle {
|
||||
icon: {
|
||||
switch (barPosition) {
|
||||
case "bottom":
|
||||
return "caret-up"
|
||||
return "caret-up";
|
||||
case "left":
|
||||
return "caret-right"
|
||||
return "caret-right";
|
||||
case "right":
|
||||
return "caret-left"
|
||||
return "caret-left";
|
||||
case "top":
|
||||
default:
|
||||
return "caret-down"
|
||||
return "caret-down";
|
||||
}
|
||||
}
|
||||
onClicked: toggleDrawer(this)
|
||||
|
||||
@@ -5,8 +5,8 @@ import Quickshell.Services.Pipewire
|
||||
import qs.Commons
|
||||
import qs.Modules.Bar.Extras
|
||||
import qs.Modules.Panels.Settings
|
||||
import qs.Services.UI
|
||||
import qs.Services.Media
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
@@ -23,12 +23,12 @@ Item {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
|
||||
@@ -48,12 +48,12 @@ Item {
|
||||
// Logger.i("Bar:Volume", "onVolumeChanged")
|
||||
if (!firstVolumeReceived) {
|
||||
// Ignore the first volume change
|
||||
firstVolumeReceived = true
|
||||
firstVolumeReceived = true;
|
||||
} else {
|
||||
// Hide any tooltip while the pill is visible / being updated
|
||||
TooltipService.hide()
|
||||
pill.show()
|
||||
externalHideTimer.restart()
|
||||
TooltipService.hide();
|
||||
pill.show();
|
||||
externalHideTimer.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,7 @@ Item {
|
||||
running: false
|
||||
interval: 1500
|
||||
onTriggered: {
|
||||
pill.hide()
|
||||
pill.hide();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,25 +84,25 @@ Item {
|
||||
|
||||
onWheel: function (delta) {
|
||||
// Hide tooltip as soon as the user starts scrolling to adjust volume
|
||||
TooltipService.hide()
|
||||
TooltipService.hide();
|
||||
|
||||
wheelAccumulator += delta
|
||||
wheelAccumulator += delta;
|
||||
if (wheelAccumulator >= 120) {
|
||||
wheelAccumulator = 0
|
||||
AudioService.increaseVolume()
|
||||
wheelAccumulator = 0;
|
||||
AudioService.increaseVolume();
|
||||
} else if (wheelAccumulator <= -120) {
|
||||
wheelAccumulator = 0
|
||||
AudioService.decreaseVolume()
|
||||
wheelAccumulator = 0;
|
||||
AudioService.decreaseVolume();
|
||||
}
|
||||
}
|
||||
onClicked: {
|
||||
PanelService.getPanel("audioPanel", screen)?.toggle(this)
|
||||
PanelService.getPanel("audioPanel", screen)?.toggle(this);
|
||||
}
|
||||
onRightClicked: {
|
||||
AudioService.setOutputMuted(!AudioService.muted)
|
||||
AudioService.setOutputMuted(!AudioService.muted);
|
||||
}
|
||||
onMiddleClicked: {
|
||||
Quickshell.execDetached(["sh", "-c", "pwvucontrol || pavucontrol"])
|
||||
Quickshell.execDetached(["sh", "-c", "pwvucontrol || pavucontrol"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,11 +21,11 @@ NIconButton {
|
||||
colorBorder: Color.transparent
|
||||
colorBorderHover: Color.transparent
|
||||
onClicked: {
|
||||
var wallpaperPanel = PanelService.getPanel("wallpaperPanel", screen)
|
||||
var wallpaperPanel = PanelService.getPanel("wallpaperPanel", screen);
|
||||
if (Settings.data.wallpaper.panelPosition === "follow_bar") {
|
||||
wallpaperPanel?.toggle(this)
|
||||
wallpaperPanel?.toggle(this);
|
||||
} else {
|
||||
wallpaperPanel?.toggle()
|
||||
wallpaperPanel?.toggle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Modules.Bar.Extras
|
||||
import qs.Services.Networking
|
||||
import qs.Services.UI
|
||||
import qs.Modules.Bar.Extras
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@@ -19,12 +19,12 @@ Item {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
|
||||
@@ -41,37 +41,37 @@ Item {
|
||||
icon: {
|
||||
try {
|
||||
if (NetworkService.ethernetConnected) {
|
||||
return NetworkService.internetConnectivity ? "ethernet" : "ethernet-off"
|
||||
return NetworkService.internetConnectivity ? "ethernet" : "ethernet-off";
|
||||
}
|
||||
let connected = false
|
||||
let signalStrength = 0
|
||||
let connected = false;
|
||||
let signalStrength = 0;
|
||||
for (const net in NetworkService.networks) {
|
||||
if (NetworkService.networks[net].connected) {
|
||||
connected = true
|
||||
signalStrength = NetworkService.networks[net].signal
|
||||
break
|
||||
connected = true;
|
||||
signalStrength = NetworkService.networks[net].signal;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return connected ? NetworkService.signalIcon(signalStrength, true) : "wifi-off"
|
||||
return connected ? NetworkService.signalIcon(signalStrength, true) : "wifi-off";
|
||||
} catch (error) {
|
||||
Logger.e("Wi-Fi", "Error getting icon:", error)
|
||||
return "wifi-off"
|
||||
Logger.e("Wi-Fi", "Error getting icon:", error);
|
||||
return "wifi-off";
|
||||
}
|
||||
}
|
||||
text: {
|
||||
try {
|
||||
if (NetworkService.ethernetConnected) {
|
||||
return ""
|
||||
return "";
|
||||
}
|
||||
for (const net in NetworkService.networks) {
|
||||
if (NetworkService.networks[net].connected) {
|
||||
return net
|
||||
return net;
|
||||
}
|
||||
}
|
||||
return ""
|
||||
return "";
|
||||
} catch (error) {
|
||||
Logger.e("Wi-Fi", "Error getting ssid:", error)
|
||||
return "error"
|
||||
Logger.e("Wi-Fi", "Error getting ssid:", error);
|
||||
return "error";
|
||||
}
|
||||
}
|
||||
autoHide: false
|
||||
@@ -81,9 +81,9 @@ Item {
|
||||
onRightClicked: NetworkService.setWifiEnabled(!Settings.data.network.wifiEnabled)
|
||||
tooltipText: {
|
||||
if (pill.text !== "") {
|
||||
return pill.text
|
||||
return pill.text;
|
||||
}
|
||||
return I18n.tr("tooltips.manage-wifi")
|
||||
return I18n.tr("tooltips.manage-wifi");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Window
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
@@ -24,23 +24,23 @@ Item {
|
||||
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
|
||||
property var widgetSettings: {
|
||||
if (section && sectionWidgetIndex >= 0) {
|
||||
var widgets = Settings.data.bar.widgets[section]
|
||||
var widgets = Settings.data.bar.widgets[section];
|
||||
if (widgets && sectionWidgetIndex < widgets.length) {
|
||||
return widgets[sectionWidgetIndex]
|
||||
return widgets[sectionWidgetIndex];
|
||||
}
|
||||
}
|
||||
return {}
|
||||
return {};
|
||||
}
|
||||
|
||||
readonly property string barPosition: Settings.data.bar.position
|
||||
readonly property bool isVertical: barPosition === "left" || barPosition === "right"
|
||||
readonly property bool density: Settings.data.bar.density
|
||||
readonly property real baseDimensionRatio: {
|
||||
const b = (density === "compact") ? 0.85 : 0.65
|
||||
const b = (density === "compact") ? 0.85 : 0.65;
|
||||
if (widgetSettings.labelMode === "none") {
|
||||
return b * 0.75
|
||||
return b * 0.75;
|
||||
}
|
||||
return b
|
||||
return b;
|
||||
}
|
||||
|
||||
readonly property string labelMode: (widgetSettings.labelMode !== undefined) ? widgetSettings.labelMode : widgetMetadata.labelMode
|
||||
@@ -68,76 +68,85 @@ Item {
|
||||
implicitHeight: isVertical ? computeHeight() : Style.barHeight
|
||||
|
||||
function getWorkspaceWidth(ws) {
|
||||
const d = Style.capsuleHeight * root.baseDimensionRatio
|
||||
const factor = ws.isActive ? 2.2 : 1
|
||||
const d = Style.capsuleHeight * root.baseDimensionRatio;
|
||||
const factor = ws.isActive ? 2.2 : 1;
|
||||
|
||||
// For name mode, calculate width based on actual text content
|
||||
if (labelMode === "name" && ws.name && ws.name.length > 0) {
|
||||
const displayText = ws.name.substring(0, characterCount)
|
||||
const textWidth = displayText.length * (d * 0.4) // Approximate width per character
|
||||
const padding = d * 0.6 // Padding on both sides
|
||||
return Math.max(d * factor, textWidth + padding)
|
||||
// Don't calculate text width if labels are off
|
||||
if (labelMode === "none") {
|
||||
return d * factor;
|
||||
}
|
||||
|
||||
return d * factor
|
||||
var displayText = ws.idx.toString();
|
||||
|
||||
if (ws.name && ws.name.length > 0) {
|
||||
if (root.labelMode === "name") {
|
||||
displayText = ws.name.substring(0, characterCount);
|
||||
} else if (root.labelMode === "index+name") {
|
||||
displayText = ws.idx.toString() + " " + ws.name.substring(0, characterCount);
|
||||
}
|
||||
}
|
||||
|
||||
const textWidth = displayText.length * (d * 0.4); // Approximate width per character
|
||||
const padding = d * 0.6;
|
||||
return Math.max(d * factor, textWidth + padding);
|
||||
}
|
||||
|
||||
function getWorkspaceHeight(ws) {
|
||||
const d = Style.capsuleHeight * root.baseDimensionRatio
|
||||
const factor = ws.isActive ? 2.2 : 1
|
||||
return d * factor
|
||||
const d = Style.capsuleHeight * root.baseDimensionRatio;
|
||||
const factor = ws.isActive ? 2.2 : 1;
|
||||
return d * factor;
|
||||
}
|
||||
|
||||
function computeWidth() {
|
||||
let total = 0
|
||||
let total = 0;
|
||||
for (var i = 0; i < localWorkspaces.count; i++) {
|
||||
const ws = localWorkspaces.get(i)
|
||||
total += getWorkspaceWidth(ws)
|
||||
const ws = localWorkspaces.get(i);
|
||||
total += getWorkspaceWidth(ws);
|
||||
}
|
||||
total += Math.max(localWorkspaces.count - 1, 0) * spacingBetweenPills
|
||||
total += horizontalPadding * 2
|
||||
return Math.round(total)
|
||||
total += Math.max(localWorkspaces.count - 1, 0) * spacingBetweenPills;
|
||||
total += horizontalPadding * 2;
|
||||
return Math.round(total);
|
||||
}
|
||||
|
||||
function computeHeight() {
|
||||
let total = 0
|
||||
let total = 0;
|
||||
for (var i = 0; i < localWorkspaces.count; i++) {
|
||||
const ws = localWorkspaces.get(i)
|
||||
total += getWorkspaceHeight(ws)
|
||||
const ws = localWorkspaces.get(i);
|
||||
total += getWorkspaceHeight(ws);
|
||||
}
|
||||
total += Math.max(localWorkspaces.count - 1, 0) * spacingBetweenPills
|
||||
total += horizontalPadding * 2
|
||||
return Math.round(total)
|
||||
total += Math.max(localWorkspaces.count - 1, 0) * spacingBetweenPills;
|
||||
total += horizontalPadding * 2;
|
||||
return Math.round(total);
|
||||
}
|
||||
|
||||
function getFocusedLocalIndex() {
|
||||
for (var i = 0; i < localWorkspaces.count; i++) {
|
||||
if (localWorkspaces.get(i).isFocused === true)
|
||||
return i
|
||||
return i;
|
||||
}
|
||||
return -1
|
||||
return -1;
|
||||
}
|
||||
|
||||
function switchByOffset(offset) {
|
||||
if (localWorkspaces.count === 0)
|
||||
return
|
||||
var current = getFocusedLocalIndex()
|
||||
return;
|
||||
var current = getFocusedLocalIndex();
|
||||
if (current < 0)
|
||||
current = 0
|
||||
var next = (current + offset) % localWorkspaces.count
|
||||
current = 0;
|
||||
var next = (current + offset) % localWorkspaces.count;
|
||||
if (next < 0)
|
||||
next = localWorkspaces.count - 1
|
||||
const ws = localWorkspaces.get(next)
|
||||
next = localWorkspaces.count - 1;
|
||||
const ws = localWorkspaces.get(next);
|
||||
if (ws && ws.idx !== undefined)
|
||||
CompositorService.switchToWorkspace(ws)
|
||||
CompositorService.switchToWorkspace(ws);
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
refreshWorkspaces()
|
||||
refreshWorkspaces();
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
root.isDestroying = true
|
||||
root.isDestroying = true;
|
||||
}
|
||||
|
||||
onScreenChanged: refreshWorkspaces()
|
||||
@@ -146,40 +155,40 @@ Item {
|
||||
Connections {
|
||||
target: CompositorService
|
||||
function onWorkspacesChanged() {
|
||||
refreshWorkspaces()
|
||||
refreshWorkspaces();
|
||||
}
|
||||
}
|
||||
|
||||
function refreshWorkspaces() {
|
||||
localWorkspaces.clear()
|
||||
localWorkspaces.clear();
|
||||
if (screen !== null) {
|
||||
for (var i = 0; i < CompositorService.workspaces.count; i++) {
|
||||
const ws = CompositorService.workspaces.get(i)
|
||||
const ws = CompositorService.workspaces.get(i);
|
||||
if (ws.output.toLowerCase() === screen.name.toLowerCase()) {
|
||||
if (hideUnoccupied && !ws.isOccupied && !ws.isFocused) {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
localWorkspaces.append(ws)
|
||||
localWorkspaces.append(ws);
|
||||
}
|
||||
}
|
||||
}
|
||||
workspaceRepeaterHorizontal.model = localWorkspaces
|
||||
workspaceRepeaterVertical.model = localWorkspaces
|
||||
updateWorkspaceFocus()
|
||||
workspaceRepeaterHorizontal.model = localWorkspaces;
|
||||
workspaceRepeaterVertical.model = localWorkspaces;
|
||||
updateWorkspaceFocus();
|
||||
}
|
||||
|
||||
function triggerUnifiedWave() {
|
||||
effectColor = Color.mPrimary
|
||||
masterAnimation.restart()
|
||||
effectColor = Color.mPrimary;
|
||||
masterAnimation.restart();
|
||||
}
|
||||
|
||||
function updateWorkspaceFocus() {
|
||||
for (var i = 0; i < localWorkspaces.count; i++) {
|
||||
const ws = localWorkspaces.get(i)
|
||||
const ws = localWorkspaces.get(i);
|
||||
if (ws.isFocused === true) {
|
||||
root.triggerUnifiedWave()
|
||||
root.workspaceChanged(ws.id, Color.mPrimary)
|
||||
break
|
||||
root.triggerUnifiedWave();
|
||||
root.workspaceChanged(ws.id, Color.mPrimary);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -228,8 +237,8 @@ Item {
|
||||
interval: 150
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
root.wheelCooldown = false
|
||||
root.wheelAccumulatedDelta = 0
|
||||
root.wheelCooldown = false;
|
||||
root.wheelAccumulatedDelta = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,24 +249,24 @@ Item {
|
||||
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
||||
onWheel: function (event) {
|
||||
if (root.wheelCooldown)
|
||||
return
|
||||
return;
|
||||
// Prefer vertical delta, fall back to horizontal if needed
|
||||
var dy = event.angleDelta.y
|
||||
var dx = event.angleDelta.x
|
||||
var useDy = Math.abs(dy) >= Math.abs(dx)
|
||||
var delta = useDy ? dy : dx
|
||||
var dy = event.angleDelta.y;
|
||||
var dx = event.angleDelta.x;
|
||||
var useDy = Math.abs(dy) >= Math.abs(dx);
|
||||
var delta = useDy ? dy : dx;
|
||||
// One notch is typically 120
|
||||
root.wheelAccumulatedDelta += delta
|
||||
var step = 120
|
||||
root.wheelAccumulatedDelta += delta;
|
||||
var step = 120;
|
||||
if (Math.abs(root.wheelAccumulatedDelta) >= step) {
|
||||
var direction = root.wheelAccumulatedDelta > 0 ? -1 : 1
|
||||
var direction = root.wheelAccumulatedDelta > 0 ? -1 : 1;
|
||||
// For vertical layout, natural mapping: wheel up -> previous, down -> next (already handled by sign)
|
||||
// For horizontal layout, same mapping using vertical wheel
|
||||
root.switchByOffset(direction)
|
||||
root.wheelCooldown = true
|
||||
wheelDebounce.restart()
|
||||
root.wheelAccumulatedDelta = 0
|
||||
event.accepted = true
|
||||
root.switchByOffset(direction);
|
||||
root.wheelCooldown = true;
|
||||
wheelDebounce.restart();
|
||||
root.wheelAccumulatedDelta = 0;
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -289,11 +298,15 @@ Item {
|
||||
x: (pill.width - width) / 2
|
||||
y: (pill.height - height) / 2 + (height - contentHeight) / 2
|
||||
text: {
|
||||
if (labelMode === "name" && model.name && model.name.length > 0) {
|
||||
return model.name.substring(0, characterCount)
|
||||
} else {
|
||||
return model.idx.toString()
|
||||
if (model.name && model.name.length > 0) {
|
||||
if (root.labelMode === "name") {
|
||||
return model.name.substring(0, characterCount);
|
||||
}
|
||||
if (root.labelMode === "index+name") {
|
||||
return (model.idx.toString() + " " + model.name.substring(0, characterCount))
|
||||
}
|
||||
}
|
||||
return model.idx.toString();
|
||||
}
|
||||
family: Settings.data.ui.fontFixed
|
||||
pointSize: model.isActive ? workspacePillContainer.height * 0.45 : workspacePillContainer.height * 0.42
|
||||
@@ -303,13 +316,13 @@ Item {
|
||||
wrapMode: Text.Wrap
|
||||
color: {
|
||||
if (model.isFocused)
|
||||
return Color.mOnPrimary
|
||||
return Color.mOnPrimary;
|
||||
if (model.isUrgent)
|
||||
return Color.mOnError
|
||||
return Color.mOnError;
|
||||
if (model.isOccupied)
|
||||
return Color.mOnSecondary
|
||||
return Color.mOnSecondary;
|
||||
|
||||
return Color.mOnSecondary
|
||||
return Color.mOnSecondary;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -318,13 +331,13 @@ Item {
|
||||
radius: width * 0.5
|
||||
color: {
|
||||
if (model.isFocused)
|
||||
return Color.mPrimary
|
||||
return Color.mPrimary;
|
||||
if (model.isUrgent)
|
||||
return Color.mError
|
||||
return Color.mError;
|
||||
if (model.isOccupied)
|
||||
return Color.mSecondary
|
||||
return Color.mSecondary;
|
||||
|
||||
return Qt.alpha(Color.mSecondary, 0.3)
|
||||
return Qt.alpha(Color.mSecondary, 0.3);
|
||||
}
|
||||
scale: model.isActive ? 1.0 : 0.9
|
||||
z: 0
|
||||
@@ -334,7 +347,7 @@ Item {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
CompositorService.switchToWorkspace(model)
|
||||
CompositorService.switchToWorkspace(model);
|
||||
}
|
||||
hoverEnabled: true
|
||||
}
|
||||
@@ -434,11 +447,15 @@ Item {
|
||||
x: (pillVertical.width - width) / 2
|
||||
y: (pillVertical.height - height) / 2 + (height - contentHeight) / 2
|
||||
text: {
|
||||
if (labelMode === "name" && model.name && model.name.length > 0) {
|
||||
return model.name.substring(0, characterCount)
|
||||
} else {
|
||||
return model.idx.toString()
|
||||
if (model.name && model.name.length > 0) {
|
||||
if (root.labelMode === "name") {
|
||||
return model.name.substring(0, characterCount);
|
||||
}
|
||||
if (root.labelMode === "index+name") {
|
||||
return (model.idx.toString() + model.name.substring(0, 1))
|
||||
}
|
||||
}
|
||||
return model.idx.toString();
|
||||
}
|
||||
family: Settings.data.ui.fontFixed
|
||||
pointSize: model.isActive ? workspacePillContainerVertical.width * 0.45 : workspacePillContainerVertical.width * 0.42
|
||||
@@ -448,13 +465,13 @@ Item {
|
||||
wrapMode: Text.Wrap
|
||||
color: {
|
||||
if (model.isFocused)
|
||||
return Color.mOnPrimary
|
||||
return Color.mOnPrimary;
|
||||
if (model.isUrgent)
|
||||
return Color.mOnError
|
||||
return Color.mOnError;
|
||||
if (model.isOccupied)
|
||||
return Color.mOnSecondary
|
||||
return Color.mOnSecondary;
|
||||
|
||||
return Color.mOnSurface
|
||||
return Color.mOnSurface;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -463,13 +480,13 @@ Item {
|
||||
radius: width * 0.5
|
||||
color: {
|
||||
if (model.isFocused)
|
||||
return Color.mPrimary
|
||||
return Color.mPrimary;
|
||||
if (model.isUrgent)
|
||||
return Color.mError
|
||||
return Color.mError;
|
||||
if (model.isOccupied)
|
||||
return Color.mSecondary
|
||||
return Color.mSecondary;
|
||||
|
||||
return Color.mOutline
|
||||
return Color.mOutline;
|
||||
}
|
||||
scale: model.isActive ? 1.0 : 0.9
|
||||
z: 0
|
||||
@@ -479,7 +496,7 @@ Item {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
CompositorService.switchToWorkspace(model)
|
||||
CompositorService.switchToWorkspace(model);
|
||||
}
|
||||
hoverEnabled: true
|
||||
}
|
||||
|
||||
+87
-87
@@ -1,7 +1,7 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
@@ -26,7 +26,7 @@ Loader {
|
||||
target: BarService
|
||||
function onBarReadyChanged(screenName) {
|
||||
if (screenName === modelData.name) {
|
||||
barIsReady = true
|
||||
barIsReady = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ Loader {
|
||||
Connections {
|
||||
target: ToplevelManager ? ToplevelManager.toplevels : null
|
||||
function onValuesChanged() {
|
||||
updateDockApps()
|
||||
updateDockApps();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,17 +43,17 @@ Loader {
|
||||
Connections {
|
||||
target: Settings.data.dock
|
||||
function onPinnedAppsChanged() {
|
||||
updateDockApps()
|
||||
updateDockApps();
|
||||
}
|
||||
function onOnlySameOutputChanged() {
|
||||
updateDockApps()
|
||||
updateDockApps();
|
||||
}
|
||||
}
|
||||
|
||||
// Initial update when component is ready
|
||||
Component.onCompleted: {
|
||||
if (ToplevelManager) {
|
||||
updateDockApps()
|
||||
updateDockApps();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,33 +93,33 @@ Loader {
|
||||
// Function to close any open context menu
|
||||
function closeAllContextMenus() {
|
||||
if (currentContextMenu && currentContextMenu.visible) {
|
||||
currentContextMenu.hide()
|
||||
currentContextMenu.hide();
|
||||
}
|
||||
}
|
||||
|
||||
// Function to update the combined dock apps model
|
||||
function updateDockApps() {
|
||||
const runningApps = ToplevelManager ? (ToplevelManager.toplevels.values || []) : []
|
||||
const pinnedApps = Settings.data.dock.pinnedApps || []
|
||||
const combined = []
|
||||
const processedAppIds = new Set()
|
||||
const runningApps = ToplevelManager ? (ToplevelManager.toplevels.values || []) : [];
|
||||
const pinnedApps = Settings.data.dock.pinnedApps || [];
|
||||
const combined = [];
|
||||
const processedAppIds = new Set();
|
||||
|
||||
// Strategy: Maintain app positions as much as possible
|
||||
// 1. First pass: Add all running apps (both pinned and non-pinned) in their current order
|
||||
runningApps.forEach(toplevel => {
|
||||
if (toplevel && toplevel.appId && !(Settings.data.dock.onlySameOutput && toplevel.screens && !toplevel.screens.includes(modelData))) {
|
||||
const isPinned = pinnedApps.includes(toplevel.appId)
|
||||
const appType = isPinned ? "pinned-running" : "running"
|
||||
const isPinned = pinnedApps.includes(toplevel.appId);
|
||||
const appType = isPinned ? "pinned-running" : "running";
|
||||
|
||||
combined.push({
|
||||
"type": appType,
|
||||
"toplevel": toplevel,
|
||||
"appId": toplevel.appId,
|
||||
"title": toplevel.title
|
||||
})
|
||||
processedAppIds.add(toplevel.appId)
|
||||
});
|
||||
processedAppIds.add(toplevel.appId);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// 2. Second pass: Add non-running pinned apps at the end
|
||||
pinnedApps.forEach(pinnedAppId => {
|
||||
@@ -130,11 +130,11 @@ Loader {
|
||||
"toplevel": null,
|
||||
"appId": pinnedAppId,
|
||||
"title": pinnedAppId
|
||||
})
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
dockApps = combined
|
||||
dockApps = combined;
|
||||
}
|
||||
|
||||
// Timer to unload dock after hide animation completes
|
||||
@@ -143,7 +143,7 @@ Loader {
|
||||
interval: hideAnimationDuration + 50 // Add small buffer
|
||||
onTriggered: {
|
||||
if (hidden && autoHide) {
|
||||
dockLoaded = false
|
||||
dockLoaded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -155,14 +155,14 @@ Loader {
|
||||
onTriggered: {
|
||||
// Force menuHovered to false if no menu is current or visible
|
||||
if (!root.currentContextMenu || !root.currentContextMenu.visible) {
|
||||
menuHovered = false
|
||||
menuHovered = false;
|
||||
}
|
||||
if (autoHide && !dockHovered && !anyAppHovered && !peekHovered && !menuHovered) {
|
||||
hidden = true
|
||||
unloadTimer.restart() // Start unload timer when hiding
|
||||
hidden = true;
|
||||
unloadTimer.restart(); // Start unload timer when hiding
|
||||
} else if (autoHide && !dockHovered && !peekHovered) {
|
||||
// Restart timer if menu is closing (handles race condition)
|
||||
restart()
|
||||
restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -173,9 +173,9 @@ Loader {
|
||||
interval: showDelay
|
||||
onTriggered: {
|
||||
if (autoHide) {
|
||||
dockLoaded = true // Load dock immediately
|
||||
hidden = false // Then trigger show animation
|
||||
unloadTimer.stop() // Cancel any pending unload
|
||||
dockLoaded = true; // Load dock immediately
|
||||
hidden = false; // Then trigger show animation
|
||||
unloadTimer.stop(); // Cancel any pending unload
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -183,14 +183,14 @@ Loader {
|
||||
// Watch for autoHide setting changes
|
||||
onAutoHideChanged: {
|
||||
if (!autoHide) {
|
||||
hidden = false
|
||||
dockLoaded = true
|
||||
hideTimer.stop()
|
||||
showTimer.stop()
|
||||
unloadTimer.stop()
|
||||
hidden = false;
|
||||
dockLoaded = true;
|
||||
hideTimer.stop();
|
||||
showTimer.stop();
|
||||
unloadTimer.stop();
|
||||
} else {
|
||||
hidden = true
|
||||
unloadTimer.restart() // Schedule unload after animation
|
||||
hidden = true;
|
||||
unloadTimer.restart(); // Schedule unload after animation
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,16 +218,16 @@ Loader {
|
||||
hoverEnabled: true
|
||||
|
||||
onEntered: {
|
||||
peekHovered = true
|
||||
peekHovered = true;
|
||||
if (hidden) {
|
||||
showTimer.start()
|
||||
showTimer.start();
|
||||
}
|
||||
}
|
||||
|
||||
onExited: {
|
||||
peekHovered = false
|
||||
peekHovered = false;
|
||||
if (!hidden && !dockHovered && !anyAppHovered && !menuHovered) {
|
||||
hideTimer.restart()
|
||||
hideTimer.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -260,9 +260,9 @@ Loader {
|
||||
margins.bottom: {
|
||||
switch (Settings.data.bar.position) {
|
||||
case "bottom":
|
||||
return (Style.barHeight + Style.marginM) + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL + floatingMargin : floatingMargin)
|
||||
return (Style.barHeight + Style.marginM) + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL + floatingMargin : floatingMargin);
|
||||
default:
|
||||
return floatingMargin
|
||||
return floatingMargin;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,24 +313,24 @@ Loader {
|
||||
hoverEnabled: true
|
||||
|
||||
onEntered: {
|
||||
dockHovered = true
|
||||
dockHovered = true;
|
||||
if (autoHide) {
|
||||
showTimer.stop()
|
||||
hideTimer.stop()
|
||||
unloadTimer.stop() // Cancel unload if hovering
|
||||
showTimer.stop();
|
||||
hideTimer.stop();
|
||||
unloadTimer.stop(); // Cancel unload if hovering
|
||||
}
|
||||
}
|
||||
|
||||
onExited: {
|
||||
dockHovered = false
|
||||
dockHovered = false;
|
||||
if (autoHide && !anyAppHovered && !peekHovered && !menuHovered) {
|
||||
hideTimer.restart()
|
||||
hideTimer.restart();
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
// Close any open context menu when clicking on the dock background
|
||||
closeAllContextMenus()
|
||||
closeAllContextMenus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,8 +342,8 @@ Loader {
|
||||
|
||||
function getAppIcon(appData): string {
|
||||
if (!appData || !appData.appId)
|
||||
return ""
|
||||
return ThemeIcons.iconForAppId(appData.appId?.toLowerCase())
|
||||
return "";
|
||||
return ThemeIcons.iconForAppId(appData.appId?.toLowerCase());
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
@@ -371,7 +371,7 @@ Loader {
|
||||
Connections {
|
||||
target: modelData?.toplevel
|
||||
function onClosed() {
|
||||
Qt.callLater(root.updateDockApps)
|
||||
Qt.callLater(root.updateDockApps);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -452,9 +452,9 @@ Loader {
|
||||
onHoveredChanged: {
|
||||
// Only update menuHovered if this menu is current and visible
|
||||
if (root.currentContextMenu === contextMenu && contextMenu.visible) {
|
||||
menuHovered = hovered
|
||||
menuHovered = hovered;
|
||||
} else {
|
||||
menuHovered = false
|
||||
menuHovered = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -462,26 +462,26 @@ Loader {
|
||||
target: contextMenu
|
||||
function onRequestClose() {
|
||||
// Clear current menu immediately to prevent hover updates
|
||||
root.currentContextMenu = null
|
||||
hideTimer.stop()
|
||||
contextMenu.hide()
|
||||
menuHovered = false
|
||||
anyAppHovered = false
|
||||
root.currentContextMenu = null;
|
||||
hideTimer.stop();
|
||||
contextMenu.hide();
|
||||
menuHovered = false;
|
||||
anyAppHovered = false;
|
||||
}
|
||||
}
|
||||
onAppClosed: root.updateDockApps // Force immediate dock update when app is closed
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
root.currentContextMenu = contextMenu
|
||||
anyAppHovered = false
|
||||
root.currentContextMenu = contextMenu;
|
||||
anyAppHovered = false;
|
||||
} else if (root.currentContextMenu === contextMenu) {
|
||||
root.currentContextMenu = null
|
||||
hideTimer.stop()
|
||||
menuHovered = false
|
||||
anyAppHovered = false
|
||||
root.currentContextMenu = null;
|
||||
hideTimer.stop();
|
||||
menuHovered = false;
|
||||
anyAppHovered = false;
|
||||
// Restart hide timer after menu closes
|
||||
if (autoHide && !dockHovered && !anyAppHovered && !peekHovered && !menuHovered) {
|
||||
hideTimer.restart()
|
||||
hideTimer.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -496,28 +496,28 @@ Loader {
|
||||
acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
|
||||
|
||||
onEntered: {
|
||||
anyAppHovered = true
|
||||
const appName = appButton.appTitle || appButton.appId || "Unknown"
|
||||
const tooltipText = appName.length > 40 ? appName.substring(0, 37) + "..." : appName
|
||||
anyAppHovered = true;
|
||||
const appName = appButton.appTitle || appButton.appId || "Unknown";
|
||||
const tooltipText = appName.length > 40 ? appName.substring(0, 37) + "..." : appName;
|
||||
if (!contextMenu.visible) {
|
||||
TooltipService.show(Screen, appButton, tooltipText, "top")
|
||||
TooltipService.show(Screen, appButton, tooltipText, "top");
|
||||
}
|
||||
if (autoHide) {
|
||||
showTimer.stop()
|
||||
hideTimer.stop()
|
||||
unloadTimer.stop() // Cancel unload if hovering app
|
||||
showTimer.stop();
|
||||
hideTimer.stop();
|
||||
unloadTimer.stop(); // Cancel unload if hovering app
|
||||
}
|
||||
}
|
||||
|
||||
onExited: {
|
||||
anyAppHovered = false
|
||||
TooltipService.hide()
|
||||
anyAppHovered = false;
|
||||
TooltipService.hide();
|
||||
// Clear menuHovered if no current menu or menu not visible
|
||||
if (!root.currentContextMenu || !root.currentContextMenu.visible) {
|
||||
menuHovered = false
|
||||
menuHovered = false;
|
||||
}
|
||||
if (autoHide && !dockHovered && !peekHovered && !menuHovered) {
|
||||
hideTimer.restart()
|
||||
hideTimer.restart();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -525,33 +525,33 @@ Loader {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
// If right-clicking on the same app with an open context menu, close it
|
||||
if (root.currentContextMenu === contextMenu && contextMenu.visible) {
|
||||
root.closeAllContextMenus()
|
||||
return
|
||||
root.closeAllContextMenus();
|
||||
return;
|
||||
}
|
||||
// Close any other existing context menu first
|
||||
root.closeAllContextMenus()
|
||||
root.closeAllContextMenus();
|
||||
// Hide tooltip when showing context menu
|
||||
TooltipService.hideImmediately()
|
||||
contextMenu.show(appButton, modelData.toplevel || modelData)
|
||||
return
|
||||
TooltipService.hideImmediately();
|
||||
contextMenu.show(appButton, modelData.toplevel || modelData);
|
||||
return;
|
||||
}
|
||||
|
||||
// Close any existing context menu for non-right-click actions
|
||||
root.closeAllContextMenus()
|
||||
root.closeAllContextMenus();
|
||||
|
||||
// Check if toplevel is still valid (not a stale reference)
|
||||
const isValidToplevel = modelData?.toplevel && ToplevelManager && ToplevelManager.toplevels.values.includes(modelData.toplevel)
|
||||
const isValidToplevel = modelData?.toplevel && ToplevelManager && ToplevelManager.toplevels.values.includes(modelData.toplevel);
|
||||
|
||||
if (mouse.button === Qt.MiddleButton && isValidToplevel && modelData.toplevel.close) {
|
||||
modelData.toplevel.close()
|
||||
Qt.callLater(root.updateDockApps) // Force immediate dock update
|
||||
modelData.toplevel.close();
|
||||
Qt.callLater(root.updateDockApps); // Force immediate dock update
|
||||
} else if (mouse.button === Qt.LeftButton) {
|
||||
if (isValidToplevel && modelData.toplevel.activate) {
|
||||
// Running app - activate it
|
||||
modelData.toplevel.activate()
|
||||
modelData.toplevel.activate();
|
||||
} else if (modelData?.appId) {
|
||||
// Pinned app not running - launch it
|
||||
Quickshell.execDetached(["gtk-launch", modelData.appId])
|
||||
Quickshell.execDetached(["gtk-launch", modelData.appId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+56
-57
@@ -31,21 +31,21 @@ PopupWindow {
|
||||
|
||||
function initItems() {
|
||||
// Is this a running app?
|
||||
const isRunning = root.toplevel && ToplevelManager && ToplevelManager.toplevels.values.includes(root.toplevel)
|
||||
const isRunning = root.toplevel && ToplevelManager && ToplevelManager.toplevels.values.includes(root.toplevel);
|
||||
|
||||
// Is this a pinned app?
|
||||
const isPinned = root.toplevel && root.isAppPinned(root.toplevel.appId)
|
||||
const isPinned = root.toplevel && root.isAppPinned(root.toplevel.appId);
|
||||
|
||||
var next = []
|
||||
var next = [];
|
||||
if (isRunning) {
|
||||
// Focus item
|
||||
next.push({
|
||||
"icon": "eye",
|
||||
"text": I18n.tr("dock.menu.focus"),
|
||||
"action": function () {
|
||||
handleFocus()
|
||||
handleFocus();
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// Pin/Unpin item
|
||||
@@ -53,9 +53,9 @@ PopupWindow {
|
||||
"icon": !isPinned ? "pin" : "unpin",
|
||||
"text": !isPinned ? I18n.tr("dock.menu.pin") : I18n.tr("dock.menu.unpin"),
|
||||
"action": function () {
|
||||
handlePin()
|
||||
handlePin();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
if (isRunning) {
|
||||
// Close item
|
||||
@@ -63,55 +63,54 @@ PopupWindow {
|
||||
"icon": "close",
|
||||
"text": I18n.tr("dock.menu.close"),
|
||||
"action": function () {
|
||||
handleClose()
|
||||
handleClose();
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// Create a menu entry for each app-specific action definied in its .desktop file
|
||||
if (typeof DesktopEntries !== 'undefined' && DesktopEntries.byId) {
|
||||
const entry = (DesktopEntries.heuristicLookup) ? DesktopEntries.heuristicLookup(appId) : DesktopEntries.byId(appId)
|
||||
const entry = (DesktopEntries.heuristicLookup) ? DesktopEntries.heuristicLookup(appId) : DesktopEntries.byId(appId);
|
||||
if (entry != null) {
|
||||
entry.actions.forEach(function (action) {
|
||||
next.push({
|
||||
"icon": "",
|
||||
"text": action.name,
|
||||
"action": function () {
|
||||
action.execute()
|
||||
action.execute();
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
root.items = next
|
||||
root.items = next;
|
||||
}
|
||||
|
||||
// Helper functions for pin/unpin functionality
|
||||
function isAppPinned(appId) {
|
||||
if (!appId)
|
||||
return false
|
||||
const pinnedApps = Settings.data.dock.pinnedApps || []
|
||||
return pinnedApps.includes(appId)
|
||||
return false;
|
||||
const pinnedApps = Settings.data.dock.pinnedApps || [];
|
||||
return pinnedApps.includes(appId);
|
||||
}
|
||||
|
||||
function toggleAppPin(appId) {
|
||||
if (!appId)
|
||||
return
|
||||
|
||||
let pinnedApps = (Settings.data.dock.pinnedApps || []).slice() // Create a copy
|
||||
const isPinned = pinnedApps.includes(appId)
|
||||
return;
|
||||
let pinnedApps = (Settings.data.dock.pinnedApps || []).slice(); // Create a copy
|
||||
const isPinned = pinnedApps.includes(appId);
|
||||
|
||||
if (isPinned) {
|
||||
// Unpin: remove from array
|
||||
pinnedApps = pinnedApps.filter(id => id !== appId)
|
||||
pinnedApps = pinnedApps.filter(id => id !== appId);
|
||||
} else {
|
||||
// Pin: add to array
|
||||
pinnedApps.push(appId)
|
||||
pinnedApps.push(appId);
|
||||
}
|
||||
|
||||
// Update the settings
|
||||
Settings.data.dock.pinnedApps = pinnedApps
|
||||
Settings.data.dock.pinnedApps = pinnedApps;
|
||||
}
|
||||
|
||||
anchor.item: anchorItem
|
||||
@@ -120,62 +119,62 @@ PopupWindow {
|
||||
|
||||
function show(item, toplevelData) {
|
||||
if (!item) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
anchorItem = item
|
||||
toplevel = toplevelData
|
||||
initItems()
|
||||
visible = true
|
||||
canAutoClose = false
|
||||
gracePeriodTimer.restart()
|
||||
anchorItem = item;
|
||||
toplevel = toplevelData;
|
||||
initItems();
|
||||
visible = true;
|
||||
canAutoClose = false;
|
||||
gracePeriodTimer.restart();
|
||||
}
|
||||
|
||||
function hide() {
|
||||
visible = false
|
||||
root.items.length = 0
|
||||
visible = false;
|
||||
root.items.length = 0;
|
||||
}
|
||||
|
||||
// Helper function to determine which menu item is under the mouse
|
||||
function getHoveredItem(mouseY) {
|
||||
const itemHeight = 32
|
||||
const startY = Style.marginM
|
||||
const relativeY = mouseY - startY
|
||||
const itemHeight = 32;
|
||||
const startY = Style.marginM;
|
||||
const relativeY = mouseY - startY;
|
||||
|
||||
if (relativeY < 0)
|
||||
return -1
|
||||
return -1;
|
||||
|
||||
const itemIndex = Math.floor(relativeY / itemHeight)
|
||||
return itemIndex >= 0 && itemIndex < root.items.length ? itemIndex : -1
|
||||
const itemIndex = Math.floor(relativeY / itemHeight);
|
||||
return itemIndex >= 0 && itemIndex < root.items.length ? itemIndex : -1;
|
||||
}
|
||||
|
||||
function handleFocus() {
|
||||
if (root.toplevel?.activate) {
|
||||
root.toplevel.activate()
|
||||
root.toplevel.activate();
|
||||
}
|
||||
root.requestClose()
|
||||
root.requestClose();
|
||||
}
|
||||
|
||||
function handlePin() {
|
||||
if (root.toplevel?.appId) {
|
||||
root.toggleAppPin(root.toplevel.appId)
|
||||
root.toggleAppPin(root.toplevel.appId);
|
||||
}
|
||||
root.requestClose()
|
||||
root.requestClose();
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
// Check if toplevel is still valid before trying to close it
|
||||
const isValidToplevel = root.toplevel && ToplevelManager && ToplevelManager.toplevels.values.includes(root.toplevel)
|
||||
const isValidToplevel = root.toplevel && ToplevelManager && ToplevelManager.toplevels.values.includes(root.toplevel);
|
||||
|
||||
if (isValidToplevel && root.toplevel.close) {
|
||||
root.toplevel.close()
|
||||
root.toplevel.close();
|
||||
// Trigger immediate dock update callback if provided
|
||||
if (root.onAppClosed && typeof root.onAppClosed === "function") {
|
||||
Qt.callLater(root.onAppClosed)
|
||||
Qt.callLater(root.onAppClosed);
|
||||
}
|
||||
}
|
||||
root.hide()
|
||||
root.requestClose()
|
||||
root.hide();
|
||||
root.requestClose();
|
||||
}
|
||||
|
||||
// Short delay to ignore spurious events
|
||||
@@ -184,9 +183,9 @@ PopupWindow {
|
||||
interval: 1500
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
root.canAutoClose = true
|
||||
root.canAutoClose = true;
|
||||
if (!menuMouseArea.containsMouse) {
|
||||
closeTimer.start()
|
||||
closeTimer.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,7 +196,7 @@ PopupWindow {
|
||||
repeat: false
|
||||
running: false
|
||||
onTriggered: {
|
||||
root.hide()
|
||||
root.hide();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,25 +215,25 @@ PopupWindow {
|
||||
cursorShape: root.hoveredItem >= 0 ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
|
||||
onEntered: {
|
||||
closeTimer.stop()
|
||||
closeTimer.stop();
|
||||
}
|
||||
|
||||
onExited: {
|
||||
root.hoveredItem = -1
|
||||
root.hoveredItem = -1;
|
||||
if (root.canAutoClose) {
|
||||
// Only close if grace period has passed
|
||||
closeTimer.start()
|
||||
closeTimer.start();
|
||||
}
|
||||
}
|
||||
|
||||
onPositionChanged: mouse => {
|
||||
root.hoveredItem = root.getHoveredItem(mouse.y)
|
||||
root.hoveredItem = root.getHoveredItem(mouse.y);
|
||||
}
|
||||
|
||||
onClicked: mouse => {
|
||||
const clickedItem = root.getHoveredItem(mouse.y)
|
||||
const clickedItem = root.getHoveredItem(mouse.y);
|
||||
if (clickedItem >= 0) {
|
||||
root.items[clickedItem].action.call()
|
||||
root.items[clickedItem].action.call();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,24 +18,24 @@ Scope {
|
||||
|
||||
onCurrentTextChanged: {
|
||||
if (currentText !== "") {
|
||||
showFailure = false
|
||||
errorMessage = ""
|
||||
showFailure = false;
|
||||
errorMessage = "";
|
||||
}
|
||||
}
|
||||
|
||||
function tryUnlock() {
|
||||
if (!pamAvailable) {
|
||||
errorMessage = "PAM not available"
|
||||
showFailure = true
|
||||
return
|
||||
errorMessage = "PAM not available";
|
||||
showFailure = true;
|
||||
return;
|
||||
}
|
||||
|
||||
root.unlockInProgress = true
|
||||
errorMessage = ""
|
||||
showFailure = false
|
||||
root.unlockInProgress = true;
|
||||
errorMessage = "";
|
||||
showFailure = false;
|
||||
|
||||
Logger.i("LockContext", "Starting PAM authentication for user:", pam.user)
|
||||
pam.start()
|
||||
Logger.i("LockContext", "Starting PAM authentication for user:", pam.user);
|
||||
pam.start();
|
||||
}
|
||||
|
||||
PamContext {
|
||||
@@ -44,48 +44,48 @@ Scope {
|
||||
user: HostService.username
|
||||
|
||||
onPamMessage: {
|
||||
Logger.i("LockContext", "PAM message:", message, "isError:", messageIsError, "responseRequired:", responseRequired)
|
||||
Logger.i("LockContext", "PAM message:", message, "isError:", messageIsError, "responseRequired:", responseRequired);
|
||||
|
||||
if (messageIsError) {
|
||||
errorMessage = message
|
||||
errorMessage = message;
|
||||
} else {
|
||||
infoMessage = message
|
||||
infoMessage = message;
|
||||
}
|
||||
|
||||
if (responseRequired) {
|
||||
Logger.i("LockContext", "Responding to PAM with password")
|
||||
respond(root.currentText)
|
||||
Logger.i("LockContext", "Responding to PAM with password");
|
||||
respond(root.currentText);
|
||||
}
|
||||
}
|
||||
|
||||
onResponseRequiredChanged: {
|
||||
Logger.i("LockContext", "Response required changed:", responseRequired)
|
||||
Logger.i("LockContext", "Response required changed:", responseRequired);
|
||||
if (responseRequired && root.unlockInProgress) {
|
||||
Logger.i("LockContext", "Automatically responding to PAM")
|
||||
respond(root.currentText)
|
||||
Logger.i("LockContext", "Automatically responding to PAM");
|
||||
respond(root.currentText);
|
||||
}
|
||||
}
|
||||
|
||||
onCompleted: result => {
|
||||
Logger.i("LockContext", "PAM completed with result:", result)
|
||||
Logger.i("LockContext", "PAM completed with result:", result);
|
||||
if (result === PamResult.Success) {
|
||||
Logger.i("LockContext", "Authentication successful")
|
||||
root.unlocked()
|
||||
Logger.i("LockContext", "Authentication successful");
|
||||
root.unlocked();
|
||||
} else {
|
||||
Logger.i("LockContext", "Authentication failed")
|
||||
errorMessage = "Authentication failed"
|
||||
showFailure = true
|
||||
root.failed()
|
||||
Logger.i("LockContext", "Authentication failed");
|
||||
errorMessage = "Authentication failed";
|
||||
showFailure = true;
|
||||
root.failed();
|
||||
}
|
||||
root.unlockInProgress = false
|
||||
root.unlockInProgress = false;
|
||||
}
|
||||
|
||||
onError: {
|
||||
Logger.i("LockContext", "PAM error:", error, "message:", message)
|
||||
errorMessage = message || "Authentication error"
|
||||
showFailure = true
|
||||
root.unlockInProgress = false
|
||||
root.failed()
|
||||
Logger.i("LockContext", "PAM error:", error, "message:", message);
|
||||
errorMessage = message || "Authentication error";
|
||||
showFailure = true;
|
||||
root.unlockInProgress = false;
|
||||
root.failed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Io
|
||||
import Quickshell.Services.Pam
|
||||
import Quickshell.Services.UPower
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import qs.Commons
|
||||
import qs.Services.Compositor
|
||||
import qs.Services.Hardware
|
||||
import qs.Services.Keyboard
|
||||
import qs.Services.Location
|
||||
import qs.Services.Media
|
||||
import qs.Services.Compositor
|
||||
import qs.Services.UI
|
||||
import qs.Services.System
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
import qs.Widgets.AudioSpectrum
|
||||
|
||||
@@ -31,14 +31,14 @@ Loader {
|
||||
interval: 250
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
lockScreen.active = false
|
||||
lockScreen.active = false;
|
||||
// Reset the deprecation flag when unlocking
|
||||
lockScreen.triggeredViaDeprecatedCall = false
|
||||
lockScreen.triggeredViaDeprecatedCall = false;
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleUnloadAfterUnlock() {
|
||||
unloadAfterUnlockTimer.start()
|
||||
unloadAfterUnlockTimer.start();
|
||||
}
|
||||
|
||||
sourceComponent: Component {
|
||||
@@ -48,12 +48,12 @@ Loader {
|
||||
LockContext {
|
||||
id: lockContext
|
||||
onUnlocked: {
|
||||
lockSession.locked = false
|
||||
lockScreen.scheduleUnloadAfterUnlock()
|
||||
lockContext.currentText = ""
|
||||
lockSession.locked = false;
|
||||
lockScreen.scheduleUnloadAfterUnlock();
|
||||
lockContext.currentText = "";
|
||||
}
|
||||
onFailed: {
|
||||
lockContext.currentText = ""
|
||||
lockContext.currentText = "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,21 +130,20 @@ Loader {
|
||||
smooth: false
|
||||
|
||||
onPaint: {
|
||||
const ctx = getContext("2d")
|
||||
const ctx = getContext("2d");
|
||||
if (!ctx)
|
||||
return
|
||||
return;
|
||||
ctx.reset();
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
ctx.reset()
|
||||
ctx.clearRect(0, 0, width, height)
|
||||
ctx.fillStyle = parent.cornerColor;
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
ctx.fillStyle = parent.cornerColor
|
||||
ctx.fillRect(0, 0, width, height)
|
||||
|
||||
ctx.globalCompositeOperation = "destination-out"
|
||||
ctx.fillStyle = "#ffffff"
|
||||
ctx.beginPath()
|
||||
ctx.arc(width, height, parent.cornerRadius, 0, 2 * Math.PI)
|
||||
ctx.fill()
|
||||
ctx.globalCompositeOperation = "destination-out";
|
||||
ctx.fillStyle = "#ffffff";
|
||||
ctx.beginPath();
|
||||
ctx.arc(width, height, parent.cornerRadius, 0, 2 * Math.PI);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
onWidthChanged: if (available)
|
||||
@@ -164,21 +163,20 @@ Loader {
|
||||
smooth: true
|
||||
|
||||
onPaint: {
|
||||
const ctx = getContext("2d")
|
||||
const ctx = getContext("2d");
|
||||
if (!ctx)
|
||||
return
|
||||
return;
|
||||
ctx.reset();
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
ctx.reset()
|
||||
ctx.clearRect(0, 0, width, height)
|
||||
ctx.fillStyle = parent.cornerColor;
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
ctx.fillStyle = parent.cornerColor
|
||||
ctx.fillRect(0, 0, width, height)
|
||||
|
||||
ctx.globalCompositeOperation = "destination-out"
|
||||
ctx.fillStyle = "#ffffff"
|
||||
ctx.beginPath()
|
||||
ctx.arc(0, height, parent.cornerRadius, 0, 2 * Math.PI)
|
||||
ctx.fill()
|
||||
ctx.globalCompositeOperation = "destination-out";
|
||||
ctx.fillStyle = "#ffffff";
|
||||
ctx.beginPath();
|
||||
ctx.arc(0, height, parent.cornerRadius, 0, 2 * Math.PI);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
onWidthChanged: if (available)
|
||||
@@ -198,21 +196,20 @@ Loader {
|
||||
smooth: true
|
||||
|
||||
onPaint: {
|
||||
const ctx = getContext("2d")
|
||||
const ctx = getContext("2d");
|
||||
if (!ctx)
|
||||
return
|
||||
return;
|
||||
ctx.reset();
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
ctx.reset()
|
||||
ctx.clearRect(0, 0, width, height)
|
||||
ctx.fillStyle = parent.cornerColor;
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
ctx.fillStyle = parent.cornerColor
|
||||
ctx.fillRect(0, 0, width, height)
|
||||
|
||||
ctx.globalCompositeOperation = "destination-out"
|
||||
ctx.fillStyle = "#ffffff"
|
||||
ctx.beginPath()
|
||||
ctx.arc(width, 0, parent.cornerRadius, 0, 2 * Math.PI)
|
||||
ctx.fill()
|
||||
ctx.globalCompositeOperation = "destination-out";
|
||||
ctx.fillStyle = "#ffffff";
|
||||
ctx.beginPath();
|
||||
ctx.arc(width, 0, parent.cornerRadius, 0, 2 * Math.PI);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
onWidthChanged: if (available)
|
||||
@@ -232,21 +229,20 @@ Loader {
|
||||
smooth: true
|
||||
|
||||
onPaint: {
|
||||
const ctx = getContext("2d")
|
||||
const ctx = getContext("2d");
|
||||
if (!ctx)
|
||||
return
|
||||
return;
|
||||
ctx.reset();
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
ctx.reset()
|
||||
ctx.clearRect(0, 0, width, height)
|
||||
ctx.fillStyle = parent.cornerColor;
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
ctx.fillStyle = parent.cornerColor
|
||||
ctx.fillRect(0, 0, width, height)
|
||||
|
||||
ctx.globalCompositeOperation = "destination-out"
|
||||
ctx.fillStyle = "#ffffff"
|
||||
ctx.beginPath()
|
||||
ctx.arc(0, 0, parent.cornerRadius, 0, 2 * Math.PI)
|
||||
ctx.fill()
|
||||
ctx.globalCompositeOperation = "destination-out";
|
||||
ctx.fillStyle = "#ffffff";
|
||||
ctx.beginPath();
|
||||
ctx.arc(0, 0, parent.cornerRadius, 0, 2 * Math.PI);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
onWidthChanged: if (available)
|
||||
@@ -347,7 +343,7 @@ Loader {
|
||||
// Date below
|
||||
NText {
|
||||
text: {
|
||||
var lang = I18n.locale.name.split("_")[0]
|
||||
var lang = I18n.locale.name.split("_")[0];
|
||||
var formats = {
|
||||
"de": "dddd, d. MMMM",
|
||||
"es": "dddd, d 'de' MMMM",
|
||||
@@ -356,8 +352,8 @@ Loader {
|
||||
"zh": "yyyy年M月d日 dddd",
|
||||
"uk": "dddd, d MMMM",
|
||||
"tr": "dddd, d MMMM"
|
||||
}
|
||||
return I18n.locale.toString(Time.now, formats[lang] || "dddd, MMMM d")
|
||||
};
|
||||
return I18n.locale.toString(Time.now, formats[lang] || "dddd, MMMM d");
|
||||
}
|
||||
pointSize: Style.fontSizeXL
|
||||
font.weight: Font.Medium
|
||||
@@ -487,15 +483,15 @@ Loader {
|
||||
// Compact status indicators container (compact mode only)
|
||||
Rectangle {
|
||||
width: {
|
||||
var hasBattery = UPower.displayDevice && UPower.displayDevice.ready && UPower.displayDevice.isPresent
|
||||
var hasKeyboard = keyboardLayout.currentLayout !== "Unknown"
|
||||
var hasBattery = UPower.displayDevice && UPower.displayDevice.ready && UPower.displayDevice.isPresent;
|
||||
var hasKeyboard = keyboardLayout.currentLayout !== "Unknown";
|
||||
|
||||
if (hasBattery && hasKeyboard) {
|
||||
return 200
|
||||
return 200;
|
||||
} else if (hasBattery || hasKeyboard) {
|
||||
return 120
|
||||
return 120;
|
||||
} else {
|
||||
return 0
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
height: 40
|
||||
@@ -708,14 +704,14 @@ Loader {
|
||||
|
||||
NText {
|
||||
text: {
|
||||
var temp = LocationService.data.weather.current_weather.temperature
|
||||
var suffix = "C"
|
||||
var temp = LocationService.data.weather.current_weather.temperature;
|
||||
var suffix = "C";
|
||||
if (Settings.data.location.useFahrenheit) {
|
||||
temp = LocationService.celsiusToFahrenheit(temp)
|
||||
suffix = "F"
|
||||
temp = LocationService.celsiusToFahrenheit(temp);
|
||||
suffix = "F";
|
||||
}
|
||||
temp = Math.round(temp)
|
||||
return temp + "°" + suffix
|
||||
temp = Math.round(temp);
|
||||
return temp + "°" + suffix;
|
||||
}
|
||||
pointSize: Style.fontSizeXL
|
||||
font.weight: Style.fontWeightBold
|
||||
@@ -724,14 +720,14 @@ Loader {
|
||||
|
||||
NText {
|
||||
text: {
|
||||
var wind = LocationService.data.weather.current_weather.windspeed
|
||||
var unit = "km/h"
|
||||
var wind = LocationService.data.weather.current_weather.windspeed;
|
||||
var unit = "km/h";
|
||||
if (Settings.data.location.useFahrenheit) {
|
||||
wind = wind * 0.621371 // Convert km/h to mph
|
||||
unit = "mph"
|
||||
wind = wind * 0.621371; // Convert km/h to mph
|
||||
unit = "mph";
|
||||
}
|
||||
wind = Math.round(wind)
|
||||
return wind + " " + unit
|
||||
wind = Math.round(wind);
|
||||
return wind + " " + unit;
|
||||
}
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
@@ -773,8 +769,8 @@ Loader {
|
||||
|
||||
NText {
|
||||
text: {
|
||||
var weatherDate = new Date(LocationService.data.weather.daily.time[index].replace(/-/g, "/"))
|
||||
return I18n.locale.toString(weatherDate, "ddd")
|
||||
var weatherDate = new Date(LocationService.data.weather.daily.time[index].replace(/-/g, "/"));
|
||||
return I18n.locale.toString(weatherDate, "ddd");
|
||||
}
|
||||
pointSize: Style.fontSizeM
|
||||
color: Color.mOnSurfaceVariant
|
||||
@@ -791,15 +787,15 @@ Loader {
|
||||
|
||||
NText {
|
||||
text: {
|
||||
var max = LocationService.data.weather.daily.temperature_2m_max[index]
|
||||
var min = LocationService.data.weather.daily.temperature_2m_min[index]
|
||||
var max = LocationService.data.weather.daily.temperature_2m_max[index];
|
||||
var min = LocationService.data.weather.daily.temperature_2m_min[index];
|
||||
if (Settings.data.location.useFahrenheit) {
|
||||
max = LocationService.celsiusToFahrenheit(max)
|
||||
min = LocationService.celsiusToFahrenheit(min)
|
||||
max = LocationService.celsiusToFahrenheit(max);
|
||||
min = LocationService.celsiusToFahrenheit(min);
|
||||
}
|
||||
max = Math.round(max)
|
||||
min = Math.round(min)
|
||||
return max + "°/" + min + "°"
|
||||
max = Math.round(max);
|
||||
min = Math.round(min);
|
||||
return max + "°/" + min + "°";
|
||||
}
|
||||
pointSize: Style.fontSizeM
|
||||
font.weight: Style.fontWeightMedium
|
||||
@@ -916,7 +912,7 @@ Loader {
|
||||
|
||||
Keys.onPressed: function (event) {
|
||||
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
||||
lockContext.tryUnlock()
|
||||
lockContext.tryUnlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ import Quickshell
|
||||
import Quickshell.Wayland
|
||||
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import qs.Modules.MainScreen
|
||||
import qs.Services.UI
|
||||
|
||||
// ------------------------------
|
||||
// MainScreen for each screen (manages bar + all panels)
|
||||
@@ -16,14 +16,25 @@ Variants {
|
||||
|
||||
property bool shouldBeActive: {
|
||||
if (!modelData || !modelData.name) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
Logger.d("Shell", "MainScreen activated for", modelData?.name)
|
||||
return true
|
||||
|
||||
let shouldLoad = true;
|
||||
if (!Settings.data.general.allowPanelsOnScreenWithoutBar) {
|
||||
// Check if bar is configured for this screen
|
||||
var monitors = Settings.data.bar.monitors || [];
|
||||
shouldLoad = monitors.length === 0 || monitors.includes(modelData?.name);
|
||||
}
|
||||
|
||||
if (shouldLoad) {
|
||||
Logger.d("AllScreens", "Screen activated: ", modelData?.name);
|
||||
}
|
||||
return shouldLoad;
|
||||
}
|
||||
|
||||
property bool windowLoaded: false
|
||||
|
||||
// Main Screen loader - Bar and panels backgrounds
|
||||
Loader {
|
||||
id: windowLoader
|
||||
active: parent.shouldBeActive
|
||||
@@ -33,7 +44,7 @@ Variants {
|
||||
|
||||
onLoaded: {
|
||||
// Signal that window is loaded so exclusion zone can be created
|
||||
parent.windowLoaded = true
|
||||
parent.windowLoaded = true;
|
||||
}
|
||||
|
||||
sourceComponent: MainScreen {
|
||||
@@ -45,11 +56,11 @@ Variants {
|
||||
Loader {
|
||||
active: {
|
||||
if (!parent.windowLoaded || !parent.shouldBeActive || !BarService.isVisible)
|
||||
return false
|
||||
return false;
|
||||
|
||||
// Check if bar is configured for this screen
|
||||
var monitors = Settings.data.bar.monitors || []
|
||||
return monitors.length === 0 || monitors.includes(modelData?.name)
|
||||
var monitors = Settings.data.bar.monitors || [];
|
||||
return monitors.length === 0 || monitors.includes(modelData?.name);
|
||||
}
|
||||
asynchronous: false
|
||||
|
||||
@@ -58,7 +69,7 @@ Variants {
|
||||
}
|
||||
|
||||
onLoaded: {
|
||||
Logger.d("Shell", "BarContentWindow created for", modelData?.name)
|
||||
Logger.d("AllScreens", "BarContentWindow created for", modelData?.name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,11 +78,11 @@ Variants {
|
||||
Loader {
|
||||
active: {
|
||||
if (!parent.windowLoaded || !parent.shouldBeActive || !BarService.isVisible)
|
||||
return false
|
||||
return false;
|
||||
|
||||
// Check if bar is configured for this screen
|
||||
var monitors = Settings.data.bar.monitors || []
|
||||
return monitors.length === 0 || monitors.includes(modelData?.name)
|
||||
var monitors = Settings.data.bar.monitors || [];
|
||||
return monitors.length === 0 || monitors.includes(modelData?.name);
|
||||
}
|
||||
asynchronous: false
|
||||
|
||||
@@ -80,14 +91,21 @@ Variants {
|
||||
}
|
||||
|
||||
onLoaded: {
|
||||
Logger.d("Shell", "BarExclusionZone created for", modelData?.name)
|
||||
Logger.d("AllScreens", "BarExclusionZone created for", modelData?.name);
|
||||
}
|
||||
}
|
||||
|
||||
// TrayMenuWindow - separate window for tray context menus
|
||||
// This must be a top-level PanelWindow.
|
||||
// Disabled when bar is hidden or not configured for this screen
|
||||
Loader {
|
||||
active: parent.windowLoaded && parent.shouldBeActive
|
||||
active: {
|
||||
if (!parent.windowLoaded || !parent.shouldBeActive || !BarService.isVisible)
|
||||
return false;
|
||||
|
||||
// Check if bar is configured for this screen
|
||||
var monitors = Settings.data.bar.monitors || [];
|
||||
return monitors.length === 0 || monitors.includes(modelData?.name);
|
||||
}
|
||||
asynchronous: false
|
||||
|
||||
sourceComponent: TrayMenuWindow {
|
||||
@@ -95,7 +113,7 @@ Variants {
|
||||
}
|
||||
|
||||
onLoaded: {
|
||||
Logger.d("Shell", "TrayMenuWindow created for", modelData?.name)
|
||||
Logger.d("AllScreens", "TrayMenuWindow created for", modelData?.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,17 +3,16 @@ import QtQuick.Shapes
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
|
||||
/**
|
||||
* AllBackgrounds - Unified Shape container for all bar and panel backgrounds
|
||||
*
|
||||
* Unified shadow system. This component contains a single Shape
|
||||
* with multiple ShapePath children (one for bar, one for each panel type).
|
||||
*
|
||||
* Benefits:
|
||||
* - Single GPU-accelerated rendering pass for all backgrounds
|
||||
* - Unified shadow system (one MultiEffect for everything)
|
||||
*/
|
||||
* AllBackgrounds - Unified Shape container for all bar and panel backgrounds
|
||||
*
|
||||
* Unified shadow system. This component contains a single Shape
|
||||
* with multiple ShapePath children (one for bar, one for each panel type).
|
||||
*
|
||||
* Benefits:
|
||||
* - Single GPU-accelerated rendering pass for all backgrounds
|
||||
* - Unified shadow system (one MultiEffect for everything)
|
||||
*/
|
||||
Item {
|
||||
id: root
|
||||
|
||||
@@ -46,15 +45,12 @@ Item {
|
||||
enabled: false // Disable mouse input on the Shape itself
|
||||
|
||||
Component.onCompleted: {
|
||||
Logger.d("AllBackgrounds", "AllBackgrounds initialized")
|
||||
Logger.d("AllBackgrounds", " bar:", root.bar)
|
||||
Logger.d("AllBackgrounds", " windowRoot:", root.windowRoot)
|
||||
Logger.d("AllBackgrounds", "AllBackgrounds initialized");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Bar
|
||||
*/
|
||||
* Bar
|
||||
*/
|
||||
BarBackground {
|
||||
bar: root.bar
|
||||
shapeContainer: backgroundsShape
|
||||
@@ -62,10 +58,9 @@ Item {
|
||||
backgroundColor: Qt.alpha(Color.mSurface, Settings.data.bar.backgroundOpacity)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Panels
|
||||
*/
|
||||
* Panels
|
||||
*/
|
||||
|
||||
// Audio
|
||||
PanelBackground {
|
||||
@@ -74,13 +69,6 @@ Item {
|
||||
backgroundColor: panelBackgroundColor
|
||||
}
|
||||
|
||||
// Battery
|
||||
PanelBackground {
|
||||
panel: root.windowRoot.batteryPanelPlaceholder
|
||||
shapeContainer: backgroundsShape
|
||||
backgroundColor: panelBackgroundColor
|
||||
}
|
||||
|
||||
// Bluetooth
|
||||
PanelBackground {
|
||||
panel: root.windowRoot.bluetoothPanelPlaceholder
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
import QtQuick
|
||||
import QtQuick.Shapes
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import qs.Modules.MainScreen.Backgrounds
|
||||
|
||||
import qs.Services.UI
|
||||
|
||||
/**
|
||||
* BarBackground - ShapePath component for rendering the bar background
|
||||
*
|
||||
* Unified shadow system. This component is a ShapePath that will be
|
||||
* a child of the unified AllBackgrounds Shape container.
|
||||
*
|
||||
* Uses 4-state per-corner system for flexible corner rendering:
|
||||
* - State -1: No radius (flat/square corner)
|
||||
* - State 0: Normal (inner curve)
|
||||
* - State 1: Horizontal inversion (outer curve on X-axis)
|
||||
* - State 2: Vertical inversion (outer curve on Y-axis)
|
||||
*/
|
||||
* BarBackground - ShapePath component for rendering the bar background
|
||||
*
|
||||
* Unified shadow system. This component is a ShapePath that will be
|
||||
* a child of the unified AllBackgrounds Shape container.
|
||||
*
|
||||
* Uses 4-state per-corner system for flexible corner rendering:
|
||||
* - State -1: No radius (flat/square corner)
|
||||
* - State 0: Normal (inner curve)
|
||||
* - State 1: Horizontal inversion (outer curve on X-axis)
|
||||
* - State 2: Vertical inversion (outer curve on Y-axis)
|
||||
*/
|
||||
ShapePath {
|
||||
id: root
|
||||
|
||||
@@ -35,15 +34,15 @@ ShapePath {
|
||||
readonly property bool shouldShow: {
|
||||
// Check global bar visibility
|
||||
if (!BarService.isVisible)
|
||||
return false
|
||||
return false;
|
||||
|
||||
// Check screen-specific configuration
|
||||
var monitors = Settings.data.bar.monitors || []
|
||||
var screenName = windowRoot?.screen?.name || ""
|
||||
var monitors = Settings.data.bar.monitors || [];
|
||||
var screenName = windowRoot?.screen?.name || "";
|
||||
|
||||
// If no monitors specified, show on all screens
|
||||
// If monitors specified, only show if this screen is in the list
|
||||
return monitors.length === 0 || monitors.includes(screenName)
|
||||
return monitors.length === 0 || monitors.includes(screenName);
|
||||
}
|
||||
|
||||
// Corner radius (from Style)
|
||||
@@ -65,9 +64,9 @@ ShapePath {
|
||||
function getCornerRadius(cornerState) {
|
||||
// State -1 = no radius (flat corner)
|
||||
if (cornerState === -1)
|
||||
return 0
|
||||
return 0;
|
||||
// All other states use effectiveRadius
|
||||
return effectiveRadius
|
||||
return effectiveRadius;
|
||||
}
|
||||
|
||||
// Per-corner multipliers and radii based on bar's corner states (handle null bar)
|
||||
|
||||
@@ -3,22 +3,21 @@ import QtQuick.Shapes
|
||||
import qs.Commons
|
||||
import qs.Modules.MainScreen.Backgrounds
|
||||
|
||||
|
||||
/**
|
||||
* PanelBackground - ShapePath component for rendering a single background
|
||||
*
|
||||
* Unified shadow system. This component is a ShapePath that will
|
||||
* be a child of the unified AllBackgrounds Shape container.
|
||||
*
|
||||
* Reads positioning and geometry from PanelPlaceholder (via panel.panelItem).
|
||||
* The actual panel content lives in a separate SmartPanelWindow.
|
||||
*
|
||||
* Uses 4-state per-corner system for flexible corner rendering:
|
||||
* - State -1: No radius (flat/square corner)
|
||||
* - State 0: Normal (inner curve)
|
||||
* - State 1: Horizontal inversion (outer curve on X-axis)
|
||||
* - State 2: Vertical inversion (outer curve on Y-axis)
|
||||
*/
|
||||
* PanelBackground - ShapePath component for rendering a single background
|
||||
*
|
||||
* Unified shadow system. This component is a ShapePath that will
|
||||
* be a child of the unified AllBackgrounds Shape container.
|
||||
*
|
||||
* Reads positioning and geometry from PanelPlaceholder (via panel.panelItem).
|
||||
* The actual panel content lives in a separate SmartPanelWindow.
|
||||
*
|
||||
* Uses 4-state per-corner system for flexible corner rendering:
|
||||
* - State -1: No radius (flat/square corner)
|
||||
* - State 0: Normal (inner curve)
|
||||
* - State 1: Horizontal inversion (outer curve on X-axis)
|
||||
* - State 2: Vertical inversion (outer curve on Y-axis)
|
||||
*/
|
||||
ShapePath {
|
||||
id: root
|
||||
|
||||
@@ -51,9 +50,9 @@ ShapePath {
|
||||
function getCornerRadius(cornerState) {
|
||||
// State -1 = no radius (flat corner)
|
||||
if (cornerState === -1)
|
||||
return 0
|
||||
return 0;
|
||||
// All other states use effectiveRadius
|
||||
return effectiveRadius
|
||||
return effectiveRadius;
|
||||
}
|
||||
|
||||
// Per-corner multipliers and radii based on panelBg's corner states
|
||||
|
||||
@@ -4,78 +4,71 @@ import QtQuick
|
||||
import QtQuick.Shapes
|
||||
import Quickshell
|
||||
|
||||
|
||||
/**
|
||||
* ShapeCornerHelper - Utility singleton for shape corner calculations
|
||||
*
|
||||
* Uses 4-state per-corner system for flexible corner rendering:
|
||||
* - State -1: No radius (flat/square corner)
|
||||
* - State 0: Normal (inner curve)
|
||||
* - State 1: Horizontal inversion (outer curve on X-axis)
|
||||
* - State 2: Vertical inversion (outer curve on Y-axis)
|
||||
*
|
||||
* The key technique: Using PathArc direction control (Clockwise vs Counterclockwise)
|
||||
* combined with multipliers to create both inner and outer corner curves.
|
||||
*/
|
||||
* ShapeCornerHelper - Utility singleton for shape corner calculations
|
||||
*
|
||||
* Uses 4-state per-corner system for flexible corner rendering:
|
||||
* - State -1: No radius (flat/square corner)
|
||||
* - State 0: Normal (inner curve)
|
||||
* - State 1: Horizontal inversion (outer curve on X-axis)
|
||||
* - State 2: Vertical inversion (outer curve on Y-axis)
|
||||
*
|
||||
* The key technique: Using PathArc direction control (Clockwise vs Counterclockwise)
|
||||
* combined with multipliers to create both inner and outer corner curves.
|
||||
*/
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
|
||||
/**
|
||||
* Get X-axis multiplier for a corner state
|
||||
* State 1 (horizontal invert) returns -1, others return 1
|
||||
*/
|
||||
* Get X-axis multiplier for a corner state
|
||||
* State 1 (horizontal invert) returns -1, others return 1
|
||||
*/
|
||||
function getMultX(cornerState) {
|
||||
return cornerState === 1 ? -1 : 1
|
||||
return cornerState === 1 ? -1 : 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get Y-axis multiplier for a corner state
|
||||
* State 2 (vertical invert) returns -1, others return 1
|
||||
*/
|
||||
* Get Y-axis multiplier for a corner state
|
||||
* State 2 (vertical invert) returns -1, others return 1
|
||||
*/
|
||||
function getMultY(cornerState) {
|
||||
return cornerState === 2 ? -1 : 1
|
||||
return cornerState === 2 ? -1 : 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get PathArc direction for a corner based on its multipliers
|
||||
* Uses XOR logic: if X inverted differs from Y inverted, use Counterclockwise
|
||||
* This creates the outer curve effect for inverted corners
|
||||
*/
|
||||
* Get PathArc direction for a corner based on its multipliers
|
||||
* Uses XOR logic: if X inverted differs from Y inverted, use Counterclockwise
|
||||
* This creates the outer curve effect for inverted corners
|
||||
*/
|
||||
function getArcDirection(multX, multY) {
|
||||
return ((multX < 0) !== (multY < 0)) ? PathArc.Counterclockwise : PathArc.Clockwise
|
||||
return ((multX < 0) !== (multY < 0)) ? PathArc.Counterclockwise : PathArc.Clockwise;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convenience function to get arc direction directly from corner state
|
||||
*/
|
||||
* Convenience function to get arc direction directly from corner state
|
||||
*/
|
||||
function getArcDirectionFromState(cornerState) {
|
||||
const multX = getMultX(cornerState)
|
||||
const multY = getMultY(cornerState)
|
||||
return getArcDirection(multX, multY)
|
||||
const multX = getMultX(cornerState);
|
||||
const multY = getMultY(cornerState);
|
||||
return getArcDirection(multX, multY);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the "flattening" radius when shape dimensions are too small
|
||||
* Prevents visual artifacts when radius exceeds dimensions
|
||||
*/
|
||||
* Get the "flattening" radius when shape dimensions are too small
|
||||
* Prevents visual artifacts when radius exceeds dimensions
|
||||
*/
|
||||
function getFlattenedRadius(dimension, requestedRadius) {
|
||||
if (dimension < requestedRadius * 2) {
|
||||
return dimension / 2
|
||||
return dimension / 2;
|
||||
}
|
||||
return requestedRadius
|
||||
return requestedRadius;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if a shape should use flattened corners
|
||||
* Returns true if width or height is too small for the requested radius
|
||||
*/
|
||||
* Check if a shape should use flattened corners
|
||||
* Returns true if width or height is too small for the requested radius
|
||||
*/
|
||||
function shouldFlatten(width, height, radius) {
|
||||
return width < radius * 2 || height < radius * 2
|
||||
return width < radius * 2 || height < radius * 2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,19 +2,18 @@ import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import qs.Modules.Bar
|
||||
|
||||
import qs.Services.UI
|
||||
|
||||
/**
|
||||
* BarContentWindow - Separate transparent PanelWindow for bar content
|
||||
*
|
||||
* This window contains only the bar widgets (content), while the background
|
||||
* is rendered in MainScreen's unified Shape system. This separation prevents
|
||||
* fullscreen redraws when bar widgets redraw.
|
||||
*
|
||||
* This component should be instantiated once per screen by AllScreens.qml
|
||||
*/
|
||||
* BarContentWindow - Separate transparent PanelWindow for bar content
|
||||
*
|
||||
* This window contains only the bar widgets (content), while the background
|
||||
* is rendered in MainScreen's unified Shape system. This separation prevents
|
||||
* fullscreen redraws when bar widgets redraw.
|
||||
*
|
||||
* This component should be instantiated once per screen by AllScreens.qml
|
||||
*/
|
||||
PanelWindow {
|
||||
id: barWindow
|
||||
|
||||
@@ -22,7 +21,7 @@ PanelWindow {
|
||||
color: Color.transparent // Transparent - background is in MainScreen below
|
||||
|
||||
Component.onCompleted: {
|
||||
Logger.d("BarContentWindow", "Bar content window created for screen:", barWindow.screen?.name)
|
||||
Logger.d("BarContentWindow", "Bar content window created for screen:", barWindow.screen?.name);
|
||||
}
|
||||
|
||||
// Wayland layer configuration
|
||||
|
||||
@@ -3,13 +3,12 @@ import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
|
||||
|
||||
/**
|
||||
* BarExclusionZone - Invisible PanelWindow that reserves exclusive space for the bar
|
||||
*
|
||||
* This is a minimal window that works with the compositor to reserve space,
|
||||
* while the actual bar UI is rendered in MainScreen.
|
||||
*/
|
||||
* BarExclusionZone - Invisible PanelWindow that reserves exclusive space for the bar
|
||||
*
|
||||
* This is a minimal window that works with the compositor to reserve space,
|
||||
* while the actual bar UI is rendered in MainScreen.
|
||||
*/
|
||||
PanelWindow {
|
||||
id: root
|
||||
|
||||
@@ -46,11 +45,11 @@ PanelWindow {
|
||||
// Vertical bar: reserve bar height + margin on the anchored edge only
|
||||
if (barFloating) {
|
||||
// For left bar, reserve left margin; for right bar, reserve right margin
|
||||
return Style.barHeight + barMarginH
|
||||
return Style.barHeight + barMarginH;
|
||||
}
|
||||
return Style.barHeight
|
||||
return Style.barHeight;
|
||||
}
|
||||
return 0 // Auto-width when left/right anchors are true
|
||||
return 0; // Auto-width when left/right anchors are true
|
||||
}
|
||||
|
||||
implicitHeight: {
|
||||
@@ -58,17 +57,14 @@ PanelWindow {
|
||||
// Horizontal bar: reserve bar height + margin on the anchored edge only
|
||||
if (barFloating) {
|
||||
// For top bar, reserve top margin; for bottom bar, reserve bottom margin
|
||||
return Style.barHeight + barMarginV
|
||||
return Style.barHeight + barMarginV;
|
||||
}
|
||||
return Style.barHeight
|
||||
return Style.barHeight;
|
||||
}
|
||||
return 0 // Auto-height when top/bottom anchors are true
|
||||
return 0; // Auto-height when top/bottom anchors are true
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
Logger.d("BarExclusionZone", "Created for screen:", screen?.name)
|
||||
Logger.d("BarExclusionZone", " Position:", barPosition, "Exclusive:", exclusive, "Floating:", barFloating)
|
||||
Logger.d("BarExclusionZone", " Anchors - top:", anchors.top, "bottom:", anchors.bottom, "left:", anchors.left, "right:", anchors.right)
|
||||
Logger.d("BarExclusionZone", " Size:", width, "x", height, "implicitWidth:", implicitWidth, "implicitHeight:", implicitHeight)
|
||||
Logger.d("BarExclusionZone", "Created for screen:", screen?.name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,16 +2,14 @@ import QtQuick
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import "Backgrounds" as Backgrounds
|
||||
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import "Backgrounds" as Backgrounds
|
||||
|
||||
// All panels
|
||||
import qs.Modules.Bar
|
||||
import qs.Modules.Bar.Extras
|
||||
import qs.Modules.Panels.Audio
|
||||
import qs.Modules.Panels.Battery
|
||||
import qs.Modules.Panels.Bluetooth
|
||||
import qs.Modules.Panels.Calendar
|
||||
import qs.Modules.Panels.ControlCenter
|
||||
@@ -23,17 +21,16 @@ import qs.Modules.Panels.SetupWizard
|
||||
import qs.Modules.Panels.Tray
|
||||
import qs.Modules.Panels.Wallpaper
|
||||
import qs.Modules.Panels.WiFi
|
||||
|
||||
import qs.Services.UI
|
||||
|
||||
/**
|
||||
* MainScreen - Single PanelWindow per screen that manages all panels and the bar
|
||||
*/
|
||||
* MainScreen - Single PanelWindow per screen that manages all panels and the bar
|
||||
*/
|
||||
PanelWindow {
|
||||
id: root
|
||||
|
||||
// Expose panels as readonly property aliases
|
||||
readonly property alias audioPanel: audioPanel
|
||||
readonly property alias batteryPanel: batteryPanel
|
||||
readonly property alias bluetoothPanel: bluetoothPanel
|
||||
readonly property alias calendarPanel: calendarPanel
|
||||
readonly property alias controlCenterPanel: controlCenterPanel
|
||||
@@ -48,7 +45,6 @@ PanelWindow {
|
||||
|
||||
// Expose panel placeholders for AllBackgrounds
|
||||
readonly property var audioPanelPlaceholder: audioPanel.panelPlaceholder
|
||||
readonly property var batteryPanelPlaceholder: batteryPanel.panelPlaceholder
|
||||
readonly property var bluetoothPanelPlaceholder: bluetoothPanel.panelPlaceholder
|
||||
readonly property var calendarPanelPlaceholder: calendarPanel.panelPlaceholder
|
||||
readonly property var controlCenterPanelPlaceholder: controlCenterPanel.panelPlaceholder
|
||||
@@ -62,7 +58,7 @@ PanelWindow {
|
||||
readonly property var wifiPanelPlaceholder: wifiPanel.panelPlaceholder
|
||||
|
||||
Component.onCompleted: {
|
||||
Logger.d("MainScreen", "Initialized for screen:", screen?.name, "- Dimensions:", screen?.width, "x", screen?.height, "- Position:", screen?.x, ",", screen?.y)
|
||||
Logger.d("MainScreen", "Initialized for screen:", screen?.name, "- Dimensions:", screen?.width, "x", screen?.height, "- Position:", screen?.x, ",", screen?.y);
|
||||
}
|
||||
|
||||
// Wayland
|
||||
@@ -85,9 +81,9 @@ PanelWindow {
|
||||
|
||||
color: {
|
||||
if (dimmerOpacity > 0 && isPanelOpen && !isPanelClosing) {
|
||||
return Qt.alpha(Color.mShadow, dimmerOpacity)
|
||||
return Qt.alpha(Color.mShadow, dimmerOpacity);
|
||||
}
|
||||
return Color.transparent
|
||||
return Color.transparent;
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
@@ -101,15 +97,15 @@ PanelWindow {
|
||||
readonly property bool barShouldShow: {
|
||||
// Check global bar visibility
|
||||
if (!BarService.isVisible)
|
||||
return false
|
||||
return false;
|
||||
|
||||
// Check screen-specific configuration
|
||||
var monitors = Settings.data.bar.monitors || []
|
||||
var screenName = screen?.name || ""
|
||||
var monitors = Settings.data.bar.monitors || [];
|
||||
var screenName = screen?.name || "";
|
||||
|
||||
// If no monitors specified, show on all screens
|
||||
// If monitors specified, only show if this screen is in the list
|
||||
return monitors.length === 0 || monitors.includes(screenName)
|
||||
return monitors.length === 0 || monitors.includes(screenName);
|
||||
}
|
||||
|
||||
// Make everything click-through except bar
|
||||
@@ -162,145 +158,74 @@ PanelWindow {
|
||||
// ---------------------------------------
|
||||
AudioPanel {
|
||||
id: audioPanel
|
||||
objectName: "audioPanel-" + (root.screen?.name || "unknown")
|
||||
screen: root.screen
|
||||
z: 50
|
||||
|
||||
Component.onCompleted: {
|
||||
objectName = "audioPanel-" + (screen?.name || "unknown")
|
||||
PanelService.registerPanel(audioPanel)
|
||||
}
|
||||
}
|
||||
|
||||
BatteryPanel {
|
||||
id: batteryPanel
|
||||
screen: root.screen
|
||||
z: 50
|
||||
|
||||
Component.onCompleted: {
|
||||
objectName = "batteryPanel-" + (screen?.name || "unknown")
|
||||
PanelService.registerPanel(batteryPanel)
|
||||
}
|
||||
}
|
||||
|
||||
BluetoothPanel {
|
||||
id: bluetoothPanel
|
||||
objectName: "bluetoothPanel-" + (root.screen?.name || "unknown")
|
||||
screen: root.screen
|
||||
z: 50
|
||||
|
||||
Component.onCompleted: {
|
||||
objectName = "bluetoothPanel-" + (screen?.name || "unknown")
|
||||
PanelService.registerPanel(bluetoothPanel)
|
||||
}
|
||||
}
|
||||
|
||||
ControlCenterPanel {
|
||||
id: controlCenterPanel
|
||||
objectName: "controlCenterPanel-" + (root.screen?.name || "unknown")
|
||||
screen: root.screen
|
||||
z: 50
|
||||
|
||||
Component.onCompleted: {
|
||||
objectName = "controlCenterPanel-" + (screen?.name || "unknown")
|
||||
PanelService.registerPanel(controlCenterPanel)
|
||||
}
|
||||
}
|
||||
|
||||
CalendarPanel {
|
||||
id: calendarPanel
|
||||
objectName: "calendarPanel-" + (root.screen?.name || "unknown")
|
||||
screen: root.screen
|
||||
z: 50
|
||||
|
||||
Component.onCompleted: {
|
||||
objectName = "calendarPanel-" + (screen?.name || "unknown")
|
||||
PanelService.registerPanel(calendarPanel)
|
||||
}
|
||||
}
|
||||
|
||||
Launcher {
|
||||
id: launcherPanel
|
||||
objectName: "launcherPanel-" + (root.screen?.name || "unknown")
|
||||
screen: root.screen
|
||||
z: 50
|
||||
|
||||
Component.onCompleted: {
|
||||
objectName = "launcherPanel-" + (screen?.name || "unknown")
|
||||
PanelService.registerPanel(launcherPanel)
|
||||
}
|
||||
}
|
||||
|
||||
NotificationHistoryPanel {
|
||||
id: notificationHistoryPanel
|
||||
objectName: "notificationHistoryPanel-" + (root.screen?.name || "unknown")
|
||||
screen: root.screen
|
||||
z: 50
|
||||
|
||||
Component.onCompleted: {
|
||||
objectName = "notificationHistoryPanel-" + (screen?.name || "unknown")
|
||||
PanelService.registerPanel(notificationHistoryPanel)
|
||||
}
|
||||
}
|
||||
|
||||
SessionMenu {
|
||||
id: sessionMenuPanel
|
||||
objectName: "sessionMenuPanel-" + (root.screen?.name || "unknown")
|
||||
screen: root.screen
|
||||
z: 50
|
||||
|
||||
Component.onCompleted: {
|
||||
objectName = "sessionMenuPanel-" + (screen?.name || "unknown")
|
||||
PanelService.registerPanel(sessionMenuPanel)
|
||||
}
|
||||
}
|
||||
|
||||
SettingsPanel {
|
||||
id: settingsPanel
|
||||
objectName: "settingsPanel-" + (root.screen?.name || "unknown")
|
||||
screen: root.screen
|
||||
z: 50
|
||||
|
||||
Component.onCompleted: {
|
||||
objectName = "settingsPanel-" + (screen?.name || "unknown")
|
||||
PanelService.registerPanel(settingsPanel)
|
||||
}
|
||||
}
|
||||
|
||||
SetupWizard {
|
||||
id: setupWizardPanel
|
||||
objectName: "setupWizardPanel-" + (root.screen?.name || "unknown")
|
||||
screen: root.screen
|
||||
z: 50
|
||||
|
||||
Component.onCompleted: {
|
||||
objectName = "setupWizardPanel-" + (screen?.name || "unknown")
|
||||
PanelService.registerPanel(setupWizardPanel)
|
||||
}
|
||||
}
|
||||
|
||||
TrayDrawerPanel {
|
||||
id: trayDrawerPanel
|
||||
objectName: "trayDrawerPanel-" + (root.screen?.name || "unknown")
|
||||
screen: root.screen
|
||||
z: 50
|
||||
|
||||
Component.onCompleted: {
|
||||
objectName = "trayDrawerPanel-" + (screen?.name || "unknown")
|
||||
PanelService.registerPanel(trayDrawerPanel)
|
||||
}
|
||||
}
|
||||
|
||||
WallpaperPanel {
|
||||
id: wallpaperPanel
|
||||
objectName: "wallpaperPanel-" + (root.screen?.name || "unknown")
|
||||
screen: root.screen
|
||||
z: 50
|
||||
|
||||
Component.onCompleted: {
|
||||
objectName = "wallpaperPanel-" + (screen?.name || "unknown")
|
||||
PanelService.registerPanel(wallpaperPanel)
|
||||
}
|
||||
}
|
||||
|
||||
WiFiPanel {
|
||||
id: wifiPanel
|
||||
objectName: "wifiPanel-" + (root.screen?.name || "unknown")
|
||||
screen: root.screen
|
||||
z: 50
|
||||
|
||||
Component.onCompleted: {
|
||||
objectName = "wifiPanel-" + (screen?.name || "unknown")
|
||||
PanelService.registerPanel(wifiPanel)
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------
|
||||
@@ -326,94 +251,84 @@ PanelWindow {
|
||||
// Use screen dimensions directly
|
||||
x: {
|
||||
if (barPosition === "right")
|
||||
return screen.width - Style.barHeight - barMarginH - attachmentOverlap // Extend left towards panels
|
||||
return barMarginH
|
||||
return screen.width - Style.barHeight - barMarginH - attachmentOverlap; // Extend left towards panels
|
||||
return barMarginH;
|
||||
}
|
||||
y: {
|
||||
if (barPosition === "bottom")
|
||||
return screen.height - Style.barHeight - barMarginV - attachmentOverlap
|
||||
return barMarginV
|
||||
return screen.height - Style.barHeight - barMarginV - attachmentOverlap;
|
||||
return barMarginV;
|
||||
}
|
||||
width: {
|
||||
if (barIsVertical) {
|
||||
return Style.barHeight + attachmentOverlap
|
||||
return Style.barHeight + attachmentOverlap;
|
||||
}
|
||||
return screen.width - barMarginH * 2
|
||||
return screen.width - barMarginH * 2;
|
||||
}
|
||||
height: {
|
||||
if (barIsVertical) {
|
||||
return screen.height - barMarginV * 2
|
||||
return screen.height - barMarginV * 2;
|
||||
}
|
||||
return Style.barHeight + attachmentOverlap
|
||||
return Style.barHeight + attachmentOverlap;
|
||||
}
|
||||
|
||||
// Corner states (same as Bar.qml)
|
||||
readonly property int topLeftCornerState: {
|
||||
if (barFloating)
|
||||
return 0
|
||||
return 0;
|
||||
if (barPosition === "top")
|
||||
return -1
|
||||
return -1;
|
||||
if (barPosition === "left")
|
||||
return -1
|
||||
return -1;
|
||||
if (Settings.data.bar.outerCorners && (barPosition === "bottom" || barPosition === "right")) {
|
||||
return barIsVertical ? 1 : 2
|
||||
return barIsVertical ? 1 : 2;
|
||||
}
|
||||
return -1
|
||||
return -1;
|
||||
}
|
||||
|
||||
readonly property int topRightCornerState: {
|
||||
if (barFloating)
|
||||
return 0
|
||||
return 0;
|
||||
if (barPosition === "top")
|
||||
return -1
|
||||
return -1;
|
||||
if (barPosition === "right")
|
||||
return -1
|
||||
return -1;
|
||||
if (Settings.data.bar.outerCorners && (barPosition === "bottom" || barPosition === "left")) {
|
||||
return barIsVertical ? 1 : 2
|
||||
return barIsVertical ? 1 : 2;
|
||||
}
|
||||
return -1
|
||||
return -1;
|
||||
}
|
||||
|
||||
readonly property int bottomLeftCornerState: {
|
||||
if (barFloating)
|
||||
return 0
|
||||
return 0;
|
||||
if (barPosition === "bottom")
|
||||
return -1
|
||||
return -1;
|
||||
if (barPosition === "left")
|
||||
return -1
|
||||
return -1;
|
||||
if (Settings.data.bar.outerCorners && (barPosition === "top" || barPosition === "right")) {
|
||||
return barIsVertical ? 1 : 2
|
||||
return barIsVertical ? 1 : 2;
|
||||
}
|
||||
return -1
|
||||
return -1;
|
||||
}
|
||||
|
||||
readonly property int bottomRightCornerState: {
|
||||
if (barFloating)
|
||||
return 0
|
||||
return 0;
|
||||
if (barPosition === "bottom")
|
||||
return -1
|
||||
return -1;
|
||||
if (barPosition === "right")
|
||||
return -1
|
||||
return -1;
|
||||
if (Settings.data.bar.outerCorners && (barPosition === "top" || barPosition === "left")) {
|
||||
return barIsVertical ? 1 : 2
|
||||
return barIsVertical ? 1 : 2;
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
Logger.d("MainScreen", "===== Bar placeholder loaded =====")
|
||||
Logger.d("MainScreen", " Screen:", screen?.name, "Size:", screen?.width, "x", screen?.height)
|
||||
Logger.d("MainScreen", " Bar position:", barPosition, "| isVertical:", barIsVertical)
|
||||
Logger.d("MainScreen", " Bar dimensions: x=" + x, "y=" + y, "width=" + width, "height=" + height)
|
||||
Logger.d("MainScreen", " Style.barHeight =", Style.barHeight)
|
||||
Logger.d("MainScreen", " Margins: H=" + barMarginH, "V=" + barMarginV, "| Floating:", barFloating)
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Screen Corners
|
||||
*/
|
||||
* Screen Corners
|
||||
*/
|
||||
ScreenCorners {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,13 @@ import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
|
||||
|
||||
/**
|
||||
* PanelPlaceholder - Lightweight positioning logic for panel backgrounds
|
||||
*
|
||||
* This component stays in MainScreen and provides geometry for PanelBackground rendering.
|
||||
* It contains only positioning calculations and animations, no visual content.
|
||||
* The actual panel content lives in a separate SmartPanelWindow.
|
||||
*/
|
||||
* PanelPlaceholder - Lightweight positioning logic for panel backgrounds
|
||||
*
|
||||
* This component stays in MainScreen and provides geometry for PanelBackground rendering.
|
||||
* It contains only positioning calculations and animations, no visual content.
|
||||
* The actual panel content lives in a separate SmartPanelWindow.
|
||||
*/
|
||||
Item {
|
||||
id: root
|
||||
|
||||
@@ -71,13 +70,13 @@ Item {
|
||||
readonly property bool allowAttach: Settings.data.ui.panelsAttachedToBar || root.forceAttachToBar
|
||||
readonly property bool allowAttachToBar: {
|
||||
if (!(Settings.data.ui.panelsAttachedToBar || root.forceAttachToBar) || Settings.data.bar.backgroundOpacity < 1.0) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
// A panel can only be attached to a bar if there is a bar on that screen
|
||||
var monitors = Settings.data.bar.monitors || []
|
||||
var result = monitors.length === 0 || monitors.includes(root.screen?.name || "")
|
||||
return result
|
||||
var monitors = Settings.data.bar.monitors || [];
|
||||
var result = monitors.length === 0 || monitors.includes(root.screen?.name || "");
|
||||
return result;
|
||||
}
|
||||
|
||||
// Effective anchor properties (depend on allowAttach)
|
||||
@@ -97,17 +96,17 @@ Item {
|
||||
|
||||
function onUiScaleRatioChanged() {
|
||||
if (root.isPanelVisible) {
|
||||
root.setPosition()
|
||||
root.setPosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Public function to update content size from SmartPanelWindow
|
||||
function updateContentSize(w, h) {
|
||||
contentPreferredWidth = w
|
||||
contentPreferredHeight = h
|
||||
contentPreferredWidth = w;
|
||||
contentPreferredHeight = h;
|
||||
if (isPanelVisible) {
|
||||
setPosition()
|
||||
setPosition();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,45 +114,45 @@ Item {
|
||||
function setPosition() {
|
||||
// Don't calculate position if parent dimensions aren't available yet
|
||||
if (!root.width || !root.height) {
|
||||
Logger.d("PanelPlaceholder", "Skipping setPosition - dimensions not ready:", root.width, "x", root.height, panelName)
|
||||
Qt.callLater(setPosition)
|
||||
return
|
||||
Logger.d("PanelPlaceholder", "Skipping setPosition - dimensions not ready:", root.width, "x", root.height, panelName);
|
||||
Qt.callLater(setPosition);
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate panel dimensions first (needed for positioning)
|
||||
var w
|
||||
var w;
|
||||
// Priority 1: Content-driven size (dynamic)
|
||||
if (contentPreferredWidth > 0) {
|
||||
w = contentPreferredWidth
|
||||
w = contentPreferredWidth;
|
||||
} // Priority 2: Ratio-based size
|
||||
else if (root.preferredWidthRatio !== undefined) {
|
||||
w = Math.round(Math.max(root.width * root.preferredWidthRatio, root.preferredWidth))
|
||||
w = Math.round(Math.max(root.width * root.preferredWidthRatio, root.preferredWidth));
|
||||
} // Priority 3: Static preferred width
|
||||
else {
|
||||
w = root.preferredWidth
|
||||
w = root.preferredWidth;
|
||||
}
|
||||
var panelWidth = Math.min(w, root.width - Style.marginL * 2)
|
||||
var panelWidth = Math.min(w, root.width - Style.marginL * 2);
|
||||
|
||||
var h
|
||||
var h;
|
||||
// Priority 1: Content-driven size (dynamic)
|
||||
if (contentPreferredHeight > 0) {
|
||||
h = contentPreferredHeight
|
||||
h = contentPreferredHeight;
|
||||
} // Priority 2: Ratio-based size
|
||||
else if (root.preferredHeightRatio !== undefined) {
|
||||
h = Math.round(Math.max(root.height * root.preferredHeightRatio, root.preferredHeight))
|
||||
h = Math.round(Math.max(root.height * root.preferredHeightRatio, root.preferredHeight));
|
||||
} // Priority 3: Static preferred height
|
||||
else {
|
||||
h = root.preferredHeight
|
||||
h = root.preferredHeight;
|
||||
}
|
||||
var panelHeight = Math.min(h, root.height - Style.barHeight - Style.marginL * 2)
|
||||
var panelHeight = Math.min(h, root.height - Style.barHeight - Style.marginL * 2);
|
||||
|
||||
// Update panelBackground target size (will be animated)
|
||||
panelBackground.targetWidth = panelWidth
|
||||
panelBackground.targetHeight = panelHeight
|
||||
panelBackground.targetWidth = panelWidth;
|
||||
panelBackground.targetHeight = panelHeight;
|
||||
|
||||
// Calculate position
|
||||
var calculatedX
|
||||
var calculatedY
|
||||
var calculatedX;
|
||||
var calculatedY;
|
||||
|
||||
// ===== X POSITIONING =====
|
||||
if (root.useButtonPosition && root.width > 0 && panelWidth > 0) {
|
||||
@@ -162,111 +161,111 @@ Item {
|
||||
if (allowAttach) {
|
||||
// Attached panels: align with bar edge (left or right side)
|
||||
if (root.barPosition === "left") {
|
||||
var leftBarEdge = root.barMarginH + Style.barHeight
|
||||
calculatedX = leftBarEdge
|
||||
var leftBarEdge = root.barMarginH + Style.barHeight;
|
||||
calculatedX = leftBarEdge;
|
||||
} else {
|
||||
// right
|
||||
var rightBarEdge = root.width - root.barMarginH - Style.barHeight
|
||||
calculatedX = rightBarEdge - panelWidth
|
||||
var rightBarEdge = root.width - root.barMarginH - Style.barHeight;
|
||||
calculatedX = rightBarEdge - panelWidth;
|
||||
}
|
||||
} else {
|
||||
// Detached panels: center on button X position
|
||||
var panelX = root.buttonPosition.x + root.buttonWidth / 2 - panelWidth / 2
|
||||
var minX = Style.marginL
|
||||
var maxX = root.width - panelWidth - Style.marginL
|
||||
var panelX = root.buttonPosition.x + root.buttonWidth / 2 - panelWidth / 2;
|
||||
var minX = Style.marginL;
|
||||
var maxX = root.width - panelWidth - Style.marginL;
|
||||
|
||||
// Account for vertical bar taking up space
|
||||
if (root.barPosition === "left") {
|
||||
minX = root.barMarginH + Style.barHeight + Style.marginL
|
||||
minX = root.barMarginH + Style.barHeight + Style.marginL;
|
||||
} else if (root.barPosition === "right") {
|
||||
maxX = root.width - root.barMarginH - Style.barHeight - panelWidth - Style.marginL
|
||||
maxX = root.width - root.barMarginH - Style.barHeight - panelWidth - Style.marginL;
|
||||
}
|
||||
|
||||
panelX = Math.max(minX, Math.min(panelX, maxX))
|
||||
calculatedX = panelX
|
||||
panelX = Math.max(minX, Math.min(panelX, maxX));
|
||||
calculatedX = panelX;
|
||||
}
|
||||
} else {
|
||||
// For horizontal bars, center panel on button X position
|
||||
var panelX = root.buttonPosition.x + root.buttonWidth / 2 - panelWidth / 2
|
||||
var panelX = root.buttonPosition.x + root.buttonWidth / 2 - panelWidth / 2;
|
||||
if (allowAttach) {
|
||||
var cornerInset = root.barFloating ? Style.radiusL * 2 : 0
|
||||
var barLeftEdge = root.barMarginH + cornerInset
|
||||
var barRightEdge = root.width - root.barMarginH - cornerInset
|
||||
panelX = Math.max(barLeftEdge, Math.min(panelX, barRightEdge - panelWidth))
|
||||
var cornerInset = root.barFloating ? Style.radiusL * 2 : 0;
|
||||
var barLeftEdge = root.barMarginH + cornerInset;
|
||||
var barRightEdge = root.width - root.barMarginH - cornerInset;
|
||||
panelX = Math.max(barLeftEdge, Math.min(panelX, barRightEdge - panelWidth));
|
||||
} else {
|
||||
panelX = Math.max(Style.marginL, Math.min(panelX, root.width - panelWidth - Style.marginL))
|
||||
panelX = Math.max(Style.marginL, Math.min(panelX, root.width - panelWidth - Style.marginL));
|
||||
}
|
||||
calculatedX = panelX
|
||||
calculatedX = panelX;
|
||||
}
|
||||
} else {
|
||||
// Standard anchor positioning
|
||||
if (root.panelAnchorHorizontalCenter) {
|
||||
if (root.barIsVertical) {
|
||||
if (root.barPosition === "left") {
|
||||
var availableStart = root.barMarginH + Style.barHeight
|
||||
var availableWidth = root.width - availableStart
|
||||
calculatedX = availableStart + (availableWidth - panelWidth) / 2
|
||||
var availableStart = root.barMarginH + Style.barHeight;
|
||||
var availableWidth = root.width - availableStart;
|
||||
calculatedX = availableStart + (availableWidth - panelWidth) / 2;
|
||||
} else if (root.barPosition === "right") {
|
||||
var availableWidth = root.width - root.barMarginH - Style.barHeight
|
||||
calculatedX = (availableWidth - panelWidth) / 2
|
||||
var availableWidth = root.width - root.barMarginH - Style.barHeight;
|
||||
calculatedX = (availableWidth - panelWidth) / 2;
|
||||
} else {
|
||||
calculatedX = (root.width - panelWidth) / 2
|
||||
calculatedX = (root.width - panelWidth) / 2;
|
||||
}
|
||||
} else {
|
||||
calculatedX = (root.width - panelWidth) / 2
|
||||
calculatedX = (root.width - panelWidth) / 2;
|
||||
}
|
||||
} else if (root.effectivePanelAnchorRight) {
|
||||
if (allowAttach && root.barIsVertical && root.barPosition === "right") {
|
||||
var rightBarEdge = root.width - root.barMarginH - Style.barHeight
|
||||
calculatedX = rightBarEdge - panelWidth
|
||||
var rightBarEdge = root.width - root.barMarginH - Style.barHeight;
|
||||
calculatedX = rightBarEdge - panelWidth;
|
||||
} else if (allowAttach) {
|
||||
// Account for corner inset when bar is floating, horizontal, AND panel is on same edge as bar
|
||||
var panelOnSameEdgeAsBar = (root.barPosition === "top" && root.effectivePanelAnchorTop) || (root.barPosition === "bottom" && root.effectivePanelAnchorBottom)
|
||||
var panelOnSameEdgeAsBar = (root.barPosition === "top" && root.effectivePanelAnchorTop) || (root.barPosition === "bottom" && root.effectivePanelAnchorBottom);
|
||||
if (!root.barIsVertical && root.barFloating && panelOnSameEdgeAsBar) {
|
||||
var rightCornerInset = Style.radiusL * 2
|
||||
calculatedX = root.width - root.barMarginH - rightCornerInset - panelWidth
|
||||
var rightCornerInset = Style.radiusL * 2;
|
||||
calculatedX = root.width - root.barMarginH - rightCornerInset - panelWidth;
|
||||
} else {
|
||||
calculatedX = root.width - panelWidth
|
||||
calculatedX = root.width - panelWidth;
|
||||
}
|
||||
} else {
|
||||
calculatedX = root.width - panelWidth - Style.marginL
|
||||
calculatedX = root.width - panelWidth - Style.marginL;
|
||||
}
|
||||
} else if (root.effectivePanelAnchorLeft) {
|
||||
if (allowAttach && root.barIsVertical && root.barPosition === "left") {
|
||||
var leftBarEdge = root.barMarginH + Style.barHeight
|
||||
calculatedX = leftBarEdge
|
||||
var leftBarEdge = root.barMarginH + Style.barHeight;
|
||||
calculatedX = leftBarEdge;
|
||||
} else if (allowAttach) {
|
||||
// Account for corner inset when bar is floating, horizontal, AND panel is on same edge as bar
|
||||
var panelOnSameEdgeAsBar = (root.barPosition === "top" && root.effectivePanelAnchorTop) || (root.barPosition === "bottom" && root.effectivePanelAnchorBottom)
|
||||
var panelOnSameEdgeAsBar = (root.barPosition === "top" && root.effectivePanelAnchorTop) || (root.barPosition === "bottom" && root.effectivePanelAnchorBottom);
|
||||
if (!root.barIsVertical && root.barFloating && panelOnSameEdgeAsBar) {
|
||||
var leftCornerInset = Style.radiusL * 2
|
||||
calculatedX = root.barMarginH + leftCornerInset
|
||||
var leftCornerInset = Style.radiusL * 2;
|
||||
calculatedX = root.barMarginH + leftCornerInset;
|
||||
} else {
|
||||
calculatedX = 0
|
||||
calculatedX = 0;
|
||||
}
|
||||
} else {
|
||||
calculatedX = Style.marginL
|
||||
calculatedX = Style.marginL;
|
||||
}
|
||||
} else {
|
||||
// No explicit anchor: default to centering on bar
|
||||
if (root.barIsVertical) {
|
||||
if (root.barPosition === "left") {
|
||||
var availableStart = root.barMarginH + Style.barHeight
|
||||
var availableWidth = root.width - availableStart - Style.marginL
|
||||
calculatedX = availableStart + (availableWidth - panelWidth) / 2
|
||||
var availableStart = root.barMarginH + Style.barHeight;
|
||||
var availableWidth = root.width - availableStart - Style.marginL;
|
||||
calculatedX = availableStart + (availableWidth - panelWidth) / 2;
|
||||
} else {
|
||||
var availableWidth = root.width - root.barMarginH - Style.barHeight - Style.marginL
|
||||
calculatedX = Style.marginL + (availableWidth - panelWidth) / 2
|
||||
var availableWidth = root.width - root.barMarginH - Style.barHeight - Style.marginL;
|
||||
calculatedX = Style.marginL + (availableWidth - panelWidth) / 2;
|
||||
}
|
||||
} else {
|
||||
if (allowAttach) {
|
||||
var cornerInset = Style.radiusL + (root.barFloating ? Style.radiusL : 0)
|
||||
var barLeftEdge = root.barMarginH + cornerInset
|
||||
var barRightEdge = root.width - root.barMarginH - cornerInset
|
||||
var centeredX = (root.width - panelWidth) / 2
|
||||
calculatedX = Math.max(barLeftEdge, Math.min(centeredX, barRightEdge - panelWidth))
|
||||
var cornerInset = Style.radiusL + (root.barFloating ? Style.radiusL : 0);
|
||||
var barLeftEdge = root.barMarginH + cornerInset;
|
||||
var barRightEdge = root.width - root.barMarginH - cornerInset;
|
||||
var centeredX = (root.width - panelWidth) / 2;
|
||||
calculatedX = Math.max(barLeftEdge, Math.min(centeredX, barRightEdge - panelWidth));
|
||||
} else {
|
||||
calculatedX = (root.width - panelWidth) / 2
|
||||
calculatedX = (root.width - panelWidth) / 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -274,76 +273,76 @@ Item {
|
||||
|
||||
// Edge snapping for X
|
||||
if (allowAttach && !root.barFloating && root.width > 0 && panelWidth > 0) {
|
||||
var leftEdgePos = root.barMarginH
|
||||
var leftEdgePos = root.barMarginH;
|
||||
if (root.barPosition === "left") {
|
||||
leftEdgePos = root.barMarginH + Style.barHeight
|
||||
leftEdgePos = root.barMarginH + Style.barHeight;
|
||||
}
|
||||
|
||||
var rightEdgePos = root.width - root.barMarginH - panelWidth
|
||||
var rightEdgePos = root.width - root.barMarginH - panelWidth;
|
||||
if (root.barPosition === "right") {
|
||||
rightEdgePos = root.width - root.barMarginH - Style.barHeight - panelWidth
|
||||
rightEdgePos = root.width - root.barMarginH - Style.barHeight - panelWidth;
|
||||
}
|
||||
|
||||
// Only snap to left edge if panel is actually meant to be at left
|
||||
var shouldSnapToLeft = root.effectivePanelAnchorLeft || (!root.hasExplicitHorizontalAnchor && root.barPosition === "left")
|
||||
var shouldSnapToLeft = root.effectivePanelAnchorLeft || (!root.hasExplicitHorizontalAnchor && root.barPosition === "left");
|
||||
// Only snap to right edge if panel is actually meant to be at right
|
||||
var shouldSnapToRight = root.effectivePanelAnchorRight || (!root.hasExplicitHorizontalAnchor && root.barPosition === "right")
|
||||
var shouldSnapToRight = root.effectivePanelAnchorRight || (!root.hasExplicitHorizontalAnchor && root.barPosition === "right");
|
||||
|
||||
if (shouldSnapToLeft && Math.abs(calculatedX - leftEdgePos) <= root.edgeSnapDistance) {
|
||||
calculatedX = leftEdgePos
|
||||
calculatedX = leftEdgePos;
|
||||
} else if (shouldSnapToRight && Math.abs(calculatedX - rightEdgePos) <= root.edgeSnapDistance) {
|
||||
calculatedX = rightEdgePos
|
||||
calculatedX = rightEdgePos;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Y POSITIONING =====
|
||||
if (root.useButtonPosition && root.height > 0 && panelHeight > 0) {
|
||||
if (root.barPosition === "top") {
|
||||
var topBarEdge = root.barMarginV + Style.barHeight
|
||||
var topBarEdge = root.barMarginV + Style.barHeight;
|
||||
if (allowAttach) {
|
||||
calculatedY = topBarEdge
|
||||
calculatedY = topBarEdge;
|
||||
} else {
|
||||
calculatedY = topBarEdge + Style.marginM
|
||||
calculatedY = topBarEdge + Style.marginM;
|
||||
}
|
||||
} else if (root.barPosition === "bottom") {
|
||||
var bottomBarEdge = root.height - root.barMarginV - Style.barHeight
|
||||
var bottomBarEdge = root.height - root.barMarginV - Style.barHeight;
|
||||
if (allowAttach) {
|
||||
calculatedY = bottomBarEdge - panelHeight
|
||||
calculatedY = bottomBarEdge - panelHeight;
|
||||
} else {
|
||||
calculatedY = bottomBarEdge - panelHeight - Style.marginM
|
||||
calculatedY = bottomBarEdge - panelHeight - Style.marginM;
|
||||
}
|
||||
} else if (root.barIsVertical) {
|
||||
var panelY = root.buttonPosition.y + root.buttonHeight / 2 - panelHeight / 2
|
||||
var extraPadding = (allowAttach && root.barFloating) ? Style.radiusL : 0
|
||||
var panelY = root.buttonPosition.y + root.buttonHeight / 2 - panelHeight / 2;
|
||||
var extraPadding = (allowAttach && root.barFloating) ? Style.radiusL : 0;
|
||||
if (allowAttach) {
|
||||
var cornerInset = extraPadding + (root.barFloating ? Style.radiusL : 0)
|
||||
var barTopEdge = root.barMarginV + cornerInset
|
||||
var barBottomEdge = root.height - root.barMarginV - cornerInset
|
||||
panelY = Math.max(barTopEdge, Math.min(panelY, barBottomEdge - panelHeight))
|
||||
var cornerInset = extraPadding + (root.barFloating ? Style.radiusL : 0);
|
||||
var barTopEdge = root.barMarginV + cornerInset;
|
||||
var barBottomEdge = root.height - root.barMarginV - cornerInset;
|
||||
panelY = Math.max(barTopEdge, Math.min(panelY, barBottomEdge - panelHeight));
|
||||
} else {
|
||||
panelY = Math.max(Style.marginL + extraPadding, Math.min(panelY, root.height - panelHeight - Style.marginL - extraPadding))
|
||||
panelY = Math.max(Style.marginL + extraPadding, Math.min(panelY, root.height - panelHeight - Style.marginL - extraPadding));
|
||||
}
|
||||
calculatedY = panelY
|
||||
calculatedY = panelY;
|
||||
}
|
||||
} else {
|
||||
// Standard anchor positioning
|
||||
var barOffset = 0
|
||||
var barOffset = 0;
|
||||
if (!allowAttach) {
|
||||
if (root.barPosition === "top") {
|
||||
barOffset = root.barMarginV + Style.barHeight + Style.marginM
|
||||
barOffset = root.barMarginV + Style.barHeight + Style.marginM;
|
||||
} else if (root.barPosition === "bottom") {
|
||||
barOffset = root.barMarginV + Style.barHeight + Style.marginM
|
||||
barOffset = root.barMarginV + Style.barHeight + Style.marginM;
|
||||
}
|
||||
} else {
|
||||
if (root.effectivePanelAnchorTop && root.barPosition === "top") {
|
||||
calculatedY = root.barMarginV + Style.barHeight
|
||||
calculatedY = root.barMarginV + Style.barHeight;
|
||||
} else if (root.effectivePanelAnchorBottom && root.barPosition === "bottom") {
|
||||
calculatedY = root.height - root.barMarginV - Style.barHeight - panelHeight
|
||||
calculatedY = root.height - root.barMarginV - Style.barHeight - panelHeight;
|
||||
} else if (!root.hasExplicitVerticalAnchor) {
|
||||
if (root.barPosition === "top") {
|
||||
calculatedY = root.barMarginV + Style.barHeight
|
||||
calculatedY = root.barMarginV + Style.barHeight;
|
||||
} else if (root.barPosition === "bottom") {
|
||||
calculatedY = root.height - root.barMarginV - Style.barHeight - panelHeight
|
||||
calculatedY = root.height - root.barMarginV - Style.barHeight - panelHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -352,57 +351,57 @@ Item {
|
||||
if (root.panelAnchorVerticalCenter) {
|
||||
if (!root.barIsVertical) {
|
||||
if (root.barPosition === "top") {
|
||||
var availableStart = root.barMarginV + Style.barHeight
|
||||
var availableHeight = root.height - availableStart
|
||||
calculatedY = availableStart + (availableHeight - panelHeight) / 2
|
||||
var availableStart = root.barMarginV + Style.barHeight;
|
||||
var availableHeight = root.height - availableStart;
|
||||
calculatedY = availableStart + (availableHeight - panelHeight) / 2;
|
||||
} else if (root.barPosition === "bottom") {
|
||||
var availableHeight = root.height - root.barMarginV - Style.barHeight
|
||||
calculatedY = (availableHeight - panelHeight) / 2
|
||||
var availableHeight = root.height - root.barMarginV - Style.barHeight;
|
||||
calculatedY = (availableHeight - panelHeight) / 2;
|
||||
} else {
|
||||
calculatedY = (root.height - panelHeight) / 2
|
||||
calculatedY = (root.height - panelHeight) / 2;
|
||||
}
|
||||
} else {
|
||||
calculatedY = (root.height - panelHeight) / 2
|
||||
calculatedY = (root.height - panelHeight) / 2;
|
||||
}
|
||||
} else if (root.effectivePanelAnchorTop) {
|
||||
if (allowAttach) {
|
||||
calculatedY = 0
|
||||
calculatedY = 0;
|
||||
} else {
|
||||
var topBarOffset = (root.barPosition === "top") ? barOffset : 0
|
||||
calculatedY = topBarOffset + Style.marginL
|
||||
var topBarOffset = (root.barPosition === "top") ? barOffset : 0;
|
||||
calculatedY = topBarOffset + Style.marginL;
|
||||
}
|
||||
} else if (root.effectivePanelAnchorBottom) {
|
||||
if (allowAttach) {
|
||||
calculatedY = root.height - panelHeight
|
||||
calculatedY = root.height - panelHeight;
|
||||
} else {
|
||||
var bottomBarOffset = (root.barPosition === "bottom") ? barOffset : 0
|
||||
calculatedY = root.height - panelHeight - bottomBarOffset - Style.marginL
|
||||
var bottomBarOffset = (root.barPosition === "bottom") ? barOffset : 0;
|
||||
calculatedY = root.height - panelHeight - bottomBarOffset - Style.marginL;
|
||||
}
|
||||
} else {
|
||||
if (root.barIsVertical) {
|
||||
if (allowAttach) {
|
||||
var cornerInset = root.barFloating ? Style.radiusL * 2 : 0
|
||||
var barTopEdge = root.barMarginV + cornerInset
|
||||
var barBottomEdge = root.height - root.barMarginV - cornerInset
|
||||
var centeredY = (root.height - panelHeight) / 2
|
||||
calculatedY = Math.max(barTopEdge, Math.min(centeredY, barBottomEdge - panelHeight))
|
||||
var cornerInset = root.barFloating ? Style.radiusL * 2 : 0;
|
||||
var barTopEdge = root.barMarginV + cornerInset;
|
||||
var barBottomEdge = root.height - root.barMarginV - cornerInset;
|
||||
var centeredY = (root.height - panelHeight) / 2;
|
||||
calculatedY = Math.max(barTopEdge, Math.min(centeredY, barBottomEdge - panelHeight));
|
||||
} else {
|
||||
calculatedY = (root.height - panelHeight) / 2
|
||||
calculatedY = (root.height - panelHeight) / 2;
|
||||
}
|
||||
} else {
|
||||
if (allowAttach && !root.barIsVertical) {
|
||||
if (root.barPosition === "top") {
|
||||
calculatedY = root.barMarginV + Style.barHeight
|
||||
calculatedY = root.barMarginV + Style.barHeight;
|
||||
} else if (root.barPosition === "bottom") {
|
||||
calculatedY = root.height - root.barMarginV - Style.barHeight - panelHeight
|
||||
calculatedY = root.height - root.barMarginV - Style.barHeight - panelHeight;
|
||||
}
|
||||
} else {
|
||||
if (root.barPosition === "top") {
|
||||
calculatedY = barOffset + Style.marginL
|
||||
calculatedY = barOffset + Style.marginL;
|
||||
} else if (root.barPosition === "bottom") {
|
||||
calculatedY = Style.marginL
|
||||
calculatedY = Style.marginL;
|
||||
} else {
|
||||
calculatedY = Style.marginL
|
||||
calculatedY = Style.marginL;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -412,34 +411,34 @@ Item {
|
||||
|
||||
// Edge snapping for Y
|
||||
if (allowAttach && !root.barFloating && root.height > 0 && panelHeight > 0) {
|
||||
var topEdgePos = root.barMarginV
|
||||
var topEdgePos = root.barMarginV;
|
||||
if (root.barPosition === "top") {
|
||||
topEdgePos = root.barMarginV + Style.barHeight
|
||||
topEdgePos = root.barMarginV + Style.barHeight;
|
||||
}
|
||||
|
||||
var bottomEdgePos = root.height - root.barMarginV - panelHeight
|
||||
var bottomEdgePos = root.height - root.barMarginV - panelHeight;
|
||||
if (root.barPosition === "bottom") {
|
||||
bottomEdgePos = root.height - root.barMarginV - Style.barHeight - panelHeight
|
||||
bottomEdgePos = root.height - root.barMarginV - Style.barHeight - panelHeight;
|
||||
}
|
||||
|
||||
// Only snap to top edge if panel is actually meant to be at top
|
||||
var shouldSnapToTop = root.effectivePanelAnchorTop || (!root.hasExplicitVerticalAnchor && root.barPosition === "top")
|
||||
var shouldSnapToTop = root.effectivePanelAnchorTop || (!root.hasExplicitVerticalAnchor && root.barPosition === "top");
|
||||
// Only snap to bottom edge if panel is actually meant to be at bottom
|
||||
var shouldSnapToBottom = root.effectivePanelAnchorBottom || (!root.hasExplicitVerticalAnchor && root.barPosition === "bottom")
|
||||
var shouldSnapToBottom = root.effectivePanelAnchorBottom || (!root.hasExplicitVerticalAnchor && root.barPosition === "bottom");
|
||||
|
||||
if (shouldSnapToTop && Math.abs(calculatedY - topEdgePos) <= root.edgeSnapDistance) {
|
||||
calculatedY = topEdgePos
|
||||
calculatedY = topEdgePos;
|
||||
} else if (shouldSnapToBottom && Math.abs(calculatedY - bottomEdgePos) <= root.edgeSnapDistance) {
|
||||
calculatedY = bottomEdgePos
|
||||
calculatedY = bottomEdgePos;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply calculated positions (set targets for animation)
|
||||
panelBackground.targetX = calculatedX
|
||||
panelBackground.targetY = calculatedY
|
||||
panelBackground.targetX = calculatedX;
|
||||
panelBackground.targetY = calculatedY;
|
||||
|
||||
Logger.d("PanelPlaceholder", "Position calculated:", calculatedX, calculatedY, panelName)
|
||||
Logger.d("PanelPlaceholder", " Panel size:", panelWidth, "x", panelHeight)
|
||||
Logger.d("PanelPlaceholder", "Position calculated:", calculatedX, calculatedY, panelName);
|
||||
Logger.d("PanelPlaceholder", " Panel size:", panelWidth, "x", panelHeight);
|
||||
}
|
||||
|
||||
// The panel background geometry item
|
||||
@@ -469,35 +468,35 @@ Item {
|
||||
// Animation direction determination (using target position to avoid binding loops)
|
||||
readonly property bool willTouchTopBar: {
|
||||
if (!isPanelVisible)
|
||||
return false
|
||||
return false;
|
||||
if (!allowAttachToBar || root.barPosition !== "top" || root.barIsVertical)
|
||||
return false
|
||||
var targetTopBarY = root.barMarginV + Style.barHeight
|
||||
return Math.abs(panelBackground.targetY - targetTopBarY) <= 1
|
||||
return false;
|
||||
var targetTopBarY = root.barMarginV + Style.barHeight;
|
||||
return Math.abs(panelBackground.targetY - targetTopBarY) <= 1;
|
||||
}
|
||||
readonly property bool willTouchBottomBar: {
|
||||
if (!isPanelVisible)
|
||||
return false
|
||||
return false;
|
||||
if (!allowAttachToBar || root.barPosition !== "bottom" || root.barIsVertical)
|
||||
return false
|
||||
var targetBottomBarY = root.height - root.barMarginV - Style.barHeight - panelBackground.targetHeight
|
||||
return Math.abs(panelBackground.targetY - targetBottomBarY) <= 1
|
||||
return false;
|
||||
var targetBottomBarY = root.height - root.barMarginV - Style.barHeight - panelBackground.targetHeight;
|
||||
return Math.abs(panelBackground.targetY - targetBottomBarY) <= 1;
|
||||
}
|
||||
readonly property bool willTouchLeftBar: {
|
||||
if (!isPanelVisible)
|
||||
return false
|
||||
return false;
|
||||
if (!allowAttachToBar || root.barPosition !== "left" || !root.barIsVertical)
|
||||
return false
|
||||
var targetLeftBarX = root.barMarginH + Style.barHeight
|
||||
return Math.abs(panelBackground.targetX - targetLeftBarX) <= 1
|
||||
return false;
|
||||
var targetLeftBarX = root.barMarginH + Style.barHeight;
|
||||
return Math.abs(panelBackground.targetX - targetLeftBarX) <= 1;
|
||||
}
|
||||
readonly property bool willTouchRightBar: {
|
||||
if (!isPanelVisible)
|
||||
return false
|
||||
return false;
|
||||
if (!allowAttachToBar || root.barPosition !== "right" || !root.barIsVertical)
|
||||
return false
|
||||
var targetRightBarX = root.width - root.barMarginH - Style.barHeight - panelBackground.targetWidth
|
||||
return Math.abs(panelBackground.targetX - targetRightBarX) <= 1
|
||||
return false;
|
||||
var targetRightBarX = root.width - root.barMarginH - Style.barHeight - panelBackground.targetWidth;
|
||||
return Math.abs(panelBackground.targetX - targetRightBarX) <= 1;
|
||||
}
|
||||
readonly property bool willTouchTopEdge: isPanelVisible && allowAttach && panelBackground.targetY <= 1
|
||||
readonly property bool willTouchBottomEdge: isPanelVisible && allowAttach && (panelBackground.targetY + panelBackground.targetHeight) >= (root.height - 1)
|
||||
@@ -506,59 +505,59 @@ Item {
|
||||
|
||||
readonly property bool isActuallyAttachedToAnyEdge: {
|
||||
if (!isPanelVisible)
|
||||
return false
|
||||
return willTouchTopBar || willTouchBottomBar || willTouchLeftBar || willTouchRightBar || willTouchTopEdge || willTouchBottomEdge || willTouchLeftEdge || willTouchRightEdge
|
||||
return false;
|
||||
return willTouchTopBar || willTouchBottomBar || willTouchLeftBar || willTouchRightBar || willTouchTopEdge || willTouchBottomEdge || willTouchLeftEdge || willTouchRightEdge;
|
||||
}
|
||||
|
||||
readonly property bool animateFromTop: {
|
||||
if (!isPanelVisible)
|
||||
return true
|
||||
return true;
|
||||
if (willTouchTopBar)
|
||||
return true
|
||||
return true;
|
||||
if (willTouchTopEdge && !willTouchTopBar && !willTouchBottomBar && !willTouchLeftBar && !willTouchRightBar)
|
||||
return true
|
||||
return true;
|
||||
if (!isActuallyAttachedToAnyEdge)
|
||||
return true
|
||||
return false
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
readonly property bool animateFromBottom: {
|
||||
if (!isPanelVisible)
|
||||
return false
|
||||
return false;
|
||||
if (willTouchBottomBar)
|
||||
return true
|
||||
return true;
|
||||
if (willTouchBottomEdge && !willTouchTopBar && !willTouchBottomBar && !willTouchLeftBar && !willTouchRightBar)
|
||||
return true
|
||||
return false
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
readonly property bool animateFromLeft: {
|
||||
if (!isPanelVisible)
|
||||
return false
|
||||
return false;
|
||||
if (willTouchTopBar || willTouchBottomBar)
|
||||
return false
|
||||
return false;
|
||||
if (willTouchLeftBar)
|
||||
return true
|
||||
var touchingTopEdge = isPanelVisible && allowAttach && panelBackground.targetY <= 1
|
||||
var touchingBottomEdge = isPanelVisible && allowAttach && (panelBackground.targetY + panelBackground.targetHeight) >= (root.height - 1)
|
||||
return true;
|
||||
var touchingTopEdge = isPanelVisible && allowAttach && panelBackground.targetY <= 1;
|
||||
var touchingBottomEdge = isPanelVisible && allowAttach && (panelBackground.targetY + panelBackground.targetHeight) >= (root.height - 1);
|
||||
if (touchingTopEdge || touchingBottomEdge)
|
||||
return false
|
||||
return false;
|
||||
if (willTouchLeftEdge && !willTouchLeftBar && !willTouchTopBar && !willTouchBottomBar && !willTouchRightBar)
|
||||
return true
|
||||
return false
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
readonly property bool animateFromRight: {
|
||||
if (!isPanelVisible)
|
||||
return false
|
||||
return false;
|
||||
if (willTouchTopBar || willTouchBottomBar)
|
||||
return false
|
||||
return false;
|
||||
if (willTouchRightBar)
|
||||
return true
|
||||
var touchingTopEdge = isPanelVisible && allowAttach && panelBackground.targetY <= 1
|
||||
var touchingBottomEdge = isPanelVisible && allowAttach && (panelBackground.targetY + panelBackground.targetHeight) >= (root.height - 1)
|
||||
return true;
|
||||
var touchingTopEdge = isPanelVisible && allowAttach && panelBackground.targetY <= 1;
|
||||
var touchingBottomEdge = isPanelVisible && allowAttach && (panelBackground.targetY + panelBackground.targetHeight) >= (root.height - 1);
|
||||
if (touchingTopEdge || touchingBottomEdge)
|
||||
return false
|
||||
return false;
|
||||
if (willTouchRightEdge && !willTouchLeftBar && !willTouchTopBar && !willTouchBottomBar && !willTouchRightBar)
|
||||
return true
|
||||
return false
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
readonly property bool shouldAnimateWidth: !shouldAnimateHeight && (animateFromLeft || animateFromRight)
|
||||
@@ -567,17 +566,17 @@ Item {
|
||||
// Current animated width/height
|
||||
readonly property real currentWidth: {
|
||||
if (isClosing && opacityFadeComplete && shouldAnimateWidth)
|
||||
return 0
|
||||
return 0;
|
||||
if (isClosing || isPanelVisible)
|
||||
return targetWidth
|
||||
return 0
|
||||
return targetWidth;
|
||||
return 0;
|
||||
}
|
||||
readonly property real currentHeight: {
|
||||
if (isClosing && opacityFadeComplete && shouldAnimateHeight)
|
||||
return 0
|
||||
return 0;
|
||||
if (isClosing || isPanelVisible)
|
||||
return targetHeight
|
||||
return 0
|
||||
return targetHeight;
|
||||
return 0;
|
||||
}
|
||||
|
||||
width: currentWidth
|
||||
@@ -586,28 +585,28 @@ Item {
|
||||
x: {
|
||||
if (animateFromRight) {
|
||||
if (isPanelVisible || isClosing) {
|
||||
var targetRightEdge = targetX + targetWidth
|
||||
return targetRightEdge - width
|
||||
var targetRightEdge = targetX + targetWidth;
|
||||
return targetRightEdge - width;
|
||||
}
|
||||
}
|
||||
return targetX
|
||||
return targetX;
|
||||
}
|
||||
y: {
|
||||
if (animateFromBottom) {
|
||||
if (isPanelVisible || isClosing) {
|
||||
var targetBottomEdge = targetY + targetHeight
|
||||
return targetBottomEdge - height
|
||||
var targetBottomEdge = targetY + targetHeight;
|
||||
return targetBottomEdge - height;
|
||||
}
|
||||
}
|
||||
return targetY
|
||||
return targetY;
|
||||
}
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: {
|
||||
if (!panelBackground.shouldAnimateWidth)
|
||||
return 0
|
||||
return root.isClosing ? Style.animationFast : Style.animationNormal
|
||||
return 0;
|
||||
return root.isClosing ? Style.animationFast : Style.animationNormal;
|
||||
}
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: panelBackground.bezierCurve
|
||||
@@ -618,8 +617,8 @@ Item {
|
||||
NumberAnimation {
|
||||
duration: {
|
||||
if (!panelBackground.shouldAnimateHeight)
|
||||
return 0
|
||||
return root.isClosing ? Style.animationFast : Style.animationNormal
|
||||
return 0;
|
||||
return root.isClosing ? Style.animationFast : Style.animationNormal;
|
||||
}
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: panelBackground.bezierCurve
|
||||
@@ -628,75 +627,75 @@ Item {
|
||||
|
||||
// Corner states for PanelBackground to read
|
||||
property int topLeftCornerState: {
|
||||
var barInverted = allowAttachToBar && ((root.barPosition === "top" && !root.barIsVertical && root.effectivePanelAnchorTop) || (root.barPosition === "left" && root.barIsVertical && root.effectivePanelAnchorLeft))
|
||||
var barTouchInverted = touchingTopBar || touchingLeftBar
|
||||
var edgeInverted = allowAttach && (touchingLeftEdge || touchingTopEdge)
|
||||
var oppositeEdgeInverted = allowAttach && (touchingTopEdge && !root.barIsVertical && root.barPosition !== "top")
|
||||
var barInverted = allowAttachToBar && ((root.barPosition === "top" && !root.barIsVertical && root.effectivePanelAnchorTop) || (root.barPosition === "left" && root.barIsVertical && root.effectivePanelAnchorLeft));
|
||||
var barTouchInverted = touchingTopBar || touchingLeftBar;
|
||||
var edgeInverted = allowAttach && (touchingLeftEdge || touchingTopEdge);
|
||||
var oppositeEdgeInverted = allowAttach && (touchingTopEdge && !root.barIsVertical && root.barPosition !== "top");
|
||||
|
||||
if (barInverted || barTouchInverted || edgeInverted || oppositeEdgeInverted) {
|
||||
if (touchingLeftEdge && touchingTopEdge)
|
||||
return 0
|
||||
return 0;
|
||||
if (touchingLeftEdge)
|
||||
return 2
|
||||
return 2;
|
||||
if (touchingTopEdge)
|
||||
return 1
|
||||
return root.barIsVertical ? 2 : 1
|
||||
return 1;
|
||||
return root.barIsVertical ? 2 : 1;
|
||||
}
|
||||
return 0
|
||||
return 0;
|
||||
}
|
||||
|
||||
property int topRightCornerState: {
|
||||
var barInverted = allowAttachToBar && ((root.barPosition === "top" && !root.barIsVertical && root.effectivePanelAnchorTop) || (root.barPosition === "right" && root.barIsVertical && root.effectivePanelAnchorRight))
|
||||
var barTouchInverted = touchingTopBar || touchingRightBar
|
||||
var edgeInverted = allowAttach && (touchingRightEdge || touchingTopEdge)
|
||||
var oppositeEdgeInverted = allowAttach && (touchingTopEdge && !root.barIsVertical && root.barPosition !== "top")
|
||||
var barInverted = allowAttachToBar && ((root.barPosition === "top" && !root.barIsVertical && root.effectivePanelAnchorTop) || (root.barPosition === "right" && root.barIsVertical && root.effectivePanelAnchorRight));
|
||||
var barTouchInverted = touchingTopBar || touchingRightBar;
|
||||
var edgeInverted = allowAttach && (touchingRightEdge || touchingTopEdge);
|
||||
var oppositeEdgeInverted = allowAttach && (touchingTopEdge && !root.barIsVertical && root.barPosition !== "top");
|
||||
|
||||
if (barInverted || barTouchInverted || edgeInverted || oppositeEdgeInverted) {
|
||||
if (touchingRightEdge && touchingTopEdge)
|
||||
return 0
|
||||
return 0;
|
||||
if (touchingRightEdge)
|
||||
return 2
|
||||
return 2;
|
||||
if (touchingTopEdge)
|
||||
return 1
|
||||
return root.barIsVertical ? 2 : 1
|
||||
return 1;
|
||||
return root.barIsVertical ? 2 : 1;
|
||||
}
|
||||
return 0
|
||||
return 0;
|
||||
}
|
||||
|
||||
property int bottomLeftCornerState: {
|
||||
var barInverted = allowAttachToBar && ((root.barPosition === "bottom" && !root.barIsVertical && root.effectivePanelAnchorBottom) || (root.barPosition === "left" && root.barIsVertical && root.effectivePanelAnchorLeft))
|
||||
var barTouchInverted = touchingBottomBar || touchingLeftBar
|
||||
var edgeInverted = allowAttach && (touchingLeftEdge || touchingBottomEdge)
|
||||
var oppositeEdgeInverted = allowAttach && (touchingBottomEdge && !root.barIsVertical && root.barPosition !== "bottom")
|
||||
var barInverted = allowAttachToBar && ((root.barPosition === "bottom" && !root.barIsVertical && root.effectivePanelAnchorBottom) || (root.barPosition === "left" && root.barIsVertical && root.effectivePanelAnchorLeft));
|
||||
var barTouchInverted = touchingBottomBar || touchingLeftBar;
|
||||
var edgeInverted = allowAttach && (touchingLeftEdge || touchingBottomEdge);
|
||||
var oppositeEdgeInverted = allowAttach && (touchingBottomEdge && !root.barIsVertical && root.barPosition !== "bottom");
|
||||
|
||||
if (barInverted || barTouchInverted || edgeInverted || oppositeEdgeInverted) {
|
||||
if (touchingLeftEdge && touchingBottomEdge)
|
||||
return 0
|
||||
return 0;
|
||||
if (touchingLeftEdge)
|
||||
return 2
|
||||
return 2;
|
||||
if (touchingBottomEdge)
|
||||
return 1
|
||||
return root.barIsVertical ? 2 : 1
|
||||
return 1;
|
||||
return root.barIsVertical ? 2 : 1;
|
||||
}
|
||||
return 0
|
||||
return 0;
|
||||
}
|
||||
|
||||
property int bottomRightCornerState: {
|
||||
var barInverted = allowAttachToBar && ((root.barPosition === "bottom" && !root.barIsVertical && root.effectivePanelAnchorBottom) || (root.barPosition === "right" && root.barIsVertical && root.effectivePanelAnchorRight))
|
||||
var barTouchInverted = touchingBottomBar || touchingRightBar
|
||||
var edgeInverted = allowAttach && (touchingRightEdge || touchingBottomEdge)
|
||||
var oppositeEdgeInverted = allowAttach && (touchingBottomEdge && !root.barIsVertical && root.barPosition !== "bottom")
|
||||
var barInverted = allowAttachToBar && ((root.barPosition === "bottom" && !root.barIsVertical && root.effectivePanelAnchorBottom) || (root.barPosition === "right" && root.barIsVertical && root.effectivePanelAnchorRight));
|
||||
var barTouchInverted = touchingBottomBar || touchingRightBar;
|
||||
var edgeInverted = allowAttach && (touchingRightEdge || touchingBottomEdge);
|
||||
var oppositeEdgeInverted = allowAttach && (touchingBottomEdge && !root.barIsVertical && root.barPosition !== "bottom");
|
||||
|
||||
if (barInverted || barTouchInverted || edgeInverted || oppositeEdgeInverted) {
|
||||
if (touchingRightEdge && touchingBottomEdge)
|
||||
return 0
|
||||
return 0;
|
||||
if (touchingRightEdge)
|
||||
return 2
|
||||
return 2;
|
||||
if (touchingBottomEdge)
|
||||
return 1
|
||||
return root.barIsVertical ? 2 : 1
|
||||
return 1;
|
||||
return root.barIsVertical ? 2 : 1;
|
||||
}
|
||||
return 0
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,12 @@ import QtQuick
|
||||
import QtQuick.Shapes
|
||||
import qs.Commons
|
||||
|
||||
|
||||
/**
|
||||
* ScreenCorners - Shape component for rendering screen corners
|
||||
*
|
||||
* Renders concave corners at the screen edges to create a rounded screen effect.
|
||||
* Self-contained Shape component (no shadows).
|
||||
*/
|
||||
* ScreenCorners - Shape component for rendering screen corners
|
||||
*
|
||||
* Renders concave corners at the screen edges to create a rounded screen effect.
|
||||
* Self-contained Shape component (no shadows).
|
||||
*/
|
||||
Item {
|
||||
id: root
|
||||
|
||||
|
||||
@@ -3,15 +3,14 @@ import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
|
||||
|
||||
/**
|
||||
* SmartPanel - Wrapper that creates placeholder + content window
|
||||
*
|
||||
* This component is a thin wrapper that maintains backward compatibility
|
||||
* while splitting panel rendering into:
|
||||
* 1. PanelPlaceholder (in MainScreen, for background rendering)
|
||||
* 2. SmartPanelWindow (separate window, for content)
|
||||
*/
|
||||
* SmartPanel - Wrapper that creates placeholder + content window
|
||||
*
|
||||
* This component is a thin wrapper that maintains backward compatibility
|
||||
* while splitting panel rendering into:
|
||||
* 1. PanelPlaceholder (in MainScreen, for background rendering)
|
||||
* 2. SmartPanelWindow (separate window, for content)
|
||||
*/
|
||||
Item {
|
||||
id: root
|
||||
|
||||
@@ -59,60 +58,74 @@ Item {
|
||||
|
||||
// Keyboard event handlers - these can be overridden by panel implementations
|
||||
// Note: SmartPanelWindow directly calls these functions via panelWrapper reference
|
||||
function onEscapePressed() {}
|
||||
function onTabPressed() {}
|
||||
function onBackTabPressed() {}
|
||||
function onUpPressed() {}
|
||||
function onDownPressed() {}
|
||||
function onLeftPressed() {}
|
||||
function onRightPressed() {}
|
||||
function onReturnPressed() {}
|
||||
function onHomePressed() {}
|
||||
function onEndPressed() {}
|
||||
function onPageUpPressed() {}
|
||||
function onPageDownPressed() {}
|
||||
function onCtrlJPressed() {}
|
||||
function onCtrlKPressed() {}
|
||||
function onEscapePressed() {
|
||||
}
|
||||
function onTabPressed() {
|
||||
}
|
||||
function onBackTabPressed() {
|
||||
}
|
||||
function onUpPressed() {
|
||||
}
|
||||
function onDownPressed() {
|
||||
}
|
||||
function onLeftPressed() {
|
||||
}
|
||||
function onRightPressed() {
|
||||
}
|
||||
function onReturnPressed() {
|
||||
}
|
||||
function onHomePressed() {
|
||||
}
|
||||
function onEndPressed() {
|
||||
}
|
||||
function onPageUpPressed() {
|
||||
}
|
||||
function onPageDownPressed() {
|
||||
}
|
||||
function onCtrlJPressed() {
|
||||
}
|
||||
function onCtrlKPressed() {
|
||||
}
|
||||
|
||||
// Public control functions
|
||||
function toggle(buttonItem, buttonName) {
|
||||
// Ensure window is created before toggling
|
||||
if (!root.windowActive) {
|
||||
root.windowActive = true
|
||||
root.windowActive = true;
|
||||
Qt.callLater(function () {
|
||||
if (windowLoader.item) {
|
||||
windowLoader.item.toggle(buttonItem, buttonName)
|
||||
windowLoader.item.toggle(buttonItem, buttonName);
|
||||
}
|
||||
})
|
||||
});
|
||||
} else if (windowLoader.item) {
|
||||
windowLoader.item.toggle(buttonItem, buttonName)
|
||||
windowLoader.item.toggle(buttonItem, buttonName);
|
||||
}
|
||||
}
|
||||
|
||||
function open(buttonItem, buttonName) {
|
||||
// Ensure window is created before opening
|
||||
if (!root.windowActive) {
|
||||
root.windowActive = true
|
||||
root.windowActive = true;
|
||||
Qt.callLater(function () {
|
||||
if (windowLoader.item) {
|
||||
windowLoader.item.open(buttonItem, buttonName)
|
||||
windowLoader.item.open(buttonItem, buttonName);
|
||||
}
|
||||
})
|
||||
});
|
||||
} else if (windowLoader.item) {
|
||||
windowLoader.item.open(buttonItem, buttonName)
|
||||
windowLoader.item.open(buttonItem, buttonName);
|
||||
}
|
||||
}
|
||||
|
||||
function close() {
|
||||
if (windowLoader.item) {
|
||||
windowLoader.item.close()
|
||||
windowLoader.item.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Expose setPosition for panels that need to recalculate on settings changes
|
||||
function setPosition() {
|
||||
if (panelPlaceholder) {
|
||||
panelPlaceholder.setPosition()
|
||||
panelPlaceholder.setPosition();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,24 +170,17 @@ Item {
|
||||
// Forward signals
|
||||
onPanelOpened: root.opened()
|
||||
onPanelClosed: {
|
||||
root.closed()
|
||||
root.closed();
|
||||
// Destroy the window after close animation completes
|
||||
Qt.callLater(function () {
|
||||
root.windowActive = false
|
||||
})
|
||||
root.windowActive = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register with PanelService (backward compatibility)
|
||||
// Note: Registration happens in MainScreen after objectName is set
|
||||
// Register with PanelService
|
||||
Component.onCompleted: {
|
||||
// Use Qt.callLater to ensure objectName is set by parent before registering
|
||||
Qt.callLater(function () {
|
||||
if (!objectName) {
|
||||
Logger.w("SmartPanel", "Panel created without objectName - PanelService registration may fail")
|
||||
}
|
||||
PanelService.registerPanel(root)
|
||||
})
|
||||
PanelService.registerPanel(root);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,12 @@ import qs.Commons
|
||||
import qs.Services.Compositor
|
||||
import qs.Services.UI
|
||||
|
||||
|
||||
/**
|
||||
* SmartPanelWindow - Separate window for panel content
|
||||
*
|
||||
* This component runs in its own window, separate from MainScreen.
|
||||
* It follows the PanelPlaceholder for positioning and contains the actual panel content.
|
||||
*/
|
||||
* SmartPanelWindow - Separate window for panel content
|
||||
*
|
||||
* This component runs in its own window, separate from MainScreen.
|
||||
* It follows the PanelPlaceholder for positioning and contains the actual panel content.
|
||||
*/
|
||||
PanelWindow {
|
||||
id: root
|
||||
|
||||
@@ -56,6 +55,7 @@ PanelWindow {
|
||||
color: Color.transparent
|
||||
mask: null // No mask - content window is rectangular
|
||||
visible: isPanelOpen
|
||||
screen: placeholder.screen // Explicitly set screen to match placeholder
|
||||
|
||||
// Wayland layer shell configuration - fullscreen window
|
||||
WlrLayershell.layer: WlrLayer.Top
|
||||
@@ -63,13 +63,13 @@ PanelWindow {
|
||||
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
||||
WlrLayershell.keyboardFocus: {
|
||||
if (!root.isPanelOpen) {
|
||||
return WlrKeyboardFocus.None
|
||||
return WlrKeyboardFocus.None;
|
||||
}
|
||||
if (CompositorService.isHyprland) {
|
||||
// Exclusive focus on hyprland is too restrictive.
|
||||
return WlrKeyboardFocus.OnDemand
|
||||
return WlrKeyboardFocus.OnDemand;
|
||||
} else {
|
||||
return root.exclusiveKeyboard ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.OnDemand
|
||||
return root.exclusiveKeyboard ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.OnDemand;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,95 +91,95 @@ PanelWindow {
|
||||
|
||||
// Sync state to placeholder
|
||||
onIsPanelVisibleChanged: {
|
||||
placeholder.isPanelVisible = isPanelVisible
|
||||
placeholder.isPanelVisible = isPanelVisible;
|
||||
}
|
||||
onIsClosingChanged: {
|
||||
placeholder.isClosing = isClosing
|
||||
placeholder.isClosing = isClosing;
|
||||
}
|
||||
onOpacityFadeCompleteChanged: {
|
||||
placeholder.opacityFadeComplete = opacityFadeComplete
|
||||
placeholder.opacityFadeComplete = opacityFadeComplete;
|
||||
}
|
||||
|
||||
// Panel control functions
|
||||
function toggle(buttonItem, buttonName) {
|
||||
if (!isPanelOpen) {
|
||||
open(buttonItem, buttonName)
|
||||
open(buttonItem, buttonName);
|
||||
} else {
|
||||
close()
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
function open(buttonItem, buttonName) {
|
||||
if (!buttonItem && buttonName) {
|
||||
buttonItem = BarService.lookupWidget(buttonName, placeholder.screen.name)
|
||||
buttonItem = BarService.lookupWidget(buttonName, placeholder.screen.name);
|
||||
}
|
||||
|
||||
if (buttonItem) {
|
||||
placeholder.buttonItem = buttonItem
|
||||
placeholder.buttonItem = buttonItem;
|
||||
// Map button position to screen coordinates
|
||||
var buttonPos = buttonItem.mapToItem(null, 0, 0)
|
||||
placeholder.buttonPosition = Qt.point(buttonPos.x, buttonPos.y)
|
||||
placeholder.buttonWidth = buttonItem.width
|
||||
placeholder.buttonHeight = buttonItem.height
|
||||
placeholder.useButtonPosition = true
|
||||
var buttonPos = buttonItem.mapToItem(null, 0, 0);
|
||||
placeholder.buttonPosition = Qt.point(buttonPos.x, buttonPos.y);
|
||||
placeholder.buttonWidth = buttonItem.width;
|
||||
placeholder.buttonHeight = buttonItem.height;
|
||||
placeholder.useButtonPosition = true;
|
||||
} else {
|
||||
// No button provided: reset button position mode
|
||||
placeholder.buttonItem = null
|
||||
placeholder.useButtonPosition = false
|
||||
placeholder.buttonItem = null;
|
||||
placeholder.useButtonPosition = false;
|
||||
}
|
||||
|
||||
// Set isPanelOpen to trigger content loading
|
||||
isPanelOpen = true
|
||||
isPanelOpen = true;
|
||||
|
||||
// Notify PanelService
|
||||
PanelService.willOpenPanel(root)
|
||||
PanelService.willOpenPanel(root);
|
||||
}
|
||||
|
||||
function close() {
|
||||
// Start close sequence: fade opacity first
|
||||
isClosing = true
|
||||
sizeAnimationComplete = false
|
||||
closeFinalized = false
|
||||
isClosing = true;
|
||||
sizeAnimationComplete = false;
|
||||
closeFinalized = false;
|
||||
|
||||
// Stop the open animation timer if it's still running
|
||||
opacityTrigger.stop()
|
||||
openWatchdogActive = false
|
||||
openWatchdogTimer.stop()
|
||||
opacityTrigger.stop();
|
||||
openWatchdogActive = false;
|
||||
openWatchdogTimer.stop();
|
||||
|
||||
// Start close watchdog timer
|
||||
closeWatchdogActive = true
|
||||
closeWatchdogTimer.restart()
|
||||
closeWatchdogActive = true;
|
||||
closeWatchdogTimer.restart();
|
||||
|
||||
// If opacity is already 0, skip directly to size animation
|
||||
if (contentWrapper.opacity === 0.0) {
|
||||
opacityFadeComplete = true
|
||||
opacityFadeComplete = true;
|
||||
} else {
|
||||
opacityFadeComplete = false
|
||||
opacityFadeComplete = false;
|
||||
}
|
||||
|
||||
Logger.d("SmartPanelWindow", "Closing panel", placeholder.panelName)
|
||||
Logger.d("SmartPanelWindow", "Closing panel", placeholder.panelName);
|
||||
}
|
||||
|
||||
function finalizeClose() {
|
||||
// Prevent double-finalization
|
||||
if (root.closeFinalized) {
|
||||
Logger.w("SmartPanelWindow", "finalizeClose called but already finalized - ignoring", placeholder.panelName)
|
||||
return
|
||||
Logger.w("SmartPanelWindow", "finalizeClose called but already finalized - ignoring", placeholder.panelName);
|
||||
return;
|
||||
}
|
||||
|
||||
// Complete the close sequence after animations finish
|
||||
root.closeFinalized = true
|
||||
root.closeWatchdogActive = false
|
||||
closeWatchdogTimer.stop()
|
||||
root.closeFinalized = true;
|
||||
root.closeWatchdogActive = false;
|
||||
closeWatchdogTimer.stop();
|
||||
|
||||
root.isPanelVisible = false
|
||||
root.isPanelOpen = false
|
||||
root.isClosing = false
|
||||
root.opacityFadeComplete = false
|
||||
PanelService.closedPanel(root)
|
||||
panelClosed()
|
||||
root.isPanelVisible = false;
|
||||
root.isPanelOpen = false;
|
||||
root.isClosing = false;
|
||||
root.opacityFadeComplete = false;
|
||||
PanelService.closedPanel(root);
|
||||
panelClosed();
|
||||
|
||||
Logger.d("SmartPanelWindow", "Panel close finalized", placeholder.panelName)
|
||||
Logger.d("SmartPanelWindow", "Panel close finalized", placeholder.panelName);
|
||||
}
|
||||
|
||||
// Fullscreen container for click-to-close and content
|
||||
@@ -189,59 +189,59 @@ PanelWindow {
|
||||
|
||||
// Handle keyboard events directly via Keys handler
|
||||
Keys.onPressed: event => {
|
||||
Logger.d("SmartPanelWindow", "Key pressed:", event.key, "for panel:", placeholder.panelName)
|
||||
Logger.d("SmartPanelWindow", "Key pressed:", event.key, "for panel:", placeholder.panelName);
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
panelWrapper.onEscapePressed()
|
||||
panelWrapper.onEscapePressed();
|
||||
if (closeWithEscape) {
|
||||
root.close()
|
||||
event.accepted = true
|
||||
root.close();
|
||||
event.accepted = true;
|
||||
}
|
||||
} else if (panelWrapper) {
|
||||
if (event.key === Qt.Key_Up && panelWrapper.onUpPressed) {
|
||||
panelWrapper.onUpPressed()
|
||||
event.accepted = true
|
||||
panelWrapper.onUpPressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Down && panelWrapper.onDownPressed) {
|
||||
panelWrapper.onDownPressed()
|
||||
event.accepted = true
|
||||
panelWrapper.onDownPressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Left && panelWrapper.onLeftPressed) {
|
||||
panelWrapper.onLeftPressed()
|
||||
event.accepted = true
|
||||
panelWrapper.onLeftPressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Right && panelWrapper.onRightPressed) {
|
||||
panelWrapper.onRightPressed()
|
||||
event.accepted = true
|
||||
panelWrapper.onRightPressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Tab && panelWrapper.onTabPressed) {
|
||||
panelWrapper.onTabPressed()
|
||||
event.accepted = true
|
||||
panelWrapper.onTabPressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Backtab && panelWrapper.onBackTabPressed) {
|
||||
panelWrapper.onBackTabPressed()
|
||||
event.accepted = true
|
||||
panelWrapper.onBackTabPressed();
|
||||
event.accepted = true;
|
||||
} else if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && panelWrapper.onReturnPressed) {
|
||||
panelWrapper.onReturnPressed()
|
||||
event.accepted = true
|
||||
panelWrapper.onReturnPressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Home && panelWrapper.onHomePressed) {
|
||||
panelWrapper.onHomePressed()
|
||||
event.accepted = true
|
||||
panelWrapper.onHomePressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_End && panelWrapper.onEndPressed) {
|
||||
panelWrapper.onEndPressed()
|
||||
event.accepted = true
|
||||
panelWrapper.onEndPressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_PageUp && panelWrapper.onPageUpPressed) {
|
||||
panelWrapper.onPageUpPressed()
|
||||
event.accepted = true
|
||||
panelWrapper.onPageUpPressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_PageDown && panelWrapper.onPageDownPressed) {
|
||||
panelWrapper.onPageDownPressed()
|
||||
event.accepted = true
|
||||
panelWrapper.onPageDownPressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_J && (event.modifiers & Qt.ControlModifier) && panelWrapper.onCtrlJPressed) {
|
||||
panelWrapper.onCtrlJPressed()
|
||||
event.accepted = true
|
||||
panelWrapper.onCtrlJPressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_K && (event.modifiers & Qt.ControlModifier) && panelWrapper.onCtrlKPressed) {
|
||||
panelWrapper.onCtrlKPressed()
|
||||
event.accepted = true
|
||||
panelWrapper.onCtrlKPressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_N && (event.modifiers & Qt.ControlModifier) && panelWrapper.onCtrlNPressed) {
|
||||
panelWrapper.onCtrlNPressed()
|
||||
event.accepted = true
|
||||
panelWrapper.onCtrlNPressed();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_P && (event.modifiers & Qt.ControlModifier) && panelWrapper.onCtrlPPressed) {
|
||||
panelWrapper.onCtrlPPressed()
|
||||
event.accepted = true
|
||||
panelWrapper.onCtrlPPressed();
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -252,8 +252,8 @@ PanelWindow {
|
||||
enabled: root.isPanelOpen && !root.isClosing
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
onClicked: mouse => {
|
||||
root.close()
|
||||
mouse.accepted = true
|
||||
root.close();
|
||||
mouse.accepted = true;
|
||||
}
|
||||
z: 0
|
||||
}
|
||||
@@ -271,10 +271,10 @@ PanelWindow {
|
||||
// Opacity animation
|
||||
opacity: {
|
||||
if (isClosing)
|
||||
return 0.0
|
||||
return 0.0;
|
||||
if (isPanelVisible && sizeAnimationComplete)
|
||||
return 1.0
|
||||
return 0.0
|
||||
return 1.0;
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
@@ -287,33 +287,33 @@ PanelWindow {
|
||||
// Safety: Zero-duration animation handling
|
||||
if (!running && duration === 0) {
|
||||
if (root.isClosing && contentWrapper.opacity === 0.0) {
|
||||
root.opacityFadeComplete = true
|
||||
var shouldFinalizeNow = placeholder.panelItem && !placeholder.panelItem.shouldAnimateWidth && !placeholder.panelItem.shouldAnimateHeight
|
||||
root.opacityFadeComplete = true;
|
||||
var shouldFinalizeNow = placeholder.panelItem && !placeholder.panelItem.shouldAnimateWidth && !placeholder.panelItem.shouldAnimateHeight;
|
||||
if (shouldFinalizeNow) {
|
||||
Logger.d("SmartPanelWindow", "Zero-duration opacity + no size animation - finalizing", placeholder.panelName)
|
||||
Qt.callLater(root.finalizeClose)
|
||||
Logger.d("SmartPanelWindow", "Zero-duration opacity + no size animation - finalizing", placeholder.panelName);
|
||||
Qt.callLater(root.finalizeClose);
|
||||
}
|
||||
} else if (root.isPanelVisible && contentWrapper.opacity === 1.0) {
|
||||
root.openWatchdogActive = false
|
||||
openWatchdogTimer.stop()
|
||||
root.openWatchdogActive = false;
|
||||
openWatchdogTimer.stop();
|
||||
}
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
// When opacity fade completes during close, trigger size animation
|
||||
if (!running && root.isClosing && contentWrapper.opacity === 0.0) {
|
||||
root.opacityFadeComplete = true
|
||||
var shouldFinalizeNow = placeholder.panelItem && !placeholder.panelItem.shouldAnimateWidth && !placeholder.panelItem.shouldAnimateHeight
|
||||
root.opacityFadeComplete = true;
|
||||
var shouldFinalizeNow = placeholder.panelItem && !placeholder.panelItem.shouldAnimateWidth && !placeholder.panelItem.shouldAnimateHeight;
|
||||
if (shouldFinalizeNow) {
|
||||
Logger.d("SmartPanelWindow", "No animation - finalizing immediately", placeholder.panelName)
|
||||
Qt.callLater(root.finalizeClose)
|
||||
Logger.d("SmartPanelWindow", "No animation - finalizing immediately", placeholder.panelName);
|
||||
Qt.callLater(root.finalizeClose);
|
||||
} else {
|
||||
Logger.d("SmartPanelWindow", "Animation will run - waiting for size animation", placeholder.panelName)
|
||||
Logger.d("SmartPanelWindow", "Animation will run - waiting for size animation", placeholder.panelName);
|
||||
}
|
||||
} // When opacity fade completes during open, stop watchdog
|
||||
else if (!running && root.isPanelVisible && contentWrapper.opacity === 1.0) {
|
||||
root.openWatchdogActive = false
|
||||
openWatchdogTimer.stop()
|
||||
root.openWatchdogActive = false;
|
||||
openWatchdogTimer.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -330,31 +330,31 @@ PanelWindow {
|
||||
onLoaded: {
|
||||
// Capture initial content-driven size if available
|
||||
if (contentLoader.item) {
|
||||
var hasWidthProp = contentLoader.item.hasOwnProperty('contentPreferredWidth')
|
||||
var hasHeightProp = contentLoader.item.hasOwnProperty('contentPreferredHeight')
|
||||
var hasWidthProp = contentLoader.item.hasOwnProperty('contentPreferredWidth');
|
||||
var hasHeightProp = contentLoader.item.hasOwnProperty('contentPreferredHeight');
|
||||
|
||||
if (hasWidthProp || hasHeightProp) {
|
||||
var initialWidth = hasWidthProp ? contentLoader.item.contentPreferredWidth : 0
|
||||
var initialHeight = hasHeightProp ? contentLoader.item.contentPreferredHeight : 0
|
||||
placeholder.updateContentSize(initialWidth, initialHeight)
|
||||
Logger.d("SmartPanelWindow", "Initial content size:", initialWidth, "x", initialHeight, placeholder.panelName)
|
||||
var initialWidth = hasWidthProp ? contentLoader.item.contentPreferredWidth : 0;
|
||||
var initialHeight = hasHeightProp ? contentLoader.item.contentPreferredHeight : 0;
|
||||
placeholder.updateContentSize(initialWidth, initialHeight);
|
||||
Logger.d("SmartPanelWindow", "Initial content size:", initialWidth, "x", initialHeight, placeholder.panelName);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate position in placeholder
|
||||
placeholder.setPosition()
|
||||
placeholder.setPosition();
|
||||
|
||||
// Make panel visible on the next frame
|
||||
Qt.callLater(function () {
|
||||
root.isPanelVisible = true
|
||||
opacityTrigger.start()
|
||||
root.isPanelVisible = true;
|
||||
opacityTrigger.start();
|
||||
|
||||
// Start open watchdog timer
|
||||
root.openWatchdogActive = true
|
||||
openWatchdogTimer.start()
|
||||
root.openWatchdogActive = true;
|
||||
openWatchdogTimer.start();
|
||||
|
||||
panelOpened()
|
||||
})
|
||||
panelOpened();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -363,7 +363,7 @@ PanelWindow {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
onClicked: mouse => {
|
||||
mouse.accepted = true // Eat the click to prevent propagation to background
|
||||
mouse.accepted = true; // Eat the click to prevent propagation to background
|
||||
}
|
||||
z: -1 // Behind content but above background click-to-close
|
||||
}
|
||||
@@ -375,13 +375,13 @@ PanelWindow {
|
||||
|
||||
function onContentPreferredWidthChanged() {
|
||||
if (root.isPanelOpen && root.isPanelVisible && contentLoader.item) {
|
||||
placeholder.updateContentSize(contentLoader.item.contentPreferredWidth, placeholder.contentPreferredHeight)
|
||||
placeholder.updateContentSize(contentLoader.item.contentPreferredWidth, placeholder.contentPreferredHeight);
|
||||
}
|
||||
}
|
||||
|
||||
function onContentPreferredHeightChanged() {
|
||||
if (root.isPanelOpen && root.isPanelVisible && contentLoader.item) {
|
||||
placeholder.updateContentSize(placeholder.contentPreferredWidth, contentLoader.item.contentPreferredHeight)
|
||||
placeholder.updateContentSize(placeholder.contentPreferredWidth, contentLoader.item.contentPreferredHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -395,7 +395,7 @@ PanelWindow {
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (root.isPanelVisible) {
|
||||
root.sizeAnimationComplete = true
|
||||
root.sizeAnimationComplete = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -407,11 +407,11 @@ PanelWindow {
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (root.openWatchdogActive) {
|
||||
Logger.w("SmartPanelWindow", "Open watchdog timeout - forcing panel visible state", placeholder.panelName)
|
||||
root.openWatchdogActive = false
|
||||
Logger.w("SmartPanelWindow", "Open watchdog timeout - forcing panel visible state", placeholder.panelName);
|
||||
root.openWatchdogActive = false;
|
||||
if (root.isPanelOpen && !root.isPanelVisible) {
|
||||
root.isPanelVisible = true
|
||||
root.sizeAnimationComplete = true
|
||||
root.isPanelVisible = true;
|
||||
root.sizeAnimationComplete = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -424,8 +424,8 @@ PanelWindow {
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (root.closeWatchdogActive && !root.closeFinalized) {
|
||||
Logger.w("SmartPanelWindow", "Close watchdog timeout - forcing panel close", placeholder.panelName)
|
||||
Qt.callLater(root.finalizeClose)
|
||||
Logger.w("SmartPanelWindow", "Close watchdog timeout - forcing panel close", placeholder.panelName);
|
||||
Qt.callLater(root.finalizeClose);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -437,14 +437,14 @@ PanelWindow {
|
||||
function onWidthChanged() {
|
||||
// When width shrinks to 0 during close and we're animating width, finalize
|
||||
if (root.isClosing && placeholder.panelItem.width === 0 && placeholder.panelItem.shouldAnimateWidth) {
|
||||
Qt.callLater(root.finalizeClose)
|
||||
Qt.callLater(root.finalizeClose);
|
||||
}
|
||||
}
|
||||
|
||||
function onHeightChanged() {
|
||||
// When height shrinks to 0 during close and we're animating height, finalize
|
||||
if (root.isClosing && placeholder.panelItem.height === 0 && placeholder.panelItem.shouldAnimateHeight) {
|
||||
Qt.callLater(root.finalizeClose)
|
||||
Qt.callLater(root.finalizeClose);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import qs.Modules.Bar.Extras
|
||||
import qs.Services.UI
|
||||
|
||||
// Separate window for TrayMenu context menus
|
||||
// This is a top-level PanelWindow (sibling to MainScreen, not nested inside it)
|
||||
@@ -30,18 +30,18 @@ PanelWindow {
|
||||
|
||||
// Register with PanelService so panels can find this window
|
||||
Component.onCompleted: {
|
||||
objectName = "trayMenuWindow-" + (screen?.name || "unknown")
|
||||
PanelService.registerTrayMenuWindow(screen, root)
|
||||
objectName = "trayMenuWindow-" + (screen?.name || "unknown");
|
||||
PanelService.registerTrayMenuWindow(screen, root);
|
||||
}
|
||||
|
||||
function open() {
|
||||
visible = true
|
||||
visible = true;
|
||||
}
|
||||
|
||||
function close() {
|
||||
visible = false
|
||||
visible = false;
|
||||
if (trayMenu.item) {
|
||||
trayMenu.item.hideMenu()
|
||||
trayMenu.item.hideMenu();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ PanelWindow {
|
||||
source: Quickshell.shellDir + "/Modules/Bar/Extras/TrayMenu.qml"
|
||||
onLoaded: {
|
||||
if (item) {
|
||||
item.screen = root.screen
|
||||
item.screen = root.screen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ import QtQuick
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Services.Notifications
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import qs.Commons
|
||||
import qs.Services.System
|
||||
import qs.Widgets
|
||||
@@ -35,7 +35,7 @@ Variants {
|
||||
target: notificationModel
|
||||
function onCountChanged() {
|
||||
if (notificationModel.count === 0 && root.active) {
|
||||
delayTimer.restart()
|
||||
delayTimer.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,30 +66,30 @@ Variants {
|
||||
// Calculate bar offsets for each edge separately
|
||||
readonly property int barOffsetTop: {
|
||||
if (barPos !== "top")
|
||||
return 0
|
||||
const floatMarginV = isFloating ? Settings.data.bar.marginVertical * Style.marginXL : 0
|
||||
return Style.barHeight + floatMarginV
|
||||
return 0;
|
||||
const floatMarginV = isFloating ? Settings.data.bar.marginVertical * Style.marginXL : 0;
|
||||
return Style.barHeight + floatMarginV;
|
||||
}
|
||||
|
||||
readonly property int barOffsetBottom: {
|
||||
if (barPos !== "bottom")
|
||||
return 0
|
||||
const floatMarginV = isFloating ? Settings.data.bar.marginVertical * Style.marginXL : 0
|
||||
return Style.barHeight + floatMarginV
|
||||
return 0;
|
||||
const floatMarginV = isFloating ? Settings.data.bar.marginVertical * Style.marginXL : 0;
|
||||
return Style.barHeight + floatMarginV;
|
||||
}
|
||||
|
||||
readonly property int barOffsetLeft: {
|
||||
if (barPos !== "left")
|
||||
return 0
|
||||
const floatMarginH = isFloating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0
|
||||
return floatMarginH
|
||||
return 0;
|
||||
const floatMarginH = isFloating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0;
|
||||
return floatMarginH;
|
||||
}
|
||||
|
||||
readonly property int barOffsetRight: {
|
||||
if (barPos !== "right")
|
||||
return 0
|
||||
const floatMarginH = isFloating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0
|
||||
return floatMarginH
|
||||
return 0;
|
||||
const floatMarginH = isFloating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0;
|
||||
return floatMarginH;
|
||||
}
|
||||
|
||||
// Anchoring
|
||||
@@ -111,31 +111,31 @@ Variants {
|
||||
|
||||
Component.onCompleted: {
|
||||
animateConnection = function (notificationId) {
|
||||
var delegate = null
|
||||
var delegate = null;
|
||||
if (notificationRepeater) {
|
||||
for (var i = 0; i < notificationRepeater.count; i++) {
|
||||
var item = notificationRepeater.itemAt(i)
|
||||
var item = notificationRepeater.itemAt(i);
|
||||
if (item?.notificationId === notificationId) {
|
||||
delegate = item
|
||||
break
|
||||
delegate = item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (delegate?.animateOut) {
|
||||
delegate.animateOut()
|
||||
delegate.animateOut();
|
||||
} else {
|
||||
NotificationService.dismissActiveNotification(notificationId)
|
||||
NotificationService.dismissActiveNotification(notificationId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
NotificationService.animateAndRemove.connect(animateConnection)
|
||||
NotificationService.animateAndRemove.connect(animateConnection);
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
if (animateConnection) {
|
||||
NotificationService.animateAndRemove.disconnect(animateConnection)
|
||||
animateConnection = null
|
||||
NotificationService.animateAndRemove.disconnect(animateConnection);
|
||||
animateConnection = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,8 +223,8 @@ Variants {
|
||||
width: parent.availableWidth * model.progress
|
||||
|
||||
color: {
|
||||
var baseColor = model.urgency === 2 ? Color.mError : model.urgency === 0 ? Color.mOnSurface : Color.mPrimary
|
||||
return Qt.alpha(baseColor, Settings.data.notifications.backgroundOpacity || 1.0)
|
||||
var baseColor = model.urgency === 2 ? Color.mError : model.urgency === 0 ? Color.mOnSurface : Color.mPrimary;
|
||||
return Qt.alpha(baseColor, Settings.data.notifications.backgroundOpacity || 1.0);
|
||||
}
|
||||
|
||||
antialiasing: true
|
||||
@@ -257,10 +257,10 @@ Variants {
|
||||
// Hover handling
|
||||
onHoverCountChanged: {
|
||||
if (hoverCount > 0) {
|
||||
resumeTimer.stop()
|
||||
NotificationService.pauseTimeout(notificationId)
|
||||
resumeTimer.stop();
|
||||
NotificationService.pauseTimeout(notificationId);
|
||||
} else {
|
||||
resumeTimer.start()
|
||||
resumeTimer.start();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,7 +270,7 @@ Variants {
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (hoverCount === 0) {
|
||||
NotificationService.resumeTimeout(notificationId)
|
||||
NotificationService.resumeTimeout(notificationId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -284,30 +284,30 @@ Variants {
|
||||
onExited: parent.hoverCount--
|
||||
onClicked: {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
animateOut()
|
||||
animateOut();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Animation setup
|
||||
function triggerEntryAnimation() {
|
||||
animInDelayTimer.stop()
|
||||
removalTimer.stop()
|
||||
resumeTimer.stop()
|
||||
isRemoving = false
|
||||
hoverCount = 0
|
||||
animInDelayTimer.stop();
|
||||
removalTimer.stop();
|
||||
resumeTimer.stop();
|
||||
isRemoving = false;
|
||||
hoverCount = 0;
|
||||
if (Settings.data.general.animationDisabled) {
|
||||
slideOffset = 0
|
||||
scaleValue = 1.0
|
||||
opacityValue = 1.0
|
||||
return
|
||||
slideOffset = 0;
|
||||
scaleValue = 1.0;
|
||||
opacityValue = 1.0;
|
||||
return;
|
||||
}
|
||||
|
||||
slideOffset = slideInOffset
|
||||
scaleValue = 0.8
|
||||
opacityValue = 0.0
|
||||
animInDelayTimer.interval = animationDelay
|
||||
animInDelayTimer.start()
|
||||
slideOffset = slideInOffset;
|
||||
scaleValue = 0.8;
|
||||
opacityValue = 0.0;
|
||||
animInDelayTimer.interval = animationDelay;
|
||||
animInDelayTimer.start();
|
||||
}
|
||||
|
||||
Component.onCompleted: triggerEntryAnimation()
|
||||
@@ -320,23 +320,23 @@ Variants {
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (card.isRemoving)
|
||||
return
|
||||
slideOffset = 0
|
||||
scaleValue = 1.0
|
||||
opacityValue = 1.0
|
||||
return;
|
||||
slideOffset = 0;
|
||||
scaleValue = 1.0;
|
||||
opacityValue = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
function animateOut() {
|
||||
if (isRemoving)
|
||||
return
|
||||
animInDelayTimer.stop()
|
||||
resumeTimer.stop()
|
||||
isRemoving = true
|
||||
return;
|
||||
animInDelayTimer.stop();
|
||||
resumeTimer.stop();
|
||||
isRemoving = true;
|
||||
if (!Settings.data.general.animationDisabled) {
|
||||
slideOffset = slideOutOffset
|
||||
scaleValue = 0.8
|
||||
opacityValue = 0.0
|
||||
slideOffset = slideOutOffset;
|
||||
scaleValue = 0.8;
|
||||
opacityValue = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,13 +345,13 @@ Variants {
|
||||
interval: Style.animationSlow
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
NotificationService.dismissActiveNotification(notificationId)
|
||||
NotificationService.dismissActiveNotification(notificationId);
|
||||
}
|
||||
}
|
||||
|
||||
onIsRemovingChanged: {
|
||||
if (isRemoving) {
|
||||
removalTimer.start()
|
||||
removalTimer.start();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -478,9 +478,9 @@ Variants {
|
||||
property string parentNotificationId: notificationId
|
||||
property var parsedActions: {
|
||||
try {
|
||||
return model.actionsJson ? JSON.parse(model.actionsJson) : []
|
||||
return model.actionsJson ? JSON.parse(model.actionsJson) : [];
|
||||
} catch (e) {
|
||||
return []
|
||||
return [];
|
||||
}
|
||||
}
|
||||
visible: parsedActions.length > 0
|
||||
@@ -495,11 +495,11 @@ Variants {
|
||||
onExited: card.hoverCount--
|
||||
|
||||
text: {
|
||||
var actionText = actionData.text || "Open"
|
||||
var actionText = actionData.text || "Open";
|
||||
if (actionText.includes(",")) {
|
||||
return actionText.split(",")[1] || actionText
|
||||
return actionText.split(",")[1] || actionText;
|
||||
}
|
||||
return actionText
|
||||
return actionText;
|
||||
}
|
||||
fontSize: Style.fontSizeS
|
||||
backgroundColor: Color.mPrimary
|
||||
@@ -508,7 +508,7 @@ Variants {
|
||||
outlined: false
|
||||
implicitHeight: 24
|
||||
onClicked: {
|
||||
NotificationService.invokeAction(parent.parentNotificationId, actionData.identifier)
|
||||
NotificationService.invokeAction(parent.parentNotificationId, actionData.identifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -528,8 +528,8 @@ Variants {
|
||||
anchors.rightMargin: Style.marginXL
|
||||
|
||||
onClicked: {
|
||||
NotificationService.removeFromHistory(model.id)
|
||||
animateOut()
|
||||
NotificationService.removeFromHistory(model.id);
|
||||
animateOut();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+142
-133
@@ -1,15 +1,15 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Services.Pipewire
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
import qs.Services.Hardware
|
||||
import qs.Services.Media
|
||||
import qs.Services.System
|
||||
import qs.Widgets
|
||||
|
||||
// Unified OSD component that displays volume, input volume, and brightness changes
|
||||
Variants {
|
||||
@@ -52,54 +52,54 @@ Variants {
|
||||
switch (currentOSDType) {
|
||||
case "volume":
|
||||
if (isMuted)
|
||||
return "volume-mute"
|
||||
return "volume-mute";
|
||||
if (currentVolume <= Number.EPSILON)
|
||||
return "volume-zero"
|
||||
return currentVolume <= 0.5 ? "volume-low" : "volume-high"
|
||||
return "volume-zero";
|
||||
return currentVolume <= 0.5 ? "volume-low" : "volume-high";
|
||||
case "inputVolume":
|
||||
return isInputMuted ? "microphone-off" : "microphone"
|
||||
return isInputMuted ? "microphone-off" : "microphone";
|
||||
case "brightness":
|
||||
return currentBrightness <= 0.5 ? "brightness-low" : "brightness-high"
|
||||
return currentBrightness <= 0.5 ? "brightness-low" : "brightness-high";
|
||||
default:
|
||||
return ""
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function getCurrentValue() {
|
||||
switch (currentOSDType) {
|
||||
case "volume":
|
||||
return isMuted ? 0 : currentVolume
|
||||
return isMuted ? 0 : currentVolume;
|
||||
case "inputVolume":
|
||||
return isInputMuted ? 0 : currentInputVolume
|
||||
return isInputMuted ? 0 : currentInputVolume;
|
||||
case "brightness":
|
||||
return currentBrightness
|
||||
return currentBrightness;
|
||||
default:
|
||||
return 0
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
function getMaxValue() {
|
||||
if (currentOSDType === "volume" || currentOSDType === "inputVolume") {
|
||||
return Settings.data.audio.volumeOverdrive ? 1.5 : 1.0
|
||||
return Settings.data.audio.volumeOverdrive ? 1.5 : 1.0;
|
||||
}
|
||||
return 1.0
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
function getDisplayPercentage() {
|
||||
const value = getCurrentValue()
|
||||
const max = getMaxValue()
|
||||
const pct = Math.round(Math.min(max, value) * 100)
|
||||
return pct + "%"
|
||||
const value = getCurrentValue();
|
||||
const max = getMaxValue();
|
||||
const pct = Math.round(Math.min(max, value) * 100);
|
||||
return pct + "%";
|
||||
}
|
||||
|
||||
function getProgressColor() {
|
||||
const isMutedState = (currentOSDType === "volume" && isMuted) || (currentOSDType === "inputVolume" && isInputMuted)
|
||||
return isMutedState ? Color.mError : Color.mPrimary
|
||||
const isMutedState = (currentOSDType === "volume" && isMuted) || (currentOSDType === "inputVolume" && isInputMuted);
|
||||
return isMutedState ? Color.mError : Color.mPrimary;
|
||||
}
|
||||
|
||||
function getIconColor() {
|
||||
const isMutedState = (currentOSDType === "volume" && isMuted) || (currentOSDType === "inputVolume" && isInputMuted)
|
||||
return isMutedState ? Color.mError : Color.mOnSurface
|
||||
const isMutedState = (currentOSDType === "volume" && isMuted) || (currentOSDType === "inputVolume" && isInputMuted);
|
||||
return isMutedState ? Color.mError : Color.mOnSurface;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -108,35 +108,35 @@ Variants {
|
||||
function initializeAudioValues() {
|
||||
// Initialize output volume
|
||||
if (AudioService.sink?.ready && AudioService.sink?.audio && lastKnownVolume < 0) {
|
||||
const vol = AudioService.volume
|
||||
const vol = AudioService.volume;
|
||||
if (vol !== undefined && !isNaN(vol)) {
|
||||
lastKnownVolume = vol
|
||||
volumeInitialized = true
|
||||
muteInitialized = true
|
||||
lastKnownVolume = vol;
|
||||
volumeInitialized = true;
|
||||
muteInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize input volume
|
||||
if (AudioService.hasInput && AudioService.source?.ready && AudioService.source?.audio && lastKnownInputVolume < 0) {
|
||||
const inputVol = AudioService.inputVolume
|
||||
const inputVol = AudioService.inputVolume;
|
||||
if (inputVol !== undefined && !isNaN(inputVol)) {
|
||||
lastKnownInputVolume = inputVol
|
||||
inputInitialized = true
|
||||
lastKnownInputVolume = inputVol;
|
||||
inputInitialized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resetOutputInit() {
|
||||
lastKnownVolume = -1
|
||||
volumeInitialized = false
|
||||
muteInitialized = false
|
||||
Qt.callLater(initializeAudioValues)
|
||||
lastKnownVolume = -1;
|
||||
volumeInitialized = false;
|
||||
muteInitialized = false;
|
||||
Qt.callLater(initializeAudioValues);
|
||||
}
|
||||
|
||||
function resetInputInit() {
|
||||
lastKnownInputVolume = -1
|
||||
inputInitialized = false
|
||||
Qt.callLater(initializeAudioValues)
|
||||
lastKnownInputVolume = -1;
|
||||
inputInitialized = false;
|
||||
Qt.callLater(initializeAudioValues);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -144,48 +144,48 @@ Variants {
|
||||
// ============================================================================
|
||||
function connectBrightnessMonitors() {
|
||||
for (var i = 0; i < BrightnessService.monitors.length; i++) {
|
||||
const monitor = BrightnessService.monitors[i]
|
||||
monitor.brightnessUpdated.disconnect(onBrightnessChanged)
|
||||
monitor.brightnessUpdated.connect(onBrightnessChanged)
|
||||
const monitor = BrightnessService.monitors[i];
|
||||
monitor.brightnessUpdated.disconnect(onBrightnessChanged);
|
||||
monitor.brightnessUpdated.connect(onBrightnessChanged);
|
||||
}
|
||||
}
|
||||
|
||||
function onBrightnessChanged(newBrightness) {
|
||||
lastUpdatedBrightness = newBrightness
|
||||
lastUpdatedBrightness = newBrightness;
|
||||
|
||||
if (!brightnessInitialized) {
|
||||
brightnessInitialized = true
|
||||
return
|
||||
brightnessInitialized = true;
|
||||
return;
|
||||
}
|
||||
|
||||
showOSD("brightness")
|
||||
showOSD("brightness");
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// OSD Display Control
|
||||
// ============================================================================
|
||||
function showOSD(type) {
|
||||
currentOSDType = type
|
||||
currentOSDType = type;
|
||||
|
||||
if (!root.active) {
|
||||
root.active = true
|
||||
root.active = true;
|
||||
}
|
||||
|
||||
if (root.item) {
|
||||
root.item.showOSD()
|
||||
root.item.showOSD();
|
||||
} else {
|
||||
Qt.callLater(() => {
|
||||
if (root.item)
|
||||
root.item.showOSD()
|
||||
})
|
||||
root.item.showOSD();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function hideOSD() {
|
||||
if (root.item?.osdItem) {
|
||||
root.item.osdItem.hideImmediately()
|
||||
root.item.osdItem.hideImmediately();
|
||||
} else if (root.active) {
|
||||
root.active = false
|
||||
root.active = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,15 +199,15 @@ Variants {
|
||||
|
||||
function onReadyChanged() {
|
||||
if (Pipewire.ready)
|
||||
Qt.callLater(initializeAudioValues)
|
||||
Qt.callLater(initializeAudioValues);
|
||||
}
|
||||
|
||||
function onDefaultAudioSinkChanged() {
|
||||
resetOutputInit()
|
||||
resetOutputInit();
|
||||
}
|
||||
|
||||
function onDefaultAudioSourceChanged() {
|
||||
resetInputInit()
|
||||
resetInputInit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,72 +217,68 @@ Variants {
|
||||
|
||||
function onSinkChanged() {
|
||||
if (AudioService.sink?.ready && AudioService.sink?.audio) {
|
||||
resetOutputInit()
|
||||
resetOutputInit();
|
||||
}
|
||||
}
|
||||
|
||||
function onSourceChanged() {
|
||||
if (AudioService.hasInput && AudioService.source?.ready && AudioService.source?.audio) {
|
||||
resetInputInit()
|
||||
resetInputInit();
|
||||
}
|
||||
}
|
||||
|
||||
function onVolumeChanged() {
|
||||
if (lastKnownVolume < 0) {
|
||||
initializeAudioValues()
|
||||
initializeAudioValues();
|
||||
if (lastKnownVolume < 0)
|
||||
return
|
||||
return;
|
||||
}
|
||||
if (!volumeInitialized)
|
||||
return
|
||||
|
||||
return;
|
||||
if (Math.abs(AudioService.volume - lastKnownVolume) > 0.001) {
|
||||
lastKnownVolume = AudioService.volume
|
||||
showOSD("volume")
|
||||
lastKnownVolume = AudioService.volume;
|
||||
showOSD("volume");
|
||||
}
|
||||
}
|
||||
|
||||
function onMutedChanged() {
|
||||
if (lastKnownVolume < 0) {
|
||||
initializeAudioValues()
|
||||
initializeAudioValues();
|
||||
if (lastKnownVolume < 0)
|
||||
return
|
||||
return;
|
||||
}
|
||||
if (!muteInitialized)
|
||||
return
|
||||
showOSD("volume")
|
||||
return;
|
||||
showOSD("volume");
|
||||
}
|
||||
|
||||
function onInputVolumeChanged() {
|
||||
if (!AudioService.hasInput)
|
||||
return
|
||||
|
||||
return;
|
||||
if (lastKnownInputVolume < 0) {
|
||||
initializeAudioValues()
|
||||
initializeAudioValues();
|
||||
if (lastKnownInputVolume < 0)
|
||||
return
|
||||
return;
|
||||
}
|
||||
if (!inputInitialized)
|
||||
return
|
||||
|
||||
return;
|
||||
if (Math.abs(AudioService.inputVolume - lastKnownInputVolume) > 0.001) {
|
||||
lastKnownInputVolume = AudioService.inputVolume
|
||||
showOSD("inputVolume")
|
||||
lastKnownInputVolume = AudioService.inputVolume;
|
||||
showOSD("inputVolume");
|
||||
}
|
||||
}
|
||||
|
||||
function onInputMutedChanged() {
|
||||
if (!AudioService.hasInput)
|
||||
return
|
||||
|
||||
return;
|
||||
if (lastKnownInputVolume < 0) {
|
||||
initializeAudioValues()
|
||||
initializeAudioValues();
|
||||
if (lastKnownInputVolume < 0)
|
||||
return
|
||||
return;
|
||||
}
|
||||
if (!inputInitialized)
|
||||
return
|
||||
showOSD("inputVolume")
|
||||
return;
|
||||
showOSD("inputVolume");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,7 +286,7 @@ Variants {
|
||||
Connections {
|
||||
target: BrightnessService
|
||||
function onMonitorsChanged() {
|
||||
connectBrightnessMonitors()
|
||||
connectBrightnessMonitors();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,9 +297,9 @@ Variants {
|
||||
running: true
|
||||
onTriggered: {
|
||||
if (Pipewire.ready)
|
||||
initializeAudioValues()
|
||||
muteInitialized = true
|
||||
connectBrightnessMonitors()
|
||||
initializeAudioValues();
|
||||
muteInitialized = true;
|
||||
connectBrightnessMonitors();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,22 +310,21 @@ Variants {
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
if (!Pipewire.ready)
|
||||
return
|
||||
|
||||
const needsOutputInit = lastKnownVolume < 0
|
||||
const needsInputInit = AudioService.hasInput && lastKnownInputVolume < 0
|
||||
return;
|
||||
const needsOutputInit = lastKnownVolume < 0;
|
||||
const needsInputInit = AudioService.hasInput && lastKnownInputVolume < 0;
|
||||
|
||||
if (needsOutputInit || needsInputInit) {
|
||||
initializeAudioValues()
|
||||
initializeAudioValues();
|
||||
|
||||
// Stop timer if both are initialized
|
||||
const outputDone = lastKnownVolume >= 0
|
||||
const inputDone = !AudioService.hasInput || lastKnownInputVolume >= 0
|
||||
const outputDone = lastKnownVolume >= 0;
|
||||
const inputDone = !AudioService.hasInput || lastKnownInputVolume >= 0;
|
||||
if (outputDone && inputDone) {
|
||||
running = false
|
||||
running = false;
|
||||
}
|
||||
} else {
|
||||
running = false
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -352,11 +347,11 @@ Variants {
|
||||
// Dimensions
|
||||
readonly property int hWidth: Math.round(320 * Style.uiScaleRatio)
|
||||
readonly property int hHeight: Math.round(72 * Style.uiScaleRatio)
|
||||
readonly property int vWidth: Math.round(72 * Style.uiScaleRatio)
|
||||
readonly property int vWidth: Math.round(80 * Style.uiScaleRatio)
|
||||
readonly property int vHeight: Math.round(280 * Style.uiScaleRatio)
|
||||
readonly property int barThickness: {
|
||||
const base = Math.max(8, Math.round(8 * Style.uiScaleRatio))
|
||||
return base % 2 === 0 ? base : base + 1
|
||||
const base = Math.max(8, Math.round(8 * Style.uiScaleRatio));
|
||||
return base % 2 === 0 ? base : base + 1;
|
||||
}
|
||||
|
||||
anchors.top: isTop
|
||||
@@ -366,15 +361,15 @@ Variants {
|
||||
|
||||
function calculateMargin(isAnchored, position) {
|
||||
if (!isAnchored)
|
||||
return 0
|
||||
return 0;
|
||||
|
||||
let base = Style.marginM
|
||||
let base = Style.marginM;
|
||||
if (Settings.data.bar.position === position) {
|
||||
const isVertical = position === "top" || position === "bottom"
|
||||
const floatExtra = Settings.data.bar.floating ? (isVertical ? Settings.data.bar.marginVertical : Settings.data.bar.marginHorizontal) * Style.marginXL : 0
|
||||
return Style.barHeight + base + floatExtra
|
||||
const isVertical = position === "top" || position === "bottom";
|
||||
const floatExtra = Settings.data.bar.floating ? (isVertical ? Settings.data.bar.marginVertical : Settings.data.bar.marginHorizontal) * Style.marginXL : 0;
|
||||
return Style.barHeight + base + floatExtra;
|
||||
}
|
||||
return base
|
||||
return base;
|
||||
}
|
||||
|
||||
margins.top: calculateMargin(anchors.top, "top")
|
||||
@@ -389,7 +384,7 @@ Variants {
|
||||
WlrLayershell.namespace: "noctalia-osd-" + (screen?.name || "unknown")
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
WlrLayershell.layer: Settings.data.osd?.overlayLayer ? WlrLayer.Overlay : WlrLayer.Top
|
||||
exclusionMode: PanelWindow.ExclusionMode.Ignore
|
||||
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
||||
|
||||
Item {
|
||||
id: osdItem
|
||||
@@ -422,9 +417,9 @@ Variants {
|
||||
id: visibilityTimer
|
||||
interval: Style.animationNormal + 50
|
||||
onTriggered: {
|
||||
osdItem.visible = false
|
||||
root.currentOSDType = ""
|
||||
root.active = false
|
||||
osdItem.visible = false;
|
||||
root.currentOSDType = "";
|
||||
root.active = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -436,8 +431,8 @@ Variants {
|
||||
color: Qt.alpha(Color.mSurface, Settings.data.osd.backgroundOpacity || 1.0)
|
||||
border.color: Qt.alpha(Color.mOutline, Settings.data.osd.backgroundOpacity || 1.0)
|
||||
border.width: {
|
||||
const bw = Math.max(2, Style.borderM)
|
||||
return bw % 2 === 0 ? bw : bw + 1
|
||||
const bw = Math.max(2, Style.borderM);
|
||||
return bw % 2 === 0 ? bw : bw + 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,11 +453,20 @@ Variants {
|
||||
Component {
|
||||
id: horizontalContent
|
||||
RowLayout {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.margins: Style.marginL
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Style.marginL
|
||||
anchors.rightMargin: Style.marginL
|
||||
spacing: Style.marginM
|
||||
clip: true
|
||||
|
||||
// TextMetrics to measure the maximum possible percentage width (150%)
|
||||
TextMetrics {
|
||||
id: percentageMetrics
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.weight: Style.fontWeightMedium
|
||||
font.pointSize: Style.fontSizeS * (Settings.data.ui.fontFixedScale * Style.uiScaleRatio)
|
||||
text: "150%" // Maximum possible value with volumeOverdrive
|
||||
}
|
||||
|
||||
NIcon {
|
||||
icon: root.getIcon()
|
||||
@@ -480,6 +484,7 @@ Variants {
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
height: panel.barThickness
|
||||
radius: Math.round(panel.barThickness / 2)
|
||||
color: Color.mSurfaceVariant
|
||||
@@ -516,7 +521,10 @@ Variants {
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
|
||||
horizontalAlignment: Text.AlignRight
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
Layout.preferredWidth: Math.round(50 * Style.uiScaleRatio)
|
||||
Layout.fillWidth: false
|
||||
Layout.preferredWidth: Math.ceil(percentageMetrics.width) + Math.round(8 * Style.uiScaleRatio)
|
||||
Layout.maximumWidth: Math.ceil(percentageMetrics.width) + Math.round(8 * Style.uiScaleRatio)
|
||||
Layout.minimumWidth: Math.ceil(percentageMetrics.width)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -528,6 +536,7 @@ Variants {
|
||||
anchors.topMargin: Style.marginL
|
||||
anchors.bottomMargin: Style.marginL
|
||||
spacing: Style.marginS
|
||||
clip: true
|
||||
|
||||
NText {
|
||||
text: root.getDisplayPercentage()
|
||||
@@ -594,39 +603,39 @@ Variants {
|
||||
}
|
||||
|
||||
function show() {
|
||||
hideTimer.stop()
|
||||
visibilityTimer.stop()
|
||||
osdItem.visible = true
|
||||
hideTimer.stop();
|
||||
visibilityTimer.stop();
|
||||
osdItem.visible = true;
|
||||
|
||||
Qt.callLater(() => {
|
||||
osdItem.opacity = 1
|
||||
osdItem.scale = 1.0
|
||||
})
|
||||
osdItem.opacity = 1;
|
||||
osdItem.scale = 1.0;
|
||||
});
|
||||
|
||||
hideTimer.start()
|
||||
hideTimer.start();
|
||||
}
|
||||
|
||||
function hide() {
|
||||
hideTimer.stop()
|
||||
visibilityTimer.stop()
|
||||
osdItem.opacity = 0
|
||||
osdItem.scale = 0.85
|
||||
visibilityTimer.start()
|
||||
hideTimer.stop();
|
||||
visibilityTimer.stop();
|
||||
osdItem.opacity = 0;
|
||||
osdItem.scale = 0.85;
|
||||
visibilityTimer.start();
|
||||
}
|
||||
|
||||
function hideImmediately() {
|
||||
hideTimer.stop()
|
||||
visibilityTimer.stop()
|
||||
osdItem.opacity = 0
|
||||
osdItem.scale = 0.85
|
||||
osdItem.visible = false
|
||||
root.currentOSDType = ""
|
||||
root.active = false
|
||||
hideTimer.stop();
|
||||
visibilityTimer.stop();
|
||||
osdItem.opacity = 0;
|
||||
osdItem.scale = 0.85;
|
||||
osdItem.visible = false;
|
||||
root.currentOSDType = "";
|
||||
root.active = false;
|
||||
}
|
||||
}
|
||||
|
||||
function showOSD() {
|
||||
osdItem.show()
|
||||
osdItem.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Services.Pipewire
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
import qs.Modules.MainScreen
|
||||
import qs.Services.Media
|
||||
import qs.Widgets
|
||||
|
||||
SmartPanel {
|
||||
id: root
|
||||
@@ -25,7 +25,7 @@ SmartPanel {
|
||||
target: AudioService.sink?.audio ? AudioService.sink?.audio : null
|
||||
function onVolumeChanged() {
|
||||
if (!localOutputVolumeChanging) {
|
||||
localOutputVolume = AudioService.volume
|
||||
localOutputVolume = AudioService.volume;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ SmartPanel {
|
||||
target: AudioService.source?.audio ? AudioService.source?.audio : null
|
||||
function onVolumeChanged() {
|
||||
if (!localInputVolumeChanging) {
|
||||
localInputVolume = AudioService.inputVolume
|
||||
localInputVolume = AudioService.inputVolume;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,10 +46,10 @@ SmartPanel {
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
if (Math.abs(localOutputVolume - AudioService.volume) >= 0.01) {
|
||||
AudioService.setVolume(localOutputVolume)
|
||||
AudioService.setVolume(localOutputVolume);
|
||||
}
|
||||
if (Math.abs(localInputVolume - AudioService.inputVolume) >= 0.01) {
|
||||
AudioService.setInputVolume(localInputVolume)
|
||||
AudioService.setInputVolume(localInputVolume);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,7 +95,7 @@ SmartPanel {
|
||||
tooltipText: I18n.tr("tooltips.output-muted")
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: {
|
||||
AudioService.setOutputMuted(!AudioService.muted)
|
||||
AudioService.setOutputMuted(!AudioService.muted);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ SmartPanel {
|
||||
tooltipText: I18n.tr("tooltips.input-muted")
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: {
|
||||
AudioService.setInputMuted(!AudioService.inputMuted)
|
||||
AudioService.setInputMuted(!AudioService.inputMuted);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ SmartPanel {
|
||||
tooltipText: I18n.tr("tooltips.close")
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: {
|
||||
root.close()
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,8 +179,8 @@ SmartPanel {
|
||||
text: modelData.description
|
||||
checked: AudioService.sink?.id === modelData.id
|
||||
onClicked: {
|
||||
AudioService.setAudioSink(modelData)
|
||||
localOutputVolume = AudioService.volume
|
||||
AudioService.setAudioSink(modelData);
|
||||
localOutputVolume = AudioService.volume;
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
import qs.Services.Hardware
|
||||
import qs.Widgets
|
||||
import qs.Modules.MainScreen
|
||||
|
||||
SmartPanel {
|
||||
id: root
|
||||
|
||||
property var optionsModel: []
|
||||
|
||||
function updateOptionsModel() {
|
||||
let newOptions = [{
|
||||
"id": BatteryService.ChargingMode.Full,
|
||||
"label": "battery.panel.full"
|
||||
}, {
|
||||
"id": BatteryService.ChargingMode.Balanced,
|
||||
"label": "battery.panel.balanced"
|
||||
}, {
|
||||
"id": BatteryService.ChargingMode.Lifespan,
|
||||
"label": "battery.panel.lifespan"
|
||||
}]
|
||||
root.optionsModel = newOptions
|
||||
}
|
||||
|
||||
onOpened: {
|
||||
updateOptionsModel()
|
||||
}
|
||||
|
||||
ButtonGroup {
|
||||
id: batteryGroup
|
||||
}
|
||||
|
||||
Component {
|
||||
id: optionsComponent
|
||||
ColumnLayout {
|
||||
spacing: Style.marginM
|
||||
Repeater {
|
||||
model: root.optionsModel
|
||||
delegate: NRadioButton {
|
||||
ButtonGroup.group: batteryGroup
|
||||
required property var modelData
|
||||
text: I18n.tr(modelData.label, {
|
||||
"percent": BatteryService.getThresholdValue(modelData.id)
|
||||
})
|
||||
checked: BatteryService.chargingMode === modelData.id
|
||||
onClicked: {
|
||||
BatteryService.setChargingMode(modelData.id)
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: disabledComponent
|
||||
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
spacing: Style.marginM
|
||||
|
||||
NIcon {
|
||||
icon: "recharging"
|
||||
pointSize: 48
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: I18n.tr("battery.panel.disabled")
|
||||
pointSize: Style.fontSizeL
|
||||
color: Color.mOnSurfaceVariant
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
panelContent: Item {
|
||||
anchors.fill: parent
|
||||
|
||||
property real contentPreferredWidth: Math.round(340 * Style.uiScaleRatio)
|
||||
property real contentPreferredHeight: Math.round(mainLayout.implicitHeight + Style.marginM * 2)
|
||||
|
||||
ColumnLayout {
|
||||
id: mainLayout
|
||||
anchors.centerIn: parent
|
||||
width: parent.contentPreferredWidth - Style.marginM * 2
|
||||
anchors.margins: Style.marginM
|
||||
spacing: Style.marginM
|
||||
|
||||
// HEADER
|
||||
NBox {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: header.implicitHeight + Style.marginM * 2
|
||||
|
||||
RowLayout {
|
||||
id: header
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginM
|
||||
spacing: Style.marginM
|
||||
|
||||
NIcon {
|
||||
icon: "battery-4"
|
||||
pointSize: Style.fontSizeXXL
|
||||
color: Color.mPrimary
|
||||
}
|
||||
|
||||
NText {
|
||||
text: I18n.tr("battery.panel.title")
|
||||
pointSize: Style.fontSizeL
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NToggle {
|
||||
id: batteryManagerSwitch
|
||||
checked: BatteryService.chargingMode !== BatteryService.ChargingMode.Disabled
|
||||
onToggled: checked => BatteryService.toggleEnabled(checked)
|
||||
baseSize: Style.baseWidgetSize * 0.65
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "close"
|
||||
tooltipText: I18n.tr("tooltips.close")
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: {
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NBox {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: loader.implicitHeight + Style.marginM * 2
|
||||
|
||||
Loader {
|
||||
id: loader
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - Style.marginM * 2
|
||||
sourceComponent: BatteryService.chargingMode === BatteryService.ChargingMode.Disabled ? disabledComponent : optionsComponent
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Bluetooth
|
||||
import Quickshell.Wayland
|
||||
@@ -13,9 +13,7 @@ NBox {
|
||||
|
||||
property string label: ""
|
||||
property string tooltipText: ""
|
||||
property var model: {
|
||||
|
||||
}
|
||||
property var model: {}
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: column.implicitHeight + Style.marginM * 2
|
||||
@@ -52,10 +50,10 @@ NBox {
|
||||
|
||||
function getContentColor(defaultColor = Color.mOnSurface) {
|
||||
if (modelData.pairing || modelData.state === BluetoothDeviceState.Connecting)
|
||||
return Color.mPrimary
|
||||
return Color.mPrimary;
|
||||
if (modelData.blocked)
|
||||
return Color.mError
|
||||
return defaultColor
|
||||
return Color.mError;
|
||||
return defaultColor;
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
@@ -154,33 +152,33 @@ NBox {
|
||||
fontWeight: Style.fontWeightMedium
|
||||
backgroundColor: {
|
||||
if (device.canDisconnect && !isBusy) {
|
||||
return Color.mError
|
||||
return Color.mError;
|
||||
}
|
||||
return Color.mPrimary
|
||||
return Color.mPrimary;
|
||||
}
|
||||
tooltipText: root.tooltipText
|
||||
text: {
|
||||
if (modelData.pairing) {
|
||||
return I18n.tr("bluetooth.panel.pairing")
|
||||
return I18n.tr("bluetooth.panel.pairing");
|
||||
}
|
||||
if (modelData.blocked) {
|
||||
return I18n.tr("bluetooth.panel.blocked")
|
||||
return I18n.tr("bluetooth.panel.blocked");
|
||||
}
|
||||
if (modelData.connected) {
|
||||
return I18n.tr("bluetooth.panel.disconnect")
|
||||
return I18n.tr("bluetooth.panel.disconnect");
|
||||
}
|
||||
return I18n.tr("bluetooth.panel.connect")
|
||||
return I18n.tr("bluetooth.panel.connect");
|
||||
}
|
||||
icon: (isBusy ? "busy" : null)
|
||||
onClicked: {
|
||||
if (modelData.connected) {
|
||||
BluetoothService.disconnectDevice(modelData)
|
||||
BluetoothService.disconnectDevice(modelData);
|
||||
} else {
|
||||
BluetoothService.connectDeviceWithTrust(modelData)
|
||||
BluetoothService.connectDeviceWithTrust(modelData);
|
||||
}
|
||||
}
|
||||
onRightClicked: {
|
||||
BluetoothService.forgetDevice(modelData)
|
||||
BluetoothService.forgetDevice(modelData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Bluetooth
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import qs.Services.Networking
|
||||
import qs.Widgets
|
||||
import qs.Modules.MainScreen
|
||||
import qs.Services.Networking
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
|
||||
SmartPanel {
|
||||
id: root
|
||||
@@ -65,7 +65,7 @@ SmartPanel {
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: {
|
||||
if (BluetoothService.adapter) {
|
||||
BluetoothService.adapter.discovering = !BluetoothService.adapter.discovering
|
||||
BluetoothService.adapter.discovering = !BluetoothService.adapter.discovering;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,7 +75,7 @@ SmartPanel {
|
||||
tooltipText: I18n.tr("tooltips.close")
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: {
|
||||
root.close()
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -143,9 +143,9 @@ SmartPanel {
|
||||
label: I18n.tr("bluetooth.panel.connected-devices")
|
||||
property var items: {
|
||||
if (!BluetoothService.adapter || !Bluetooth.devices)
|
||||
return []
|
||||
var filtered = Bluetooth.devices.values.filter(dev => dev && !dev.blocked && dev.connected)
|
||||
return BluetoothService.sortDevices(filtered)
|
||||
return [];
|
||||
var filtered = Bluetooth.devices.values.filter(dev => dev && !dev.blocked && dev.connected);
|
||||
return BluetoothService.sortDevices(filtered);
|
||||
}
|
||||
model: items
|
||||
visible: items.length > 0
|
||||
@@ -158,9 +158,9 @@ SmartPanel {
|
||||
tooltipText: I18n.tr("tooltips.connect-disconnect-devices")
|
||||
property var items: {
|
||||
if (!BluetoothService.adapter || !Bluetooth.devices)
|
||||
return []
|
||||
var filtered = Bluetooth.devices.values.filter(dev => dev && !dev.blocked && !dev.connected && (dev.paired || dev.trusted))
|
||||
return BluetoothService.sortDevices(filtered)
|
||||
return [];
|
||||
var filtered = Bluetooth.devices.values.filter(dev => dev && !dev.blocked && !dev.connected && (dev.paired || dev.trusted));
|
||||
return BluetoothService.sortDevices(filtered);
|
||||
}
|
||||
model: items
|
||||
visible: items.length > 0
|
||||
@@ -172,9 +172,9 @@ SmartPanel {
|
||||
label: I18n.tr("bluetooth.panel.available-devices")
|
||||
property var items: {
|
||||
if (!BluetoothService.adapter || !Bluetooth.devices)
|
||||
return []
|
||||
var filtered = Bluetooth.devices.values.filter(dev => dev && !dev.blocked && !dev.paired && !dev.trusted)
|
||||
return BluetoothService.sortDevices(filtered)
|
||||
return [];
|
||||
var filtered = Bluetooth.devices.values.filter(dev => dev && !dev.blocked && !dev.paired && !dev.trusted);
|
||||
return BluetoothService.sortDevices(filtered);
|
||||
}
|
||||
model: items
|
||||
visible: items.length > 0
|
||||
@@ -187,13 +187,13 @@ SmartPanel {
|
||||
Layout.preferredHeight: columnScanning.implicitHeight + Style.marginM * 2
|
||||
visible: {
|
||||
if (!BluetoothService.adapter || !BluetoothService.adapter.discovering || !Bluetooth.devices) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
var availableCount = Bluetooth.devices.values.filter(dev => {
|
||||
return dev && !dev.paired && !dev.pairing && !dev.blocked && (dev.signalStrength === undefined || dev.signalStrength > 0)
|
||||
}).length
|
||||
return (availableCount === 0)
|
||||
return dev && !dev.paired && !dev.pairing && !dev.blocked && (dev.signalStrength === undefined || dev.signalStrength > 0);
|
||||
}).length;
|
||||
return (availableCount === 0);
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
|
||||
@@ -21,22 +21,22 @@ SmartPanel {
|
||||
|
||||
// Helper function to calculate ISO week number
|
||||
function getISOWeekNumber(date) {
|
||||
const target = new Date(date.valueOf())
|
||||
const dayNr = (date.getDay() + 6) % 7
|
||||
target.setDate(target.getDate() - dayNr + 3)
|
||||
const firstThursday = new Date(target.getFullYear(), 0, 4)
|
||||
const diff = target - firstThursday
|
||||
const oneWeek = 1000 * 60 * 60 * 24 * 7
|
||||
const weekNumber = 1 + Math.round(diff / oneWeek)
|
||||
return weekNumber
|
||||
const target = new Date(date.valueOf());
|
||||
const dayNr = (date.getDay() + 6) % 7;
|
||||
target.setDate(target.getDate() - dayNr + 3);
|
||||
const firstThursday = new Date(target.getFullYear(), 0, 4);
|
||||
const diff = target - firstThursday;
|
||||
const oneWeek = 1000 * 60 * 60 * 24 * 7;
|
||||
const weekNumber = 1 + Math.round(diff / oneWeek);
|
||||
return weekNumber;
|
||||
}
|
||||
|
||||
// Helper function to check if an event is all-day
|
||||
function isAllDayEvent(event) {
|
||||
const duration = event.end - event.start
|
||||
const startDate = new Date(event.start * 1000)
|
||||
const isAtMidnight = startDate.getHours() === 0 && startDate.getMinutes() === 0
|
||||
return duration === 86400 && isAtMidnight
|
||||
const duration = event.end - event.start;
|
||||
const startDate = new Date(event.start * 1000);
|
||||
const isAtMidnight = startDate.getHours() === 0 && startDate.getMinutes() === 0;
|
||||
return duration === 86400 && isAtMidnight;
|
||||
}
|
||||
|
||||
panelContent: Item {
|
||||
@@ -50,12 +50,12 @@ SmartPanel {
|
||||
|
||||
property real calendarGridHeight: {
|
||||
// Calculate number of weeks in the calendar grid
|
||||
const numWeeks = grid.daysModel ? Math.ceil(grid.daysModel.length / 7) : 5
|
||||
const numWeeks = grid.daysModel ? Math.ceil(grid.daysModel.length / 7) : 5;
|
||||
|
||||
// Calendar grid height (dynamic based on number of weeks)
|
||||
const rowHeight = Style.baseWidgetSize * 0.9 + Style.marginXXS
|
||||
const rowHeight = Style.baseWidgetSize * 0.9 + Style.marginXXS;
|
||||
|
||||
return numWeeks * rowHeight
|
||||
return numWeeks * rowHeight;
|
||||
}
|
||||
ColumnLayout {
|
||||
id: content
|
||||
@@ -69,17 +69,17 @@ SmartPanel {
|
||||
readonly property bool weatherReady: Settings.data.location.weatherEnabled && (LocationService.data.weather !== null)
|
||||
|
||||
function checkIsCurrentMonth() {
|
||||
return (now.getMonth() === grid.month) && (now.getFullYear() === grid.year)
|
||||
return (now.getMonth() === grid.month) && (now.getFullYear() === grid.year);
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
isCurrentMonth = checkIsCurrentMonth()
|
||||
isCurrentMonth = checkIsCurrentMonth();
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Time
|
||||
function onNowChanged() {
|
||||
content.isCurrentMonth = content.checkIsCurrentMonth()
|
||||
content.isCurrentMonth = content.checkIsCurrentMonth();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ SmartPanel {
|
||||
target: I18n
|
||||
function onLanguageChanged() {
|
||||
// Force update of day names when language changes
|
||||
grid.month = grid.month
|
||||
grid.month = grid.month;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,11 +180,11 @@ SmartPanel {
|
||||
NText {
|
||||
text: {
|
||||
if (!Settings.data.location.weatherEnabled)
|
||||
return ""
|
||||
return "";
|
||||
if (!content.weatherReady)
|
||||
return I18n.tr("calendar.weather.loading")
|
||||
const chunks = Settings.data.location.name.split(",")
|
||||
return chunks[0]
|
||||
return I18n.tr("calendar.weather.loading");
|
||||
const chunks = Settings.data.location.name.split(",");
|
||||
return chunks[0];
|
||||
}
|
||||
pointSize: Style.fontSizeM
|
||||
font.weight: Style.fontWeightMedium
|
||||
@@ -227,11 +227,11 @@ SmartPanel {
|
||||
id: calendar
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: {
|
||||
const navigationHeight = Style.baseWidgetSize // Navigation buttons row
|
||||
const dayNamesHeight = Style.baseWidgetSize * 0.6 // Day names header row
|
||||
const innerMargins = Style.marginM * 2 // Top and bottom margins inside NBox
|
||||
const innerSpacing = Style.marginS * 2 // Spacing between nav, dayNames, and grid (2 gaps)
|
||||
return navigationHeight + dayNamesHeight + calendarGridHeight + innerMargins + innerSpacing
|
||||
const navigationHeight = Style.baseWidgetSize; // Navigation buttons row
|
||||
const dayNamesHeight = Style.baseWidgetSize * 0.6; // Day names header row
|
||||
const innerMargins = Style.marginM * 2; // Top and bottom margins inside NBox
|
||||
const innerSpacing = Style.marginS * 2; // Spacing between nav, dayNames, and grid (2 gaps)
|
||||
return navigationHeight + dayNamesHeight + calendarGridHeight + innerMargins + innerSpacing;
|
||||
}
|
||||
|
||||
Behavior on Layout.preferredWidth {
|
||||
@@ -257,46 +257,46 @@ SmartPanel {
|
||||
NIconButton {
|
||||
icon: "chevron-left"
|
||||
onClicked: {
|
||||
let newDate = new Date(grid.year, grid.month - 1, 1)
|
||||
grid.year = newDate.getFullYear()
|
||||
grid.month = newDate.getMonth()
|
||||
content.isCurrentMonth = content.checkIsCurrentMonth()
|
||||
const now = new Date()
|
||||
const monthStart = new Date(grid.year, grid.month, 1)
|
||||
const monthEnd = new Date(grid.year, grid.month + 1, 0)
|
||||
let newDate = new Date(grid.year, grid.month - 1, 1);
|
||||
grid.year = newDate.getFullYear();
|
||||
grid.month = newDate.getMonth();
|
||||
content.isCurrentMonth = content.checkIsCurrentMonth();
|
||||
const now = new Date();
|
||||
const monthStart = new Date(grid.year, grid.month, 1);
|
||||
const monthEnd = new Date(grid.year, grid.month + 1, 0);
|
||||
|
||||
const daysBehind = Math.max(0, Math.ceil((now - monthStart) / (24 * 60 * 60 * 1000)))
|
||||
const daysAhead = Math.max(0, Math.ceil((monthEnd - now) / (24 * 60 * 60 * 1000)))
|
||||
const daysBehind = Math.max(0, Math.ceil((now - monthStart) / (24 * 60 * 60 * 1000)));
|
||||
const daysAhead = Math.max(0, Math.ceil((monthEnd - now) / (24 * 60 * 60 * 1000)));
|
||||
|
||||
CalendarService.loadEvents(daysAhead + 30, daysBehind + 30)
|
||||
CalendarService.loadEvents(daysAhead + 30, daysBehind + 30);
|
||||
}
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "calendar"
|
||||
onClicked: {
|
||||
grid.month = now.getMonth()
|
||||
grid.year = now.getFullYear()
|
||||
content.isCurrentMonth = true
|
||||
CalendarService.loadEvents()
|
||||
grid.month = now.getMonth();
|
||||
grid.year = now.getFullYear();
|
||||
content.isCurrentMonth = true;
|
||||
CalendarService.loadEvents();
|
||||
}
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "chevron-right"
|
||||
onClicked: {
|
||||
let newDate = new Date(grid.year, grid.month + 1, 1)
|
||||
grid.year = newDate.getFullYear()
|
||||
grid.month = newDate.getMonth()
|
||||
content.isCurrentMonth = content.checkIsCurrentMonth()
|
||||
const now = new Date()
|
||||
const monthStart = new Date(grid.year, grid.month, 1)
|
||||
const monthEnd = new Date(grid.year, grid.month + 1, 0)
|
||||
let newDate = new Date(grid.year, grid.month + 1, 1);
|
||||
grid.year = newDate.getFullYear();
|
||||
grid.month = newDate.getMonth();
|
||||
content.isCurrentMonth = content.checkIsCurrentMonth();
|
||||
const now = new Date();
|
||||
const monthStart = new Date(grid.year, grid.month, 1);
|
||||
const monthEnd = new Date(grid.year, grid.month + 1, 0);
|
||||
|
||||
const daysBehind = Math.max(0, Math.ceil((now - monthStart) / (24 * 60 * 60 * 1000)))
|
||||
const daysAhead = Math.max(0, Math.ceil((monthEnd - now) / (24 * 60 * 60 * 1000)))
|
||||
const daysBehind = Math.max(0, Math.ceil((now - monthStart) / (24 * 60 * 60 * 1000)));
|
||||
const daysAhead = Math.max(0, Math.ceil((monthEnd - now) / (24 * 60 * 60 * 1000)));
|
||||
|
||||
CalendarService.loadEvents(daysAhead + 30, daysBehind + 30)
|
||||
CalendarService.loadEvents(daysAhead + 30, daysBehind + 30);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -324,9 +324,9 @@ SmartPanel {
|
||||
NText {
|
||||
anchors.centerIn: parent
|
||||
text: {
|
||||
let dayIndex = (content.firstDayOfWeek + index) % 7
|
||||
const dayName = I18n.locale.dayName(dayIndex, Locale.ShortFormat)
|
||||
return dayName.substring(0, 2).toUpperCase()
|
||||
let dayIndex = (content.firstDayOfWeek + index) % 7;
|
||||
const dayName = I18n.locale.dayName(dayIndex, Locale.ShortFormat);
|
||||
return dayName.substring(0, 2).toUpperCase();
|
||||
}
|
||||
color: Color.mPrimary
|
||||
pointSize: Style.fontSizeS
|
||||
@@ -345,55 +345,55 @@ SmartPanel {
|
||||
// Helper function to check if a date has events
|
||||
function hasEventsOnDate(year, month, day) {
|
||||
if (!CalendarService.available || CalendarService.events.length === 0)
|
||||
return false
|
||||
return false;
|
||||
|
||||
const targetDate = new Date(year, month, day)
|
||||
const targetStart = new Date(targetDate.getFullYear(), targetDate.getMonth(), targetDate.getDate()).getTime() / 1000
|
||||
const targetEnd = targetStart + 86400 // +24 hours
|
||||
const targetDate = new Date(year, month, day);
|
||||
const targetStart = new Date(targetDate.getFullYear(), targetDate.getMonth(), targetDate.getDate()).getTime() / 1000;
|
||||
const targetEnd = targetStart + 86400; // +24 hours
|
||||
|
||||
return CalendarService.events.some(event => {
|
||||
// Check if event starts or overlaps with this day
|
||||
return (event.start >= targetStart && event.start < targetEnd) || (event.end > targetStart && event.end <= targetEnd) || (event.start < targetStart && event.end > targetEnd)
|
||||
})
|
||||
return (event.start >= targetStart && event.start < targetEnd) || (event.end > targetStart && event.end <= targetEnd) || (event.start < targetStart && event.end > targetEnd);
|
||||
});
|
||||
}
|
||||
|
||||
// Helper function to get events for a specific date
|
||||
function getEventsForDate(year, month, day) {
|
||||
if (!CalendarService.available || CalendarService.events.length === 0)
|
||||
return []
|
||||
return [];
|
||||
|
||||
const targetDate = new Date(year, month, day)
|
||||
const targetStart = Math.floor(new Date(targetDate.getFullYear(), targetDate.getMonth(), targetDate.getDate()).getTime() / 1000)
|
||||
const targetEnd = targetStart + 86400 // +24 hours
|
||||
const targetDate = new Date(year, month, day);
|
||||
const targetStart = Math.floor(new Date(targetDate.getFullYear(), targetDate.getMonth(), targetDate.getDate()).getTime() / 1000);
|
||||
const targetEnd = targetStart + 86400; // +24 hours
|
||||
|
||||
return CalendarService.events.filter(event => {
|
||||
return (event.start >= targetStart && event.start < targetEnd) || (event.end > targetStart && event.end <= targetEnd) || (event.start < targetStart && event.end > targetEnd)
|
||||
})
|
||||
return (event.start >= targetStart && event.start < targetEnd) || (event.end > targetStart && event.end <= targetEnd) || (event.start < targetStart && event.end > targetEnd);
|
||||
});
|
||||
}
|
||||
|
||||
// Helper function to check if an event is multi-day
|
||||
function isMultiDayEvent(event) {
|
||||
if (isAllDayEvent(event)) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
const startDate = new Date(event.start * 1000)
|
||||
const endDate = new Date(event.end * 1000)
|
||||
const startDate = new Date(event.start * 1000);
|
||||
const endDate = new Date(event.end * 1000);
|
||||
|
||||
const startDateOnly = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate())
|
||||
const endDateOnly = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate())
|
||||
const startDateOnly = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate());
|
||||
const endDateOnly = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate());
|
||||
|
||||
return startDateOnly.getTime() !== endDateOnly.getTime()
|
||||
return startDateOnly.getTime() !== endDateOnly.getTime();
|
||||
}
|
||||
|
||||
// Helper function to get color for a specific event
|
||||
function getEventColor(event, isToday) {
|
||||
if (isMultiDayEvent(event)) {
|
||||
return isToday ? Color.mOnSecondary : Color.mTertiary
|
||||
return isToday ? Color.mOnSecondary : Color.mTertiary;
|
||||
} else if (isAllDayEvent(event)) {
|
||||
return isToday ? Color.mOnSecondary : Color.mSecondary
|
||||
return isToday ? Color.mOnSecondary : Color.mSecondary;
|
||||
} else {
|
||||
return isToday ? Color.mOnSecondary : Color.mPrimary
|
||||
return isToday ? Color.mOnSecondary : Color.mPrimary;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -402,9 +402,9 @@ SmartPanel {
|
||||
visible: Settings.data.location.showWeekNumberInCalendar
|
||||
Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 : 0
|
||||
Layout.preferredHeight: {
|
||||
const numWeeks = weekNumbers ? weekNumbers.length : 5
|
||||
const rowHeight = Style.baseWidgetSize * 0.9 + Style.marginXXS
|
||||
return numWeeks * rowHeight
|
||||
const numWeeks = weekNumbers ? weekNumbers.length : 5;
|
||||
const rowHeight = Style.baseWidgetSize * 0.9 + Style.marginXXS;
|
||||
return numWeeks * rowHeight;
|
||||
}
|
||||
spacing: Style.marginXXS
|
||||
|
||||
@@ -417,33 +417,33 @@ SmartPanel {
|
||||
|
||||
property var weekNumbers: {
|
||||
if (!grid.daysModel || grid.daysModel.length === 0)
|
||||
return []
|
||||
return [];
|
||||
|
||||
const weeks = []
|
||||
const numWeeks = Math.ceil(grid.daysModel.length / 7)
|
||||
const weeks = [];
|
||||
const numWeeks = Math.ceil(grid.daysModel.length / 7);
|
||||
|
||||
for (var i = 0; i < numWeeks; i++) {
|
||||
const dayIndex = i * 7
|
||||
const dayIndex = i * 7;
|
||||
if (dayIndex < grid.daysModel.length) {
|
||||
const weekDay = grid.daysModel[dayIndex]
|
||||
const date = new Date(weekDay.year, weekDay.month, weekDay.day)
|
||||
const weekDay = grid.daysModel[dayIndex];
|
||||
const date = new Date(weekDay.year, weekDay.month, weekDay.day);
|
||||
|
||||
// Get Thursday of this week for ISO week calculation
|
||||
const firstDayOfWeek = content.firstDayOfWeek
|
||||
let thursday = new Date(date)
|
||||
const firstDayOfWeek = content.firstDayOfWeek;
|
||||
let thursday = new Date(date);
|
||||
if (firstDayOfWeek === 0) {
|
||||
thursday.setDate(date.getDate() + 4)
|
||||
thursday.setDate(date.getDate() + 4);
|
||||
} else if (firstDayOfWeek === 1) {
|
||||
thursday.setDate(date.getDate() + 3)
|
||||
thursday.setDate(date.getDate() + 3);
|
||||
} else {
|
||||
let daysToThursday = (4 - firstDayOfWeek + 7) % 7
|
||||
thursday.setDate(date.getDate() + daysToThursday)
|
||||
let daysToThursday = (4 - firstDayOfWeek + 7) % 7;
|
||||
thursday.setDate(date.getDate() + daysToThursday);
|
||||
}
|
||||
|
||||
weeks.push(root.getISOWeekNumber(thursday))
|
||||
weeks.push(root.getISOWeekNumber(thursday));
|
||||
}
|
||||
}
|
||||
return weeks
|
||||
return weeks;
|
||||
}
|
||||
|
||||
Repeater {
|
||||
@@ -466,9 +466,9 @@ SmartPanel {
|
||||
id: grid
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: {
|
||||
const numWeeks = daysModel ? Math.ceil(daysModel.length / 7) : 5
|
||||
const rowHeight = Style.baseWidgetSize * 0.9 + Style.marginXXS
|
||||
return numWeeks * rowHeight
|
||||
const numWeeks = daysModel ? Math.ceil(daysModel.length / 7) : 5;
|
||||
const rowHeight = Style.baseWidgetSize * 0.9 + Style.marginXXS;
|
||||
return numWeeks * rowHeight;
|
||||
}
|
||||
columns: 7
|
||||
columnSpacing: Style.marginXXS
|
||||
@@ -486,51 +486,51 @@ SmartPanel {
|
||||
|
||||
// Calculate days to display
|
||||
property var daysModel: {
|
||||
const firstOfMonth = new Date(year, month, 1)
|
||||
const lastOfMonth = new Date(year, month + 1, 0)
|
||||
const daysInMonth = lastOfMonth.getDate()
|
||||
const firstOfMonth = new Date(year, month, 1);
|
||||
const lastOfMonth = new Date(year, month + 1, 0);
|
||||
const daysInMonth = lastOfMonth.getDate();
|
||||
|
||||
// Get first day of week (0 = Sunday, 1 = Monday, etc.)
|
||||
const firstDayOfWeek = content.firstDayOfWeek
|
||||
const firstOfMonthDayOfWeek = firstOfMonth.getDay()
|
||||
const firstDayOfWeek = content.firstDayOfWeek;
|
||||
const firstOfMonthDayOfWeek = firstOfMonth.getDay();
|
||||
|
||||
// Calculate days before first of month
|
||||
let daysBefore = (firstOfMonthDayOfWeek - firstDayOfWeek + 7) % 7
|
||||
let daysBefore = (firstOfMonthDayOfWeek - firstDayOfWeek + 7) % 7;
|
||||
|
||||
// Calculate days after last of month to complete the week
|
||||
const lastOfMonthDayOfWeek = lastOfMonth.getDay()
|
||||
const daysAfter = (firstDayOfWeek - lastOfMonthDayOfWeek - 1 + 7) % 7
|
||||
const lastOfMonthDayOfWeek = lastOfMonth.getDay();
|
||||
const daysAfter = (firstDayOfWeek - lastOfMonthDayOfWeek - 1 + 7) % 7;
|
||||
|
||||
// Build array of day objects
|
||||
const days = []
|
||||
const today = new Date()
|
||||
const days = [];
|
||||
const today = new Date();
|
||||
|
||||
// Previous month days
|
||||
const prevMonth = new Date(year, month, 0)
|
||||
const prevMonthDays = prevMonth.getDate()
|
||||
const prevMonth = new Date(year, month, 0);
|
||||
const prevMonthDays = prevMonth.getDate();
|
||||
for (var i = daysBefore - 1; i >= 0; i--) {
|
||||
const day = prevMonthDays - i
|
||||
const date = new Date(year, month - 1, day)
|
||||
const day = prevMonthDays - i;
|
||||
const date = new Date(year, month - 1, day);
|
||||
days.push({
|
||||
"day": day,
|
||||
"month": month - 1,
|
||||
"year": month === 0 ? year - 1 : year,
|
||||
"today": false,
|
||||
"currentMonth": false
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// Current month days
|
||||
for (var day = 1; day <= daysInMonth; day++) {
|
||||
const date = new Date(year, month, day)
|
||||
const isToday = date.getFullYear() === today.getFullYear() && date.getMonth() === today.getMonth() && date.getDate() === today.getDate()
|
||||
const date = new Date(year, month, day);
|
||||
const isToday = date.getFullYear() === today.getFullYear() && date.getMonth() === today.getMonth() && date.getDate() === today.getDate();
|
||||
days.push({
|
||||
"day": day,
|
||||
"month": month,
|
||||
"year": year,
|
||||
"today": isToday,
|
||||
"currentMonth": true
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// Next month days (only if needed to complete the week)
|
||||
@@ -541,10 +541,10 @@ SmartPanel {
|
||||
"year": month === 11 ? year + 1 : year,
|
||||
"today": false,
|
||||
"currentMonth": false
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
return days
|
||||
return days;
|
||||
}
|
||||
|
||||
Repeater {
|
||||
@@ -566,10 +566,10 @@ SmartPanel {
|
||||
text: modelData.day
|
||||
color: {
|
||||
if (modelData.today)
|
||||
return Color.mOnSecondary
|
||||
return Color.mOnSecondary;
|
||||
if (modelData.currentMonth)
|
||||
return Color.mOnSurface
|
||||
return Color.mOnSurfaceVariant
|
||||
return Color.mOnSurface;
|
||||
return Color.mOnSurfaceVariant;
|
||||
}
|
||||
opacity: modelData.currentMonth ? 1.0 : 0.4
|
||||
pointSize: Style.fontSizeM
|
||||
@@ -602,35 +602,35 @@ SmartPanel {
|
||||
enabled: Settings.data.location.showCalendarEvents
|
||||
|
||||
onEntered: {
|
||||
const events = parent.parent.parent.parent.getEventsForDate(modelData.year, modelData.month, modelData.day)
|
||||
const events = parent.parent.parent.parent.getEventsForDate(modelData.year, modelData.month, modelData.day);
|
||||
if (events.length > 0) {
|
||||
const summaries = events.map(event => {
|
||||
if (isAllDayEvent(event)) {
|
||||
return event.summary
|
||||
return event.summary;
|
||||
} else {
|
||||
// Always format with '0' padding to ensure proper horizontal alignment
|
||||
const timeFormat = Settings.data.location.use12hourFormat ? "hh:mm AP" : "HH:mm"
|
||||
const start = new Date(event.start * 1000)
|
||||
const startFormatted = I18n.locale.toString(start, timeFormat)
|
||||
const end = new Date(event.end * 1000)
|
||||
const endFormatted = I18n.locale.toString(end, timeFormat)
|
||||
return `${startFormatted}-${endFormatted} ${event.summary}`
|
||||
const timeFormat = Settings.data.location.use12hourFormat ? "hh:mm AP" : "HH:mm";
|
||||
const start = new Date(event.start * 1000);
|
||||
const startFormatted = I18n.locale.toString(start, timeFormat);
|
||||
const end = new Date(event.end * 1000);
|
||||
const endFormatted = I18n.locale.toString(end, timeFormat);
|
||||
return `${startFormatted}-${endFormatted} ${event.summary}`;
|
||||
}
|
||||
}).join('\n')
|
||||
TooltipService.show(screen, parent, summaries, "auto", Style.tooltipDelay, Settings.data.ui.fontFixed)
|
||||
}).join('\n');
|
||||
TooltipService.show(screen, parent, summaries, "auto", Style.tooltipDelay, Settings.data.ui.fontFixed);
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
const dateWithSlashes = `${(modelData.month + 1).toString().padStart(2, '0')}/${modelData.day.toString().padStart(2, '0')}/${modelData.year.toString().substring(2)}`
|
||||
const dateWithSlashes = `${(modelData.month + 1).toString().padStart(2, '0')}/${modelData.day.toString().padStart(2, '0')}/${modelData.year.toString().substring(2)}`;
|
||||
if (ProgramCheckerService.gnomeCalendarAvailable) {
|
||||
Quickshell.execDetached(["gnome-calendar", "--date", dateWithSlashes])
|
||||
root.close()
|
||||
Quickshell.execDetached(["gnome-calendar", "--date", dateWithSlashes]);
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
onExited: {
|
||||
TooltipService.hide()
|
||||
TooltipService.hide();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,10 +16,10 @@ NBox {
|
||||
property bool localInputVolumeChanging: false
|
||||
|
||||
Component.onCompleted: {
|
||||
var vol = AudioService.volume
|
||||
localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0
|
||||
var inputVol = AudioService.inputVolume
|
||||
localInputVolume = (inputVol !== undefined && !isNaN(inputVol)) ? inputVol : 0
|
||||
var vol = AudioService.volume;
|
||||
localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0;
|
||||
var inputVol = AudioService.inputVolume;
|
||||
localInputVolume = (inputVol !== undefined && !isNaN(inputVol)) ? inputVol : 0;
|
||||
}
|
||||
|
||||
// Timer to debounce volume changes
|
||||
@@ -29,10 +29,10 @@ NBox {
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
if (Math.abs(localOutputVolume - AudioService.volume) >= 0.01) {
|
||||
AudioService.setVolume(localOutputVolume)
|
||||
AudioService.setVolume(localOutputVolume);
|
||||
}
|
||||
if (Math.abs(localInputVolume - AudioService.inputVolume) >= 0.01) {
|
||||
AudioService.setInputVolume(localInputVolume)
|
||||
AudioService.setInputVolume(localInputVolume);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,8 +42,8 @@ NBox {
|
||||
target: AudioService
|
||||
function onVolumeChanged() {
|
||||
if (!localOutputVolumeChanging) {
|
||||
var vol = AudioService.volume
|
||||
localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0
|
||||
var vol = AudioService.volume;
|
||||
localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,8 +52,8 @@ NBox {
|
||||
target: AudioService.sink?.audio ? AudioService.sink?.audio : null
|
||||
function onVolumeChanged() {
|
||||
if (!localOutputVolumeChanging) {
|
||||
var vol = AudioService.volume
|
||||
localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0
|
||||
var vol = AudioService.volume;
|
||||
localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,8 +62,8 @@ NBox {
|
||||
target: AudioService
|
||||
function onInputVolumeChanged() {
|
||||
if (!localInputVolumeChanging) {
|
||||
var vol = AudioService.inputVolume
|
||||
localInputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0
|
||||
var vol = AudioService.inputVolume;
|
||||
localInputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,8 +72,8 @@ NBox {
|
||||
target: AudioService.source?.audio ? AudioService.source?.audio : null
|
||||
function onVolumeChanged() {
|
||||
if (!localInputVolumeChanging) {
|
||||
var vol = AudioService.inputVolume
|
||||
localInputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0
|
||||
var vol = AudioService.inputVolume;
|
||||
localInputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -105,7 +105,7 @@ NBox {
|
||||
colorFgHover: Color.mOnHover
|
||||
onClicked: {
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
AudioService.sink.audio.muted = !AudioService.muted
|
||||
AudioService.sink.audio.muted = !AudioService.muted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services.Media
|
||||
@@ -22,7 +22,7 @@ NBox {
|
||||
target: WallpaperService
|
||||
function onWallpaperChanged(screenName, path) {
|
||||
if (screenName === screen.name) {
|
||||
wallpaper = path
|
||||
wallpaper = path;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,8 +48,8 @@ NBox {
|
||||
|
||||
// Background image that covers everything
|
||||
Image {
|
||||
readonly property int dim: Math.round(256 * Style.uiScaleRatio)
|
||||
id: bgImage
|
||||
readonly property int dim: Math.round(256 * Style.uiScaleRatio)
|
||||
anchors.fill: parent
|
||||
source: MediaService.trackArtUrl || wallpaper
|
||||
sourceSize: Qt.size(dim, dim)
|
||||
@@ -81,13 +81,13 @@ NBox {
|
||||
sourceComponent: {
|
||||
switch (Settings.data.audio.visualizerType) {
|
||||
case "linear":
|
||||
return linearComponent
|
||||
return linearComponent;
|
||||
case "mirrored":
|
||||
return mirroredComponent
|
||||
return mirroredComponent;
|
||||
case "wave":
|
||||
return waveComponent
|
||||
return waveComponent;
|
||||
default:
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,8 +164,8 @@ NBox {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
var menuItems = []
|
||||
var players = MediaService.getAvailablePlayers()
|
||||
var menuItems = [];
|
||||
var players = MediaService.getAvailablePlayers();
|
||||
for (var i = 0; i < players.length; i++) {
|
||||
menuItems.push({
|
||||
"label": players[i].identity,
|
||||
@@ -173,10 +173,10 @@ NBox {
|
||||
"icon": "disc",
|
||||
"enabled": true,
|
||||
"visible": true
|
||||
})
|
||||
});
|
||||
}
|
||||
playerContextMenu.model = menuItems
|
||||
playerContextMenu.openAtItem(playerSelectorButton, playerSelectorButton.width - playerContextMenu.width, playerSelectorButton.height)
|
||||
playerContextMenu.model = menuItems;
|
||||
playerContextMenu.openAtItem(playerSelectorButton, playerSelectorButton.width - playerContextMenu.width, playerSelectorButton.height);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,9 +187,9 @@ NBox {
|
||||
verticalPolicy: ScrollBar.AlwaysOff
|
||||
|
||||
onTriggered: function (action) {
|
||||
var index = parseInt(action)
|
||||
var index = parseInt(action);
|
||||
if (!isNaN(index)) {
|
||||
MediaService.switchToPlayer(index)
|
||||
MediaService.switchToPlayer(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -276,11 +276,11 @@ NBox {
|
||||
property real seekEpsilon: 0.01
|
||||
property real progressRatio: {
|
||||
if (!MediaService.currentPlayer || MediaService.trackLength <= 0)
|
||||
return 0
|
||||
const r = MediaService.currentPosition / MediaService.trackLength
|
||||
return 0;
|
||||
const r = MediaService.currentPosition / MediaService.trackLength;
|
||||
if (isNaN(r) || !isFinite(r))
|
||||
return 0
|
||||
return Math.max(0, Math.min(1, r))
|
||||
return 0;
|
||||
return Math.max(0, Math.min(1, r));
|
||||
}
|
||||
property real effectiveRatio: (MediaService.isSeeking && localSeekRatio >= 0) ? Math.max(0, Math.min(1, localSeekRatio)) : progressRatio
|
||||
|
||||
@@ -290,10 +290,10 @@ NBox {
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (MediaService.isSeeking && progressWrapper.localSeekRatio >= 0) {
|
||||
const next = Math.max(0, Math.min(1, progressWrapper.localSeekRatio))
|
||||
const next = Math.max(0, Math.min(1, progressWrapper.localSeekRatio));
|
||||
if (progressWrapper.lastSentSeekRatio < 0 || Math.abs(next - progressWrapper.lastSentSeekRatio) >= progressWrapper.seekEpsilon) {
|
||||
MediaService.seekByRatio(next)
|
||||
progressWrapper.lastSentSeekRatio = next
|
||||
MediaService.seekByRatio(next);
|
||||
progressWrapper.lastSentSeekRatio = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -310,21 +310,21 @@ NBox {
|
||||
heightRatio: 0.6
|
||||
|
||||
onMoved: {
|
||||
progressWrapper.localSeekRatio = value
|
||||
seekDebounce.restart()
|
||||
progressWrapper.localSeekRatio = value;
|
||||
seekDebounce.restart();
|
||||
}
|
||||
onPressedChanged: {
|
||||
if (pressed) {
|
||||
MediaService.isSeeking = true
|
||||
progressWrapper.localSeekRatio = value
|
||||
MediaService.seekByRatio(value)
|
||||
progressWrapper.lastSentSeekRatio = value
|
||||
MediaService.isSeeking = true;
|
||||
progressWrapper.localSeekRatio = value;
|
||||
MediaService.seekByRatio(value);
|
||||
progressWrapper.lastSentSeekRatio = value;
|
||||
} else {
|
||||
seekDebounce.stop()
|
||||
MediaService.seekByRatio(value)
|
||||
MediaService.isSeeking = false
|
||||
progressWrapper.localSeekRatio = -1
|
||||
progressWrapper.lastSentSeekRatio = -1
|
||||
seekDebounce.stop();
|
||||
MediaService.seekByRatio(value);
|
||||
MediaService.isSeeking = false;
|
||||
progressWrapper.localSeekRatio = -1;
|
||||
progressWrapper.lastSentSeekRatio = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,9 +61,9 @@ NBox {
|
||||
icon: "settings"
|
||||
tooltipText: I18n.tr("tooltips.open-settings")
|
||||
onClicked: {
|
||||
var panel = PanelService.getPanel("settingsPanel", screen)
|
||||
panel.requestedTab = SettingsPanel.Tab.General
|
||||
panel.open()
|
||||
var panel = PanelService.getPanel("settingsPanel", screen);
|
||||
panel.requestedTab = SettingsPanel.Tab.General;
|
||||
panel.open();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,8 +71,8 @@ NBox {
|
||||
icon: "power"
|
||||
tooltipText: I18n.tr("tooltips.session-menu")
|
||||
onClicked: {
|
||||
PanelService.getPanel("sessionMenuPanel", screen)?.open()
|
||||
PanelService.getPanel("controlCenterPanel", screen)?.close()
|
||||
PanelService.getPanel("sessionMenuPanel", screen)?.open();
|
||||
PanelService.getPanel("controlCenterPanel", screen)?.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ NBox {
|
||||
icon: "close"
|
||||
tooltipText: I18n.tr("tooltips.close")
|
||||
onClicked: {
|
||||
PanelService.getPanel("controlCenterPanel", screen)?.close()
|
||||
PanelService.getPanel("controlCenterPanel", screen)?.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,14 +102,14 @@ NBox {
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
var uptimeSeconds = parseFloat(this.text.trim().split(' ')[0])
|
||||
uptimeText = Time.formatVagueHumanReadableDuration(uptimeSeconds)
|
||||
uptimeProcess.running = false
|
||||
var uptimeSeconds = parseFloat(this.text.trim().split(' ')[0]);
|
||||
uptimeText = Time.formatVagueHumanReadableDuration(uptimeSeconds);
|
||||
uptimeProcess.running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateSystemInfo() {
|
||||
uptimeProcess.running = true
|
||||
uptimeProcess.running = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
import qs.Modules.Panels.ControlCenter
|
||||
import qs.Modules.Panels.ControlCenter.Cards
|
||||
import qs.Widgets
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
@@ -28,9 +28,11 @@ NBox {
|
||||
height: content.widgetHeight
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
// Highlight color based on thresholds
|
||||
fillColor: (SystemStatService.cpuUsage > Settings.data.systemMonitor.cpuCriticalThreshold) ? (Settings.data.systemMonitor.useCustomColors ? (Settings.data.systemMonitor.criticalColor
|
||||
|| Color.mError) : Color.mError) : (SystemStatService.cpuUsage > Settings.data.systemMonitor.cpuWarningThreshold) ? (Settings.data.systemMonitor.useCustomColors ? (Settings.data.systemMonitor.warningColor
|
||||
|| Color.mTertiary) : Color.mTertiary) : Color.mPrimary
|
||||
fillColor: (SystemStatService.cpuUsage > Settings.data.systemMonitor.cpuCriticalThreshold) ? (Settings.data.systemMonitor.useCustomColors ? (Settings.data.systemMonitor.criticalColor || Color.mError) : Color.mError) : (SystemStatService.cpuUsage > Settings.data.systemMonitor.cpuWarningThreshold) ? (Settings.data.systemMonitor.useCustomColors ? (
|
||||
Settings.data.systemMonitor.warningColor
|
||||
|| Color.mTertiary) :
|
||||
Color.mTertiary) :
|
||||
Color.mPrimary
|
||||
textColor: (SystemStatService.cpuUsage > Settings.data.systemMonitor.cpuCriticalThreshold) ? Color.mSurfaceVariant : (SystemStatService.cpuUsage > Settings.data.systemMonitor.cpuWarningThreshold) ? Color.mSurfaceVariant : Color.mOnSurface
|
||||
}
|
||||
NCircleStat {
|
||||
@@ -42,9 +44,11 @@ NBox {
|
||||
height: content.widgetHeight
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
// Highlight color based on thresholds
|
||||
fillColor: (SystemStatService.cpuTemp > Settings.data.systemMonitor.tempCriticalThreshold) ? (Settings.data.systemMonitor.useCustomColors ? (Settings.data.systemMonitor.criticalColor
|
||||
|| Color.mError) : Color.mError) : (SystemStatService.cpuTemp > Settings.data.systemMonitor.tempWarningThreshold) ? (Settings.data.systemMonitor.useCustomColors ? (Settings.data.systemMonitor.warningColor
|
||||
|| Color.mTertiary) : Color.mTertiary) : Color.mPrimary
|
||||
fillColor: (SystemStatService.cpuTemp > Settings.data.systemMonitor.tempCriticalThreshold) ? (Settings.data.systemMonitor.useCustomColors ? (Settings.data.systemMonitor.criticalColor || Color.mError) : Color.mError) : (SystemStatService.cpuTemp > Settings.data.systemMonitor.tempWarningThreshold) ? (Settings.data.systemMonitor.useCustomColors ? (
|
||||
Settings.data.systemMonitor.warningColor
|
||||
|| Color.mTertiary) :
|
||||
Color.mTertiary) :
|
||||
Color.mPrimary
|
||||
textColor: (SystemStatService.cpuTemp > Settings.data.systemMonitor.tempCriticalThreshold) ? Color.mSurfaceVariant : (SystemStatService.cpuTemp > Settings.data.systemMonitor.tempWarningThreshold) ? Color.mSurfaceVariant : Color.mOnSurface
|
||||
}
|
||||
NCircleStat {
|
||||
@@ -55,9 +59,11 @@ NBox {
|
||||
height: content.widgetHeight
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
// Highlight color based on thresholds
|
||||
fillColor: (SystemStatService.memPercent > Settings.data.systemMonitor.memCriticalThreshold) ? (Settings.data.systemMonitor.useCustomColors ? (Settings.data.systemMonitor.criticalColor
|
||||
|| Color.mError) : Color.mError) : (SystemStatService.memPercent > Settings.data.systemMonitor.memWarningThreshold) ? (Settings.data.systemMonitor.useCustomColors ? (Settings.data.systemMonitor.warningColor
|
||||
|| Color.mTertiary) : Color.mTertiary) : Color.mPrimary
|
||||
fillColor: (SystemStatService.memPercent > Settings.data.systemMonitor.memCriticalThreshold) ? (Settings.data.systemMonitor.useCustomColors ? (Settings.data.systemMonitor.criticalColor || Color.mError) : Color.mError) : (SystemStatService.memPercent > Settings.data.systemMonitor.memWarningThreshold) ? (Settings.data.systemMonitor.useCustomColors ? (
|
||||
Settings.data.systemMonitor.warningColor
|
||||
|| Color.mTertiary) :
|
||||
Color.mTertiary) :
|
||||
Color.mPrimary
|
||||
textColor: (SystemStatService.memPercent > Settings.data.systemMonitor.memCriticalThreshold) ? Color.mSurfaceVariant : (SystemStatService.memPercent > Settings.data.systemMonitor.memWarningThreshold) ? Color.mSurfaceVariant : Color.mOnSurface
|
||||
}
|
||||
NCircleStat {
|
||||
@@ -68,11 +74,12 @@ NBox {
|
||||
height: content.widgetHeight
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
// Highlight color based on thresholds
|
||||
fillColor: ((SystemStatService.diskPercents["/"]
|
||||
?? 0) > Settings.data.systemMonitor.diskCriticalThreshold) ? (Settings.data.systemMonitor.useCustomColors ? (Settings.data.systemMonitor.criticalColor
|
||||
|| Color.mError) : Color.mError) : ((SystemStatService.diskPercents["/"]
|
||||
?? 0) > Settings.data.systemMonitor.diskWarningThreshold) ? (Settings.data.systemMonitor.useCustomColors ? (Settings.data.systemMonitor.warningColor
|
||||
|| Color.mTertiary) : Color.mTertiary) : Color.mPrimary
|
||||
fillColor: ((SystemStatService.diskPercents["/"] ?? 0) > Settings.data.systemMonitor.diskCriticalThreshold) ? (Settings.data.systemMonitor.useCustomColors ? (Settings.data.systemMonitor.criticalColor || Color.mError) : Color.mError) : ((SystemStatService.diskPercents["/"] ?? 0) > Settings.data.systemMonitor.diskWarningThreshold) ? (
|
||||
Settings.data.systemMonitor.useCustomColors
|
||||
? (Settings.data.systemMonitor.warningColor
|
||||
|| Color.mTertiary) :
|
||||
Color.mTertiary) :
|
||||
Color.mPrimary
|
||||
textColor: ((SystemStatService.diskPercents["/"] ?? 0) > Settings.data.systemMonitor.diskCriticalThreshold) ? Color.mSurfaceVariant : ((SystemStatService.diskPercents["/"] ?? 0) > Settings.data.systemMonitor.diskWarningThreshold) ? Color.mSurfaceVariant : Color.mOnSurface
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,8 +43,8 @@ NBox {
|
||||
NText {
|
||||
text: {
|
||||
// Ensure the name is not too long if one had to specify the country
|
||||
const chunks = Settings.data.location.name.split(",")
|
||||
return chunks[0]
|
||||
const chunks = Settings.data.location.name.split(",");
|
||||
return chunks[0];
|
||||
}
|
||||
pointSize: Style.fontSizeL
|
||||
font.weight: Style.fontWeightBold
|
||||
@@ -56,16 +56,16 @@ NBox {
|
||||
visible: weatherReady
|
||||
text: {
|
||||
if (!weatherReady) {
|
||||
return ""
|
||||
return "";
|
||||
}
|
||||
var temp = LocationService.data.weather.current_weather.temperature
|
||||
var suffix = "C"
|
||||
var temp = LocationService.data.weather.current_weather.temperature;
|
||||
var suffix = "C";
|
||||
if (Settings.data.location.useFahrenheit) {
|
||||
temp = LocationService.celsiusToFahrenheit(temp)
|
||||
var suffix = "F"
|
||||
temp = LocationService.celsiusToFahrenheit(temp);
|
||||
var suffix = "F";
|
||||
}
|
||||
temp = Math.round(temp)
|
||||
return `${temp}°${suffix}`
|
||||
temp = Math.round(temp);
|
||||
return `${temp}°${suffix}`;
|
||||
}
|
||||
pointSize: showLocation ? Style.fontSizeXL : Style.fontSizeXL * 1.6
|
||||
font.weight: Style.fontWeightBold
|
||||
@@ -103,8 +103,8 @@ NBox {
|
||||
NText {
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
|
||||
text: {
|
||||
var weatherDate = new Date(LocationService.data.weather.daily.time[index].replace(/-/g, "/"))
|
||||
return I18n.locale.toString(weatherDate, "ddd")
|
||||
var weatherDate = new Date(LocationService.data.weather.daily.time[index].replace(/-/g, "/"));
|
||||
return I18n.locale.toString(weatherDate, "ddd");
|
||||
}
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
@@ -117,15 +117,15 @@ NBox {
|
||||
NText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: {
|
||||
var max = LocationService.data.weather.daily.temperature_2m_max[index]
|
||||
var min = LocationService.data.weather.daily.temperature_2m_min[index]
|
||||
var max = LocationService.data.weather.daily.temperature_2m_max[index];
|
||||
var min = LocationService.data.weather.daily.temperature_2m_min[index];
|
||||
if (Settings.data.location.useFahrenheit) {
|
||||
max = LocationService.celsiusToFahrenheit(max)
|
||||
min = LocationService.celsiusToFahrenheit(min)
|
||||
max = LocationService.celsiusToFahrenheit(max);
|
||||
min = LocationService.celsiusToFahrenheit(min);
|
||||
}
|
||||
max = Math.round(max)
|
||||
min = Math.round(min)
|
||||
return `${max}°/${min}°`
|
||||
max = Math.round(max);
|
||||
min = Math.round(min);
|
||||
return `${max}°/${min}°`;
|
||||
}
|
||||
pointSize: Style.fontSizeXS
|
||||
color: Color.mOnSurfaceVariant
|
||||
|
||||
@@ -17,8 +17,8 @@ SmartPanel {
|
||||
|
||||
// Check if there's a bar on this screen
|
||||
readonly property bool hasBarOnScreen: {
|
||||
var monitors = Settings.data.bar.monitors || []
|
||||
return monitors.length === 0 || monitors.includes(screen?.name)
|
||||
var monitors = Settings.data.bar.monitors || [];
|
||||
return monitors.length === 0 || monitors.includes(screen?.name);
|
||||
}
|
||||
|
||||
// When position is "close_to_bar_button" but there's no bar, fall back to center
|
||||
@@ -33,39 +33,37 @@ SmartPanel {
|
||||
|
||||
preferredWidth: Math.round(460 * Style.uiScaleRatio)
|
||||
preferredHeight: {
|
||||
var height = 0
|
||||
var count = 0
|
||||
var height = 0;
|
||||
var count = 0;
|
||||
for (var i = 0; i < Settings.data.controlCenter.cards.length; i++) {
|
||||
const card = Settings.data.controlCenter.cards[i]
|
||||
const card = Settings.data.controlCenter.cards[i];
|
||||
if (!card.enabled)
|
||||
continue
|
||||
|
||||
const contributes = (card.id !== "weather-card" || Settings.data.location.weatherEnabled)
|
||||
continue;
|
||||
const contributes = (card.id !== "weather-card" || Settings.data.location.weatherEnabled);
|
||||
if (!contributes)
|
||||
continue
|
||||
|
||||
count++
|
||||
continue;
|
||||
count++;
|
||||
switch (card.id) {
|
||||
case "profile-card":
|
||||
height += profileHeight
|
||||
break
|
||||
height += profileHeight;
|
||||
break;
|
||||
case "shortcuts-card":
|
||||
height += shortcutsHeight
|
||||
break
|
||||
height += shortcutsHeight;
|
||||
break;
|
||||
case "audio-card":
|
||||
height += audioHeight
|
||||
break
|
||||
height += audioHeight;
|
||||
break;
|
||||
case "weather-card":
|
||||
height += weatherHeight
|
||||
break
|
||||
height += weatherHeight;
|
||||
break;
|
||||
case "media-sysmon-card":
|
||||
height += mediaSysMonHeight
|
||||
break
|
||||
height += mediaSysMonHeight;
|
||||
break;
|
||||
default:
|
||||
break
|
||||
break;
|
||||
}
|
||||
}
|
||||
return height + (count + 1) * Style.marginL
|
||||
return height + (count + 1) * Style.marginL;
|
||||
}
|
||||
|
||||
readonly property int profileHeight: Math.round(64 * Style.uiScaleRatio)
|
||||
@@ -77,11 +75,11 @@ SmartPanel {
|
||||
property int weatherHeight: Math.round(210 * Style.uiScaleRatio)
|
||||
|
||||
onOpened: {
|
||||
MediaService.autoSwitchingPaused = true
|
||||
MediaService.autoSwitchingPaused = true;
|
||||
}
|
||||
|
||||
onClosed: {
|
||||
MediaService.autoSwitchingPaused = false
|
||||
MediaService.autoSwitchingPaused = false;
|
||||
}
|
||||
|
||||
panelContent: Item {
|
||||
@@ -103,31 +101,31 @@ SmartPanel {
|
||||
Layout.preferredHeight: {
|
||||
switch (modelData.id) {
|
||||
case "profile-card":
|
||||
return profileHeight
|
||||
return profileHeight;
|
||||
case "shortcuts-card":
|
||||
return shortcutsHeight
|
||||
return shortcutsHeight;
|
||||
case "audio-card":
|
||||
return audioHeight
|
||||
return audioHeight;
|
||||
case "weather-card":
|
||||
return weatherHeight
|
||||
return weatherHeight;
|
||||
case "media-sysmon-card":
|
||||
return mediaSysMonHeight
|
||||
return mediaSysMonHeight;
|
||||
default:
|
||||
return 0
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
sourceComponent: {
|
||||
switch (modelData.id) {
|
||||
case "profile-card":
|
||||
return profileCard
|
||||
return profileCard;
|
||||
case "shortcuts-card":
|
||||
return shortcutsCard
|
||||
return shortcutsCard;
|
||||
case "audio-card":
|
||||
return audioCard
|
||||
return audioCard;
|
||||
case "weather-card":
|
||||
return weatherCard
|
||||
return weatherCard;
|
||||
case "media-sysmon-card":
|
||||
return mediaSysMonCard
|
||||
return mediaSysMonCard;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -153,7 +151,7 @@ SmartPanel {
|
||||
id: weatherCard
|
||||
WeatherCard {
|
||||
Component.onCompleted: {
|
||||
root.weatherHeight = this.height
|
||||
root.weatherHeight = this.height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Services.UI
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@@ -18,7 +18,7 @@ Item {
|
||||
implicitHeight: getImplicitSize(loader.item, "implicitHeight")
|
||||
|
||||
function getImplicitSize(item, prop) {
|
||||
return (item && item.visible) ? item[prop] : 0
|
||||
return (item && item.visible) ? item[prop] : 0;
|
||||
}
|
||||
|
||||
Loader {
|
||||
@@ -29,36 +29,36 @@ Item {
|
||||
|
||||
onLoaded: {
|
||||
if (!item)
|
||||
return
|
||||
return;
|
||||
|
||||
// Apply properties to loaded widget
|
||||
for (var prop in widgetProps) {
|
||||
if (item.hasOwnProperty(prop)) {
|
||||
item[prop] = widgetProps[prop]
|
||||
item[prop] = widgetProps[prop];
|
||||
}
|
||||
}
|
||||
|
||||
// Set screen property
|
||||
if (item.hasOwnProperty("screen")) {
|
||||
item.screen = widgetScreen
|
||||
item.screen = widgetScreen;
|
||||
}
|
||||
|
||||
// Call custom onLoaded if it exists
|
||||
if (item.hasOwnProperty("onLoaded")) {
|
||||
item.onLoaded()
|
||||
item.onLoaded();
|
||||
}
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
// Explicitly clear references
|
||||
widgetProps = null
|
||||
widgetProps = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Error handling
|
||||
Component.onCompleted: {
|
||||
if (!ControlCenterWidgetRegistry.hasWidget(widgetId)) {
|
||||
Logger.w("ControlCenterWidgetLoader", "Widget not found in registry:", widgetId)
|
||||
Logger.w("ControlCenterWidgetLoader", "Widget not found in registry:", widgetId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,28 +33,28 @@ Item {
|
||||
|
||||
function _updatePropertiesFromSettings() {
|
||||
if (!widgetSettings) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
onClickedCommand = widgetSettings.onClicked || ""
|
||||
onRightClickedCommand = widgetSettings.onRightClicked || ""
|
||||
onMiddleClickedCommand = widgetSettings.onMiddleClicked || ""
|
||||
stateChecksJson = widgetSettings.stateChecksJson || "[]" // Populate from widgetSettings
|
||||
onClickedCommand = widgetSettings.onClicked || "";
|
||||
onRightClickedCommand = widgetSettings.onRightClicked || "";
|
||||
onMiddleClickedCommand = widgetSettings.onMiddleClicked || "";
|
||||
stateChecksJson = widgetSettings.stateChecksJson || "[]"; // Populate from widgetSettings
|
||||
try {
|
||||
_parsedStateChecks = JSON.parse(stateChecksJson)
|
||||
_parsedStateChecks = JSON.parse(stateChecksJson);
|
||||
} catch (e) {
|
||||
console.error("CustomButton: Failed to parse stateChecksJson:", e.message)
|
||||
_parsedStateChecks = []
|
||||
console.error("CustomButton: Failed to parse stateChecksJson:", e.message);
|
||||
_parsedStateChecks = [];
|
||||
}
|
||||
generalTooltipText = widgetSettings.generalTooltipText || "Custom Button"
|
||||
enableOnStateLogic = widgetSettings.enableOnStateLogic || false
|
||||
generalTooltipText = widgetSettings.generalTooltipText || "Custom Button";
|
||||
enableOnStateLogic = widgetSettings.enableOnStateLogic || false;
|
||||
|
||||
updateState()
|
||||
updateState();
|
||||
}
|
||||
|
||||
function onWidgetSettingsChanged() {
|
||||
if (widgetSettings) {
|
||||
_updatePropertiesFromSettings()
|
||||
_updatePropertiesFromSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,16 +64,16 @@ Item {
|
||||
running: false
|
||||
command: _currentStateCheckIndex !== -1 && _parsedStateChecks.length > _currentStateCheckIndex ? ["sh", "-c", _parsedStateChecks[_currentStateCheckIndex].command] : []
|
||||
onExited: function (exitCode, stdout, stderr) {
|
||||
var currentCheckItem = _parsedStateChecks[_currentStateCheckIndex]
|
||||
var currentCommand = currentCheckItem.command
|
||||
var currentCheckItem = _parsedStateChecks[_currentStateCheckIndex];
|
||||
var currentCommand = currentCheckItem.command;
|
||||
if (exitCode === 0) {
|
||||
// Command succeeded, this is the active state
|
||||
_isHot = true
|
||||
_activeStateIcon = currentCheckItem.icon || widgetSettings.icon || "heart"
|
||||
_isHot = true;
|
||||
_activeStateIcon = currentCheckItem.icon || widgetSettings.icon || "heart";
|
||||
} else {
|
||||
// Command failed, try next one
|
||||
_currentStateCheckIndex++
|
||||
_checkNextState()
|
||||
_currentStateCheckIndex++;
|
||||
_checkNextState();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -85,53 +85,53 @@ Item {
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (enableOnStateLogic && _parsedStateChecks.length > 0) {
|
||||
_currentStateCheckIndex = 0
|
||||
_checkNextState()
|
||||
_currentStateCheckIndex = 0;
|
||||
_checkNextState();
|
||||
} else {
|
||||
_isHot = false
|
||||
_activeStateIcon = widgetSettings.icon || "heart"
|
||||
_isHot = false;
|
||||
_activeStateIcon = widgetSettings.icon || "heart";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _checkNextState() {
|
||||
if (_currentStateCheckIndex < _parsedStateChecks.length) {
|
||||
var currentCheckItem = _parsedStateChecks[_currentStateCheckIndex]
|
||||
var currentCheckItem = _parsedStateChecks[_currentStateCheckIndex];
|
||||
if (currentCheckItem && currentCheckItem.command) {
|
||||
stateCheckProcessExecutor.running = true
|
||||
stateCheckProcessExecutor.running = true;
|
||||
} else {
|
||||
_currentStateCheckIndex++
|
||||
_checkNextState()
|
||||
_currentStateCheckIndex++;
|
||||
_checkNextState();
|
||||
}
|
||||
} else {
|
||||
// All checks failed
|
||||
_isHot = false
|
||||
_activeStateIcon = widgetSettings.icon || "heart"
|
||||
_isHot = false;
|
||||
_activeStateIcon = widgetSettings.icon || "heart";
|
||||
}
|
||||
}
|
||||
|
||||
function updateState() {
|
||||
if (!enableOnStateLogic || _parsedStateChecks.length === 0) {
|
||||
_isHot = false
|
||||
_activeStateIcon = widgetSettings.icon || "heart"
|
||||
return
|
||||
_isHot = false;
|
||||
_activeStateIcon = widgetSettings.icon || "heart";
|
||||
return;
|
||||
}
|
||||
stateUpdateTimer.restart()
|
||||
stateUpdateTimer.restart();
|
||||
}
|
||||
|
||||
function _buildTooltipText() {
|
||||
let tooltip = generalTooltipText
|
||||
let tooltip = generalTooltipText;
|
||||
if (onClickedCommand) {
|
||||
tooltip += `\nLeft click: ${onClickedCommand}`
|
||||
tooltip += `\nLeft click: ${onClickedCommand}`;
|
||||
}
|
||||
if (onRightClickedCommand) {
|
||||
tooltip += `\nRight click: ${onRightClickedCommand}`
|
||||
tooltip += `\nRight click: ${onRightClickedCommand}`;
|
||||
}
|
||||
if (onMiddleClickedCommand) {
|
||||
tooltip += `\nMiddle click: ${onMiddleClickedCommand}`
|
||||
tooltip += `\nMiddle click: ${onMiddleClickedCommand}`;
|
||||
}
|
||||
|
||||
return tooltip
|
||||
return tooltip;
|
||||
}
|
||||
|
||||
NIconButtonHot {
|
||||
@@ -141,20 +141,20 @@ Item {
|
||||
tooltipText: _buildTooltipText()
|
||||
onClicked: {
|
||||
if (onClickedCommand) {
|
||||
Quickshell.execDetached(["sh", "-c", onClickedCommand])
|
||||
updateState()
|
||||
Quickshell.execDetached(["sh", "-c", onClickedCommand]);
|
||||
updateState();
|
||||
}
|
||||
}
|
||||
onRightClicked: {
|
||||
if (onRightClickedCommand) {
|
||||
Quickshell.execDetached(["sh", "-c", onRightClickedCommand])
|
||||
updateState()
|
||||
Quickshell.execDetached(["sh", "-c", onRightClickedCommand]);
|
||||
updateState();
|
||||
}
|
||||
}
|
||||
onMiddleClicked: {
|
||||
if (onMiddleClickedCommand) {
|
||||
Quickshell.execDetached(["sh", "-c", onMiddleClickedCommand])
|
||||
updateState()
|
||||
Quickshell.execDetached(["sh", "-c", onMiddleClickedCommand]);
|
||||
updateState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,19 +16,19 @@ NIconButtonHot {
|
||||
|
||||
onClicked: {
|
||||
if (!Settings.data.nightLight.enabled) {
|
||||
Settings.data.nightLight.enabled = true
|
||||
Settings.data.nightLight.forced = false
|
||||
Settings.data.nightLight.enabled = true;
|
||||
Settings.data.nightLight.forced = false;
|
||||
} else if (Settings.data.nightLight.enabled && !Settings.data.nightLight.forced) {
|
||||
Settings.data.nightLight.forced = true
|
||||
Settings.data.nightLight.forced = true;
|
||||
} else {
|
||||
Settings.data.nightLight.enabled = false
|
||||
Settings.data.nightLight.forced = false
|
||||
Settings.data.nightLight.enabled = false;
|
||||
Settings.data.nightLight.forced = false;
|
||||
}
|
||||
}
|
||||
|
||||
onRightClicked: {
|
||||
var settingsPanel = PanelService.getPanel("settingsPanel", screen)
|
||||
settingsPanel.requestedTab = SettingsPanel.Tab.Display
|
||||
settingsPanel.open()
|
||||
var settingsPanel = PanelService.getPanel("settingsPanel", screen);
|
||||
settingsPanel.requestedTab = SettingsPanel.Tab.Display;
|
||||
settingsPanel.open();
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user