Switched to qmlformat.

This commit is contained in:
ItsLemmy
2025-11-16 17:07:03 -05:00
parent 32905224b9
commit 3ff5b7639f
223 changed files with 9970 additions and 9658 deletions

View File

@@ -2,38 +2,28 @@
set -euo pipefail set -euo pipefail
# QML Formatter Script # QML Formatter Script
# Uses: https://github.com/jesperhh/qmlfmt
# Install: AUR package "qmlfmt-git" (requires qt6-5compat)
if command -v qmlfmt &>/dev/null; then # Find qmlformat binary
echo "Using 'qmlfmt' for formatting." if command -v qmlformat &>/dev/null; then
format_file() { qmlfmt -e -b 360 -t 2 -i 2 -w "$1" || { echo "Failed: $1" >&2; return 1; }; } QMLFORMAT="qmlformat"
elif [ -x "/usr/lib/qt6/bin/qmlformat" ]; then
QMLFORMAT="/usr/lib/qt6/bin/qmlformat"
else else
echo "No 'qmlfmt' found in PATH." >&2 echo "No 'qmlformat' found in PATH or /usr/lib/qt6/bin." >&2
exit 1 exit 1
fi 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 export -f format_file
# Find all .qml files # Find all .qml files
mapfile -t all_files < <(find "${1:-.}" -name "*.qml" -type f) mapfile -t all_files < <(find "${1:-.}" -name "*.qml" -type f)
[ ${#all_files[@]} -eq 0 ] && { echo "No QML files found"; exit 0; } [ ${#all_files[@]} -eq 0 ] && { echo "No QML files found"; exit 0; }
echo "Scanning ${#all_files[@]} files for array destructuring..." echo "Formatting ${#all_files[@]} files..."
safe_files=() printf '%s\0' "${all_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[@]}" | \
xargs -0 -P "${QMLFMT_JOBS:-$(nproc)}" -I {} bash -c 'format_file "$@"' _ {} \ xargs -0 -P "${QMLFMT_JOBS:-$(nproc)}" -I {} bash -c 'format_file "$@"' _ {} \
&& echo "Done" || { echo "Errors occurred" >&2; exit 1; } && echo "Done" || { echo "Errors occurred" >&2; exit 1; }

View File

@@ -1,29 +0,0 @@
#!/usr/bin/env -S bash
set -euo pipefail
# QML Formatter Script
# 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 '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 "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; }

View File

@@ -5,16 +5,15 @@ import Quickshell
import Quickshell.Io import Quickshell.Io
import qs.Commons import qs.Commons
/* /*
Noctalia is not strictly a Material Design project, it supports both some predefined Noctalia is not strictly a Material Design project, it supports both some predefined
color schemes and dynamic color generation from the wallpaper (using Matugen). color schemes and dynamic color generation from the wallpaper (using Matugen).
We ultimately decided to use a restricted set of colors that follows the We ultimately decided to use a restricted set of colors that follows the
Material Design 3 naming convention. Material Design 3 naming convention.
NOTE: All color names are prefixed with 'm' (e.g., mPrimary) to prevent QML from 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). misinterpreting them as signals (e.g., the 'onPrimary' property name).
*/ */
Singleton { Singleton {
id: root id: root
@@ -87,25 +86,25 @@ Singleton {
printErrors: false printErrors: false
watchChanges: true watchChanges: true
onFileChanged: { onFileChanged: {
Logger.i("Color", "Reloading colors from disk") Logger.i("Color", "Reloading colors from disk");
reload() reload();
} }
onAdapterUpdated: { onAdapterUpdated: {
Logger.i("Color", "Writing colors to disk") Logger.i("Color", "Writing colors to disk");
writeAdapter() writeAdapter();
} }
// Trigger initial load when path changes from empty to actual path // Trigger initial load when path changes from empty to actual path
onPathChanged: { onPathChanged: {
if (path !== undefined) { if (path !== undefined) {
reload() reload();
} }
} }
onLoadFailed: function (error) { onLoadFailed: function (error) {
// Error code 2 = ENOENT (No such file or directory) // Error code 2 = ENOENT (No such file or directory)
if (error === 2 || error.toString().includes("No such file")) { if (error === 2 || error.toString().includes("No such file")) {
// File doesn't exist, create it with default values // File doesn't exist, create it with default values
writeAdapter() writeAdapter();
} }
} }
JsonAdapter { JsonAdapter {

View File

@@ -33,13 +33,13 @@ Singleton {
onExited: function (exitCode, exitStatus) { onExited: function (exitCode, exitStatus) {
if (exitCode === 0) { if (exitCode === 0) {
var output = stdoutCollector.text || "" var output = stdoutCollector.text || "";
parseDirectoryListing(output) parseDirectoryListing(output);
} else { } else {
Logger.e("I18n", `Failed to scan translation directory`) Logger.e("I18n", `Failed to scan translation directory`);
// Fallback to default languages // Fallback to default languages
availableLanguages = ["en"] availableLanguages = ["en"];
detectLanguage() detectLanguage();
} }
} }
} }
@@ -51,21 +51,21 @@ Singleton {
onFileChanged: reload() onFileChanged: reload()
onLoaded: { onLoaded: {
try { try {
var data = JSON.parse(text()) var data = JSON.parse(text());
root.translations = data root.translations = data;
Logger.i("I18n", `Loaded translations for "${root.langCode}"`) Logger.i("I18n", `Loaded translations for "${root.langCode}"`);
Logger.d("I18n", `Available root keys: ${Object.keys(data).join(", ")}`) Logger.d("I18n", `Available root keys: ${Object.keys(data).join(", ")}`);
root.isLoaded = true root.isLoaded = true;
root.translationsLoaded() root.translationsLoaded();
} catch (e) { } catch (e) {
Logger.e("I18n", `Failed to parse translation file: ${e}`) Logger.e("I18n", `Failed to parse translation file: ${e}`);
setLanguage("en") setLanguage("en");
} }
} }
onLoadFailed: function (error) { onLoadFailed: function (error) {
setLanguage("en") setLanguage("en");
Logger.e("I18n", `Failed to load translation file: ${error}`) Logger.e("I18n", `Failed to load translation file: ${error}`);
} }
} }
@@ -76,162 +76,161 @@ Singleton {
onFileChanged: reload() onFileChanged: reload()
onLoaded: { onLoaded: {
try { try {
var data = JSON.parse(text()) var data = JSON.parse(text());
root.fallbackTranslations = data root.fallbackTranslations = data;
Logger.d("I18n", `Loaded english fallback translations`) Logger.d("I18n", `Loaded english fallback translations`);
} catch (e) { } 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) { 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: { Component.onCompleted: {
Logger.i("I18n", "Service started") Logger.i("I18n", "Service started");
scanAvailableLanguages() scanAvailableLanguages();
} }
// ------------------------------------------- // -------------------------------------------
function scanAvailableLanguages() { function scanAvailableLanguages() {
Logger.d("I18n", "Scanning for available translation files...") Logger.d("I18n", "Scanning for available translation files...");
directoryScanner.running = true directoryScanner.running = true;
} }
// ------------------------------------------- // -------------------------------------------
function parseDirectoryListing(output) { function parseDirectoryListing(output) {
var languages = [] var languages = [];
try { try {
if (!output || output.trim() === "") { if (!output || output.trim() === "") {
Logger.w("I18n", "Empty directory listing output") Logger.w("I18n", "Empty directory listing output");
availableLanguages = ["en"] availableLanguages = ["en"];
detectLanguage() detectLanguage();
return return;
} }
const entries = output.trim().split('\n') const entries = output.trim().split('\n');
for (var i = 0; i < entries.length; i++) { for (var i = 0; i < entries.length; i++) {
const entry = entries[i].trim() const entry = entries[i].trim();
if (entry && entry.endsWith('.json')) { if (entry && entry.endsWith('.json')) {
// Extract language code from filename (e.g., "en.json" -> "en") // 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) { if (langCode.length >= 2 && langCode.length <= 5) {
// Basic validation for language codes // Basic validation for language codes
languages.push(langCode) languages.push(langCode);
} }
} }
} }
// Sort languages alphabetically, but ensure "en" comes first if available // Sort languages alphabetically, but ensure "en" comes first if available
languages.sort() languages.sort();
const enIndex = languages.indexOf("en") const enIndex = languages.indexOf("en");
if (enIndex > 0) { if (enIndex > 0) {
languages.splice(enIndex, 1) languages.splice(enIndex, 1);
languages.unshift("en") languages.unshift("en");
} }
if (languages.length === 0) { if (languages.length === 0) {
Logger.w("I18n", "No translation files found, using fallback") Logger.w("I18n", "No translation files found, using fallback");
languages = ["en"] // Fallback languages = ["en"]; // Fallback
} }
availableLanguages = languages availableLanguages = languages;
Logger.d("I18n", `Found ${languages.length} available languages: ${languages.join(', ')}`) Logger.d("I18n", `Found ${languages.length} available languages: ${languages.join(', ')}`);
// Detect language after scanning // Detect language after scanning
detectLanguage() detectLanguage();
} catch (e) { } catch (e) {
Logger.e("I18n", `Failed to parse directory listing: ${e}`) Logger.e("I18n", `Failed to parse directory listing: ${e}`);
// Fallback to default languages // Fallback to default languages
availableLanguages = ["en"] availableLanguages = ["en"];
detectLanguage() detectLanguage();
} }
} }
// ------------------------------------------- // -------------------------------------------
function 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) { if (availableLanguages.length === 0) {
Logger.w("I18n", "No available languages found") Logger.w("I18n", "No available languages found");
return return;
} }
var detectedLang = "" var detectedLang = "";
var detectedFullLocale = "" var detectedFullLocale = "";
// First, determine the system's preferred language // First, determine the system's preferred language
for (var i = 0; i < Qt.locale().uiLanguages.length; i++) { 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)) { if (availableLanguages.includes(fullUserLang)) {
detectedLang = fullUserLang detectedLang = fullUserLang;
detectedFullLocale = fullUserLang detectedFullLocale = fullUserLang;
break break;
} }
const shortUserLang = fullUserLang.substring(0, 2) const shortUserLang = fullUserLang.substring(0, 2);
if (availableLanguages.includes(shortUserLang)) { if (availableLanguages.includes(shortUserLang)) {
detectedLang = shortUserLang detectedLang = shortUserLang;
detectedFullLocale = fullUserLang detectedFullLocale = fullUserLang;
break break;
} }
} }
// If no system language is found among available languages, fallback // If no system language is found among available languages, fallback
if (detectedLang === "") { if (detectedLang === "") {
detectedLang = availableLanguages.includes("en") ? "en" : availableLanguages[0] detectedLang = availableLanguages.includes("en") ? "en" : availableLanguages[0];
detectedFullLocale = detectedLang detectedFullLocale = detectedLang;
} }
root.systemDetectedLangCode = detectedLang root.systemDetectedLangCode = detectedLang;
root.fullLocaleCode = detectedFullLocale root.fullLocaleCode = detectedFullLocale;
Logger.d("I18n", `System detected language: "${root.systemDetectedLangCode}" (full locale: "${root.fullLocaleCode}")`) Logger.d("I18n", `System detected language: "${root.systemDetectedLangCode}" (full locale: "${root.fullLocaleCode}")`);
// Now, apply the language: user-defined, then system-detected // Now, apply the language: user-defined, then system-detected
if (Settings.data.general.language !== "" && availableLanguages.includes(Settings.data.general.language)) { if (Settings.data.general.language !== "" && availableLanguages.includes(Settings.data.general.language)) {
Logger.d("I18n", `User-defined language found: "${Settings.data.general.language}"`) Logger.d("I18n", `User-defined language found: "${Settings.data.general.language}"`);
setLanguage(Settings.data.general.language) setLanguage(Settings.data.general.language);
} else { } else {
Logger.d("I18n", `No user-defined language, using system detected: "${root.systemDetectedLangCode}"`) Logger.d("I18n", `No user-defined language, using system detected: "${root.systemDetectedLangCode}"`);
setLanguage(root.systemDetectedLangCode, root.fullLocaleCode) setLanguage(root.systemDetectedLangCode, root.fullLocaleCode);
} }
} }
// ------------------------------------------- // -------------------------------------------
function setLanguage(newLangCode, fullLocale) { function setLanguage(newLangCode, fullLocale) {
if (typeof fullLocale === "undefined") { if (typeof fullLocale === "undefined") {
fullLocale = newLangCode fullLocale = newLangCode;
} }
if (newLangCode !== langCode && availableLanguages.includes(newLangCode)) { if (newLangCode !== langCode && availableLanguages.includes(newLangCode)) {
langCode = newLangCode langCode = newLangCode;
fullLocaleCode = fullLocale fullLocaleCode = fullLocale;
locale = Qt.locale(fullLocale) locale = Qt.locale(fullLocale);
Logger.i("I18n", `Language set to "${langCode}" with locale "${fullLocale}"`) Logger.i("I18n", `Language set to "${langCode}" with locale "${fullLocale}"`);
languageChanged(langCode) languageChanged(langCode);
loadTranslations() loadTranslations();
} else if (!availableLanguages.includes(newLangCode)) { } else if (!availableLanguages.includes(newLangCode)) {
Logger.w("I18n", `Language "${newLangCode}" is not available`) Logger.w("I18n", `Language "${newLangCode}" is not available`);
} }
} }
// ------------------------------------------- // -------------------------------------------
function loadTranslations() { function loadTranslations() {
if (langCode === "") if (langCode === "")
return return;
const filePath = `file://${Quickshell.shellDir}/Assets/Translations/${langCode}.json`;
const filePath = `file://${Quickshell.shellDir}/Assets/Translations/${langCode}.json` fileView.path = filePath;
fileView.path = filePath isLoaded = false;
isLoaded = false Logger.d("I18n", `Loading translations: ${langCode}`);
Logger.d("I18n", `Loading translations: ${langCode}`)
// Only load fallback translations if we are not using english and english is available // Only load fallback translations if we are not using english and english is available
if (langCode !== "en" && availableLanguages.includes("en")) { 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 // Check if a translation exists
function hasTranslation(key) { function hasTranslation(key) {
if (!isLoaded) if (!isLoaded)
return false return false;
const keys = key.split(".") const keys = key.split(".");
var value = translations var value = translations;
for (var i = 0; i < keys.length; i++) { for (var i = 0; i < keys.length; i++) {
if (value && typeof value === "object" && keys[i] in value) { if (value && typeof value === "object" && keys[i] in value) {
value = value[keys[i]] value = value[keys[i]];
} else { } else {
return false return false;
} }
} }
return typeof value === "string" return typeof value === "string";
} }
// ------------------------------------------- // -------------------------------------------
// Get all translation keys (useful for debugging) // Get all translation keys (useful for debugging)
function getAllKeys(obj, prefix) { function getAllKeys(obj, prefix) {
if (typeof obj === "undefined") if (typeof obj === "undefined")
obj = translations obj = translations;
if (typeof prefix === "undefined") if (typeof prefix === "undefined")
prefix = "" prefix = "";
var keys = [] var keys = [];
for (var key in (obj || {})) { for (var key in (obj || {})) {
const value = obj[key] const value = obj[key];
const fullKey = prefix ? `${prefix}.${key}` : key const fullKey = prefix ? `${prefix}.${key}` : key;
if (typeof value === "object" && value !== null) { if (typeof value === "object" && value !== null) {
keys = keys.concat(getAllKeys(value, fullKey)) keys = keys.concat(getAllKeys(value, fullKey));
} else if (typeof value === "string") { } else if (typeof value === "string") {
keys.push(fullKey) keys.push(fullKey);
} }
} }
return keys return keys;
} }
// ------------------------------------------- // -------------------------------------------
// Reload translations (useful for development) // Reload translations (useful for development)
function reload() { function reload() {
Logger.d("I18n", "Reloading translations") Logger.d("I18n", "Reloading translations");
loadTranslations() loadTranslations();
} }
// ------------------------------------------- // -------------------------------------------
// Main translation function // Main translation function
function tr(key, interpolations) { function tr(key, interpolations) {
if (typeof interpolations === "undefined") if (typeof interpolations === "undefined")
interpolations = {} interpolations = {};
if (!isLoaded) { if (!isLoaded) {
//Logger.d("I18n", "Translations not loaded yet") //Logger.d("I18n", "Translations not loaded yet")
return key return key;
} }
// Navigate nested keys (e.g., "menu.file.open") // Navigate nested keys (e.g., "menu.file.open")
const keys = key.split(".") const keys = key.split(".");
// Look-up translation in the active language // Look-up translation in the active language
var value = translations var value = translations;
var notFound = false var notFound = false;
for (var i = 0; i < keys.length; i++) { for (var i = 0; i < keys.length; i++) {
if (value && typeof value === "object" && keys[i] in value) { if (value && typeof value === "object" && keys[i] in value) {
value = value[keys[i]] value = value[keys[i]];
} else { } else {
Logger.d("I18n", `Translation key "${key}" not found at part "${keys[i]}"`) Logger.d("I18n", `Translation key "${key}" not found at part "${keys[i]}"`);
Logger.d("I18n", `Available keys: ${Object.keys(value || {}).join(", ")}`) Logger.d("I18n", `Available keys: ${Object.keys(value || {}).join(", ")}`);
notFound = true notFound = true;
break break;
} }
} }
// Fallback to english if not found // Fallback to english if not found
if (notFound && availableLanguages.includes("en") && langCode !== "en") { if (notFound && availableLanguages.includes("en") && langCode !== "en") {
value = fallbackTranslations value = fallbackTranslations;
for (var i = 0; i < keys.length; i++) { for (var i = 0; i < keys.length; i++) {
if (value && typeof value === "object" && keys[i] in value) { if (value && typeof value === "object" && keys[i] in value) {
value = value[keys[i]] value = value[keys[i]];
} else { } else {
// Indicate this key does not even exists in the english fallback // Indicate this key does not even exists in the english fallback
return `## ${key} ##` return `## ${key} ##`;
} }
} }
// Make untranslated string easy to spot // Make untranslated string easy to spot
value = `<i>${value}</i>` value = `<i>${value}</i>`;
} else if (notFound) { } else if (notFound) {
// No fallback available // No fallback available
return `## ${key} ##` return `## ${key} ##`;
} }
if (typeof value !== "string") { if (typeof value !== "string") {
Logger.d("I18n", `Translation key "${key}" is not a string`) Logger.d("I18n", `Translation key "${key}" is not a string`);
return key return key;
} }
// Handle interpolations (e.g., "Hello {name}!") // Handle interpolations (e.g., "Hello {name}!")
var result = value var result = value;
for (var placeholder in interpolations) { for (var placeholder in interpolations) {
const regex = new RegExp(`\\{${placeholder}\\}`, 'g') const regex = new RegExp(`\\{${placeholder}\\}`, 'g');
result = result.replace(regex, interpolations[placeholder]) result = result.replace(regex, interpolations[placeholder]);
} }
return result return result;
} }
// ------------------------------------------- // -------------------------------------------
// Plural translation function // Plural translation function
function trp(key, count, defaultSingular, defaultPlural, interpolations) { function trp(key, count, defaultSingular, defaultPlural, interpolations) {
if (typeof defaultSingular === "undefined") if (typeof defaultSingular === "undefined")
defaultSingular = "" defaultSingular = "";
if (typeof defaultPlural === "undefined") if (typeof defaultPlural === "undefined")
defaultPlural = "" defaultPlural = "";
if (typeof interpolations === "undefined") if (typeof interpolations === "undefined")
interpolations = {} interpolations = {};
const pluralKey = count === 1 ? key : `${key}_plural` const pluralKey = count === 1 ? key : `${key}_plural`;
const defaultValue = count === 1 ? defaultSingular : defaultPlural const defaultValue = count === 1 ? defaultSingular : defaultPlural;
// Merge interpolations with count (QML doesn't support spread operator) // Merge interpolations with count (QML doesn't support spread operator)
var finalInterpolations = { var finalInterpolations = {
"count": count "count": count
} };
for (var prop in interpolations) { for (var prop in interpolations) {
finalInterpolations[prop] = interpolations[prop] finalInterpolations[prop] = interpolations[prop];
} }
return tr(pluralKey, finalInterpolations) return tr(pluralKey, finalInterpolations);
} }
} }

View File

@@ -26,15 +26,15 @@ Singleton {
signal fontReloaded signal fontReloaded
Component.onCompleted: { Component.onCompleted: {
Logger.i("Icons", "Service started") Logger.i("Icons", "Service started");
loadFontWithCacheBusting() loadFontWithCacheBusting();
} }
Connections { Connections {
target: Quickshell target: Quickshell
function onReloadCompleted() { function onReloadCompleted() {
Logger.d("Icons", "Quickshell reload completed - forcing font reload") Logger.d("Icons", "Quickshell reload completed - forcing font reload");
reloadFont() reloadFont();
} }
} }
@@ -42,20 +42,20 @@ Singleton {
function get(iconName) { function get(iconName) {
// Check in aliases first // Check in aliases first
if (aliases[iconName] !== undefined) { if (aliases[iconName] !== undefined) {
iconName = aliases[iconName] iconName = aliases[iconName];
} }
// Find the appropriate codepoint // Find the appropriate codepoint
return icons[iconName] return icons[iconName];
} }
function loadFontWithCacheBusting() { function loadFontWithCacheBusting() {
Logger.d("Icons", "Loading font with cache busting") Logger.d("Icons", "Loading font with cache busting");
// Destroy old loader first // Destroy old loader first
if (currentFontLoader) { if (currentFontLoader) {
currentFontLoader.destroy() currentFontLoader.destroy();
currentFontLoader = null currentFontLoader = null;
} }
// Create new loader with cache-busting URL // Create new loader with cache-busting URL
@@ -64,22 +64,22 @@ Singleton {
FontLoader { FontLoader {
source: "${cacheBustingPath}" source: "${cacheBustingPath}"
} }
`, root, "dynamicFontLoader_" + fontVersion) `, root, "dynamicFontLoader_" + fontVersion);
// Connect to the new loader's status changes // Connect to the new loader's status changes
currentFontLoader.statusChanged.connect(function () { currentFontLoader.statusChanged.connect(function () {
if (currentFontLoader.status === FontLoader.Ready) { if (currentFontLoader.status === FontLoader.Ready) {
Logger.d("Icons", "Font loaded successfully:", currentFontLoader.name, "(version " + fontVersion + ")") Logger.d("Icons", "Font loaded successfully:", currentFontLoader.name, "(version " + fontVersion + ")");
fontReloaded() fontReloaded();
} else if (currentFontLoader.status === FontLoader.Error) { } 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() { function reloadFont() {
Logger.d("Icons", "Forcing font reload...") Logger.d("Icons", "Forcing font reload...");
fontVersion++ fontVersion++;
loadFontWithCacheBusting() loadFontWithCacheBusting();
} }
} }

View File

@@ -310,8 +310,8 @@ Singleton {
"align-left": "\u{ea09}", "align-left": "\u{ea09}",
"align-left-2": "\u{ff00}", "align-left-2": "\u{ff00}",
"align-right": "\u{ea0a}", "align-right": "\u{ea0a}",
"alpha"//"align-right-2": "\u{feff}", //"align-right-2": "\u{feff}",
: "\u{f543}", "alpha": "\u{f543}",
"alphabet-arabic": "\u{ff2f}", "alphabet-arabic": "\u{ff2f}",
"alphabet-bangla": "\u{ff2e}", "alphabet-bangla": "\u{ff2e}",
"alphabet-cyrillic": "\u{f1df}", "alphabet-cyrillic": "\u{f1df}",
@@ -3143,8 +3143,8 @@ Singleton {
"friends": "\u{eab0}", "friends": "\u{eab0}",
"friends-off": "\u{f136}", "friends-off": "\u{f136}",
"frustum": "\u{fa9f}", "frustum": "\u{fa9f}",
"frustum-plus"//"frustum-off": "\u{fa9d}", //"frustum-off": "\u{fa9d}",
: "\u{fa9e}", "frustum-plus": "\u{fa9e}",
"function": "\u{f225}", "function": "\u{f225}",
"function-filled": "\u{fc2b}", "function-filled": "\u{fc2b}",
"function-off": "\u{f3f0}", "function-off": "\u{f3f0}",
@@ -3403,13 +3403,13 @@ Singleton {
"hexagon-letter-x": "\u{f479}", "hexagon-letter-x": "\u{f479}",
"hexagon-letter-x-filled": "\u{fe30}", "hexagon-letter-x-filled": "\u{fe30}",
"hexagon-letter-y": "\u{f47a}", "hexagon-letter-y": "\u{f47a}",
"hexagon-letter-z"//"hexagon-letter-y-filled": "\u{fe2f}", //"hexagon-letter-y-filled": "\u{fe2f}",
: "\u{f47b}", "hexagon-letter-z": "\u{f47b}",
"hexagon-minus"//"hexagon-letter-z-filled": "\u{fe2e}", //"hexagon-letter-z-filled": "\u{fe2e}",
: "\u{fc8f}", "hexagon-minus": "\u{fc8f}",
"hexagon-minus-2": "\u{fc8e}", "hexagon-minus-2": "\u{fc8e}",
"hexagon-number-0"//"hexagon-minus-filled": "\u{fe2d}", //"hexagon-minus-filled": "\u{fe2d}",
: "\u{f459}", "hexagon-number-0": "\u{f459}",
"hexagon-number-0-filled": "\u{f74c}", "hexagon-number-0-filled": "\u{f74c}",
"hexagon-number-1": "\u{f45a}", "hexagon-number-1": "\u{f45a}",
"hexagon-number-1-filled": "\u{f74d}", "hexagon-number-1-filled": "\u{f74d}",
@@ -3432,8 +3432,8 @@ Singleton {
"hexagon-off": "\u{ee9c}", "hexagon-off": "\u{ee9c}",
"hexagon-plus": "\u{fc45}", "hexagon-plus": "\u{fc45}",
"hexagon-plus-2": "\u{fc90}", "hexagon-plus-2": "\u{fc90}",
"hexagonal-prism"//"hexagon-plus-filled": "\u{fe2c}", //"hexagon-plus-filled": "\u{fe2c}",
: "\u{faa5}", "hexagonal-prism": "\u{faa5}",
"hexagonal-prism-off": "\u{faa3}", "hexagonal-prism-off": "\u{faa3}",
"hexagonal-prism-plus": "\u{faa4}", "hexagonal-prism-plus": "\u{faa4}",
"hexagonal-pyramid": "\u{faa8}", "hexagonal-pyramid": "\u{faa8}",
@@ -3463,8 +3463,8 @@ Singleton {
"home-eco": "\u{f351}", "home-eco": "\u{f351}",
"home-edit": "\u{f352}", "home-edit": "\u{f352}",
"home-exclamation": "\u{f33c}", "home-exclamation": "\u{f33c}",
"home-hand"//"home-filled": "\u{fe2b}", //"home-filled": "\u{fe2b}",
: "\u{f504}", "home-hand": "\u{f504}",
"home-heart": "\u{f353}", "home-heart": "\u{f353}",
"home-infinity": "\u{f505}", "home-infinity": "\u{f505}",
"home-link": "\u{f354}", "home-link": "\u{f354}",
@@ -3582,8 +3582,8 @@ Singleton {
"ironing-2-filled": "\u{1006e}", "ironing-2-filled": "\u{1006e}",
"ironing-3": "\u{f2f6}", "ironing-3": "\u{f2f6}",
"ironing-3-filled": "\u{1006d}", "ironing-3-filled": "\u{1006d}",
"ironing-off"//"ironing-filled": "\u{fe2a}", //"ironing-filled": "\u{fe2a}",
: "\u{f2f7}", "ironing-off": "\u{f2f7}",
"ironing-steam": "\u{f2f9}", "ironing-steam": "\u{f2f9}",
"ironing-steam-filled": "\u{1006c}", "ironing-steam-filled": "\u{1006c}",
"ironing-steam-off": "\u{f2f8}", "ironing-steam-off": "\u{f2f8}",
@@ -3593,8 +3593,8 @@ Singleton {
"italic": "\u{eb93}", "italic": "\u{eb93}",
"jacket": "\u{f661}", "jacket": "\u{f661}",
"jetpack": "\u{f581}", "jetpack": "\u{f581}",
"jewish-star"//"jetpack-filled": "\u{fe29}", //"jetpack-filled": "\u{fe29}",
: "\u{f3ff}", "jewish-star": "\u{f3ff}",
"jewish-star-filled": "\u{f67e}", "jewish-star-filled": "\u{f67e}",
"join-bevel": "\u{ff4c}", "join-bevel": "\u{ff4c}",
"join-round": "\u{ff4b}", "join-round": "\u{ff4b}",
@@ -3608,8 +3608,8 @@ Singleton {
"kering": "\u{efb8}", "kering": "\u{efb8}",
"kerning": "\u{efb8}", "kerning": "\u{efb8}",
"key": "\u{eac7}", "key": "\u{eac7}",
"key-off"//"key-filled": "\u{fe28}", //"key-filled": "\u{fe28}",
: "\u{f14b}", "key-off": "\u{f14b}",
"keyboard": "\u{ebd6}", "keyboard": "\u{ebd6}",
"keyboard-filled": "\u{100a2}", "keyboard-filled": "\u{100a2}",
"keyboard-hide": "\u{ec7e}", "keyboard-hide": "\u{ec7e}",
@@ -3665,20 +3665,20 @@ Singleton {
"layers-union": "\u{eacb}", "layers-union": "\u{eacb}",
"layout": "\u{eadb}", "layout": "\u{eadb}",
"layout-2": "\u{eacc}", "layout-2": "\u{eacc}",
"layout-align-left"//"layout-2-filled": "\u{fe27}", //"layout-2-filled": "\u{fe27}",
// "layout-align-bottom": "\u{eacd}", //"layout-align-bottom": "\u{eacd}",
//"layout-align-bottom-filled": "\u{fe26}", //"layout-align-bottom-filled": "\u{fe26}",
// "layout-align-center": "\u{eace}", //"layout-align-center": "\u{eace}",
//"layout-align-center-filled": "\u{fe25}", //"layout-align-center-filled": "\u{fe25}",
: "\u{eacf}", "layout-align-left": "\u{eacf}",
"layout-align-middle"// "layout-align-left-filled": "\u{fe24}", //"layout-align-left-filled": "\u{fe24}",
: "\u{ead0}", "layout-align-middle": "\u{ead0}",
"layout-align-right"//"layout-align-middle-filled": "\u{fe23}", //"layout-align-middle-filled": "\u{fe23}",
: "\u{ead1}", "layout-align-right": "\u{ead1}",
"layout-align-top"//"layout-align-right-filled": "\u{fe22}", //"layout-align-right-filled": "\u{fe22}",
: "\u{ead2}", "layout-align-top": "\u{ead2}",
"layout-board"//"layout-align-top-filled": "\u{fe21}", //"layout-align-top-filled": "\u{fe21}",
: "\u{ef95}", "layout-board": "\u{ef95}",
"layout-board-filled": "\u{10182}", "layout-board-filled": "\u{10182}",
"layout-board-split": "\u{ef94}", "layout-board-split": "\u{ef94}",
"layout-board-split-filled": "\u{10183}", "layout-board-split-filled": "\u{10183}",
@@ -3690,8 +3690,8 @@ Singleton {
"layout-bottombar-filled": "\u{fc37}", "layout-bottombar-filled": "\u{fc37}",
"layout-bottombar-inactive": "\u{fd45}", "layout-bottombar-inactive": "\u{fd45}",
"layout-cards": "\u{ec13}", "layout-cards": "\u{ec13}",
"layout-collage"// "layout-cards-filled": "\u{fe20}", //"layout-cards-filled": "\u{fe20}",
: "\u{f389}", "layout-collage": "\u{f389}",
"layout-columns": "\u{ead4}", "layout-columns": "\u{ead4}",
"layout-dashboard": "\u{f02c}", "layout-dashboard": "\u{f02c}",
"layout-dashboard-filled": "\u{fe1f}", "layout-dashboard-filled": "\u{fe1f}",
@@ -4172,14 +4172,14 @@ Singleton {
"microphone": "\u{eaf0}", "microphone": "\u{eaf0}",
"microphone-2": "\u{ef2c}", "microphone-2": "\u{ef2c}",
"microphone-2-off": "\u{f40d}", "microphone-2-off": "\u{f40d}",
"microphone-off"//"microphone-filled": "\u{fe0f}", //"microphone-filled": "\u{fe0f}",
: "\u{ed16}", "microphone-off": "\u{ed16}",
"microscope": "\u{ef64}", "microscope": "\u{ef64}",
"microscope-filled": "\u{10166}", "microscope-filled": "\u{10166}",
"microscope-off": "\u{f40e}", "microscope-off": "\u{f40e}",
"microwave": "\u{f248}", "microwave": "\u{f248}",
"microwave-off"//"microwave-filled": "\u{fe0e}", //"microwave-filled": "\u{fe0e}",
: "\u{f264}", "microwave-off": "\u{f264}",
"military-award": "\u{f079}", "military-award": "\u{f079}",
"military-rank": "\u{efcf}", "military-rank": "\u{efcf}",
"military-rank-filled": "\u{ff5e}", "military-rank-filled": "\u{ff5e}",
@@ -4413,18 +4413,18 @@ Singleton {
"number-4-small": "\u{fcf9}", "number-4-small": "\u{fcf9}",
"number-40-small": "\u{fffa}", "number-40-small": "\u{fffa}",
"number-41-small": "\u{fff9}", "number-41-small": "\u{fff9}",
"number-5"//"number-42-small": "\u{fff8}", //"number-42-small": "\u{fff8}",
// "number-43-small": "\u{fff7}", //"number-43-small": "\u{fff7}",
// "number-44-small": "\u{fff6}", //"number-44-small": "\u{fff6}",
// "number-45-small": "\u{fff5}", //"number-45-small": "\u{fff5}",
// "number-46-small": "\u{fff4}", //"number-46-small": "\u{fff4}",
// "number-47-small": "\u{fff3}", //"number-47-small": "\u{fff3}",
// "number-48-small": "\u{fff2}", //"number-48-small": "\u{fff2}",
// "number-49-small": "\u{fff1}", //"number-49-small": "\u{fff1}",
: "\u{edf5}", "number-5": "\u{edf5}",
"number-5-small": "\u{fcfa}", "number-5-small": "\u{fcfa}",
"number-51-small"// "number-50-small": "\u{fff0}", //"number-50-small": "\u{fff0}",
: "\u{ffef}", "number-51-small": "\u{ffef}",
"number-52-small": "\u{ffee}", "number-52-small": "\u{ffee}",
"number-53-small": "\u{ffed}", "number-53-small": "\u{ffed}",
"number-54-small": "\u{ffec}", "number-54-small": "\u{ffec}",
@@ -4864,11 +4864,11 @@ Singleton {
"quote": "\u{efbe}", "quote": "\u{efbe}",
"quote-filled": "\u{1009c}", "quote-filled": "\u{1009c}",
"quote-off": "\u{f188}", "quote-off": "\u{f188}",
"radar"//"quotes": "\u{fb1e}", //"quotes": "\u{fb1e}",
: "\u{f017}", "radar": "\u{f017}",
"radar-2": "\u{f016}", "radar-2": "\u{f016}",
"radar-off"//"radar-filled": "\u{fe0d}", //"radar-filled": "\u{fe0d}",
: "\u{f41f}", "radar-off": "\u{f41f}",
"radio": "\u{ef2d}", "radio": "\u{ef2d}",
"radio-off": "\u{f420}", "radio-off": "\u{f420}",
"radioactive": "\u{ecc0}", "radioactive": "\u{ecc0}",
@@ -4928,12 +4928,12 @@ Singleton {
"regex-off": "\u{f421}", "regex-off": "\u{f421}",
"registered": "\u{eb14}", "registered": "\u{eb14}",
"relation-many-to-many": "\u{ed7f}", "relation-many-to-many": "\u{ed7f}",
"relation-one-to-many"//"relation-many-to-many-filled": "\u{fe0c}", //"relation-many-to-many-filled": "\u{fe0c}",
: "\u{ed80}", "relation-one-to-many": "\u{ed80}",
"relation-one-to-one"//"relation-one-to-many-filled": "\u{fe0b}", //"relation-one-to-many-filled": "\u{fe0b}",
: "\u{ed81}", "relation-one-to-one": "\u{ed81}",
"reload"//"relation-one-to-one-filled": "\u{fe0a}", //"relation-one-to-one-filled": "\u{fe0a}",
: "\u{f3ae}", "reload": "\u{f3ae}",
"reorder": "\u{fc15}", "reorder": "\u{fc15}",
"repeat": "\u{eb72}", "repeat": "\u{eb72}",
"repeat-off": "\u{f18e}", "repeat-off": "\u{f18e}",
@@ -5085,8 +5085,8 @@ Singleton {
"search": "\u{eb1c}", "search": "\u{eb1c}",
"search-off": "\u{f19c}", "search-off": "\u{f19c}",
"section": "\u{eed5}", "section": "\u{eed5}",
"section-sign"//"section-filled": "\u{fe09}", //"section-filled": "\u{fe09}",
: "\u{f019}", "section-sign": "\u{f019}",
"seeding": "\u{ed51}", "seeding": "\u{ed51}",
"seeding-filled": "\u{10006}", "seeding-filled": "\u{10006}",
"seeding-off": "\u{f19d}", "seeding-off": "\u{f19d}",
@@ -5294,8 +5294,8 @@ Singleton {
"sort-z-a": "\u{f550}", "sort-z-a": "\u{f550}",
"sos": "\u{f24a}", "sos": "\u{f24a}",
"soup": "\u{ef2e}", "soup": "\u{ef2e}",
"soup-off"//"soup-filled": "\u{fe08}", //"soup-filled": "\u{fe08}",
: "\u{f42d}", "soup-off": "\u{f42d}",
"source-code": "\u{f4a2}", "source-code": "\u{f4a2}",
"space": "\u{ec0c}", "space": "\u{ec0c}",
"space-off": "\u{f1aa}", "space-off": "\u{f1aa}",
@@ -5388,22 +5388,22 @@ Singleton {
"square-half": "\u{effb}", "square-half": "\u{effb}",
"square-key": "\u{f638}", "square-key": "\u{f638}",
"square-letter-a": "\u{f47c}", "square-letter-a": "\u{f47c}",
"square-letter-b"//"square-letter-a-filled": "\u{fe07}", //"square-letter-a-filled": "\u{fe07}",
: "\u{f47d}", "square-letter-b": "\u{f47d}",
"square-letter-c"//"square-letter-b-filled": "\u{fe06}", //"square-letter-b-filled": "\u{fe06}",
: "\u{f47e}", "square-letter-c": "\u{f47e}",
"square-letter-d"//"square-letter-c-filled": "\u{fe05}", //"square-letter-c-filled": "\u{fe05}",
: "\u{f47f}", "square-letter-d": "\u{f47f}",
"square-letter-e"//"square-letter-d-filled": "\u{fe04}", //"square-letter-d-filled": "\u{fe04}",
: "\u{f480}", "square-letter-e": "\u{f480}",
"square-letter-f"//"square-letter-e-filled": "\u{fe03}", //"square-letter-e-filled": "\u{fe03}",
: "\u{f481}", "square-letter-f": "\u{f481}",
"square-letter-g"//"square-letter-f-filled": "\u{fe02}", //"square-letter-f-filled": "\u{fe02}",
: "\u{f482}", "square-letter-g": "\u{f482}",
"square-letter-h"//"square-letter-g-filled": "\u{fe01}", //"square-letter-g-filled": "\u{fe01}",
: "\u{f483}", "square-letter-h": "\u{f483}",
"square-letter-i"//"square-letter-h-filled": "\u{fe00}", //"square-letter-h-filled": "\u{fe00}",
: "\u{f484}", "square-letter-i": "\u{f484}",
"square-letter-i-filled": "\u{fdff}", "square-letter-i-filled": "\u{fdff}",
"square-letter-j": "\u{f485}", "square-letter-j": "\u{f485}",
"square-letter-j-filled": "\u{fdfe}", "square-letter-j-filled": "\u{fdfe}",

View File

@@ -7,63 +7,63 @@ Singleton {
id: root id: root
function _formatMessage(...args) { function _formatMessage(...args) {
var t = Time.getFormattedTimestamp() var t = Time.getFormattedTimestamp();
if (args.length > 1) { if (args.length > 1) {
const maxLength = 14 const maxLength = 14;
var module = args.shift().substring(0, maxLength).padStart(maxLength, " ") var module = args.shift().substring(0, maxLength).padStart(maxLength, " ");
return `\x1b[36m[${t}]\x1b[0m \x1b[35m${module}\x1b[0m ` + args.join(" ") return `\x1b[36m[${t}]\x1b[0m \x1b[35m${module}\x1b[0m ` + args.join(" ");
} else { } else {
return `[\x1b[36m[${t}]\x1b[0m ` + args.join(" ") return `[\x1b[36m[${t}]\x1b[0m ` + args.join(" ");
} }
} }
function _getStackTrace() { function _getStackTrace() {
try { try {
throw new Error("Stack trace") throw new Error("Stack trace");
} catch (e) { } catch (e) {
return e.stack return e.stack;
} }
} }
// Debug log (only when Settings.isDebug is true) // Debug log (only when Settings.isDebug is true)
function d(...args) { function d(...args) {
if (Settings && Settings.isDebug) { if (Settings?.isDebug) {
var msg = _formatMessage(...args) var msg = _formatMessage(...args);
console.debug(msg) console.debug(msg);
} }
} }
// Info log (always visible) // Info log (always visible)
function i(...args) { function i(...args) {
var msg = _formatMessage(...args) var msg = _formatMessage(...args);
console.info(msg) console.info(msg);
} }
// Warning log (always visible) // Warning log (always visible)
function w(...args) { function w(...args) {
var msg = _formatMessage(...args) var msg = _formatMessage(...args);
console.warn(msg) console.warn(msg);
} }
// Error log (always visible) // Error log (always visible)
function e(...args) { function e(...args) {
var msg = _formatMessage(...args) var msg = _formatMessage(...args);
console.error(msg) console.error(msg);
} }
function callStack() { function callStack() {
var stack = _getStackTrace() var stack = _getStackTrace();
Logger.i("Debug", "--------------------------") Logger.i("Debug", "--------------------------");
Logger.i("Debug", "Current call stack") Logger.i("Debug", "Current call stack");
// Split the stack into lines and log each one // 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++) { 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) { if (line.length > 0) {
// Only log non-empty lines // Only log non-empty lines
Logger.i("Debug", `- ${line}`) Logger.i("Debug", `- ${line}`);
} }
} }
Logger.i("Debug", "--------------------------") Logger.i("Debug", "--------------------------");
} }
} }

View File

@@ -3,9 +3,9 @@ pragma Singleton
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
import "../Helpers/QtObj2JS.js" as QtObj2JS
import qs.Commons import qs.Commons
import qs.Services.UI import qs.Services.UI
import "../Helpers/QtObj2JS.js" as QtObj2JS
Singleton { Singleton {
id: root id: root
@@ -42,30 +42,30 @@ Singleton {
// Ensure directories exist before FileView tries to read files // Ensure directories exist before FileView tries to read files
Component.onCompleted: { Component.onCompleted: {
// ensure settings dir exists // ensure settings dir exists
Quickshell.execDetached(["mkdir", "-p", configDir]) Quickshell.execDetached(["mkdir", "-p", configDir]);
Quickshell.execDetached(["mkdir", "-p", cacheDir]) Quickshell.execDetached(["mkdir", "-p", cacheDir]);
Quickshell.execDetached(["mkdir", "-p", cacheDirImagesWallpapers]) Quickshell.execDetached(["mkdir", "-p", cacheDirImagesWallpapers]);
Quickshell.execDetached(["mkdir", "-p", cacheDirImagesNotifications]) Quickshell.execDetached(["mkdir", "-p", cacheDirImagesNotifications]);
// Mark directories as created and trigger file loading // Mark directories as created and trigger file loading
directoriesCreated = true directoriesCreated = true;
// This should only be activated once when the settings structure has changed // 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 // Then it should be commented out again, regular users don't need to generate
// default settings on every start // default settings on every start
if (isDebug) { if (isDebug) {
generateDefaultSettings() generateDefaultSettings();
} }
// Patch-in the local default, resolved to user's home // Patch-in the local default, resolved to user's home
adapter.general.avatarImage = defaultAvatar adapter.general.avatarImage = defaultAvatar;
adapter.screenRecorder.directory = defaultVideosDirectory adapter.screenRecorder.directory = defaultVideosDirectory;
adapter.wallpaper.directory = defaultWallpapersDirectory adapter.wallpaper.directory = defaultWallpapersDirectory;
adapter.wallpaper.defaultWallpaper = Quickshell.shellDir + "/Assets/Wallpaper/noctalia.png" adapter.wallpaper.defaultWallpaper = Quickshell.shellDir + "/Assets/Wallpaper/noctalia.png";
// Set the adapter to the settingsFileView to trigger the real settings load // 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 // Don't write settings to disk immediately
@@ -75,7 +75,7 @@ Singleton {
running: false running: false
interval: 1000 interval: 1000
onTriggered: { onTriggered: {
root.saveImmediate() root.saveImmediate();
} }
} }
@@ -90,31 +90,31 @@ Singleton {
// Trigger initial load when path changes from empty to actual path // Trigger initial load when path changes from empty to actual path
onPathChanged: { onPathChanged: {
if (path !== undefined) { if (path !== undefined) {
reload() reload();
} }
} }
onLoaded: function () { onLoaded: function () {
if (!isLoaded) { if (!isLoaded) {
Logger.i("Settings", "Settings loaded") Logger.i("Settings", "Settings loaded");
upgradeSettingsData() upgradeSettingsData();
validateMonitorConfigurations() validateMonitorConfigurations();
isLoaded = true isLoaded = true;
// Emit the signal // Emit the signal
root.settingsLoaded() root.settingsLoaded();
// Finally, update our local settings version // Finally, update our local settings version
adapter.settingsVersion = settingsVersion adapter.settingsVersion = settingsVersion;
} }
} }
onLoadFailed: function (error) { onLoadFailed: function (error) {
if (error.toString().includes("No such file") || error === 2) { if (error.toString().includes("No such file") || error === 2) {
// File doesn't exist, create it with default values // File doesn't exist, create it with default values
writeAdapter() writeAdapter();
// Also write to fallback if set // Also write to fallback if set
if (Quickshell.env("NOCTALIA_SETTINGS_FALLBACK")) { if (Quickshell.env("NOCTALIA_SETTINGS_FALLBACK")) {
settingsFallbackFileView.writeAdapter() settingsFallbackFileView.writeAdapter();
} }
} }
} }
@@ -156,33 +156,48 @@ Singleton {
// Widget configuration for modular bar system // Widget configuration for modular bar system
property JsonObject widgets property JsonObject widgets
widgets: JsonObject { widgets: JsonObject {
property list<var> left: [{ property list<var> left: [
{
"id": "ControlCenter" "id": "ControlCenter"
}, { },
{
"id": "SystemMonitor" "id": "SystemMonitor"
}, { },
{
"id": "ActiveWindow" "id": "ActiveWindow"
}, { },
{
"id": "MediaMini" "id": "MediaMini"
}] }
property list<var> center: [{ ]
property list<var> center: [
{
"id": "Workspace" "id": "Workspace"
}] }
property list<var> right: [{ ]
property list<var> right: [
{
"id": "ScreenRecorder" "id": "ScreenRecorder"
}, { },
{
"id": "Tray" "id": "Tray"
}, { },
{
"id": "NotificationHistory" "id": "NotificationHistory"
}, { },
{
"id": "Battery" "id": "Battery"
}, { },
{
"id": "Volume" "id": "Volume"
}, { },
{
"id": "Brightness" "id": "Brightness"
}, { },
{
"id": "Clock" "id": "Clock"
}] }
]
} }
} }
@@ -294,41 +309,57 @@ Singleton {
property string position: "close_to_bar_button" property string position: "close_to_bar_button"
property JsonObject shortcuts property JsonObject shortcuts
shortcuts: JsonObject { shortcuts: JsonObject {
property list<var> left: [{ property list<var> left: [
{
"id": "WiFi" "id": "WiFi"
}, { },
{
"id": "Bluetooth" "id": "Bluetooth"
}, { },
{
"id": "ScreenRecorder" "id": "ScreenRecorder"
}, { },
{
"id": "WallpaperSelector" "id": "WallpaperSelector"
}] }
property list<var> right: [{ ]
property list<var> right: [
{
"id": "Notifications" "id": "Notifications"
}, { },
{
"id": "PowerProfile" "id": "PowerProfile"
}, { },
{
"id": "KeepAwake" "id": "KeepAwake"
}, { },
{
"id": "NightLight" "id": "NightLight"
}] }
]
} }
property list<var> cards: [{ property list<var> cards: [
{
"id": "profile-card", "id": "profile-card",
"enabled": true "enabled": true
}, { },
{
"id": "shortcuts-card", "id": "shortcuts-card",
"enabled": true "enabled": true
}, { },
{
"id": "audio-card", "id": "audio-card",
"enabled": true "enabled": true
}, { },
{
"id": "weather-card", "id": "weather-card",
"enabled": true "enabled": true
}, { },
{
"id": "media-sysmon-card", "id": "media-sysmon-card",
"enabled": true "enabled": true
}] }
]
} }
// system monitor // system monitor
@@ -371,25 +402,32 @@ Singleton {
property int countdownDuration: 10000 property int countdownDuration: 10000
property string position: "center" property string position: "center"
property bool showHeader: true property bool showHeader: true
property list<var> powerOptions: [{ property list<var> powerOptions: [
{
"action": "lock", "action": "lock",
"enabled": true "enabled": true
}, { },
{
"action": "suspend", "action": "suspend",
"enabled": true "enabled": true
}, { },
{
"action": "hibernate", "action": "hibernate",
"enabled": true "enabled": true
}, { },
{
"action": "reboot", "action": "reboot",
"enabled": true "enabled": true
}, { },
{
"action": "logout", "action": "logout",
"enabled": true "enabled": true
}, { },
{
"action": "shutdown", "action": "shutdown",
"enabled": true "enabled": true
}] }
]
} }
// notifications // notifications
@@ -493,81 +531,81 @@ Singleton {
// Function to preprocess paths by expanding "~" to user's home directory // Function to preprocess paths by expanding "~" to user's home directory
function preprocessPath(path) { function preprocessPath(path) {
if (typeof path !== "string" || path === "") { if (typeof path !== "string" || path === "") {
return path return path;
} }
// Expand "~" to user's home directory // Expand "~" to user's home directory
if (path.startsWith("~/")) { if (path.startsWith("~/")) {
return Quickshell.env("HOME") + path.substring(1) return Quickshell.env("HOME") + path.substring(1);
} else if (path === "~") { } else if (path === "~") {
return Quickshell.env("HOME") return Quickshell.env("HOME");
} }
return path return path;
} }
// ----------------------------------------------------- // -----------------------------------------------------
// Public function to trigger immediate settings saving // Public function to trigger immediate settings saving
function saveImmediate() { function saveImmediate() {
settingsFileView.writeAdapter() settingsFileView.writeAdapter();
// Write to fallback location if set // Write to fallback location if set
if (Quickshell.env("NOCTALIA_SETTINGS_FALLBACK")) { 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 // Generate default settings at the root of the repo
function generateDefaultSettings() { function generateDefaultSettings() {
try { try {
Logger.d("Settings", "Generating settings-default.json") Logger.d("Settings", "Generating settings-default.json");
// Prepare a clean JSON // Prepare a clean JSON
var plainAdapter = QtObj2JS.qtObjectToPlainObject(adapter) var plainAdapter = QtObj2JS.qtObjectToPlainObject(adapter);
var jsonData = JSON.stringify(plainAdapter, null, 2) 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 // Encode transfer it has base64 to avoid any escaping issue
var base64Data = Qt.btoa(jsonData) var base64Data = Qt.btoa(jsonData);
Quickshell.execDetached(["sh", "-c", `echo "${base64Data}" | base64 -d > "${defaultPath}"`]) Quickshell.execDetached(["sh", "-c", `echo "${base64Data}" | base64 -d > "${defaultPath}"`]);
} catch (error) { } 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 to validate monitor configurations
function validateMonitorConfigurations() { function validateMonitorConfigurations() {
var availableScreenNames = [] var availableScreenNames = [];
for (var i = 0; i < Quickshell.screens.length; i++) { 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", "Available monitors: [" + availableScreenNames.join(", ") + "]");
Logger.d("Settings", "Configured bar monitors: [" + adapter.bar.monitors.join(", ") + "]") Logger.d("Settings", "Configured bar monitors: [" + adapter.bar.monitors.join(", ") + "]");
// Check bar monitors // Check bar monitors
if (adapter.bar.monitors.length > 0) { if (adapter.bar.monitors.length > 0) {
var hasValidBarMonitor = false var hasValidBarMonitor = false;
for (var j = 0; j < adapter.bar.monitors.length; j++) { for (var j = 0; j < adapter.bar.monitors.length; j++) {
if (availableScreenNames.includes(adapter.bar.monitors[j])) { if (availableScreenNames.includes(adapter.bar.monitors[j])) {
hasValidBarMonitor = true hasValidBarMonitor = true;
break break;
} }
} }
if (!hasValidBarMonitor) { if (!hasValidBarMonitor) {
Logger.w("Settings", "No configured bar monitors found on system, clearing bar monitor list to show on all screens") Logger.w("Settings", "No configured bar monitors found on system, clearing bar monitor list to show on all screens");
adapter.bar.monitors = [] adapter.bar.monitors = [];
} else { } else
//Logger.i("Settings", "Found valid bar monitors, keeping configuration") //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") //Logger.i("Settings", "Bar monitor list is empty, will show on all available screens")
} {}
} }
// ----------------------------------------------------- // -----------------------------------------------------
@@ -576,50 +614,50 @@ Singleton {
function upgradeSettingsData() { function upgradeSettingsData() {
// Wait for BarWidgetRegistry to be ready // Wait for BarWidgetRegistry to be ready
if (!BarWidgetRegistry.widgets || Object.keys(BarWidgetRegistry.widgets).length === 0) { if (!BarWidgetRegistry.widgets || Object.keys(BarWidgetRegistry.widgets).length === 0) {
Logger.w("Settings", "BarWidgetRegistry not ready, deferring upgrade") Logger.w("Settings", "BarWidgetRegistry not ready, deferring upgrade");
Qt.callLater(upgradeSettingsData) Qt.callLater(upgradeSettingsData);
return return;
} }
const sections = ["left", "center", "right"] const sections = ["left", "center", "right"];
// ----------------- // -----------------
// 1st. convert old widget id to new id // 1st. convert old widget id to new id
for (var s = 0; s < sections.length; s++) { 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++) { 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) { switch (widget.id) {
case "DarkModeToggle": case "DarkModeToggle":
widget.id = "DarkMode" widget.id = "DarkMode";
break break;
case "PowerToggle": case "PowerToggle":
widget.id = "SessionMenu" widget.id = "SessionMenu";
break break;
case "ScreenRecorderIndicator": case "ScreenRecorderIndicator":
widget.id = "ScreenRecorder" widget.id = "ScreenRecorder";
break break;
case "SidePanelToggle": case "SidePanelToggle":
widget.id = "ControlCenter" widget.id = "ControlCenter";
break break;
} }
} }
} }
// ----------------- // -----------------
// 2nd. remove any non existing widget type // 2nd. remove any non existing widget type
var removedWidget = false var removedWidget = false;
for (var s = 0; s < sections.length; s++) { for (var s = 0; s < sections.length; s++) {
const sectionName = sections[s] const sectionName = sections[s];
const widgets = adapter.bar.widgets[sectionName] const widgets = adapter.bar.widgets[sectionName];
// Iterate backward through the widgets array, so it does not break when removing a widget // Iterate backward through the widgets array, so it does not break when removing a widget
for (var i = widgets.length - 1; i >= 0; i--) { for (var i = widgets.length - 1; i >= 0; i--) {
var widget = widgets[i] var widget = widgets[i];
if (!BarWidgetRegistry.hasWidget(widget.id)) { if (!BarWidgetRegistry.hasWidget(widget.id)) {
Logger.w(`Settings`, `Deleted invalid widget ${widget.id}`) Logger.w(`Settings`, `Deleted invalid widget ${widget.id}`);
widgets.splice(i, 1) widgets.splice(i, 1);
removedWidget = true removedWidget = true;
} }
} }
} }
@@ -627,18 +665,18 @@ Singleton {
// ----------------- // -----------------
// 3nd. upgrade widget settings // 3nd. upgrade widget settings
for (var s = 0; s < sections.length; s++) { 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++) { 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 // 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) { if ((reg === undefined) || (reg.allowUserSettings === undefined) || !reg.allowUserSettings) {
continue continue;
} }
if (upgradeWidget(widget)) { 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 +685,14 @@ Singleton {
// 4th. safety check // 4th. safety check
// if a widget was deleted, ensure we still have a control center // if a widget was deleted, ensure we still have a control center
if (removedWidget) { if (removedWidget) {
var gotControlCenter = false var gotControlCenter = false;
for (var s = 0; s < sections.length; s++) { 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++) { 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") { if (widget.id === "ControlCenter") {
gotControlCenter = true gotControlCenter = true;
break break;
} }
} }
} }
@@ -663,8 +701,8 @@ Singleton {
//const obj = JSON.parse('{"id": "ControlCenter"}'); //const obj = JSON.parse('{"id": "ControlCenter"}');
adapter.bar.widgets["right"].push(({ adapter.bar.widgets["right"].push(({
"id": "ControlCenter" "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 +712,31 @@ Singleton {
if (adapter.settingsVersion < 21) { if (adapter.settingsVersion < 21) {
// Read raw JSON file to access properties not in adapter schema // Read raw JSON file to access properties not in adapter schema
try { try {
var rawJson = settingsFileView.text() var rawJson = settingsFileView.text();
if (rawJson) { if (rawJson) {
var parsed = JSON.parse(rawJson) var parsed = JSON.parse(rawJson);
var anyDiscordEnabled = false var anyDiscordEnabled = false;
// Check if any Discord client was enabled // 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) { if (parsed.templates) {
for (var i = 0; i < discordClients.length; i++) { for (var i = 0; i < discordClients.length; i++) {
if (parsed.templates[discordClients[i]]) { if (parsed.templates[discordClients[i]]) {
anyDiscordEnabled = true anyDiscordEnabled = true;
break break;
} }
} }
} }
// Set unified discord property // 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) { } 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 +746,20 @@ Singleton {
if (adapter.settingsVersion < 22) { if (adapter.settingsVersion < 22) {
// Read raw JSON file to access properties not in adapter schema // Read raw JSON file to access properties not in adapter schema
try { try {
var rawJson = settingsFileView.text() var rawJson = settingsFileView.text();
if (rawJson) { if (rawJson) {
var parsed = JSON.parse(rawJson) var parsed = JSON.parse(rawJson);
if (parsed.appLauncher && parsed.appLauncher.backgroundOpacity !== undefined) { if (parsed.appLauncher && parsed.appLauncher.backgroundOpacity !== undefined) {
var oldOpacity = parsed.appLauncher.backgroundOpacity var oldOpacity = parsed.appLauncher.backgroundOpacity;
if (adapter.ui) { if (adapter.ui) {
adapter.ui.panelBackgroundOpacity = oldOpacity adapter.ui.panelBackgroundOpacity = oldOpacity;
Logger.i("Settings", "Migrated appLauncher.backgroundOpacity to ui.panelBackgroundOpacity (value:", oldOpacity + ")") Logger.i("Settings", "Migrated appLauncher.backgroundOpacity to ui.panelBackgroundOpacity (value:", oldOpacity + ")");
} }
} }
} }
} catch (error) { } 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 +770,23 @@ Singleton {
if (adapter.settingsVersion < 23) { if (adapter.settingsVersion < 23) {
// Read raw JSON file to access dimDesktop property // Read raw JSON file to access dimDesktop property
try { try {
var rawJson = settingsFileView.text() var rawJson = settingsFileView.text();
if (rawJson) { if (rawJson) {
var parsed = JSON.parse(rawJson) var parsed = JSON.parse(rawJson);
if (parsed.general && parsed.general.dimDesktop === true) { if (parsed.general && parsed.general.dimDesktop === true) {
// Check if dimmerOpacity exists in raw JSON (not adapter default) // 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 dimmerOpacity wasn't explicitly set in JSON or was 0, set it to 0.8 (80% dimming)
if (dimmerOpacityInJson === undefined || dimmerOpacityInJson === 0) { if (dimmerOpacityInJson === undefined || dimmerOpacityInJson === 0) {
adapter.general.dimmerOpacity = 0.8 adapter.general.dimmerOpacity = 0.8;
Logger.i("Settings", "Migrated dimDesktop=true: set dimmerOpacity to 0.8 (80% dimming)") Logger.i("Settings", "Migrated dimDesktop=true: set dimmerOpacity to 0.8 (80% dimming)");
} }
} }
} }
} catch (error) { } 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 +794,35 @@ Singleton {
// ----------------------------------------------------- // -----------------------------------------------------
function upgradeWidget(widget) { function upgradeWidget(widget) {
// Backup the widget definition before altering // Backup the widget definition before altering
const widgetBefore = JSON.stringify(widget) const widgetBefore = JSON.stringify(widget);
// Get all existing custom settings keys // 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 // Delete deprecated user settings from the wiget
for (const k of Object.keys(widget)) { for (const k of Object.keys(widget)) {
if (k === "id" || k === "allowUserSettings") { if (k === "id" || k === "allowUserSettings") {
continue continue;
} }
if (!keys.includes(k)) { if (!keys.includes(k)) {
delete widget[k] delete widget[k];
} }
} }
// Inject missing default setting (metaData) from BarWidgetRegistry // Inject missing default setting (metaData) from BarWidgetRegistry
for (var i = 0; i < keys.length; i++) { for (var i = 0; i < keys.length; i++) {
const k = keys[i] const k = keys[i];
if (k === "id" || k === "allowUserSettings") { if (k === "id" || k === "allowUserSettings") {
continue continue;
} }
if (widget[k] === undefined) { 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 // Compare settings, to detect if something has been upgraded
const widgetAfter = JSON.stringify(widget) const widgetAfter = JSON.stringify(widget);
return (widgetAfter !== widgetBefore) return (widgetAfter !== widgetBefore);
} }
} }

View File

@@ -7,11 +7,6 @@ import qs.Services.Power
Singleton { Singleton {
id: root id: root
/*
Preset sizes for font, radii, ?
*/
// Font size // Font size
readonly property real fontSizeXXS: 8 readonly property real fontSizeXXS: 8
readonly property real fontSizeXS: 9 readonly property real fontSizeXS: 9
@@ -86,29 +81,27 @@ Singleton {
readonly property real barHeight: { readonly property real barHeight: {
switch (Settings.data.bar.density) { switch (Settings.data.bar.density) {
case "mini": 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": 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": 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: default:
case "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;
} }
} }
readonly property real capsuleHeight: { readonly property real capsuleHeight: {
switch (Settings.data.bar.density) { switch (Settings.data.bar.density) {
case "mini": case "mini":
return Math.round(barHeight * 1.0) return Math.round(barHeight * 1.0);
case "compact": case "compact":
return Math.round(barHeight * 0.85) return Math.round(barHeight * 0.85);
case "comfortable": case "comfortable":
return Math.round(barHeight * 0.73) return Math.round(barHeight * 0.73);
default: default:
case "default": case "default":
return Math.round(barHeight * 0.82) return Math.round(barHeight * 0.82);
} }
} }
} }

View File

@@ -7,46 +7,46 @@ Singleton {
id: root id: root
function iconFromName(iconName, fallbackName) { function iconFromName(iconName, fallbackName) {
const fallback = fallbackName || "application-x-executable" const fallback = fallbackName || "application-x-executable";
try { try {
if (iconName && typeof Quickshell !== 'undefined' && Quickshell.iconPath) { if (iconName && typeof Quickshell !== 'undefined' && Quickshell.iconPath) {
const p = Quickshell.iconPath(iconName, fallback) const p = Quickshell.iconPath(iconName, fallback);
if (p && p !== "") if (p && p !== "")
return p return p;
} }
} catch (e) { } catch (e)
// ignore and fall back // ignore and fall back
} {}
try { try {
return Quickshell.iconPath ? (Quickshell.iconPath(fallback, true) || "") : "" return Quickshell.iconPath ? (Quickshell.iconPath(fallback, true) || "") : "";
} catch (e2) { } catch (e2) {
return "" return "";
} }
} }
// Resolve icon path for a DesktopEntries appId - safe on missing entries // Resolve icon path for a DesktopEntries appId - safe on missing entries
function iconForAppId(appId, fallbackName) { function iconForAppId(appId, fallbackName) {
const fallback = fallbackName || "application-x-executable" const fallback = fallbackName || "application-x-executable";
if (!appId) if (!appId)
return iconFromName(fallback, fallback) return iconFromName(fallback, fallback);
try { try {
if (typeof DesktopEntries === 'undefined' || !DesktopEntries.byId) if (typeof DesktopEntries === 'undefined' || !DesktopEntries.byId)
return iconFromName(fallback, fallback) return iconFromName(fallback, fallback);
const entry = (DesktopEntries.heuristicLookup) ? DesktopEntries.heuristicLookup(appId) : DesktopEntries.byId(appId) const entry = (DesktopEntries.heuristicLookup) ? DesktopEntries.heuristicLookup(appId) : DesktopEntries.byId(appId);
const name = entry && entry.icon ? entry.icon : "" const name = entry && entry.icon ? entry.icon : "";
return iconFromName(name || fallback, fallback) return iconFromName(name || fallback, fallback);
} catch (e) { } catch (e) {
return iconFromName(fallback, fallback) return iconFromName(fallback, fallback);
} }
} }
// Distro logo helper (absolute path or empty string) // Distro logo helper (absolute path or empty string)
function distroLogoPath() { function distroLogoPath() {
try { try {
return (typeof OSInfo !== 'undefined' && OSInfo.distroIconPath) ? OSInfo.distroIconPath : "" return (typeof OSInfo !== 'undefined' && OSInfo.distroIconPath) ? OSInfo.distroIconPath : "";
} catch (e) { } catch (e) {
return "" return "";
} }
} }
} }

View File

@@ -1,7 +1,7 @@
pragma Singleton pragma Singleton
import QtQuick
import Quickshell import Quickshell
import QtQuick
import qs.Commons import qs.Commons
Singleton { Singleton {
@@ -12,7 +12,7 @@ Singleton {
// Returns a Unix Timestamp (in seconds) // Returns a Unix Timestamp (in seconds)
readonly property int timestamp: { readonly property int timestamp: {
return Math.floor(root.now / 1000) return Math.floor(root.now / 1000);
} }
Timer { Timer {
@@ -22,89 +22,89 @@ Singleton {
running: true running: true
triggeredOnStart: false triggeredOnStart: false
onTriggered: { onTriggered: {
var newTime = new Date() var newTime = new Date();
root.now = newTime root.now = newTime;
// Adjust next interval to sync with the start of the next second // Adjust next interval to sync with the start of the next second
var msIntoSecond = newTime.getMilliseconds() var msIntoSecond = newTime.getMilliseconds();
if (msIntoSecond > 100) { if (msIntoSecond > 100) {
// If we're more than 100ms into the second, adjust for next time // If we're more than 100ms into the second, adjust for next time
updateTimer.interval = 1000 - msIntoSecond + 10 // +10ms buffer updateTimer.interval = 1000 - msIntoSecond + 10; // +10ms buffer
updateTimer.restart() updateTimer.restart();
} else { } else {
updateTimer.interval = 1000 updateTimer.interval = 1000;
} }
} }
} }
Component.onCompleted: { Component.onCompleted: {
// Start by syncing to the next second boundary // Start by syncing to the next second boundary
var now = new Date() var now = new Date();
var msUntilNextSecond = 1000 - now.getMilliseconds() var msUntilNextSecond = 1000 - now.getMilliseconds();
updateTimer.interval = msUntilNextSecond + 10 // +10ms buffer updateTimer.interval = msUntilNextSecond + 10; // +10ms buffer
updateTimer.restart() updateTimer.restart();
} }
// Formats a Date object into a YYYYMMDD-HHMMSS string. // Formats a Date object into a YYYYMMDD-HHMMSS string.
function getFormattedTimestamp(date) { function getFormattedTimestamp(date) {
if (!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 // getMonth() is zero-based, so we add 1
const month = String(date.getMonth() + 1).padStart(2, '0') const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0') const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0') const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0') const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).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 // Format an easy to read approximate duration ex: 4h 32m
// Used to display the time remaining on the Battery widget, computer uptime, etc.. // Used to display the time remaining on the Battery widget, computer uptime, etc..
function formatVagueHumanReadableDuration(totalSeconds) { function formatVagueHumanReadableDuration(totalSeconds) {
if (typeof totalSeconds !== 'number' || totalSeconds < 0) { if (typeof totalSeconds !== 'number' || totalSeconds < 0) {
return '0s' return '0s';
} }
// Floor the input to handle decimal seconds // Floor the input to handle decimal seconds
totalSeconds = Math.floor(totalSeconds) totalSeconds = Math.floor(totalSeconds);
const days = Math.floor(totalSeconds / 86400) const days = Math.floor(totalSeconds / 86400);
const hours = Math.floor((totalSeconds % 86400) / 3600) const hours = Math.floor((totalSeconds % 86400) / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60) const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60 const seconds = totalSeconds % 60;
const parts = [] const parts = [];
if (days) if (days)
parts.push(`${days}d`) parts.push(`${days}d`);
if (hours) if (hours)
parts.push(`${hours}h`) parts.push(`${hours}h`);
if (minutes) if (minutes)
parts.push(`${minutes}m`) parts.push(`${minutes}m`);
// Only show seconds if no hours and no minutes // Only show seconds if no hours and no minutes
if (!hours && !minutes) { if (!hours && !minutes) {
parts.push(`${seconds}s`) parts.push(`${seconds}s`);
} }
return parts.join(' ') return parts.join(' ');
} }
// Format a date into // Format a date into
function formatRelativeTime(date) { function formatRelativeTime(date) {
if (!date) if (!date)
return "" return "";
const diff = Date.now() - date.getTime() const diff = Date.now() - date.getTime();
if (diff < 60000) if (diff < 60000)
return "now" return "now";
if (diff < 3600000) if (diff < 3600000)
return `${Math.floor(diff / 60000)}m ago` return `${Math.floor(diff / 60000)}m ago`;
if (diff < 86400000) if (diff < 86400000)
return `${Math.floor(diff / 3600000)}h ago` return `${Math.floor(diff / 3600000)}h ago`;
return `${Math.floor(diff / 86400000)}d ago` return `${Math.floor(diff / 86400000)}d ago`;
} }
} }

View File

@@ -48,17 +48,17 @@ Variants {
Component.onCompleted: setWallpaperInitial() Component.onCompleted: setWallpaperInitial()
Component.onDestruction: { Component.onDestruction: {
transitionAnimation.stop() transitionAnimation.stop();
debounceTimer.stop() debounceTimer.stop();
shaderLoader.active = false shaderLoader.active = false;
currentWallpaper.source = "" currentWallpaper.source = "";
nextWallpaper.source = "" nextWallpaper.source = "";
} }
Connections { Connections {
target: Settings.data.wallpaper target: Settings.data.wallpaper
function onFillModeChanged() { function onFillModeChanged() {
fillMode = WallpaperService.getFillModeUniform() fillMode = WallpaperService.getFillModeUniform();
} }
} }
@@ -69,8 +69,8 @@ Variants {
if (screenName === modelData.name) { if (screenName === modelData.name) {
// Update wallpaper display // Update wallpaper display
// Set wallpaper immediately on startup // Set wallpaper immediately on startup
futureWallpaper = path futureWallpaper = path;
debounceTimer.restart() debounceTimer.restart();
} }
} }
} }
@@ -80,9 +80,9 @@ Variants {
function onDisplayScalesChanged() { function onDisplayScalesChanged() {
// Recalculate image sizes without interrupting startup transition // Recalculate image sizes without interrupting startup transition
if (isStartupTransition) { if (isStartupTransition) {
return return;
} }
recalculateImageSizes() recalculateImageSizes();
} }
} }
@@ -105,7 +105,7 @@ Variants {
running: false running: false
repeat: false repeat: false
onTriggered: { onTriggered: {
changeWallpaper() changeWallpaper();
} }
} }
@@ -123,18 +123,18 @@ Variants {
sourceSize: undefined sourceSize: undefined
onStatusChanged: { onStatusChanged: {
if (status === Image.Error) { 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) { } else if (status === Image.Ready && !dimensionsCalculated) {
dimensionsCalculated = true dimensionsCalculated = true;
const optimalSize = calculateOptimalWallpaperSize(implicitWidth, implicitHeight) const optimalSize = calculateOptimalWallpaperSize(implicitWidth, implicitHeight);
if (optimalSize !== false) { if (optimalSize !== false) {
sourceSize = optimalSize sourceSize = optimalSize;
} }
} }
} }
onSourceChanged: { onSourceChanged: {
dimensionsCalculated = false dimensionsCalculated = false;
sourceSize = undefined sourceSize = undefined;
} }
} }
@@ -152,18 +152,18 @@ Variants {
sourceSize: undefined sourceSize: undefined
onStatusChanged: { onStatusChanged: {
if (status === Image.Error) { 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) { } else if (status === Image.Ready && !dimensionsCalculated) {
dimensionsCalculated = true dimensionsCalculated = true;
const optimalSize = calculateOptimalWallpaperSize(implicitWidth, implicitHeight) const optimalSize = calculateOptimalWallpaperSize(implicitWidth, implicitHeight);
if (optimalSize !== false) { if (optimalSize !== false) {
sourceSize = optimalSize sourceSize = optimalSize;
} }
} }
} }
onSourceChanged: { onSourceChanged: {
dimensionsCalculated = false dimensionsCalculated = false;
sourceSize = undefined sourceSize = undefined;
} }
} }
@@ -176,15 +176,15 @@ Variants {
sourceComponent: { sourceComponent: {
switch (transitionType) { switch (transitionType) {
case "wipe": case "wipe":
return wipeShaderComponent return wipeShaderComponent;
case "disc": case "disc":
return discShaderComponent return discShaderComponent;
case "stripes": case "stripes":
return stripesShaderComponent return stripesShaderComponent;
case "fade": case "fade":
case "none": case "none":
default: default:
return fadeShaderComponent return fadeShaderComponent;
} }
} }
} }
@@ -307,64 +307,64 @@ Variants {
easing.type: Easing.InOutCubic easing.type: Easing.InOutCubic
onFinished: { onFinished: {
// Assign new image to current BEFORE clearing to prevent flicker // Assign new image to current BEFORE clearing to prevent flicker
const tempSource = nextWallpaper.source const tempSource = nextWallpaper.source;
currentWallpaper.source = tempSource currentWallpaper.source = tempSource;
transitionProgress = 0.0 transitionProgress = 0.0;
// Now clear nextWallpaper after currentWallpaper has the new source // Now clear nextWallpaper after currentWallpaper has the new source
// Force complete cleanup to free texture memory (~18-25MB per monitor) // Force complete cleanup to free texture memory (~18-25MB per monitor)
Qt.callLater(() => { Qt.callLater(() => {
nextWallpaper.source = "" nextWallpaper.source = "";
nextWallpaper.sourceSize = undefined nextWallpaper.sourceSize = undefined;
Qt.callLater(() => { Qt.callLater(() => {
currentWallpaper.asynchronous = true currentWallpaper.asynchronous = true;
}) });
}) });
} }
} }
// ------------------------------------------------------ // ------------------------------------------------------
function calculateOptimalWallpaperSize(wpWidth, wpHeight) { function calculateOptimalWallpaperSize(wpWidth, wpHeight) {
const compositorScale = CompositorService.getDisplayScale(modelData.name) const compositorScale = CompositorService.getDisplayScale(modelData.name);
const screenWidth = modelData.width * compositorScale const screenWidth = modelData.width * compositorScale;
const screenHeight = modelData.height * compositorScale const screenHeight = modelData.height * compositorScale;
if (wpWidth <= screenWidth || wpHeight <= screenHeight || wpWidth <= 0 || wpHeight <= 0) { if (wpWidth <= screenWidth || wpHeight <= screenHeight || wpWidth <= 0 || wpHeight <= 0) {
// Do not resize if wallpaper is smaller than one of the screen dimension // Do not resize if wallpaper is smaller than one of the screen dimension
return return;
} }
const imageAspectRatio = wpWidth / wpHeight const imageAspectRatio = wpWidth / wpHeight;
var dim = Qt.size(0, 0) var dim = Qt.size(0, 0);
if (screenWidth >= screenHeight) { if (screenWidth >= screenHeight) {
const w = Math.min(screenWidth, wpWidth) const w = Math.min(screenWidth, wpWidth);
dim = Qt.size(Math.round(w), Math.round(w / imageAspectRatio)) dim = Qt.size(Math.round(w), Math.round(w / imageAspectRatio));
} else { } else {
const h = Math.min(screenHeight, wpHeight) const h = Math.min(screenHeight, wpHeight);
dim = Qt.size(Math.round(h * imageAspectRatio), Math.round(h)) 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) Logger.d("Background", `Wallpaper resized on ${modelData.name} ${screenWidth}x${screenHeight} @ ${compositorScale}x`, "src:", wpWidth, wpHeight, "dst:", dim.width, dim.height);
return dim return dim;
} }
// ------------------------------------------------------ // ------------------------------------------------------
function recalculateImageSizes() { function recalculateImageSizes() {
// Re-evaluate and apply optimal sourceSize for both images when ready // Re-evaluate and apply optimal sourceSize for both images when ready
if (currentWallpaper.status === Image.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) { if (optimal !== undefined && optimal !== false) {
currentWallpaper.sourceSize = optimal currentWallpaper.sourceSize = optimal;
} else { } else {
currentWallpaper.sourceSize = undefined currentWallpaper.sourceSize = undefined;
} }
} }
if (nextWallpaper.status === Image.Ready) { if (nextWallpaper.status === Image.Ready) {
const optimal2 = calculateOptimalWallpaperSize(nextWallpaper.implicitWidth, nextWallpaper.implicitHeight) const optimal2 = calculateOptimalWallpaperSize(nextWallpaper.implicitWidth, nextWallpaper.implicitHeight);
if (optimal2 !== undefined && optimal2 !== false) { if (optimal2 !== undefined && optimal2 !== false) {
nextWallpaper.sourceSize = optimal2 nextWallpaper.sourceSize = optimal2;
} else { } else {
nextWallpaper.sourceSize = undefined nextWallpaper.sourceSize = undefined;
} }
} }
} }
@@ -373,104 +373,104 @@ Variants {
function setWallpaperInitial() { function setWallpaperInitial() {
// On startup, defer assigning wallpaper until the service cache is ready, retries every tick // On startup, defer assigning wallpaper until the service cache is ready, retries every tick
if (!WallpaperService || !WallpaperService.isInitialized) { if (!WallpaperService || !WallpaperService.isInitialized) {
Qt.callLater(setWallpaperInitial) Qt.callLater(setWallpaperInitial);
return return;
} }
const wallpaperPath = WallpaperService.getWallpaper(modelData.name) const wallpaperPath = WallpaperService.getWallpaper(modelData.name);
futureWallpaper = wallpaperPath futureWallpaper = wallpaperPath;
performStartupTransition() performStartupTransition();
} }
// ------------------------------------------------------ // ------------------------------------------------------
function setWallpaperImmediate(source) { function setWallpaperImmediate(source) {
transitionAnimation.stop() transitionAnimation.stop();
transitionProgress = 0.0 transitionProgress = 0.0;
// Clear nextWallpaper completely to free texture memory // Clear nextWallpaper completely to free texture memory
nextWallpaper.source = "" nextWallpaper.source = "";
nextWallpaper.sourceSize = undefined nextWallpaper.sourceSize = undefined;
currentWallpaper.source = "" currentWallpaper.source = "";
Qt.callLater(() => { Qt.callLater(() => {
currentWallpaper.source = source currentWallpaper.source = source;
}) });
} }
// ------------------------------------------------------ // ------------------------------------------------------
function setWallpaperWithTransition(source) { function setWallpaperWithTransition(source) {
if (source === currentWallpaper.source) { if (source === currentWallpaper.source) {
return return;
} }
if (transitioning) { if (transitioning) {
// We are interrupting a transition - handle cleanup properly // We are interrupting a transition - handle cleanup properly
transitionAnimation.stop() transitionAnimation.stop();
transitionProgress = 0 transitionProgress = 0;
// Assign nextWallpaper to currentWallpaper BEFORE clearing to prevent flicker // Assign nextWallpaper to currentWallpaper BEFORE clearing to prevent flicker
const newCurrentSource = nextWallpaper.source const newCurrentSource = nextWallpaper.source;
currentWallpaper.source = newCurrentSource currentWallpaper.source = newCurrentSource;
// Now clear nextWallpaper after current has the new source // Now clear nextWallpaper after current has the new source
Qt.callLater(() => { Qt.callLater(() => {
nextWallpaper.source = "" nextWallpaper.source = "";
// Now set the next wallpaper after a brief delay // Now set the next wallpaper after a brief delay
Qt.callLater(() => { Qt.callLater(() => {
nextWallpaper.source = source nextWallpaper.source = source;
currentWallpaper.asynchronous = false currentWallpaper.asynchronous = false;
transitionAnimation.start() transitionAnimation.start();
}) });
}) });
return return;
} }
nextWallpaper.source = source nextWallpaper.source = source;
currentWallpaper.asynchronous = false currentWallpaper.asynchronous = false;
transitionAnimation.start() transitionAnimation.start();
} }
// ------------------------------------------------------ // ------------------------------------------------------
// Main method that actually trigger the wallpaper change // Main method that actually trigger the wallpaper change
function changeWallpaper() { function changeWallpaper() {
// Get the transitionType from the settings // Get the transitionType from the settings
transitionType = Settings.data.wallpaper.transitionType transitionType = Settings.data.wallpaper.transitionType;
if (transitionType == "random") { if (transitionType == "random") {
var index = Math.floor(Math.random() * allTransitions.length) var index = Math.floor(Math.random() * allTransitions.length);
transitionType = allTransitions[index] transitionType = allTransitions[index];
} }
// Ensure the transition type really exists // Ensure the transition type really exists
if (transitionType !== "none" && !allTransitions.includes(transitionType)) { if (transitionType !== "none" && !allTransitions.includes(transitionType)) {
transitionType = "fade" transitionType = "fade";
} }
//Logger.i("Background", "New wallpaper: ", futureWallpaper, "On:", modelData.name, "Transition:", transitionType) //Logger.i("Background", "New wallpaper: ", futureWallpaper, "On:", modelData.name, "Transition:", transitionType)
switch (transitionType) { switch (transitionType) {
case "none": case "none":
setWallpaperImmediate(futureWallpaper) setWallpaperImmediate(futureWallpaper);
break break;
case "wipe": case "wipe":
wipeDirection = Math.random() * 4 wipeDirection = Math.random() * 4;
setWallpaperWithTransition(futureWallpaper) setWallpaperWithTransition(futureWallpaper);
break break;
case "disc": case "disc":
discCenterX = Math.random() discCenterX = Math.random();
discCenterY = Math.random() discCenterY = Math.random();
setWallpaperWithTransition(futureWallpaper) setWallpaperWithTransition(futureWallpaper);
break break;
case "stripes": case "stripes":
stripesCount = Math.round(Math.random() * 20 + 4) stripesCount = Math.round(Math.random() * 20 + 4);
stripesAngle = Math.random() * 360 stripesAngle = Math.random() * 360;
setWallpaperWithTransition(futureWallpaper) setWallpaperWithTransition(futureWallpaper);
break break;
default: default:
setWallpaperWithTransition(futureWallpaper) setWallpaperWithTransition(futureWallpaper);
break break;
} }
} }
@@ -478,46 +478,46 @@ Variants {
// Dedicated function for startup animation // Dedicated function for startup animation
function performStartupTransition() { function performStartupTransition() {
// Get the transitionType from the settings // Get the transitionType from the settings
transitionType = Settings.data.wallpaper.transitionType transitionType = Settings.data.wallpaper.transitionType;
if (transitionType == "random") { if (transitionType == "random") {
var index = Math.floor(Math.random() * allTransitions.length) var index = Math.floor(Math.random() * allTransitions.length);
transitionType = allTransitions[index] transitionType = allTransitions[index];
} }
// Ensure the transition type really exists // Ensure the transition type really exists
if (transitionType !== "none" && !allTransitions.includes(transitionType)) { if (transitionType !== "none" && !allTransitions.includes(transitionType)) {
transitionType = "fade" transitionType = "fade";
} }
// Apply transitionType so the shader loader picks the correct shader // Apply transitionType so the shader loader picks the correct shader
this.transitionType = transitionType this.transitionType = transitionType;
switch (transitionType) { switch (transitionType) {
case "none": case "none":
setWallpaperImmediate(futureWallpaper) setWallpaperImmediate(futureWallpaper);
break break;
case "wipe": case "wipe":
wipeDirection = Math.random() * 4 wipeDirection = Math.random() * 4;
setWallpaperWithTransition(futureWallpaper) setWallpaperWithTransition(futureWallpaper);
break break;
case "disc": case "disc":
// Force center origin for elegant startup animation // Force center origin for elegant startup animation
discCenterX = 0.5 discCenterX = 0.5;
discCenterY = 0.5 discCenterY = 0.5;
setWallpaperWithTransition(futureWallpaper) setWallpaperWithTransition(futureWallpaper);
break break;
case "stripes": case "stripes":
stripesCount = Math.round(Math.random() * 20 + 4) stripesCount = Math.round(Math.random() * 20 + 4);
stripesAngle = Math.random() * 360 stripesAngle = Math.random() * 360;
setWallpaperWithTransition(futureWallpaper) setWallpaperWithTransition(futureWallpaper);
break break;
default: default:
setWallpaperWithTransition(futureWallpaper) setWallpaperWithTransition(futureWallpaper);
break break;
} }
// Mark startup transition complete // Mark startup transition complete
isStartupTransition = false isStartupTransition = false;
} }
} }
} }

View File

@@ -1,6 +1,6 @@
import QtQuick import QtQuick
import Quickshell
import QtQuick.Effects import QtQuick.Effects
import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import qs.Commons import qs.Commons
import qs.Services.Compositor import qs.Services.Compositor
@@ -20,16 +20,16 @@ Loader {
Component.onCompleted: { Component.onCompleted: {
if (modelData) { 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: { Component.onDestruction: {
// Clean up resources to prevent memory leak when overviewEnabled is toggled off // Clean up resources to prevent memory leak when overviewEnabled is toggled off
timerDisableFx.stop() timerDisableFx.stop();
bgImage.layer.enabled = false bgImage.layer.enabled = false;
bgImage.source = "" bgImage.source = "";
} }
// External state management // External state management
@@ -37,19 +37,19 @@ Loader {
target: WallpaperService target: WallpaperService
function onWallpaperChanged(screenName, path) { function onWallpaperChanged(screenName, path) {
if (screenName === modelData.name) { if (screenName === modelData.name) {
wallpaper = path wallpaper = path;
} }
} }
} }
function setWallpaperInitial() { function setWallpaperInitial() {
if (!WallpaperService || !WallpaperService.isInitialized) { if (!WallpaperService || !WallpaperService.isInitialized) {
Qt.callLater(setWallpaperInitial) Qt.callLater(setWallpaperInitial);
return return;
} }
const wallpaperPath = WallpaperService.getWallpaper(modelData.name) const wallpaperPath = WallpaperService.getWallpaper(modelData.name);
if (wallpaperPath && wallpaperPath !== wallpaper) { if (wallpaperPath && wallpaperPath !== wallpaper) {
wallpaper = wallpaperPath wallpaper = wallpaperPath;
} }
} }

View File

@@ -2,13 +2,13 @@ import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Wayland
import Quickshell.Services.UPower import Quickshell.Services.UPower
import Quickshell.Wayland
import qs.Commons import qs.Commons
import qs.Modules.Bar.Extras
import qs.Modules.Notification
import qs.Services.UI import qs.Services.UI
import qs.Widgets import qs.Widgets
import qs.Modules.Notification
import qs.Modules.Bar.Extras
// Bar Component // Bar Component
Item { Item {
@@ -34,9 +34,9 @@ Item {
// Register bar when screen becomes available // Register bar when screen becomes available
onScreenChanged: { onScreenChanged: {
if (screen && screen.name) { if (screen && screen.name) {
Logger.d("Bar", "Bar screen set to:", screen.name) Logger.d("Bar", "Bar screen set to:", screen.name);
Logger.d("Bar", " Position:", barPosition, "Floating:", barFloating) Logger.d("Bar", " Position:", barPosition, "Floating:", barFloating);
BarService.registerBar(screen.name) BarService.registerBar(screen.name);
} }
} }
@@ -46,12 +46,12 @@ Item {
anchors.fill: parent anchors.fill: parent
active: { active: {
if (root.screen === null || root.screen === undefined) { if (root.screen === null || root.screen === undefined) {
return false return false;
} }
var monitors = Settings.data.bar.monitors || [] var monitors = Settings.data.bar.monitors || [];
var result = monitors.length === 0 || monitors.includes(root.screen.name) var result = monitors.length === 0 || monitors.includes(root.screen.name);
return result return result;
} }
sourceComponent: Item { sourceComponent: Item {
@@ -75,73 +75,73 @@ Item {
readonly property int topLeftCornerState: { readonly property int topLeftCornerState: {
// Floating bar: always simple rounded corners // Floating bar: always simple rounded corners
if (barFloating) if (barFloating)
return 0 return 0;
// Top bar: top corners against screen edge = no radius // Top bar: top corners against screen edge = no radius
if (barPosition === "top") if (barPosition === "top")
return -1 return -1;
// Left bar: top-left against screen edge = no radius // Left bar: top-left against screen edge = no radius
if (barPosition === "left") if (barPosition === "left")
return -1 return -1;
// Bottom/Right bar with outerCorners: inverted corner // Bottom/Right bar with outerCorners: inverted corner
if (Settings.data.bar.outerCorners && (barPosition === "bottom" || barPosition === "right")) { 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 // No outerCorners = square
return -1 return -1;
} }
readonly property int topRightCornerState: { readonly property int topRightCornerState: {
// Floating bar: always simple rounded corners // Floating bar: always simple rounded corners
if (barFloating) if (barFloating)
return 0 return 0;
// Top bar: top corners against screen edge = no radius // Top bar: top corners against screen edge = no radius
if (barPosition === "top") if (barPosition === "top")
return -1 return -1;
// Right bar: top-right against screen edge = no radius // Right bar: top-right against screen edge = no radius
if (barPosition === "right") if (barPosition === "right")
return -1 return -1;
// Bottom/Left bar with outerCorners: inverted corner // Bottom/Left bar with outerCorners: inverted corner
if (Settings.data.bar.outerCorners && (barPosition === "bottom" || barPosition === "left")) { if (Settings.data.bar.outerCorners && (barPosition === "bottom" || barPosition === "left")) {
return barIsVertical ? 1 : 2 return barIsVertical ? 1 : 2;
} }
// No outerCorners = square // No outerCorners = square
return -1 return -1;
} }
readonly property int bottomLeftCornerState: { readonly property int bottomLeftCornerState: {
// Floating bar: always simple rounded corners // Floating bar: always simple rounded corners
if (barFloating) if (barFloating)
return 0 return 0;
// Bottom bar: bottom corners against screen edge = no radius // Bottom bar: bottom corners against screen edge = no radius
if (barPosition === "bottom") if (barPosition === "bottom")
return -1 return -1;
// Left bar: bottom-left against screen edge = no radius // Left bar: bottom-left against screen edge = no radius
if (barPosition === "left") if (barPosition === "left")
return -1 return -1;
// Top/Right bar with outerCorners: inverted corner // Top/Right bar with outerCorners: inverted corner
if (Settings.data.bar.outerCorners && (barPosition === "top" || barPosition === "right")) { if (Settings.data.bar.outerCorners && (barPosition === "top" || barPosition === "right")) {
return barIsVertical ? 1 : 2 return barIsVertical ? 1 : 2;
} }
// No outerCorners = square // No outerCorners = square
return -1 return -1;
} }
readonly property int bottomRightCornerState: { readonly property int bottomRightCornerState: {
// Floating bar: always simple rounded corners // Floating bar: always simple rounded corners
if (barFloating) if (barFloating)
return 0 return 0;
// Bottom bar: bottom corners against screen edge = no radius // Bottom bar: bottom corners against screen edge = no radius
if (barPosition === "bottom") if (barPosition === "bottom")
return -1 return -1;
// Right bar: bottom-right against screen edge = no radius // Right bar: bottom-right against screen edge = no radius
if (barPosition === "right") if (barPosition === "right")
return -1 return -1;
// Top/Left bar with outerCorners: inverted corner // Top/Left bar with outerCorners: inverted corner
if (Settings.data.bar.outerCorners && (barPosition === "top" || barPosition === "left")) { if (Settings.data.bar.outerCorners && (barPosition === "top" || barPosition === "left")) {
return barIsVertical ? 1 : 2 return barIsVertical ? 1 : 2;
} }
// No outerCorners = square // No outerCorners = square
return -1 return -1;
} }
MouseArea { MouseArea {
@@ -151,14 +151,14 @@ Item {
preventStealing: true preventStealing: true
onClicked: function (mouse) { onClicked: function (mouse) {
if (mouse.button === Qt.RightButton) { 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") { if (Settings.data.controlCenter.position === "close_to_bar_button") {
// Will attempt to open the panel next to the bar button if any. // Will attempt to open the panel next to the bar button if any.
controlCenterPanel?.toggle(null, "ControlCenter") controlCenterPanel?.toggle(null, "ControlCenter");
} else { } else {
controlCenterPanel?.toggle() controlCenterPanel?.toggle();
} }
mouse.accepted = true mouse.accepted = true;
} }
} }
} }

View File

@@ -3,13 +3,12 @@ import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import qs.Commons import qs.Commons
/** /**
* BarExclusionZone - Invisible PanelWindow that reserves exclusive space for the bar * BarExclusionZone - Invisible PanelWindow that reserves exclusive space for the bar
* *
* This is a minimal window that works with the compositor to reserve space, * This is a minimal window that works with the compositor to reserve space,
* while the actual bar UI is rendered in NFullScreenWindow. * while the actual bar UI is rendered in NFullScreenWindow.
*/ */
PanelWindow { PanelWindow {
id: root id: root
@@ -46,11 +45,11 @@ PanelWindow {
// Vertical bar: reserve bar height + margin on the anchored edge only // Vertical bar: reserve bar height + margin on the anchored edge only
if (barFloating) { if (barFloating) {
// For left bar, reserve left margin; for right bar, reserve right margin // 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: { implicitHeight: {
@@ -58,17 +57,17 @@ PanelWindow {
// Horizontal bar: reserve bar height + margin on the anchored edge only // Horizontal bar: reserve bar height + margin on the anchored edge only
if (barFloating) { if (barFloating) {
// For top bar, reserve top margin; for bottom bar, reserve bottom margin // 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: { Component.onCompleted: {
Logger.d("BarExclusionZone", "Created for screen:", screen?.name) Logger.d("BarExclusionZone", "Created for screen:", screen?.name);
Logger.d("BarExclusionZone", " Position:", barPosition, "Exclusive:", exclusive, "Floating:", barFloating) 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", " 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", " Size:", width, "x", height, "implicitWidth:", implicitWidth, "implicitHeight:", implicitHeight);
} }
} }

View File

@@ -92,19 +92,19 @@ Item {
function show() { function show() {
if (pillLoader.item && pillLoader.item.show) { if (pillLoader.item && pillLoader.item.show) {
pillLoader.item.show() pillLoader.item.show();
} }
} }
function hide() { function hide() {
if (pillLoader.item && pillLoader.item.hide) { if (pillLoader.item && pillLoader.item.hide) {
pillLoader.item.hide() pillLoader.item.hide();
} }
} }
function showDelayed() { function showDelayed() {
if (pillLoader.item && pillLoader.item.showDelayed) { if (pillLoader.item && pillLoader.item.showDelayed) {
pillLoader.item.showDelayed() pillLoader.item.showDelayed();
} }
} }
} }

View File

@@ -45,18 +45,18 @@ Item {
readonly property real iconSize: { readonly property real iconSize: {
switch (root.density) { switch (root.density) {
case "compact": case "compact":
return Math.max(1, Math.round(pillHeight * 0.65)) return Math.max(1, Math.round(pillHeight * 0.65));
default: default:
return Math.max(1, Math.round(pillHeight * 0.48)) return Math.max(1, Math.round(pillHeight * 0.48));
} }
} }
readonly property real textSize: { readonly property real textSize: {
switch (root.density) { switch (root.density) {
case "compact": case "compact":
return Math.max(1, Math.round(pillHeight * 0.45)) return Math.max(1, Math.round(pillHeight * 0.45));
default: 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 target: root
function onTooltipTextChanged() { function onTooltipTextChanged() {
if (hovered) { if (hovered) {
TooltipService.updateText(root.tooltipText) TooltipService.updateText(root.tooltipText);
} }
} }
} }
@@ -97,13 +97,13 @@ Item {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
x: { x: {
// Better text horizontal centering // Better text horizontal centering
var centerX = (parent.width - width) / 2 var centerX = (parent.width - width) / 2;
var offset = oppositeDirection ? Style.marginXS : -Style.marginXS var offset = oppositeDirection ? Style.marginXS : -Style.marginXS;
if (forceOpen) { if (forceOpen) {
// If its force open, the icon disc background is the same color as the bg pill move text slightly // 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 text: root.text + root.suffix
family: Settings.data.ui.fontFixed family: Settings.data.ui.fontFixed
@@ -179,11 +179,11 @@ Item {
easing.type: Easing.OutCubic easing.type: Easing.OutCubic
} }
onStarted: { onStarted: {
showPill = true showPill = true;
} }
onStopped: { onStopped: {
delayedHideAnim.start() delayedHideAnim.start();
root.shown() root.shown();
} }
} }
@@ -195,7 +195,7 @@ Item {
} }
ScriptAction { ScriptAction {
script: if (shouldAnimateHide) { script: if (shouldAnimateHide) {
hideAnim.start() hideAnim.start();
} }
} }
} }
@@ -220,9 +220,9 @@ Item {
easing.type: Easing.InCubic easing.type: Easing.InCubic
} }
onStopped: { onStopped: {
showPill = false showPill = false;
shouldAnimateHide = false shouldAnimateHide = false;
root.hidden() root.hidden();
} }
} }
@@ -231,7 +231,7 @@ Item {
interval: Style.pillDelay interval: Style.pillDelay
onTriggered: { onTriggered: {
if (!showPill) { if (!showPill) {
showAnim.start() showAnim.start();
} }
} }
} }
@@ -241,31 +241,31 @@ Item {
hoverEnabled: true hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onEntered: { onEntered: {
hovered = true hovered = true;
root.entered() root.entered();
TooltipService.show(Screen, pill, root.tooltipText, BarService.getTooltipDirection(), Style.tooltipDelayLong) TooltipService.show(Screen, pill, root.tooltipText, BarService.getTooltipDirection(), Style.tooltipDelayLong);
if (forceClose) { if (forceClose) {
return return;
} }
if (!forceOpen) { if (!forceOpen) {
showDelayed() showDelayed();
} }
} }
onExited: { onExited: {
hovered = false hovered = false;
root.exited() root.exited();
if (!forceOpen && !forceClose) { if (!forceOpen && !forceClose) {
hide() hide();
} }
TooltipService.hide() TooltipService.hide();
} }
onClicked: function (mouse) { onClicked: function (mouse) {
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
root.clicked() root.clicked();
} else if (mouse.button === Qt.RightButton) { } else if (mouse.button === Qt.RightButton) {
root.rightClicked() root.rightClicked();
} else if (mouse.button === Qt.MiddleButton) { } else if (mouse.button === Qt.MiddleButton) {
root.middleClicked() root.middleClicked();
} }
} }
onWheel: wheel => root.wheel(wheel.angleDelta.y) onWheel: wheel => root.wheel(wheel.angleDelta.y)
@@ -273,43 +273,43 @@ Item {
function show() { function show() {
if (!showPill) { if (!showPill) {
shouldAnimateHide = autoHide shouldAnimateHide = autoHide;
showAnim.start() showAnim.start();
} else { } else {
hideAnim.stop() hideAnim.stop();
delayedHideAnim.restart() delayedHideAnim.restart();
} }
} }
function hide() { function hide() {
if (forceOpen) { if (forceOpen) {
return return;
} }
if (showPill) { if (showPill) {
hideAnim.start() hideAnim.start();
} }
showTimer.stop() showTimer.stop();
} }
function showDelayed() { function showDelayed() {
if (!showPill) { if (!showPill) {
shouldAnimateHide = autoHide shouldAnimateHide = autoHide;
showTimer.start() showTimer.start();
} else { } else {
hideAnim.stop() hideAnim.stop();
delayedHideAnim.restart() delayedHideAnim.restart();
} }
} }
onForceOpenChanged: { onForceOpenChanged: {
if (forceOpen) { if (forceOpen) {
// Immediately lock open without animations // Immediately lock open without animations
showAnim.stop() showAnim.stop();
hideAnim.stop() hideAnim.stop();
delayedHideAnim.stop() delayedHideAnim.stop();
showPill = true showPill = true;
} else { } else {
hide() hide();
} }
} }
} }

View File

@@ -55,18 +55,18 @@ Item {
readonly property real iconSize: { readonly property real iconSize: {
switch (root.density) { switch (root.density) {
case "compact": case "compact":
return Math.max(1, Math.round(pillHeight * 0.65)) return Math.max(1, Math.round(pillHeight * 0.65));
default: default:
return Math.max(1, Math.round(pillHeight * 0.48)) return Math.max(1, Math.round(pillHeight * 0.48));
} }
} }
readonly property real textSize: { readonly property real textSize: {
switch (root.density) { switch (root.density) {
case "compact": case "compact":
return Math.max(1, Math.round(pillHeight * 0.38)) return Math.max(1, Math.round(pillHeight * 0.38));
default: 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 target: root
function onTooltipTextChanged() { function onTooltipTextChanged() {
if (hovered) { if (hovered) {
TooltipService.updateText(root.tooltipText) TooltipService.updateText(root.tooltipText);
} }
} }
} }
@@ -123,11 +123,11 @@ Item {
visible: revealed visible: revealed
function getVerticalCenterOffset() { 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) { if (forceOpen) {
offset += oppositeDirection ? -Style.marginXXS : Style.marginXXS offset += oppositeDirection ? -Style.marginXXS : Style.marginXXS;
} }
return offset return offset;
} }
} }
Behavior on width { Behavior on width {
@@ -212,11 +212,11 @@ Item {
easing.type: Easing.OutCubic easing.type: Easing.OutCubic
} }
onStarted: { onStarted: {
showPill = true showPill = true;
} }
onStopped: { onStopped: {
delayedHideAnim.start() delayedHideAnim.start();
root.shown() root.shown();
} }
} }
@@ -228,7 +228,7 @@ Item {
} }
ScriptAction { ScriptAction {
script: if (shouldAnimateHide) { script: if (shouldAnimateHide) {
hideAnim.start() hideAnim.start();
} }
} }
} }
@@ -261,9 +261,9 @@ Item {
easing.type: Easing.InCubic easing.type: Easing.InCubic
} }
onStopped: { onStopped: {
showPill = false showPill = false;
shouldAnimateHide = false shouldAnimateHide = false;
root.hidden() root.hidden();
} }
} }
@@ -272,7 +272,7 @@ Item {
interval: Style.pillDelay interval: Style.pillDelay
onTriggered: { onTriggered: {
if (!showPill) { if (!showPill) {
showAnim.start() showAnim.start();
} }
} }
} }
@@ -282,31 +282,31 @@ Item {
hoverEnabled: true hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onEntered: { onEntered: {
hovered = true hovered = true;
root.entered() root.entered();
TooltipService.show(Screen, pill, root.tooltipText, BarService.getTooltipDirection(), Style.tooltipDelayLong) TooltipService.show(Screen, pill, root.tooltipText, BarService.getTooltipDirection(), Style.tooltipDelayLong);
if (forceClose) { if (forceClose) {
return return;
} }
if (!forceOpen) { if (!forceOpen) {
showDelayed() showDelayed();
} }
} }
onExited: { onExited: {
hovered = false hovered = false;
root.exited() root.exited();
if (!forceOpen && !forceClose) { if (!forceOpen && !forceClose) {
hide() hide();
} }
TooltipService.hide() TooltipService.hide();
} }
onClicked: function (mouse) { onClicked: function (mouse) {
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
root.clicked() root.clicked();
} else if (mouse.button === Qt.RightButton) { } else if (mouse.button === Qt.RightButton) {
root.rightClicked() root.rightClicked();
} else if (mouse.button === Qt.MiddleButton) { } else if (mouse.button === Qt.MiddleButton) {
root.middleClicked() root.middleClicked();
} }
} }
onWheel: wheel => root.wheel(wheel.angleDelta.y) onWheel: wheel => root.wheel(wheel.angleDelta.y)
@@ -314,43 +314,43 @@ Item {
function show() { function show() {
if (!showPill) { if (!showPill) {
shouldAnimateHide = autoHide shouldAnimateHide = autoHide;
showAnim.start() showAnim.start();
} else { } else {
hideAnim.stop() hideAnim.stop();
delayedHideAnim.restart() delayedHideAnim.restart();
} }
} }
function hide() { function hide() {
if (forceOpen) { if (forceOpen) {
return return;
} }
if (showPill) { if (showPill) {
hideAnim.start() hideAnim.start();
} }
showTimer.stop() showTimer.stop();
} }
function showDelayed() { function showDelayed() {
if (!showPill) { if (!showPill) {
shouldAnimateHide = autoHide shouldAnimateHide = autoHide;
showTimer.start() showTimer.start();
} else { } else {
hideAnim.stop() hideAnim.stop();
delayedHideAnim.restart() delayedHideAnim.restart();
} }
} }
onForceOpenChanged: { onForceOpenChanged: {
if (forceOpen) { if (forceOpen) {
// Immediately lock open without animations // Immediately lock open without animations
showAnim.stop() showAnim.stop();
hideAnim.stop() hideAnim.stop();
delayedHideAnim.stop() delayedHideAnim.stop();
showPill = true showPill = true;
} else { } else {
hide() hide();
} }
} }
} }

View File

@@ -1,7 +1,7 @@
import QtQuick import QtQuick
import Quickshell import Quickshell
import qs.Services.UI
import qs.Commons import qs.Commons
import qs.Services.UI
Item { Item {
id: root id: root
@@ -25,7 +25,7 @@ Item {
visible: loader.item ? ((loader.item.opacity > 0.0) || (loader.item.hasOwnProperty("hideMode") && loader.item.hideMode === "transparent")) : false visible: loader.item ? ((loader.item.opacity > 0.0) || (loader.item.hasOwnProperty("hideMode") && loader.item.hideMode === "transparent")) : false
function getImplicitSize(item, prop) { function getImplicitSize(item, prop) {
return (item && item.visible) ? Math.round(item[prop]) : 0 return (item && item.visible) ? Math.round(item[prop]) : 0;
} }
Loader { Loader {
@@ -36,42 +36,41 @@ Item {
onLoaded: { onLoaded: {
if (!item) if (!item)
return return;
Logger.d("BarWidgetLoader", "Loading widget", widgetId, "on screen:", widgetScreen.name);
Logger.d("BarWidgetLoader", "Loading widget", widgetId, "on screen:", widgetScreen.name)
// Apply properties to loaded widget // Apply properties to loaded widget
for (var prop in widgetProps) { for (var prop in widgetProps) {
if (item.hasOwnProperty(prop)) { if (item.hasOwnProperty(prop)) {
item[prop] = widgetProps[prop] item[prop] = widgetProps[prop];
} }
} }
// Set screen property // Set screen property
if (item.hasOwnProperty("screen")) { if (item.hasOwnProperty("screen")) {
item.screen = widgetScreen item.screen = widgetScreen;
} }
// Set scaling property // Set scaling property
if (item.hasOwnProperty("scaling")) { if (item.hasOwnProperty("scaling")) {
item.scaling = Qt.binding(function () { item.scaling = Qt.binding(function () {
return root.scaling return root.scaling;
}) });
} }
// Register this widget instance with BarService // 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 // Call custom onLoaded if it exists
if (item.hasOwnProperty("onLoaded")) { if (item.hasOwnProperty("onLoaded")) {
item.onLoaded() item.onLoaded();
} }
} }
Component.onDestruction: { Component.onDestruction: {
// Unregister when destroyed // Unregister when destroyed
if (widgetScreen && section) { if (widgetScreen && section) {
BarService.unregisterWidget(widgetScreen.name, section, widgetId, sectionIndex) BarService.unregisterWidget(widgetScreen.name, section, widgetId, sectionIndex);
} }
} }
} }
@@ -79,7 +78,7 @@ Item {
// Error handling // Error handling
Component.onCompleted: { Component.onCompleted: {
if (!BarWidgetRegistry.hasWidget(widgetId)) { if (!BarWidgetRegistry.hasWidget(widgetId)) {
Logger.w("BarWidgetLoader", "Widget not found in registry:", widgetId) Logger.w("BarWidgetLoader", "Widget not found in registry:", widgetId);
} }
} }
} }

View File

@@ -25,20 +25,20 @@ PopupWindow {
// Compute if current tray item is pinned // Compute if current tray item is pinned
readonly property bool isPinned: { readonly property bool isPinned: {
if (!trayItem || widgetSection === "" || widgetIndex < 0) if (!trayItem || widgetSection === "" || widgetIndex < 0)
return false return false;
var widgets = Settings.data.bar.widgets[widgetSection] var widgets = Settings.data.bar.widgets[widgetSection];
if (!widgets || widgetIndex >= widgets.length) if (!widgets || widgetIndex >= widgets.length)
return false return false;
var widgetSettings = widgets[widgetIndex] var widgetSettings = widgets[widgetIndex];
if (!widgetSettings || widgetSettings.id !== "Tray") if (!widgetSettings || widgetSettings.id !== "Tray")
return false return false;
var pinnedList = widgetSettings.pinned || [] var pinnedList = widgetSettings.pinned || [];
const itemName = trayItem.tooltipTitle || trayItem.name || trayItem.id || "" const itemName = trayItem.tooltipTitle || trayItem.name || trayItem.id || "";
for (var i = 0; i < pinnedList.length; i++) { for (var i = 0; i < pinnedList.length; i++) {
if (pinnedList[i] === itemName) if (pinnedList[i] === itemName)
return true return true;
} }
return false return false;
} }
readonly property int menuWidth: 220 readonly property int menuWidth: 220
@@ -53,47 +53,47 @@ PopupWindow {
anchor.rect.x: anchorX anchor.rect.x: anchorX
anchor.rect.y: { anchor.rect.y: {
if (isSubMenu) { if (isSubMenu) {
const offsetY = Settings.data.bar.position === "bottom" ? -10 : 10 const offsetY = Settings.data.bar.position === "bottom" ? -10 : 10;
return anchorY + offsetY 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) { function showAt(item, x, y) {
if (!item) { if (!item) {
Logger.w("TrayMenu", "anchorItem is undefined, won't show menu.") Logger.w("TrayMenu", "anchorItem is undefined, won't show menu.");
return return;
} }
if (!opener.children || opener.children.values.length === 0) { if (!opener.children || opener.children.values.length === 0) {
//Logger.w("TrayMenu", "Menu not ready, delaying show") //Logger.w("TrayMenu", "Menu not ready, delaying show")
Qt.callLater(() => showAt(item, x, y)) Qt.callLater(() => showAt(item, x, y));
return return;
} }
anchorItem = item anchorItem = item;
anchorX = x anchorX = x;
anchorY = y anchorY = y;
visible = true visible = true;
forceActiveFocus() forceActiveFocus();
// Force update after showing. // Force update after showing.
Qt.callLater(() => { Qt.callLater(() => {
root.anchor.updateAnchor() root.anchor.updateAnchor();
}) });
} }
function hideMenu() { function hideMenu() {
visible = false visible = false;
// Clean up all submenus recursively // Clean up all submenus recursively
for (var i = 0; i < columnLayout.children.length; i++) { for (var i = 0; i < columnLayout.children.length; i++) {
const child = columnLayout.children[i] const child = columnLayout.children[i];
if (child?.subMenu) { if (child?.subMenu) {
child.subMenu.hideMenu() child.subMenu.hideMenu();
child.subMenu.destroy() child.subMenu.destroy();
child.subMenu = null child.subMenu = null;
} }
} }
} }
@@ -166,11 +166,11 @@ PopupWindow {
Layout.preferredWidth: parent.width Layout.preferredWidth: parent.width
Layout.preferredHeight: { Layout.preferredHeight: {
if (modelData?.isSeparator) { if (modelData?.isSeparator) {
return 8 return 8;
} else { } else {
// Calculate based on text content // Calculate based on text content
const textHeight = text.contentHeight || (Style.fontSizeS * 1.2) const textHeight = text.contentHeight || (Style.fontSizeS * 1.2);
return Math.max(28, textHeight + (Style.marginS * 2)) return Math.max(28, textHeight + (Style.marginS * 2));
} }
} }
@@ -237,31 +237,31 @@ PopupWindow {
// Click on items with children toggles submenu // Click on items with children toggles submenu
if (entry.subMenu) { if (entry.subMenu) {
// Close existing submenu // Close existing submenu
entry.subMenu.hideMenu() entry.subMenu.hideMenu();
entry.subMenu.destroy() entry.subMenu.destroy();
entry.subMenu = null entry.subMenu = null;
} else { } else {
// Close any other open submenus first // Close any other open submenus first
for (var i = 0; i < columnLayout.children.length; i++) { for (var i = 0; i < columnLayout.children.length; i++) {
const sibling = columnLayout.children[i] const sibling = columnLayout.children[i];
if (sibling !== entry && sibling.subMenu) { if (sibling !== entry && sibling.subMenu) {
sibling.subMenu.hideMenu() sibling.subMenu.hideMenu();
sibling.subMenu.destroy() sibling.subMenu.destroy();
sibling.subMenu = null sibling.subMenu = null;
} }
} }
// Determine submenu opening direction // Determine submenu opening direction
let openLeft = false let openLeft = false;
const barPosition = Settings.data.bar.position const barPosition = Settings.data.bar.position;
const globalPos = entry.mapToItem(null, 0, 0) const globalPos = entry.mapToItem(null, 0, 0);
if (barPosition === "right") { if (barPosition === "right") {
openLeft = true openLeft = true;
} else if (barPosition === "left") { } else if (barPosition === "left") {
openLeft = false openLeft = false;
} else { } else {
openLeft = (root.widgetSection === "right") openLeft = (root.widgetSection === "right");
} }
// Open new submenu // Open new submenu
@@ -269,30 +269,30 @@ PopupWindow {
"menu": modelData, "menu": modelData,
"isSubMenu": true, "isSubMenu": true,
"screen": root.screen "screen": root.screen
}) });
if (entry.subMenu) { if (entry.subMenu) {
const overlap = 60 const overlap = 60;
entry.subMenu.anchorItem = entry entry.subMenu.anchorItem = entry;
entry.subMenu.anchorX = openLeft ? -overlap : overlap entry.subMenu.anchorX = openLeft ? -overlap : overlap;
entry.subMenu.anchorY = 0 entry.subMenu.anchorY = 0;
entry.subMenu.visible = true entry.subMenu.visible = true;
// Force anchor update with new position // Force anchor update with new position
Qt.callLater(() => { Qt.callLater(() => {
entry.subMenu.anchor.updateAnchor() entry.subMenu.anchor.updateAnchor();
}) });
} }
} }
} else { } else {
// Click on regular items triggers them // Click on regular items triggers them
modelData.triggered() modelData.triggered();
root.hideMenu() root.hideMenu();
// Close the drawer if it's open // Close the drawer if it's open
if (root.screen) { if (root.screen) {
const panel = PanelService.getPanel("trayDrawerPanel", root.screen) const panel = PanelService.getPanel("trayDrawerPanel", root.screen);
if (panel && panel.visible) { if (panel && panel.visible) {
panel.close() panel.close();
} }
} }
} }
@@ -303,8 +303,8 @@ PopupWindow {
Component.onDestruction: { Component.onDestruction: {
if (subMenu) { if (subMenu) {
subMenu.destroy() subMenu.destroy();
subMenu = null subMenu = null;
} }
} }
} }
@@ -351,9 +351,9 @@ PopupWindow {
onClicked: { onClicked: {
if (root.isPinned) { if (root.isPinned) {
root.removeFromPinned() root.removeFromPinned();
} else { } else {
root.addToPinned() root.addToPinned();
} }
} }
} }
@@ -363,72 +363,72 @@ PopupWindow {
function addToPinned() { function addToPinned() {
if (!trayItem || widgetSection === "" || widgetIndex < 0) { if (!trayItem || widgetSection === "" || widgetIndex < 0) {
Logger.w("TrayMenu", "Cannot pin: missing tray item or widget info") Logger.w("TrayMenu", "Cannot pin: missing tray item or widget info");
return return;
} }
const itemName = trayItem.tooltipTitle || trayItem.name || trayItem.id || "" const itemName = trayItem.tooltipTitle || trayItem.name || trayItem.id || "";
if (!itemName) { if (!itemName) {
Logger.w("TrayMenu", "Cannot pin: tray item has no name") Logger.w("TrayMenu", "Cannot pin: tray item has no name");
return return;
} }
var widgets = Settings.data.bar.widgets[widgetSection] var widgets = Settings.data.bar.widgets[widgetSection];
if (!widgets || widgetIndex >= widgets.length) { if (!widgets || widgetIndex >= widgets.length) {
Logger.w("TrayMenu", "Cannot pin: invalid widget index") Logger.w("TrayMenu", "Cannot pin: invalid widget index");
return return;
} }
var widgetSettings = widgets[widgetIndex] var widgetSettings = widgets[widgetIndex];
if (!widgetSettings || widgetSettings.id !== "Tray") { if (!widgetSettings || widgetSettings.id !== "Tray") {
Logger.w("TrayMenu", "Cannot pin: widget is not a Tray widget") Logger.w("TrayMenu", "Cannot pin: widget is not a Tray widget");
return return;
} }
var pinnedList = widgetSettings.pinned || [] var pinnedList = widgetSettings.pinned || [];
var newPinned = pinnedList.slice() var newPinned = pinnedList.slice();
newPinned.push(itemName) newPinned.push(itemName);
var newSettings = Object.assign({}, widgetSettings) var newSettings = Object.assign({}, widgetSettings);
newSettings.pinned = newPinned newSettings.pinned = newPinned;
widgets[widgetIndex] = newSettings widgets[widgetIndex] = newSettings;
Settings.data.bar.widgets[widgetSection] = widgets Settings.data.bar.widgets[widgetSection] = widgets;
Settings.saveImmediate() Settings.saveImmediate();
// Close drawer when pinning (drawer needs to resize) // Close drawer when pinning (drawer needs to resize)
if (screen) { if (screen) {
const panel = PanelService.getPanel("trayDrawerPanel", screen) const panel = PanelService.getPanel("trayDrawerPanel", screen);
if (panel) if (panel)
panel.close() panel.close();
} }
} }
function removeFromPinned() { function removeFromPinned() {
if (!trayItem || widgetSection === "" || widgetIndex < 0) { if (!trayItem || widgetSection === "" || widgetIndex < 0) {
Logger.w("TrayMenu", "Cannot unpin: missing tray item or widget info") Logger.w("TrayMenu", "Cannot unpin: missing tray item or widget info");
return return;
} }
const itemName = trayItem.tooltipTitle || trayItem.name || trayItem.id || "" const itemName = trayItem.tooltipTitle || trayItem.name || trayItem.id || "";
if (!itemName) { if (!itemName) {
Logger.w("TrayMenu", "Cannot unpin: tray item has no name") Logger.w("TrayMenu", "Cannot unpin: tray item has no name");
return return;
} }
var widgets = Settings.data.bar.widgets[widgetSection] var widgets = Settings.data.bar.widgets[widgetSection];
if (!widgets || widgetIndex >= widgets.length) { if (!widgets || widgetIndex >= widgets.length) {
Logger.w("TrayMenu", "Cannot unpin: invalid widget index") Logger.w("TrayMenu", "Cannot unpin: invalid widget index");
return return;
} }
var widgetSettings = widgets[widgetIndex] var widgetSettings = widgets[widgetIndex];
if (!widgetSettings || widgetSettings.id !== "Tray") { if (!widgetSettings || widgetSettings.id !== "Tray") {
Logger.w("TrayMenu", "Cannot unpin: widget is not a Tray widget") Logger.w("TrayMenu", "Cannot unpin: widget is not a Tray widget");
return return;
} }
var pinnedList = widgetSettings.pinned || [] var pinnedList = widgetSettings.pinned || [];
var newPinned = [] var newPinned = [];
for (var i = 0; i < pinnedList.length; i++) { for (var i = 0; i < pinnedList.length; i++) {
if (pinnedList[i] !== itemName) { if (pinnedList[i] !== itemName) {
newPinned.push(pinnedList[i]) newPinned.push(pinnedList[i]);
} }
} }
var newSettings = Object.assign({}, widgetSettings) var newSettings = Object.assign({}, widgetSettings);
newSettings.pinned = newPinned newSettings.pinned = newPinned;
widgets[widgetIndex] = newSettings widgets[widgetIndex] = newSettings;
Settings.data.bar.widgets[widgetSection] = widgets Settings.data.bar.widgets[widgetSection] = widgets;
Settings.saveImmediate() Settings.saveImmediate();
} }
} }

View File

@@ -23,12 +23,12 @@ Item {
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: { property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) { if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section] var widgets = Settings.data.bar.widgets[section];
if (widgets && sectionWidgetIndex < widgets.length) { if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex] return widgets[sectionWidgetIndex];
} }
} }
return {} return {};
} }
// Widget settings - matching MediaMini pattern // Widget settings - matching MediaMini pattern
@@ -73,60 +73,60 @@ Item {
} }
function calculatedVerticalDimension() { function calculatedVerticalDimension() {
return Math.round((Style.baseWidgetSize - 5) * scaling) return Math.round((Style.baseWidgetSize - 5) * scaling);
} }
function calculateContentWidth() { function calculateContentWidth() {
// Calculate the actual content width based on visible elements // Calculate the actual content width based on visible elements
var contentWidth = 0 var contentWidth = 0;
var margins = Style.marginS * scaling * 2 // Left and right margins var margins = Style.marginS * scaling * 2; // Left and right margins
// Icon width (if visible) // Icon width (if visible)
if (showIcon) { if (showIcon) {
contentWidth += 18 * scaling contentWidth += 18 * scaling;
contentWidth += Style.marginS * scaling // Spacing after icon contentWidth += Style.marginS * scaling; // Spacing after icon
} }
// Text width (use the measured width) // Text width (use the measured width)
contentWidth += fullTitleMetrics.contentWidth contentWidth += fullTitleMetrics.contentWidth;
// Additional small margin for text // Additional small margin for text
contentWidth += Style.marginXXS * 2 contentWidth += Style.marginXXS * 2;
// Add container margins // 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 // Dynamic width: adapt to content but respect maximum width setting
readonly property real dynamicWidth: { readonly property real dynamicWidth: {
// If using fixed width mode, always use maxWidth // If using fixed width mode, always use maxWidth
if (useFixedWidth) { if (useFixedWidth) {
return maxWidth return maxWidth;
} }
// Otherwise, adapt to content // Otherwise, adapt to content
if (!hasFocusedWindow) { if (!hasFocusedWindow) {
return Math.min(calculateContentWidth(), maxWidth) return Math.min(calculateContentWidth(), maxWidth);
} }
// Use content width but don't exceed user-set maximum width // Use content width but don't exceed user-set maximum width
return Math.min(calculateContentWidth(), maxWidth) return Math.min(calculateContentWidth(), maxWidth);
} }
function getAppIcon() { function getAppIcon() {
try { try {
// Try CompositorService first // Try CompositorService first
const focusedWindow = CompositorService.getFocusedWindow() const focusedWindow = CompositorService.getFocusedWindow();
if (focusedWindow && focusedWindow.appId) { if (focusedWindow && focusedWindow.appId) {
try { try {
const idValue = focusedWindow.appId const idValue = focusedWindow.appId;
const normalizedId = (typeof idValue === 'string') ? idValue : String(idValue) const normalizedId = (typeof idValue === 'string') ? idValue : String(idValue);
const iconResult = ThemeIcons.iconForAppId(normalizedId.toLowerCase()) const iconResult = ThemeIcons.iconForAppId(normalizedId.toLowerCase());
if (iconResult && iconResult !== "") { if (iconResult && iconResult !== "") {
return iconResult return iconResult;
} }
} catch (iconError) { } 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 // Fallback to ToplevelManager
if (ToplevelManager && ToplevelManager.activeToplevel) { if (ToplevelManager && ToplevelManager.activeToplevel) {
try { try {
const activeToplevel = ToplevelManager.activeToplevel const activeToplevel = ToplevelManager.activeToplevel;
if (activeToplevel.appId) { if (activeToplevel.appId) {
const idValue2 = activeToplevel.appId const idValue2 = activeToplevel.appId;
const normalizedId2 = (typeof idValue2 === 'string') ? idValue2 : String(idValue2) const normalizedId2 = (typeof idValue2 === 'string') ? idValue2 : String(idValue2);
const iconResult2 = ThemeIcons.iconForAppId(normalizedId2.toLowerCase()) const iconResult2 = ThemeIcons.iconForAppId(normalizedId2.toLowerCase());
if (iconResult2 && iconResult2 !== "") { if (iconResult2 && iconResult2 !== "") {
return iconResult2 return iconResult2;
} }
} }
} catch (fallbackError) { } 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) { } catch (e) {
Logger.w("ActiveWindow", "Error in getAppIcon:", e) Logger.w("ActiveWindow", "Error in getAppIcon:", e);
return ThemeIcons.iconFromName(fallbackIcon) return ThemeIcons.iconFromName(fallbackIcon);
} }
} }
@@ -228,10 +228,10 @@ Item {
id: titleContainer id: titleContainer
Layout.preferredWidth: { Layout.preferredWidth: {
// Calculate available width based on other elements // Calculate available width based on other elements
var iconWidth = (showIcon && windowIcon.visible ? (18 + Style.marginS) : 0) var iconWidth = (showIcon && windowIcon.visible ? (18 + Style.marginS) : 0);
var totalMargins = Style.marginXXS * 2 var totalMargins = Style.marginXXS * 2;
var availableWidth = mainContainer.width - iconWidth - totalMargins var availableWidth = mainContainer.width - iconWidth - totalMargins;
return Math.max(20, availableWidth) return Math.max(20, availableWidth);
} }
Layout.maximumWidth: Layout.preferredWidth Layout.maximumWidth: Layout.preferredWidth
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
@@ -252,8 +252,8 @@ Item {
repeat: false repeat: false
onTriggered: { onTriggered: {
if (scrollingMode === "always" && titleContainer.needsScrolling) { if (scrollingMode === "always" && titleContainer.needsScrolling) {
titleContainer.isScrolling = true titleContainer.isScrolling = true;
titleContainer.isResetting = false titleContainer.isResetting = false;
} }
} }
} }
@@ -261,29 +261,29 @@ Item {
// Update scrolling state based on mode // Update scrolling state based on mode
property var updateScrollingState: function () { property var updateScrollingState: function () {
if (scrollingMode === "never") { if (scrollingMode === "never") {
isScrolling = false isScrolling = false;
isResetting = false isResetting = false;
} else if (scrollingMode === "always") { } else if (scrollingMode === "always") {
if (needsScrolling) { if (needsScrolling) {
if (mouseArea.containsMouse) { if (mouseArea.containsMouse) {
isScrolling = false isScrolling = false;
isResetting = true isResetting = true;
} else { } else {
scrollStartTimer.restart() scrollStartTimer.restart();
} }
} else { } else {
scrollStartTimer.stop() scrollStartTimer.stop();
isScrolling = false isScrolling = false;
isResetting = false isResetting = false;
} }
} else if (scrollingMode === "hover") { } else if (scrollingMode === "hover") {
if (mouseArea.containsMouse && needsScrolling) { if (mouseArea.containsMouse && needsScrolling) {
isScrolling = true isScrolling = true;
isResetting = false isResetting = false;
} else { } else {
isScrolling = false isScrolling = false;
if (needsScrolling) { if (needsScrolling) {
isResetting = true isResetting = true;
} }
} }
} }
@@ -296,7 +296,7 @@ Item {
Connections { Connections {
target: mouseArea target: mouseArea
function onContainsMouseChanged() { function onContainsMouseChanged() {
titleContainer.updateScrollingState() titleContainer.updateScrollingState();
} }
} }
@@ -322,10 +322,10 @@ Item {
color: Color.mOnSurface color: Color.mOnSurface
onTextChanged: { onTextChanged: {
if (root.scrollingMode === "always") { if (root.scrollingMode === "always") {
titleContainer.isScrolling = false titleContainer.isScrolling = false;
titleContainer.isResetting = false titleContainer.isResetting = false;
scrollContainer.scrollX = 0 scrollContainer.scrollX = 0;
scrollStartTimer.restart() scrollStartTimer.restart();
} }
} }
} }
@@ -349,7 +349,7 @@ Item {
duration: 300 duration: 300
easing.type: Easing.OutQuad easing.type: Easing.OutQuad
onFinished: { onFinished: {
titleContainer.isResetting = false titleContainer.isResetting = false;
} }
} }
@@ -412,11 +412,11 @@ Item {
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton
onEntered: { onEntered: {
if ((windowTitle !== "") && isVerticalBar || (scrollingMode === "never")) { if ((windowTitle !== "") && isVerticalBar || (scrollingMode === "never")) {
TooltipService.show(Screen, root, windowTitle, BarService.getTooltipDirection()) TooltipService.show(Screen, root, windowTitle, BarService.getTooltipDirection());
} }
} }
onExited: { onExited: {
TooltipService.hide() TooltipService.hide();
} }
} }
} }
@@ -426,18 +426,18 @@ Item {
target: CompositorService target: CompositorService
function onActiveWindowChanged() { function onActiveWindowChanged() {
try { try {
windowIcon.source = Qt.binding(getAppIcon) windowIcon.source = Qt.binding(getAppIcon);
windowIconVertical.source = Qt.binding(getAppIcon) windowIconVertical.source = Qt.binding(getAppIcon);
} catch (e) { } catch (e) {
Logger.w("ActiveWindow", "Error in onActiveWindowChanged:", e) Logger.w("ActiveWindow", "Error in onActiveWindowChanged:", e);
} }
} }
function onWindowListChanged() { function onWindowListChanged() {
try { try {
windowIcon.source = Qt.binding(getAppIcon) windowIcon.source = Qt.binding(getAppIcon);
windowIconVertical.source = Qt.binding(getAppIcon) windowIconVertical.source = Qt.binding(getAppIcon);
} catch (e) { } catch (e) {
Logger.w("ActiveWindow", "Error in onWindowListChanged:", e) Logger.w("ActiveWindow", "Error in onWindowListChanged:", e);
} }
} }
} }

View File

@@ -1,8 +1,8 @@
import QtQuick import QtQuick
import Quickshell import Quickshell
import qs.Commons import qs.Commons
import qs.Services.UI
import qs.Services.Media import qs.Services.Media
import qs.Services.UI
import qs.Widgets import qs.Widgets
import qs.Widgets.AudioSpectrum import qs.Widgets.AudioSpectrum
@@ -22,12 +22,12 @@ Item {
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: { property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) { if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section] var widgets = Settings.data.bar.widgets[section];
if (widgets && sectionWidgetIndex < widgets.length) { if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex] return widgets[sectionWidgetIndex];
} }
} }
return {} return {};
} }
// Resolve settings: try user settings or defaults from BarWidgetRegistry // Resolve settings: try user settings or defaults from BarWidgetRegistry
@@ -38,16 +38,16 @@ Item {
readonly property color fillColor: { readonly property color fillColor: {
switch (colorName) { switch (colorName) {
case "primary": case "primary":
return Color.mPrimary return Color.mPrimary;
case "secondary": case "secondary":
return Color.mSecondary return Color.mSecondary;
case "tertiary": case "tertiary":
return Color.mTertiary return Color.mTertiary;
case "error": case "error":
return Color.mError return Color.mError;
case "onSurface": case "onSurface":
default: default:
return Color.mOnSurface return Color.mOnSurface;
} }
} }
@@ -92,13 +92,13 @@ Item {
sourceComponent: { sourceComponent: {
switch (currentVisualizerType) { switch (currentVisualizerType) {
case "linear": case "linear":
return linearComponent return linearComponent;
case "mirrored": case "mirrored":
return mirroredComponent return mirroredComponent;
case "wave": case "wave":
return waveComponent return waveComponent;
default: default:
return null return null;
} }
} }
} }
@@ -112,13 +112,13 @@ Item {
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton
onClicked: mouse => { onClicked: mouse => {
const types = ["linear", "mirrored", "wave"] const types = ["linear", "mirrored", "wave"];
const currentIndex = types.indexOf(currentVisualizerType) const currentIndex = types.indexOf(currentVisualizerType);
const nextIndex = (currentIndex + 1) % types.length const nextIndex = (currentIndex + 1) % types.length;
const newType = types[nextIndex] const newType = types[nextIndex];
// Update settings directly, maybe this should be a widget setting... // Update settings directly, maybe this should be a widget setting...
Settings.data.audio.visualizerType = newType Settings.data.audio.visualizerType = newType;
} }
} }

View File

@@ -1,12 +1,12 @@
import QtQuick import QtQuick
import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Services.UPower import Quickshell.Services.UPower
import QtQuick.Layouts
import qs.Commons import qs.Commons
import qs.Services.UI
import qs.Services.Hardware
import qs.Widgets
import qs.Modules.Bar.Extras import qs.Modules.Bar.Extras
import qs.Services.Hardware
import qs.Services.UI
import qs.Widgets
Item { Item {
id: root id: root
@@ -22,12 +22,12 @@ Item {
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: { property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) { if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section] var widgets = Settings.data.bar.widgets[section];
if (widgets && sectionWidgetIndex < widgets.length) { 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" readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
@@ -53,13 +53,13 @@ Item {
function maybeNotify(percent, charging) { function maybeNotify(percent, charging) {
// Only notify once we are a below threshold // Only notify once we are a below threshold
if (!charging && !root.hasNotifiedLowBattery && percent <= warningThreshold) { 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", { ToastService.showWarning(I18n.tr("toast.battery.low"), I18n.tr("toast.battery.low-desc", {
"percent": Math.round(percent) "percent": Math.round(percent)
})) }));
} else if (root.hasNotifiedLowBattery && (charging || percent > warningThreshold + 5)) { } else if (root.hasNotifiedLowBattery && (charging || percent > warningThreshold + 5)) {
// Reset when charging starts or when battery recovers 5% above threshold // Reset when charging starts or when battery recovers 5% above threshold
root.hasNotifiedLowBattery = false root.hasNotifiedLowBattery = false;
} }
} }
@@ -67,20 +67,20 @@ Item {
Connections { Connections {
target: UPower.displayDevice target: UPower.displayDevice
function onPercentageChanged() { function onPercentageChanged() {
var currentPercent = UPower.displayDevice.percentage * 100 var currentPercent = UPower.displayDevice.percentage * 100;
var isCharging = UPower.displayDevice.state === UPowerDeviceState.Charging var isCharging = UPower.displayDevice.state === UPowerDeviceState.Charging;
root.maybeNotify(currentPercent, isCharging) root.maybeNotify(currentPercent, isCharging);
} }
function onStateChanged() { function onStateChanged() {
var isCharging = UPower.displayDevice.state === UPowerDeviceState.Charging var isCharging = UPower.displayDevice.state === UPowerDeviceState.Charging;
// Reset notification flag when charging starts // Reset notification flag when charging starts
if (isCharging) { if (isCharging) {
root.hasNotifiedLowBattery = false root.hasNotifiedLowBattery = false;
} }
// Also re-evaluate maybeNotify, as state might have changed // Also re-evaluate maybeNotify, as state might have changed
var currentPercent = UPower.displayDevice.percentage * 100 var currentPercent = UPower.displayDevice.percentage * 100;
root.maybeNotify(currentPercent, isCharging) root.maybeNotify(currentPercent, isCharging);
} }
} }
@@ -97,49 +97,49 @@ Item {
forceClose: displayMode === "alwaysHide" || !isReady || (!testMode && !battery.isLaptopBattery) forceClose: displayMode === "alwaysHide" || !isReady || (!testMode && !battery.isLaptopBattery)
onClicked: PanelService.getPanel("batteryPanel", screen)?.toggle(this) onClicked: PanelService.getPanel("batteryPanel", screen)?.toggle(this)
tooltipText: { tooltipText: {
let lines = [] let lines = [];
if (testMode) { if (testMode) {
lines.push(`Time left: ${Time.formatVagueHumanReadableDuration(12345)}.`) lines.push(`Time left: ${Time.formatVagueHumanReadableDuration(12345)}.`);
return lines.join("\n") return lines.join("\n");
} }
if (!isReady || !battery.isLaptopBattery) { if (!isReady || !battery.isLaptopBattery) {
return I18n.tr("battery.no-battery-detected") return I18n.tr("battery.no-battery-detected");
} }
if (battery.timeToEmpty > 0) { if (battery.timeToEmpty > 0) {
lines.push(I18n.tr("battery.time-left", { lines.push(I18n.tr("battery.time-left", {
"time": Time.formatVagueHumanReadableDuration(battery.timeToEmpty) "time": Time.formatVagueHumanReadableDuration(battery.timeToEmpty)
})) }));
} }
if (battery.timeToFull > 0) { if (battery.timeToFull > 0) {
lines.push(I18n.tr("battery.time-until-full", { lines.push(I18n.tr("battery.time-until-full", {
"time": Time.formatVagueHumanReadableDuration(battery.timeToFull) "time": Time.formatVagueHumanReadableDuration(battery.timeToFull)
})) }));
} }
if (battery.changeRate !== undefined) { if (battery.changeRate !== undefined) {
const rate = battery.changeRate const rate = battery.changeRate;
if (rate > 0) { if (rate > 0) {
lines.push(charging ? I18n.tr("battery.charging-rate", { lines.push(charging ? I18n.tr("battery.charging-rate", {
"rate": rate.toFixed(2) "rate": rate.toFixed(2)
}) : I18n.tr("battery.discharging-rate", { }) : I18n.tr("battery.discharging-rate", {
"rate": rate.toFixed(2) "rate": rate.toFixed(2)
})) }));
} else if (rate < 0) { } else if (rate < 0) {
lines.push(I18n.tr("battery.discharging-rate", { lines.push(I18n.tr("battery.discharging-rate", {
"rate": Math.abs(rate).toFixed(2) "rate": Math.abs(rate).toFixed(2)
})) }));
} else { } else {
// Rate is 0 - check if plugged in (charging state) or idle // 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 { } 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) { if (battery.healthPercentage !== undefined && battery.healthPercentage > 0) {
lines.push(I18n.tr("battery.health", { lines.push(I18n.tr("battery.health", {
"percent": Math.round(battery.healthPercentage) "percent": Math.round(battery.healthPercentage)
})) }));
} }
return lines.join("\n") return lines.join("\n");
} }
} }
} }

View File

@@ -19,12 +19,12 @@ Item {
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: { property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) { if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section] var widgets = Settings.data.bar.widgets[section];
if (widgets && sectionWidgetIndex < widgets.length) { 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" 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" icon: BluetoothService.enabled ? "bluetooth" : "bluetooth-off"
text: { text: {
if (BluetoothService.connectedDevices && BluetoothService.connectedDevices.length > 0) { if (BluetoothService.connectedDevices && BluetoothService.connectedDevices.length > 0) {
const firstDevice = BluetoothService.connectedDevices[0] const firstDevice = BluetoothService.connectedDevices[0];
return firstDevice.name || firstDevice.deviceName return firstDevice.name || firstDevice.deviceName;
} }
return "" return "";
} }
suffix: { suffix: {
if (BluetoothService.connectedDevices && BluetoothService.connectedDevices.length > 1) { if (BluetoothService.connectedDevices && BluetoothService.connectedDevices.length > 1) {
return ` + ${BluetoothService.connectedDevices.length - 1}` return ` + ${BluetoothService.connectedDevices.length - 1}`;
} }
return "" return "";
} }
autoHide: false autoHide: false
forceOpen: !isBarVertical && root.displayMode === "alwaysShow" forceOpen: !isBarVertical && root.displayMode === "alwaysShow"
@@ -59,9 +59,9 @@ Item {
onRightClicked: BluetoothService.setBluetoothEnabled(!BluetoothService.enabled) onRightClicked: BluetoothService.setBluetoothEnabled(!BluetoothService.enabled)
tooltipText: { tooltipText: {
if (pill.text !== "") { if (pill.text !== "") {
return pill.text return pill.text;
} }
return I18n.tr("tooltips.bluetooth-devices") return I18n.tr("tooltips.bluetooth-devices");
} }
} }
} }

View File

@@ -21,12 +21,12 @@ Item {
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: { property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) { if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section] var widgets = Settings.data.bar.widgets[section];
if (widgets && sectionWidgetIndex < widgets.length) { 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" readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
@@ -40,13 +40,13 @@ Item {
visible: getMonitor() !== null visible: getMonitor() !== null
function getMonitor() { function getMonitor() {
return BrightnessService.getMonitorForScreen(screen) || null return BrightnessService.getMonitorForScreen(screen) || null;
} }
function getIcon() { function getIcon() {
var monitor = getMonitor() var monitor = getMonitor();
var brightness = monitor ? monitor.brightness : 0 var brightness = monitor ? monitor.brightness : 0;
return brightness <= 0.5 ? "brightness-low" : "brightness-high" return brightness <= 0.5 ? "brightness-low" : "brightness-high";
} }
// Connection used to open the pill when brightness changes // 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. // Ignore if this is the first time we receive an update.
// Most likely service just kicked off. // Most likely service just kicked off.
if (!firstBrightnessReceived) { if (!firstBrightnessReceived) {
firstBrightnessReceived = true firstBrightnessReceived = true;
return return;
} }
pill.show() pill.show();
hideTimerAfterChange.restart() hideTimerAfterChange.restart();
} }
} }
@@ -82,42 +82,42 @@ Item {
icon: getIcon() icon: getIcon()
autoHide: false // Important to be false so we can hover as long as we want autoHide: false // Important to be false so we can hover as long as we want
text: { text: {
var monitor = getMonitor() var monitor = getMonitor();
return monitor ? Math.round(monitor.brightness * 100) : "" return monitor ? Math.round(monitor.brightness * 100) : "";
} }
suffix: text.length > 0 ? "%" : "-" suffix: text.length > 0 ? "%" : "-"
forceOpen: displayMode === "alwaysShow" forceOpen: displayMode === "alwaysShow"
forceClose: displayMode === "alwaysHide" forceClose: displayMode === "alwaysHide"
tooltipText: { tooltipText: {
var monitor = getMonitor() var monitor = getMonitor();
if (!monitor) if (!monitor)
return "" return "";
return I18n.tr("tooltips.brightness-at", { return I18n.tr("tooltips.brightness-at", {
"brightness": Math.round(monitor.brightness * 100) "brightness": Math.round(monitor.brightness * 100)
}) });
} }
onWheel: function (angle) { onWheel: function (angle) {
var monitor = getMonitor() var monitor = getMonitor();
if (!monitor) if (!monitor)
return return;
if (angle > 0) { if (angle > 0) {
monitor.increaseBrightness() monitor.increaseBrightness();
} else if (angle < 0) { } else if (angle < 0) {
monitor.decreaseBrightness() monitor.decreaseBrightness();
} }
} }
onClicked: { onClicked: {
var settingsPanel = PanelService.getPanel("settingsPanel", screen) var settingsPanel = PanelService.getPanel("settingsPanel", screen);
settingsPanel.requestedTab = SettingsPanel.Tab.Display settingsPanel.requestedTab = SettingsPanel.Tab.Display;
settingsPanel.open() settingsPanel.open();
} }
onRightClicked: { onRightClicked: {
var settingsPanel = PanelService.getPanel("settingsPanel", screen) var settingsPanel = PanelService.getPanel("settingsPanel", screen);
settingsPanel.requestedTab = SettingsPanel.Tab.Display settingsPanel.requestedTab = SettingsPanel.Tab.Display;
settingsPanel.open() settingsPanel.open();
} }
} }
} }

View File

@@ -20,12 +20,12 @@ Rectangle {
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: { property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) { if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section] var widgets = Settings.data.bar.widgets[section];
if (widgets && sectionWidgetIndex < widgets.length) { if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex] return widgets[sectionWidgetIndex];
} }
} }
return {} return {};
} }
readonly property string barPosition: Settings.data.bar.position readonly property string barPosition: Settings.data.bar.position
@@ -70,9 +70,9 @@ Rectangle {
Binding on pointSize { Binding on pointSize {
value: { value: {
if (repeater.model.length == 1) { if (repeater.model.length == 1) {
return Style.fontSizeS * scaling return Style.fontSizeS * scaling;
} else { } 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 hoverEnabled: true
onEntered: { onEntered: {
if (!PanelService.getPanel("calendarPanel", screen)?.active) { 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: { onExited: {
TooltipService.hide() TooltipService.hide();
} }
onClicked: { onClicked: {
TooltipService.hide() TooltipService.hide();
PanelService.getPanel("calendarPanel", screen)?.toggle(this) PanelService.getPanel("calendarPanel", screen)?.toggle(this);
} }
} }
} }

View File

@@ -1,11 +1,11 @@
import QtQuick import QtQuick
import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Widgets import Quickshell.Widgets
import QtQuick.Effects
import qs.Commons import qs.Commons
import qs.Widgets
import qs.Services.UI
import qs.Services.System import qs.Services.System
import qs.Services.UI
import qs.Widgets
NIconButton { NIconButton {
id: root id: root
@@ -21,12 +21,12 @@ NIconButton {
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: { property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) { if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section] var widgets = Settings.data.bar.widgets[section];
if (widgets && sectionWidgetIndex < widgets.length) { if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex] return widgets[sectionWidgetIndex];
} }
} }
return {} return {};
} }
readonly property string customIcon: widgetSettings.icon || widgetMetadata.icon readonly property string customIcon: widgetSettings.icon || widgetMetadata.icon
@@ -34,8 +34,8 @@ NIconButton {
readonly property string customIconPath: widgetSettings.customIconPath || "" readonly property string customIconPath: widgetSettings.customIconPath || ""
readonly property bool colorizeDistroLogo: { readonly property bool colorizeDistroLogo: {
if (widgetSettings.colorizeDistroLogo !== undefined) if (widgetSettings.colorizeDistroLogo !== undefined)
return widgetSettings.colorizeDistroLogo return widgetSettings.colorizeDistroLogo;
return widgetMetadata.colorizeDistroLogo !== undefined ? widgetMetadata.colorizeDistroLogo : false return widgetMetadata.colorizeDistroLogo !== undefined ? widgetMetadata.colorizeDistroLogo : false;
} }
// If we have a custom path or distro logo, don't use the theme icon. // If we have a custom path or distro logo, don't use the theme icon.
@@ -51,12 +51,12 @@ NIconButton {
colorBorder: Color.transparent colorBorder: Color.transparent
colorBorderHover: useDistroLogo ? Color.mHover : Color.transparent colorBorderHover: useDistroLogo ? Color.mHover : Color.transparent
onClicked: { onClicked: {
var controlCenterPanel = PanelService.getPanel("controlCenterPanel", screen) var controlCenterPanel = PanelService.getPanel("controlCenterPanel", screen);
if (Settings.data.controlCenter.position === "close_to_bar_button") { if (Settings.data.controlCenter.position === "close_to_bar_button") {
// Willopen the panel next to the bar button. // Willopen the panel next to the bar button.
controlCenterPanel?.toggle(this) controlCenterPanel?.toggle(this);
} else { } else {
controlCenterPanel?.toggle() controlCenterPanel?.toggle();
} }
} }
onRightClicked: PanelService.getPanel("settingsPanel", screen)?.toggle() onRightClicked: PanelService.getPanel("settingsPanel", screen)?.toggle()
@@ -69,10 +69,10 @@ NIconButton {
height: width height: width
source: { source: {
if (customIconPath !== "") if (customIconPath !== "")
return customIconPath.startsWith("file://") ? customIconPath : "file://" + customIconPath return customIconPath.startsWith("file://") ? customIconPath : "file://" + customIconPath;
if (useDistroLogo) if (useDistroLogo)
return HostService.osLogo return HostService.osLogo;
return "" return "";
} }
visible: source !== "" visible: source !== ""
smooth: true smooth: true

View File

@@ -3,10 +3,10 @@ import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
import qs.Commons import qs.Commons
import qs.Modules.Bar.Extras
import qs.Modules.Panels.Settings
import qs.Services.UI import qs.Services.UI
import qs.Widgets import qs.Widgets
import qs.Modules.Panels.Settings
import qs.Modules.Bar.Extras
Item { Item {
id: root id: root
@@ -22,12 +22,12 @@ Item {
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: { property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) { if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section] var widgets = Settings.data.bar.widgets[section];
if (widgets && sectionWidgetIndex < widgets.length) { 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" readonly property bool isVerticalBar: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
@@ -63,31 +63,31 @@ Item {
autoHide: false autoHide: false
forceOpen: _dynamicText !== "" forceOpen: _dynamicText !== ""
tooltipText: { tooltipText: {
var tooltipLines = [] var tooltipLines = [];
if (hasExec) { if (hasExec) {
if (leftClickExec !== "") { if (leftClickExec !== "") {
tooltipLines.push(`Left click: ${leftClickExec}.`) tooltipLines.push(`Left click: ${leftClickExec}.`);
} }
if (rightClickExec !== "") { if (rightClickExec !== "") {
tooltipLines.push(`Right click: ${rightClickExec}.`) tooltipLines.push(`Right click: ${rightClickExec}.`);
} }
if (middleClickExec !== "") { if (middleClickExec !== "") {
tooltipLines.push(`Middle click: ${middleClickExec}.`) tooltipLines.push(`Middle click: ${middleClickExec}.`);
} }
} }
if (_dynamicTooltip !== "") { if (_dynamicTooltip !== "") {
if (tooltipLines.length > 0) { if (tooltipLines.length > 0) {
tooltipLines.push("") tooltipLines.push("");
} }
tooltipLines.push(_dynamicTooltip) tooltipLines.push(_dynamicTooltip);
} }
if (tooltipLines.length === 0) { if (tooltipLines.length === 0) {
return "Custom button, configure in settings." return "Custom button, configure in settings.";
} else { } else {
return tooltipLines.join("\n") return tooltipLines.join("\n");
} }
} }
@@ -135,121 +135,121 @@ Item {
stderr: StdioCollector {} stderr: StdioCollector {}
onExited: (exitCode, exitStatus) => { onExited: (exitCode, exitStatus) => {
if (textStream) { if (textStream) {
Logger.w("CustomButton", `Streaming text command exited (code: ${exitCode}), restarting...`) Logger.w("CustomButton", `Streaming text command exited (code: ${exitCode}), restarting...`);
return return;
} }
} }
} }
function parseDynamicContent(content) { function parseDynamicContent(content) {
var contentStr = String(content || "").trim() var contentStr = String(content || "").trim();
if (parseJson) { if (parseJson) {
var lineToParse = contentStr var lineToParse = contentStr;
if (!textStream && contentStr.includes('\n')) { 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) { if (lines.length > 0) {
lineToParse = lines[lines.length - 1] lineToParse = lines[lines.length - 1];
} }
} }
try { try {
const parsed = JSON.parse(lineToParse) const parsed = JSON.parse(lineToParse);
const text = parsed.text || "" const text = parsed.text || "";
const icon = parsed.icon || "" const icon = parsed.icon || "";
const tooltip = parsed.tooltip || "" const tooltip = parsed.tooltip || "";
if (checkCollapse(text)) { if (checkCollapse(text)) {
_dynamicText = "" _dynamicText = "";
_dynamicIcon = "" _dynamicIcon = "";
_dynamicTooltip = "" _dynamicTooltip = "";
return return;
} }
_dynamicText = text _dynamicText = text;
_dynamicIcon = icon _dynamicIcon = icon;
_dynamicTooltip = tooltip _dynamicTooltip = tooltip;
return return;
} catch (e) { } catch (e) {
Logger.w("CustomButton", `Failed to parse JSON. Content: "${lineToParse}"`) Logger.w("CustomButton", `Failed to parse JSON. Content: "${lineToParse}"`);
} }
} }
if (checkCollapse(contentStr)) { if (checkCollapse(contentStr)) {
_dynamicText = "" _dynamicText = "";
_dynamicIcon = "" _dynamicIcon = "";
_dynamicTooltip = "" _dynamicTooltip = "";
return return;
} }
_dynamicText = contentStr _dynamicText = contentStr;
_dynamicIcon = "" _dynamicIcon = "";
_dynamicTooltip = "" _dynamicTooltip = "";
} }
function checkCollapse(text) { function checkCollapse(text) {
if (!textCollapse || textCollapse.length === 0) { if (!textCollapse || textCollapse.length === 0) {
return false return false;
} }
if (textCollapse.startsWith("/") && textCollapse.endsWith("/") && textCollapse.length > 1) { if (textCollapse.startsWith("/") && textCollapse.endsWith("/") && textCollapse.length > 1) {
// Treat as regex // Treat as regex
var pattern = textCollapse.substring(1, textCollapse.length - 1) var pattern = textCollapse.substring(1, textCollapse.length - 1);
try { try {
var regex = new RegExp(pattern) var regex = new RegExp(pattern);
return regex.test(text) return regex.test(text);
} catch (e) { } catch (e) {
Logger.w("CustomButton", `Invalid regex for textCollapse: ${textCollapse} - ${e.message}`) Logger.w("CustomButton", `Invalid regex for textCollapse: ${textCollapse} - ${e.message}`);
return (textCollapse === text) // Fallback to exact match on invalid regex return (textCollapse === text); // Fallback to exact match on invalid regex
} }
} else { } else {
// Treat as plain string // Treat as plain string
return (textCollapse === text) return (textCollapse === text);
} }
} }
function onClicked() { function onClicked() {
if (leftClickExec) { if (leftClickExec) {
Quickshell.execDetached(["sh", "-c", leftClickExec]) Quickshell.execDetached(["sh", "-c", leftClickExec]);
Logger.i("CustomButton", `Executing command: ${leftClickExec}`) Logger.i("CustomButton", `Executing command: ${leftClickExec}`);
} else if (!hasExec && !leftClickUpdateText) { } else if (!hasExec && !leftClickUpdateText) {
// No script was defined, open settings // No script was defined, open settings
var settingsPanel = PanelService.getPanel("settingsPanel", screen) var settingsPanel = PanelService.getPanel("settingsPanel", screen);
settingsPanel.requestedTab = SettingsPanel.Tab.Bar settingsPanel.requestedTab = SettingsPanel.Tab.Bar;
settingsPanel.open() settingsPanel.open();
} }
if (!textStream && leftClickUpdateText) { if (!textStream && leftClickUpdateText) {
runTextCommand() runTextCommand();
} }
} }
function onRightClicked() { function onRightClicked() {
if (rightClickExec) { if (rightClickExec) {
Quickshell.execDetached(["sh", "-c", rightClickExec]) Quickshell.execDetached(["sh", "-c", rightClickExec]);
Logger.i("CustomButton", `Executing command: ${rightClickExec}`) Logger.i("CustomButton", `Executing command: ${rightClickExec}`);
} }
if (!textStream && rightClickUpdateText) { if (!textStream && rightClickUpdateText) {
runTextCommand() runTextCommand();
} }
} }
function onMiddleClicked() { function onMiddleClicked() {
if (middleClickExec) { if (middleClickExec) {
Quickshell.execDetached(["sh", "-c", middleClickExec]) Quickshell.execDetached(["sh", "-c", middleClickExec]);
Logger.i("CustomButton", `Executing command: ${middleClickExec}`) Logger.i("CustomButton", `Executing command: ${middleClickExec}`);
} }
if (!textStream && middleClickUpdateText) { if (!textStream && middleClickUpdateText) {
runTextCommand() runTextCommand();
} }
} }
function runTextCommand() { function runTextCommand() {
if (!textCommand || textCommand.length === 0) if (!textCommand || textCommand.length === 0)
return return;
if (textProc.running) if (textProc.running)
return return;
textProc.command = ["sh", "-lc", textCommand] textProc.command = ["sh", "-lc", textCommand];
textProc.running = true textProc.running = true;
} }
} }

View File

@@ -1,7 +1,7 @@
import Quickshell import Quickshell
import qs.Commons import qs.Commons
import qs.Widgets
import qs.Services.UI import qs.Services.UI
import qs.Widgets
NIconButton { NIconButton {
id: root id: root

View File

@@ -2,10 +2,10 @@ import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell import Quickshell
import qs.Commons import qs.Commons
import qs.Modules.Bar.Extras
import qs.Services.Power import qs.Services.Power
import qs.Services.UI import qs.Services.UI
import qs.Widgets import qs.Widgets
import qs.Modules.Bar.Extras
Item { Item {
id: root id: root
@@ -21,12 +21,12 @@ Item {
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: { property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) { if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section] var widgets = Settings.data.bar.widgets[section];
if (widgets && sectionWidgetIndex < widgets.length) { 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" readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
@@ -47,20 +47,20 @@ Item {
forceOpen: IdleInhibitorService.timeout !== null forceOpen: IdleInhibitorService.timeout !== null
forceClose: IdleInhibitorService.timeout == null forceClose: IdleInhibitorService.timeout == null
onWheel: function (delta) { 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 // 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) { 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) { } 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) { } else if (timeout >= 1800 && timeout < 3600) {
delta = 600 // >= 30m, increment at 10m interval delta = 600; // >= 30m, increment at 10m interval
} else if (timeout >= 3600) { } 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);
} }
} }
} }

View File

@@ -1,13 +1,13 @@
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Wayland
import Quickshell.Io import Quickshell.Io
import Quickshell.Wayland
import qs.Commons import qs.Commons
import qs.Services.UI
import qs.Services.Keyboard
import qs.Widgets
import qs.Modules.Bar.Extras import qs.Modules.Bar.Extras
import qs.Services.Keyboard
import qs.Services.UI
import qs.Widgets
Item { Item {
id: root id: root
@@ -23,12 +23,12 @@ Item {
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: { property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) { if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section] var widgets = Settings.data.bar.widgets[section];
if (widgets && sectionWidgetIndex < widgets.length) { if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex] return widgets[sectionWidgetIndex];
} }
} }
return {} return {};
} }
readonly property string displayMode: (widgetSettings.displayMode !== undefined) ? widgetSettings.displayMode : widgetMetadata.displayMode readonly property string displayMode: (widgetSettings.displayMode !== undefined) ? widgetSettings.displayMode : widgetMetadata.displayMode
@@ -53,9 +53,9 @@ Item {
}) })
forceOpen: root.displayMode === "forceOpen" forceOpen: root.displayMode === "forceOpen"
forceClose: root.displayMode === "alwaysHide" forceClose: root.displayMode === "alwaysHide"
onClicked: { onClicked:
// You could open keyboard settings here if needed. // You could open keyboard settings here if needed.
} {}
} }
} }

View File

@@ -20,12 +20,12 @@ Rectangle {
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: { property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) { if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section] var widgets = Settings.data.bar.widgets[section];
if (widgets && sectionWidgetIndex < widgets.length) { if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex] return widgets[sectionWidgetIndex];
} }
} }
return {} return {};
} }
readonly property string barPosition: Settings.data.bar.position readonly property string barPosition: Settings.data.bar.position

View File

@@ -2,11 +2,11 @@ import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell import Quickshell
import qs.Widgets.AudioSpectrum
import qs.Commons import qs.Commons
import qs.Services.Media import qs.Services.Media
import qs.Services.UI import qs.Services.UI
import qs.Widgets import qs.Widgets
import qs.Widgets.AudioSpectrum
Item { Item {
id: root id: root
@@ -23,12 +23,12 @@ Item {
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: { property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) { if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section] var widgets = Settings.data.bar.widgets[section];
if (widgets && sectionWidgetIndex < widgets.length) { 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") readonly property bool isVerticalBar: (Settings.data.bar.position === "left" || Settings.data.bar.position === "right")
@@ -49,18 +49,18 @@ Item {
readonly property string placeholderText: I18n.tr("bar.widget-settings.media-mini.no-active-player") readonly property string placeholderText: I18n.tr("bar.widget-settings.media-mini.no-active-player")
readonly property string tooltipText: { readonly property string tooltipText: {
var title = getTitle() var title = getTitle();
var controls = "" var controls = "";
if (MediaService.canGoNext) { if (MediaService.canGoNext) {
controls += "Right click for next.\n" controls += "Right click for next.\n";
} }
if (MediaService.canGoPrevious) { if (MediaService.canGoPrevious) {
controls += "Middle click for previous." controls += "Middle click for previous.";
} }
if (controls !== "") { if (controls !== "") {
return title + "\n\n" + controls return title + "\n\n" + controls;
} }
return title return title;
} }
// Hide conditions // Hide conditions
@@ -94,57 +94,57 @@ Item {
} }
function getTitle() { function getTitle() {
return MediaService.trackTitle + (MediaService.trackArtist !== "" ? ` - ${MediaService.trackArtist}` : "") return MediaService.trackTitle + (MediaService.trackArtist !== "" ? ` - ${MediaService.trackArtist}` : "");
} }
function calculatedVerticalDimension() { function calculatedVerticalDimension() {
return Math.round((Style.baseWidgetSize - 5) * scaling) return Math.round((Style.baseWidgetSize - 5) * scaling);
} }
function calculateContentWidth() { function calculateContentWidth() {
// Calculate the actual content width based on visible elements // Calculate the actual content width based on visible elements
var contentWidth = 0 var contentWidth = 0;
var margins = Style.marginS * scaling * 2 // Left and right margins var margins = Style.marginS * scaling * 2; // Left and right margins
// Icon or album art width // Icon or album art width
if (!hasActivePlayer || !showAlbumArt) { if (!hasActivePlayer || !showAlbumArt) {
// Icon width // Icon width
contentWidth += Math.round(18 * scaling) contentWidth += Math.round(18 * scaling);
} else if (showAlbumArt && hasActivePlayer) { } else if (showAlbumArt && hasActivePlayer) {
// Album art width // Album art width
contentWidth += 21 * scaling contentWidth += 21 * scaling;
} }
// Spacing between icon/art and text; only if there is text // Spacing between icon/art and text; only if there is text
if (fullTitleMetrics.contentWidth > 0) { if (fullTitleMetrics.contentWidth > 0) {
contentWidth += Style.marginS * scaling contentWidth += Style.marginS * scaling;
// Text width (use the measured width) // Text width (use the measured width)
contentWidth += fullTitleMetrics.contentWidth contentWidth += fullTitleMetrics.contentWidth;
// Additional small margin for text // Additional small margin for text
contentWidth += Style.marginXXS * 2 contentWidth += Style.marginXXS * 2;
} }
// Add container margins // 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 // Dynamic width: adapt to content but respect maximum width setting
readonly property real dynamicWidth: { readonly property real dynamicWidth: {
// If using fixed width mode, always use maxWidth // If using fixed width mode, always use maxWidth
if (useFixedWidth) { if (useFixedWidth) {
return maxWidth return maxWidth;
} }
// Otherwise, adapt to content // Otherwise, adapt to content
if (!hasActivePlayer) { if (!hasActivePlayer) {
// Keep compact when no active player // Keep compact when no active player
return calculateContentWidth() return calculateContentWidth();
} }
// Use content width but don't exceed user-set maximum width // 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 // A hidden text element to safely measure the full title width
@@ -279,11 +279,11 @@ Item {
id: titleContainer id: titleContainer
Layout.preferredWidth: { Layout.preferredWidth: {
// Calculate available width based on other elements in the row // Calculate available width based on other elements in the row
var iconWidth = (windowIcon.visible ? (18 * scaling + Style.marginS * scaling) : 0) var iconWidth = (windowIcon.visible ? (18 * scaling + Style.marginS * scaling) : 0);
var albumArtWidth = (hasActivePlayer && showAlbumArt ? (21 * scaling + Style.marginS * scaling) : 0) var albumArtWidth = (hasActivePlayer && showAlbumArt ? (21 * scaling + Style.marginS * scaling) : 0);
var totalMargins = Style.marginXXS * 2 var totalMargins = Style.marginXXS * 2;
var availableWidth = mainContainer.width - iconWidth - albumArtWidth - totalMargins var availableWidth = mainContainer.width - iconWidth - albumArtWidth - totalMargins;
return Math.max(20, availableWidth) return Math.max(20, availableWidth);
} }
Layout.maximumWidth: Layout.preferredWidth Layout.maximumWidth: Layout.preferredWidth
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
@@ -304,8 +304,8 @@ Item {
repeat: false repeat: false
onTriggered: { onTriggered: {
if (scrollingMode === "always" && titleContainer.needsScrolling) { if (scrollingMode === "always" && titleContainer.needsScrolling) {
titleContainer.isScrolling = true titleContainer.isScrolling = true;
titleContainer.isResetting = false titleContainer.isResetting = false;
} }
} }
} }
@@ -313,48 +313,48 @@ Item {
// Update scrolling state based on mode // Update scrolling state based on mode
property var updateScrollingState: function () { property var updateScrollingState: function () {
if (scrollingMode === "never") { if (scrollingMode === "never") {
isScrolling = false isScrolling = false;
isResetting = false isResetting = false;
} else if (scrollingMode === "always") { } else if (scrollingMode === "always") {
if (needsScrolling) { if (needsScrolling) {
if (mouseArea.containsMouse) { if (mouseArea.containsMouse) {
isScrolling = false isScrolling = false;
isResetting = true isResetting = true;
} else { } else {
scrollStartTimer.restart() scrollStartTimer.restart();
} }
} else { } else {
scrollStartTimer.stop() scrollStartTimer.stop();
isScrolling = false isScrolling = false;
isResetting = false isResetting = false;
} }
} else if (scrollingMode === "hover") { } else if (scrollingMode === "hover") {
if (mouseArea.containsMouse && needsScrolling) { if (mouseArea.containsMouse && needsScrolling) {
isScrolling = true isScrolling = true;
isResetting = false isResetting = false;
} else { } else {
isScrolling = false isScrolling = false;
if (needsScrolling) { if (needsScrolling) {
isResetting = true isResetting = true;
} }
} }
} }
} }
onWidthChanged: { onWidthChanged: {
containerWidth = width containerWidth = width;
updateScrollingState() updateScrollingState();
} }
Component.onCompleted: { Component.onCompleted: {
containerWidth = width containerWidth = width;
updateScrollingState() updateScrollingState();
} }
Connections { Connections {
target: mouseArea target: mouseArea
function onContainsMouseChanged() { function onContainsMouseChanged() {
titleContainer.updateScrollingState() titleContainer.updateScrollingState();
} }
} }
@@ -380,11 +380,11 @@ Item {
horizontalAlignment: hasActivePlayer ? Text.AlignLeft : Text.AlignHCenter horizontalAlignment: hasActivePlayer ? Text.AlignLeft : Text.AlignHCenter
color: hasActivePlayer ? Color.mOnSurface : Color.mOnSurfaceVariant color: hasActivePlayer ? Color.mOnSurface : Color.mOnSurfaceVariant
onTextChanged: { onTextChanged: {
titleContainer.isScrolling = false titleContainer.isScrolling = false;
titleContainer.isResetting = false titleContainer.isResetting = false;
scrollContainer.scrollX = 0 scrollContainer.scrollX = 0;
if (titleContainer.needsScrolling) { if (titleContainer.needsScrolling) {
scrollStartTimer.restart() scrollStartTimer.restart();
} }
} }
} }
@@ -408,7 +408,7 @@ Item {
duration: 300 duration: 300
easing.type: Easing.OutQuad easing.type: Easing.OutQuad
onFinished: { onFinished: {
titleContainer.isResetting = false titleContainer.isResetting = false;
} }
} }
@@ -462,28 +462,28 @@ Item {
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onClicked: mouse => { onClicked: mouse => {
if (!hasActivePlayer || !MediaService.currentPlayer || !MediaService.canPlay) { if (!hasActivePlayer || !MediaService.currentPlayer || !MediaService.canPlay) {
return return;
} }
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
MediaService.playPause() MediaService.playPause();
} else if (mouse.button == Qt.RightButton) { } else if (mouse.button == Qt.RightButton) {
MediaService.next() MediaService.next();
TooltipService.hide() TooltipService.hide();
} else if (mouse.button == Qt.MiddleButton) { } else if (mouse.button == Qt.MiddleButton) {
MediaService.previous() MediaService.previous();
TooltipService.hide() TooltipService.hide();
} }
} }
onEntered: { onEntered: {
var textToShow = hasActivePlayer ? tooltipText : placeholderText var textToShow = hasActivePlayer ? tooltipText : placeholderText;
if ((textToShow !== "") && isVerticalBar || (scrollingMode === "never")) { if ((textToShow !== "") && isVerticalBar || (scrollingMode === "never")) {
TooltipService.show(Screen, root, textToShow, BarService.getTooltipDirection()) TooltipService.show(Screen, root, textToShow, BarService.getTooltipDirection());
} }
} }
onExited: { onExited: {
TooltipService.hide() TooltipService.hide();
} }
} }
} }

View File

@@ -23,12 +23,12 @@ Item {
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: { property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) { if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section] var widgets = Settings.data.bar.widgets[section];
if (widgets && sectionWidgetIndex < widgets.length) { 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" readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
@@ -48,13 +48,13 @@ Item {
// Logger.i("Bar:Microphone", "onInputVolumeChanged") // Logger.i("Bar:Microphone", "onInputVolumeChanged")
if (!firstInputVolumeReceived) { if (!firstInputVolumeReceived) {
// Ignore the first volume change // Ignore the first volume change
firstInputVolumeReceived = true firstInputVolumeReceived = true;
} else { } else {
// If a tooltip is visible while we show the pill // If a tooltip is visible while we show the pill
// hide it so it doesn't overlap the volume slider. // hide it so it doesn't overlap the volume slider.
TooltipService.hide() TooltipService.hide();
pill.show() pill.show();
externalHideTimer.restart() externalHideTimer.restart();
} }
} }
} }
@@ -66,11 +66,11 @@ Item {
// Logger.i("Bar:Microphone", "onInputMutedChanged") // Logger.i("Bar:Microphone", "onInputMutedChanged")
if (!firstInputVolumeReceived) { if (!firstInputVolumeReceived) {
// Ignore the first mute change // Ignore the first mute change
firstInputVolumeReceived = true firstInputVolumeReceived = true;
} else { } else {
TooltipService.hide() TooltipService.hide();
pill.show() pill.show();
externalHideTimer.restart() externalHideTimer.restart();
} }
} }
} }
@@ -80,7 +80,7 @@ Item {
running: false running: false
interval: 1500 interval: 1500
onTriggered: { onTriggered: {
pill.hide() pill.hide();
} }
} }
@@ -101,25 +101,25 @@ Item {
onWheel: function (delta) { onWheel: function (delta) {
// As soon as we start scrolling to adjust volume, hide the tooltip // As soon as we start scrolling to adjust volume, hide the tooltip
TooltipService.hide() TooltipService.hide();
wheelAccumulator += delta wheelAccumulator += delta;
if (wheelAccumulator >= 120) { if (wheelAccumulator >= 120) {
wheelAccumulator = 0 wheelAccumulator = 0;
AudioService.setInputVolume(AudioService.inputVolume + AudioService.stepVolume) AudioService.setInputVolume(AudioService.inputVolume + AudioService.stepVolume);
} else if (wheelAccumulator <= -120) { } else if (wheelAccumulator <= -120) {
wheelAccumulator = 0 wheelAccumulator = 0;
AudioService.setInputVolume(AudioService.inputVolume - AudioService.stepVolume) AudioService.setInputVolume(AudioService.inputVolume - AudioService.stepVolume);
} }
} }
onClicked: { onClicked: {
PanelService.getPanel("audioPanel", screen)?.toggle(this) PanelService.getPanel("audioPanel", screen)?.toggle(this);
} }
onRightClicked: { onRightClicked: {
AudioService.setInputMuted(!AudioService.inputMuted) AudioService.setInputMuted(!AudioService.inputMuted);
} }
onMiddleClicked: { onMiddleClicked: {
Quickshell.execDetached(["pwvucontrol"]) Quickshell.execDetached(["pwvucontrol"]);
} }
} }
} }

View File

@@ -1,6 +1,6 @@
import QtQuick import QtQuick
import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import qs.Commons import qs.Commons
@@ -28,24 +28,24 @@ NIconButton {
onClicked: { onClicked: {
// Check if wlsunset is available before enabling night light // Check if wlsunset is available before enabling night light
if (!ProgramCheckerService.wlsunsetAvailable) { if (!ProgramCheckerService.wlsunsetAvailable) {
ToastService.showWarning(I18n.tr("settings.display.night-light.section.label"), I18n.tr("toast.night-light.not-installed")) ToastService.showWarning(I18n.tr("settings.display.night-light.section.label"), I18n.tr("toast.night-light.not-installed"));
return return;
} }
if (!Settings.data.nightLight.enabled) { if (!Settings.data.nightLight.enabled) {
Settings.data.nightLight.enabled = true Settings.data.nightLight.enabled = true;
Settings.data.nightLight.forced = false Settings.data.nightLight.forced = false;
} else if (Settings.data.nightLight.enabled && !Settings.data.nightLight.forced) { } else if (Settings.data.nightLight.enabled && !Settings.data.nightLight.forced) {
Settings.data.nightLight.forced = true Settings.data.nightLight.forced = true;
} else { } else {
Settings.data.nightLight.enabled = false Settings.data.nightLight.enabled = false;
Settings.data.nightLight.forced = false Settings.data.nightLight.forced = false;
} }
} }
onRightClicked: { onRightClicked: {
var settingsPanel = PanelService.getPanel("settingsPanel", screen) var settingsPanel = PanelService.getPanel("settingsPanel", screen);
settingsPanel.requestedTab = SettingsPanel.Tab.Display settingsPanel.requestedTab = SettingsPanel.Tab.Display;
settingsPanel.open() settingsPanel.open();
} }
} }

View File

@@ -1,6 +1,6 @@
import QtQuick import QtQuick
import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import qs.Commons import qs.Commons
@@ -26,6 +26,6 @@ NIconButton {
tooltipText: PowerProfileService.noctaliaPerformanceMode ? I18n.tr("tooltips.noctalia-performance-enabled") : I18n.tr("tooltips.noctalia-performance-disabled") tooltipText: PowerProfileService.noctaliaPerformanceMode ? I18n.tr("tooltips.noctalia-performance-enabled") : I18n.tr("tooltips.noctalia-performance-disabled")
tooltipDirection: BarService.getTooltipDirection() tooltipDirection: BarService.getTooltipDirection()
onClicked: { onClicked: {
PowerProfileService.toggleNoctaliaPerformance() PowerProfileService.toggleNoctaliaPerformance();
} }
} }

View File

@@ -1,11 +1,11 @@
import QtQuick import QtQuick
import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import qs.Commons import qs.Commons
import qs.Services.UI
import qs.Services.System import qs.Services.System
import qs.Services.UI
import qs.Widgets import qs.Widgets
NIconButton { NIconButton {
@@ -22,27 +22,27 @@ NIconButton {
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: { property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) { if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section] var widgets = Settings.data.bar.widgets[section];
if (widgets && sectionWidgetIndex < widgets.length) { 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 showUnreadBadge: (widgetSettings.showUnreadBadge !== undefined) ? widgetSettings.showUnreadBadge : widgetMetadata.showUnreadBadge
readonly property bool hideWhenZero: (widgetSettings.hideWhenZero !== undefined) ? widgetSettings.hideWhenZero : widgetMetadata.hideWhenZero readonly property bool hideWhenZero: (widgetSettings.hideWhenZero !== undefined) ? widgetSettings.hideWhenZero : widgetMetadata.hideWhenZero
function computeUnreadCount() { function computeUnreadCount() {
var since = NotificationService.lastSeenTs var since = NotificationService.lastSeenTs;
var count = 0 var count = 0;
var model = NotificationService.historyList var model = NotificationService.historyList;
for (var i = 0; i < model.count; i++) { for (var i = 0; i < model.count; i++) {
var item = model.get(i) var item = model.get(i);
var ts = item.timestamp instanceof Date ? item.timestamp.getTime() : item.timestamp var ts = item.timestamp instanceof Date ? item.timestamp.getTime() : item.timestamp;
if (ts > since) if (ts > since)
count++ count++;
} }
return count return count;
} }
baseSize: Style.capsuleHeight baseSize: Style.capsuleHeight
@@ -57,8 +57,8 @@ NIconButton {
colorBorderHover: Color.transparent colorBorderHover: Color.transparent
onClicked: { onClicked: {
var panel = PanelService.getPanel("notificationHistoryPanel", screen) var panel = PanelService.getPanel("notificationHistoryPanel", screen);
panel?.toggle(this) panel?.toggle(this);
} }
onRightClicked: NotificationService.doNotDisturb = !NotificationService.doNotDisturb onRightClicked: NotificationService.doNotDisturb = !NotificationService.doNotDisturb

View File

@@ -1,8 +1,8 @@
import Quickshell import Quickshell
import qs.Commons import qs.Commons
import qs.Services.UI
import qs.Services.Media import qs.Services.Media
import qs.Services.System import qs.Services.System
import qs.Services.UI
import qs.Widgets import qs.Widgets
// Screen Recording Indicator // Screen Recording Indicator
@@ -24,10 +24,10 @@ NIconButton {
function handleClick() { function handleClick() {
if (!ScreenRecorderService.isAvailable) { if (!ScreenRecorderService.isAvailable) {
ToastService.showError(I18n.tr("toast.recording.not-installed"), I18n.tr("toast.recording.not-installed-desc")) ToastService.showError(I18n.tr("toast.recording.not-installed"), I18n.tr("toast.recording.not-installed-desc"));
return return;
} }
ScreenRecorderService.toggleRecording() ScreenRecorderService.toggleRecording();
} }
onClicked: handleClick() onClicked: handleClick()

View File

@@ -19,12 +19,12 @@ NIconButton {
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: { property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) { if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section] var widgets = Settings.data.bar.widgets[section];
if (widgets && sectionWidgetIndex < widgets.length) { if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex] return widgets[sectionWidgetIndex];
} }
} }
return {} return {};
} }
readonly property string colorName: widgetSettings.colorName !== undefined ? widgetSettings.colorName : widgetMetadata.colorName readonly property string colorName: widgetSettings.colorName !== undefined ? widgetSettings.colorName : widgetMetadata.colorName
@@ -32,16 +32,16 @@ NIconButton {
readonly property color iconColor: { readonly property color iconColor: {
switch (colorName) { switch (colorName) {
case "primary": case "primary":
return Color.mPrimary return Color.mPrimary;
case "secondary": case "secondary":
return Color.mSecondary return Color.mSecondary;
case "tertiary": case "tertiary":
return Color.mTertiary return Color.mTertiary;
case "error": case "error":
return Color.mError return Color.mError;
case "onSurface": case "onSurface":
default: default:
return Color.mOnSurface return Color.mOnSurface;
} }
} }

View File

@@ -19,12 +19,12 @@ Item {
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: { property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) { if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section] var widgets = Settings.data.bar.widgets[section];
if (widgets && sectionWidgetIndex < widgets.length) { if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex] return widgets[sectionWidgetIndex];
} }
} }
return {} return {};
} }
// Use settings or defaults from BarWidgetRegistry // Use settings or defaults from BarWidgetRegistry

View File

@@ -3,8 +3,8 @@ import QtQuick.Layouts
import Quickshell import Quickshell
import qs.Commons import qs.Commons
import qs.Modules.Panels.Settings import qs.Modules.Panels.Settings
import qs.Services.UI
import qs.Services.System import qs.Services.System
import qs.Services.UI
import qs.Widgets import qs.Widgets
Rectangle { Rectangle {
@@ -21,12 +21,12 @@ Rectangle {
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: { property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) { if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section] var widgets = Settings.data.bar.widgets[section];
if (widgets && sectionWidgetIndex < widgets.length) { if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex] return widgets[sectionWidgetIndex];
} }
} }
return {} return {};
} }
readonly property string barPosition: Settings.data.bar.position readonly property string barPosition: Settings.data.bar.position
@@ -43,8 +43,8 @@ Rectangle {
readonly property real iconSize: textSize * 1.4 readonly property real iconSize: textSize * 1.4
readonly property real textSize: { readonly property real textSize: {
var base = isVertical ? width * 0.82 : height var base = isVertical ? width * 0.82 : height;
return Math.max(1, (density === "compact") ? base * 0.43 : base * 0.33) return Math.max(1, (density === "compact") ? base * 0.43 : base * 0.33);
} }
// Match Workspace widget pill sizing: base ratio depends on bar density // Match Workspace widget pill sizing: base ratio depends on bar density
@@ -178,11 +178,11 @@ Rectangle {
anchors.centerIn: parent anchors.centerIn: parent
onLoaded: { onLoaded: {
item.warning = Qt.binding(() => cpuWarning) item.warning = Qt.binding(() => cpuWarning);
item.critical = Qt.binding(() => cpuCritical) item.critical = Qt.binding(() => cpuCritical);
item.indicatorWidth = Qt.binding(() => cpuUsageContainer.width) item.indicatorWidth = Qt.binding(() => cpuUsageContainer.width);
item.warningColor = Qt.binding(() => root.warningColor) item.warningColor = Qt.binding(() => root.warningColor);
item.criticalColor = Qt.binding(() => root.criticalColor) item.criticalColor = Qt.binding(() => root.criticalColor);
} }
} }
@@ -214,11 +214,11 @@ Rectangle {
NText { NText {
text: { text: {
let usage = Math.round(SystemStatService.cpuUsage) let usage = Math.round(SystemStatService.cpuUsage);
if (usage < 100) { if (usage < 100) {
return `${usage}%` return `${usage}%`;
} else { } else {
return usage return usage;
} }
} }
family: Settings.data.ui.fontFixed family: Settings.data.ui.fontFixed
@@ -252,11 +252,11 @@ Rectangle {
anchors.centerIn: parent anchors.centerIn: parent
onLoaded: { onLoaded: {
item.warning = Qt.binding(() => tempWarning) item.warning = Qt.binding(() => tempWarning);
item.critical = Qt.binding(() => tempCritical) item.critical = Qt.binding(() => tempCritical);
item.indicatorWidth = Qt.binding(() => cpuTempContainer.width) item.indicatorWidth = Qt.binding(() => cpuTempContainer.width);
item.warningColor = Qt.binding(() => root.warningColor) item.warningColor = Qt.binding(() => root.warningColor);
item.criticalColor = Qt.binding(() => root.criticalColor) item.criticalColor = Qt.binding(() => root.criticalColor);
} }
} }
@@ -319,11 +319,11 @@ Rectangle {
anchors.centerIn: parent anchors.centerIn: parent
onLoaded: { onLoaded: {
item.warning = Qt.binding(() => memWarning) item.warning = Qt.binding(() => memWarning);
item.critical = Qt.binding(() => memCritical) item.critical = Qt.binding(() => memCritical);
item.indicatorWidth = Qt.binding(() => memoryContainer.width) item.indicatorWidth = Qt.binding(() => memoryContainer.width);
item.warningColor = Qt.binding(() => root.warningColor) item.warningColor = Qt.binding(() => root.warningColor);
item.criticalColor = Qt.binding(() => root.criticalColor) item.criticalColor = Qt.binding(() => root.criticalColor);
} }
} }
@@ -472,11 +472,11 @@ Rectangle {
anchors.centerIn: parent anchors.centerIn: parent
onLoaded: { onLoaded: {
item.warning = Qt.binding(() => diskWarning) item.warning = Qt.binding(() => diskWarning);
item.critical = Qt.binding(() => diskCritical) item.critical = Qt.binding(() => diskCritical);
item.indicatorWidth = Qt.binding(() => diskContainer.width) item.indicatorWidth = Qt.binding(() => diskContainer.width);
item.warningColor = Qt.binding(() => root.warningColor) item.warningColor = Qt.binding(() => root.warningColor);
item.criticalColor = Qt.binding(() => root.criticalColor) item.criticalColor = Qt.binding(() => root.criticalColor);
} }
} }

View File

@@ -2,8 +2,8 @@ import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Widgets
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Widgets
import qs.Commons import qs.Commons
import qs.Services.Compositor import qs.Services.Compositor
import qs.Services.UI import qs.Services.UI
@@ -27,12 +27,12 @@ Rectangle {
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: { property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) { if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section] var widgets = Settings.data.bar.widgets[section];
if (widgets && sectionWidgetIndex < widgets.length) { if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex] return widgets[sectionWidgetIndex];
} }
} }
return {} return {};
} }
property bool hasWindow: false property bool hasWindow: false
@@ -42,35 +42,35 @@ Rectangle {
function updateHasWindow() { function updateHasWindow() {
try { try {
var total = CompositorService.windows.count || 0 var total = CompositorService.windows.count || 0;
var activeIds = CompositorService.getActiveWorkspaces().map(function (ws) { var activeIds = CompositorService.getActiveWorkspaces().map(function (ws) {
return ws.id return ws.id;
}) });
var found = false var found = false;
for (var i = 0; i < total; i++) { for (var i = 0; i < total; i++) {
var w = CompositorService.windows.get(i) var w = CompositorService.windows.get(i);
if (!w) if (!w)
continue continue;
var passOutput = (!onlySameOutput) || (w.output == screen.name) var passOutput = (!onlySameOutput) || (w.output == screen.name);
var passWorkspace = (!onlyActiveWorkspaces) || (activeIds.includes(w.workspaceId)) var passWorkspace = (!onlyActiveWorkspaces) || (activeIds.includes(w.workspaceId));
if (passOutput && passWorkspace) { if (passOutput && passWorkspace) {
found = true found = true;
break break;
} }
} }
hasWindow = found hasWindow = found;
} catch (e) { } catch (e) {
hasWindow = false hasWindow = false;
} }
} }
Connections { Connections {
target: CompositorService target: CompositorService
function onWindowListChanged() { function onWindowListChanged() {
updateHasWindow() updateHasWindow();
} }
function onWorkspaceChanged() { function onWorkspaceChanged() {
updateHasWindow() updateHasWindow();
} }
} }
@@ -117,7 +117,7 @@ Rectangle {
property ShellScreen screen: root.screen property ShellScreen screen: root.screen
visible: (!onlySameOutput || modelData.output == screen.name) && (!onlyActiveWorkspaces || CompositorService.getActiveWorkspaces().map(function (ws) { visible: (!onlySameOutput || modelData.output == screen.name) && (!onlyActiveWorkspaces || CompositorService.getActiveWorkspaces().map(function (ws) {
return ws.id return ws.id;
}).includes(modelData.workspaceId)) }).includes(modelData.workspaceId))
Layout.preferredWidth: root.itemSize Layout.preferredWidth: root.itemSize
@@ -125,7 +125,6 @@ Rectangle {
Layout.alignment: Qt.AlignCenter Layout.alignment: Qt.AlignCenter
IconImage { IconImage {
id: appIcon id: appIcon
width: parent.width width: parent.width
height: parent.height height: parent.height
@@ -144,10 +143,10 @@ Rectangle {
} }
Rectangle { Rectangle {
id: iconBackground
anchors.bottomMargin: -2 anchors.bottomMargin: -2
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
id: iconBackground
width: 4 width: 4
height: 4 height: 4
color: modelData.isFocused ? Color.mPrimary : Color.transparent color: modelData.isFocused ? Color.mPrimary : Color.transparent
@@ -163,19 +162,18 @@ Rectangle {
onPressed: function (mouse) { onPressed: function (mouse) {
if (!taskbarItem.modelData) if (!taskbarItem.modelData)
return return;
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
try { try {
CompositorService.focusWindow(taskbarItem.modelData) CompositorService.focusWindow(taskbarItem.modelData);
} catch (error) { } catch (error) {
Logger.e("Taskbar", "Failed to activate toplevel: " + error) Logger.e("Taskbar", "Failed to activate toplevel: " + error);
} }
} else if (mouse.button === Qt.RightButton) { } else if (mouse.button === Qt.RightButton) {
try { try {
CompositorService.closeWindow(taskbarItem.modelData) CompositorService.closeWindow(taskbarItem.modelData);
} catch (error) { } catch (error) {
Logger.e("Taskbar", "Failed to close toplevel: " + error) Logger.e("Taskbar", "Failed to close toplevel: " + error);
} }
} }
} }

View File

@@ -26,12 +26,12 @@ Item {
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: { property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) { if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section] var widgets = Settings.data.bar.widgets[section];
if (widgets && sectionWidgetIndex < widgets.length) { 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 string labelMode: (widgetSettings.labelMode !== undefined) ? widgetSettings.labelMode : widgetMetadata.labelMode
readonly property bool hideUnoccupied: (widgetSettings.hideUnoccupied !== undefined) ? widgetSettings.hideUnoccupied : widgetMetadata.hideUnoccupied readonly property bool hideUnoccupied: (widgetSettings.hideUnoccupied !== undefined) ? widgetSettings.hideUnoccupied : widgetMetadata.hideUnoccupied
@@ -49,40 +49,39 @@ Item {
property bool wheelCooldown: false property bool wheelCooldown: false
function refreshWorkspaces() { function refreshWorkspaces() {
localWorkspaces.clear() localWorkspaces.clear();
if (!screen) if (!screen)
return return;
const screenName = screen.name.toLowerCase();
const screenName = screen.name.toLowerCase()
for (var i = 0; i < CompositorService.workspaces.count; i++) { 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) if (ws.output.toLowerCase() !== screenName)
continue continue;
if (hideUnoccupied && !ws.isOccupied && !ws.isFocused) if (hideUnoccupied && !ws.isOccupied && !ws.isFocused)
continue continue;
// Copy all properties from ws and add windows // Copy all properties from ws and add windows
var workspaceData = Object.assign({}, ws) var workspaceData = Object.assign({}, ws);
workspaceData.windows = CompositorService.getWindowsForWorkspace(ws.id) workspaceData.windows = CompositorService.getWindowsForWorkspace(ws.id);
localWorkspaces.append(workspaceData) localWorkspaces.append(workspaceData);
} }
updateWorkspaceFocus() updateWorkspaceFocus();
} }
function triggerUnifiedWave() { function triggerUnifiedWave() {
effectColor = Color.mPrimary effectColor = Color.mPrimary;
masterAnimation.restart() masterAnimation.restart();
} }
function updateWorkspaceFocus() { function updateWorkspaceFocus() {
for (var i = 0; i < localWorkspaces.count; i++) { for (var i = 0; i < localWorkspaces.count; i++) {
const ws = localWorkspaces.get(i) const ws = localWorkspaces.get(i);
if (ws.isFocused === true) { if (ws.isFocused === true) {
root.triggerUnifiedWave() root.triggerUnifiedWave();
break break;
} }
} }
} }
@@ -90,27 +89,27 @@ Item {
function getFocusedLocalIndex() { function getFocusedLocalIndex() {
for (var i = 0; i < localWorkspaces.count; i++) { for (var i = 0; i < localWorkspaces.count; i++) {
if (localWorkspaces.get(i).isFocused === true) if (localWorkspaces.get(i).isFocused === true)
return i return i;
} }
return -1 return -1;
} }
function switchByOffset(offset) { function switchByOffset(offset) {
if (localWorkspaces.count === 0) if (localWorkspaces.count === 0)
return return;
var current = getFocusedLocalIndex() var current = getFocusedLocalIndex();
if (current < 0) if (current < 0)
current = 0 current = 0;
var next = (current + offset) % localWorkspaces.count var next = (current + offset) % localWorkspaces.count;
if (next < 0) if (next < 0)
next = localWorkspaces.count - 1 next = localWorkspaces.count - 1;
const ws = localWorkspaces.get(next) const ws = localWorkspaces.get(next);
if (ws && ws.idx !== undefined) if (ws && ws.idx !== undefined)
CompositorService.switchToWorkspace(ws) CompositorService.switchToWorkspace(ws);
} }
Component.onCompleted: { Component.onCompleted: {
refreshWorkspaces() refreshWorkspaces();
} }
onScreenChanged: refreshWorkspaces() onScreenChanged: refreshWorkspaces()
@@ -123,11 +122,11 @@ Item {
target: CompositorService target: CompositorService
function onWorkspacesChanged() { function onWorkspacesChanged() {
refreshWorkspaces() refreshWorkspaces();
} }
function onWindowListChanged() { function onWindowListChanged() {
refreshWorkspaces() refreshWorkspaces();
} }
} }
@@ -164,8 +163,8 @@ Item {
interval: 150 interval: 150
repeat: false repeat: false
onTriggered: { onTriggered: {
root.wheelCooldown = false root.wheelCooldown = false;
root.wheelAccumulatedDelta = 0 root.wheelAccumulatedDelta = 0;
} }
} }
@@ -176,24 +175,24 @@ Item {
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
onWheel: function (event) { onWheel: function (event) {
if (root.wheelCooldown) if (root.wheelCooldown)
return return;
// Prefer vertical delta, fall back to horizontal if needed // Prefer vertical delta, fall back to horizontal if needed
var dy = event.angleDelta.y var dy = event.angleDelta.y;
var dx = event.angleDelta.x var dx = event.angleDelta.x;
var useDy = Math.abs(dy) >= Math.abs(dx) var useDy = Math.abs(dy) >= Math.abs(dx);
var delta = useDy ? dy : dx var delta = useDy ? dy : dx;
// One notch is typically 120 // One notch is typically 120
root.wheelAccumulatedDelta += delta root.wheelAccumulatedDelta += delta;
var step = 120 var step = 120;
if (Math.abs(root.wheelAccumulatedDelta) >= step) { 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 vertical layout, natural mapping: wheel up -> previous, down -> next (already handled by sign)
// For horizontal layout, same mapping using vertical wheel // For horizontal layout, same mapping using vertical wheel
root.switchByOffset(direction) root.switchByOffset(direction);
root.wheelCooldown = true root.wheelCooldown = true;
wheelDebounce.restart() wheelDebounce.restart();
root.wheelAccumulatedDelta = 0 root.wheelAccumulatedDelta = 0;
event.accepted = true event.accepted = true;
} }
} }
} }
@@ -221,7 +220,7 @@ Item {
enabled: !hasWindows enabled: !hasWindows
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: { onClicked: {
CompositorService.switchToWorkspace(workspaceModel) CompositorService.switchToWorkspace(workspaceModel);
} }
} }
@@ -297,22 +296,22 @@ Item {
onPressed: function (mouse) { onPressed: function (mouse) {
if (!model) { if (!model) {
return return;
} }
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
CompositorService.focusWindow(model) CompositorService.focusWindow(model);
} else if (mouse.button === Qt.RightButton) { } else if (mouse.button === Qt.RightButton) {
CompositorService.closeWindow(model) CompositorService.closeWindow(model);
} }
} }
onEntered: { onEntered: {
taskbarItem.itemHovered = true taskbarItem.itemHovered = true;
TooltipService.show(Screen, taskbarItem, model.title || model.appId || "Unknown app.", BarService.getTooltipDirection()) TooltipService.show(Screen, taskbarItem, model.title || model.appId || "Unknown app.", BarService.getTooltipDirection());
} }
onExited: { onExited: {
taskbarItem.itemHovered = false taskbarItem.itemHovered = false;
TooltipService.hide() TooltipService.hide();
} }
} }
} }
@@ -342,13 +341,13 @@ Item {
color: { color: {
if (workspaceModel.isFocused) if (workspaceModel.isFocused)
return Color.mPrimary return Color.mPrimary;
if (workspaceModel.isUrgent) if (workspaceModel.isUrgent)
return Color.mError return Color.mError;
if (hasWindows) 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 scale: workspaceModel.isActive ? 1.0 : 0.9
@@ -390,9 +389,9 @@ Item {
text: { text: {
if (root.labelMode === "name" && workspaceModel.name && workspaceModel.name.length > 0) { if (root.labelMode === "name" && workspaceModel.name && workspaceModel.name.length > 0) {
return workspaceModel.name.substring(0, root.characterCount) return workspaceModel.name.substring(0, root.characterCount);
} else { } else {
return workspaceModel.idx.toString() return workspaceModel.idx.toString();
} }
} }
@@ -406,24 +405,24 @@ Item {
color: { color: {
if (workspaceModel.isFocused) if (workspaceModel.isFocused)
return Color.mOnPrimary return Color.mOnPrimary;
if (workspaceModel.isUrgent) if (workspaceModel.isUrgent)
return Color.mOnError return Color.mOnError;
if (hasWindows) if (hasWindows)
return Color.mOnSecondary return Color.mOnSecondary;
return Color.mOnSurface return Color.mOnSurface;
} }
opacity: { opacity: {
if (workspaceModel.isFocused) if (workspaceModel.isFocused)
return 1.0 return 1.0;
if (workspaceModel.isUrgent) if (workspaceModel.isUrgent)
return 0.9 return 0.9;
if (hasWindows) if (hasWindows)
return 0.8 return 0.8;
return 0.6 return 0.6;
} }
Behavior on opacity { Behavior on opacity {

View File

@@ -1,7 +1,7 @@
import QtQuick import QtQuick
import QtQuick.Controls
import QtQuick.Effects import QtQuick.Effects
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls
import Quickshell import Quickshell
import Quickshell.Services.SystemTray import Quickshell.Services.SystemTray
import Quickshell.Widgets import Quickshell.Widgets
@@ -21,8 +21,8 @@ Rectangle {
// Get shared tray menu window from PanelService (reactive to trigger changes) // Get shared tray menu window from PanelService (reactive to trigger changes)
readonly property var trayMenuWindow: { readonly property var trayMenuWindow: {
// Reference trigger to force re-evaluation // Reference trigger to force re-evaluation
var _ = trayMenuUpdateTrigger var _ = trayMenuUpdateTrigger;
return PanelService.getTrayMenuWindow(screen) return PanelService.getTrayMenuWindow(screen);
} }
readonly property var trayMenu: trayMenuWindow ? trayMenuWindow.trayMenuLoader : null readonly property var trayMenu: trayMenuWindow ? trayMenuWindow.trayMenuLoader : null
@@ -31,7 +31,7 @@ Rectangle {
target: PanelService target: PanelService
function onTrayMenuWindowRegistered(registeredScreen) { function onTrayMenuWindowRegistered(registeredScreen) {
if (registeredScreen === screen) { if (registeredScreen === screen) {
root.trayMenuUpdateTrigger++ root.trayMenuUpdateTrigger++;
} }
} }
} }
@@ -45,12 +45,12 @@ Rectangle {
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: { property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) { if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section] var widgets = Settings.data.bar.widgets[section];
if (widgets && sectionWidgetIndex < widgets.length) { if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex] return widgets[sectionWidgetIndex];
} }
} }
return {} return {};
} }
readonly property string barPosition: Settings.data.bar.position readonly property string barPosition: Settings.data.bar.position
@@ -76,149 +76,149 @@ Rectangle {
} }
function _performFilteredItemsUpdate() { function _performFilteredItemsUpdate() {
let newItems = [] let newItems = [];
if (SystemTray.items && SystemTray.items.values) { if (SystemTray.items && SystemTray.items.values) {
const trayItems = SystemTray.items.values const trayItems = SystemTray.items.values;
for (var i = 0; i < trayItems.length; i++) { for (var i = 0; i < trayItems.length; i++) {
const item = trayItems[i] const item = trayItems[i];
if (!item) { if (!item) {
continue continue;
} }
const title = item.tooltipTitle || item.name || item.id || "" const title = item.tooltipTitle || item.name || item.id || "";
// Check if blacklisted // Check if blacklisted
let isBlacklisted = false let isBlacklisted = false;
if (root.blacklist && root.blacklist.length > 0) { if (root.blacklist && root.blacklist.length > 0) {
for (var j = 0; j < root.blacklist.length; j++) { for (var j = 0; j < root.blacklist.length; j++) {
const rule = root.blacklist[j] const rule = root.blacklist[j];
if (wildCardMatch(title, rule)) { if (wildCardMatch(title, rule)) {
isBlacklisted = true isBlacklisted = true;
break break;
} }
} }
} }
if (!isBlacklisted) { if (!isBlacklisted) {
newItems.push(item) newItems.push(item);
} }
} }
} }
// If drawer is disabled, show all items inline // If drawer is disabled, show all items inline
if (!root.drawerEnabled) { if (!root.drawerEnabled) {
filteredItems = newItems filteredItems = newItems;
dropdownItems = [] dropdownItems = [];
} else { } else {
// Build inline (pinned) and drawer (unpinned) lists // Build inline (pinned) and drawer (unpinned) lists
// If pinned list is empty, all items go to drawer (none inline) // 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 list has items, pinned items are inline, rest go to drawer
if (pinned && pinned.length > 0) { if (pinned && pinned.length > 0) {
let pinnedItems = [] let pinnedItems = [];
for (var k = 0; k < newItems.length; k++) { for (var k = 0; k < newItems.length; k++) {
const item2 = newItems[k] const item2 = newItems[k];
const title2 = item2.tooltipTitle || item2.name || item2.id || "" const title2 = item2.tooltipTitle || item2.name || item2.id || "";
for (var m = 0; m < pinned.length; m++) { for (var m = 0; m < pinned.length; m++) {
const rule2 = pinned[m] const rule2 = pinned[m];
if (wildCardMatch(title2, rule2)) { if (wildCardMatch(title2, rule2)) {
pinnedItems.push(item2) pinnedItems.push(item2);
break break;
} }
} }
} }
filteredItems = pinnedItems filteredItems = pinnedItems;
// Unpinned items go to drawer // Unpinned items go to drawer
let unpinnedItems = [] let unpinnedItems = [];
for (var v = 0; v < newItems.length; v++) { for (var v = 0; v < newItems.length; v++) {
const cand = newItems[v] const cand = newItems[v];
let isPinned = false let isPinned = false;
for (var f = 0; f < filteredItems.length; f++) { for (var f = 0; f < filteredItems.length; f++) {
if (filteredItems[f] === cand) { if (filteredItems[f] === cand) {
isPinned = true isPinned = true;
break break;
} }
} }
if (!isPinned) if (!isPinned)
unpinnedItems.push(cand) unpinnedItems.push(cand);
} }
dropdownItems = unpinnedItems dropdownItems = unpinnedItems;
} else { } else {
// No pinned items: all items go to drawer (none inline) // No pinned items: all items go to drawer (none inline)
filteredItems = [] filteredItems = [];
dropdownItems = newItems dropdownItems = newItems;
} }
} }
} }
function updateFilteredItems() { function updateFilteredItems() {
updateDebounceTimer.restart() updateDebounceTimer.restart();
} }
function wildCardMatch(str, rule) { function wildCardMatch(str, rule) {
if (!str || !rule) { if (!str || !rule) {
return false return false;
} }
//Logger.d("Tray", "wildCardMatch - Input str:", str, "rule:", rule) //Logger.d("Tray", "wildCardMatch - Input str:", str, "rule:", rule)
// Escape all special regex characters in the rule // Escape all special regex characters in the rule
let escapedRule = rule.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') let escapedRule = rule.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
// Convert '*' to '.*' for wildcard matching // Convert '*' to '.*' for wildcard matching
let pattern = escapedRule.replace(/\\\*/g, '.*') let pattern = escapedRule.replace(/\\\*/g, '.*');
// Add ^ and $ to match the entire string // Add ^ and $ to match the entire string
pattern = '^' + pattern + '$' pattern = '^' + pattern + '$';
//Logger.d("Tray", "wildCardMatch - Generated pattern:", pattern) //Logger.d("Tray", "wildCardMatch - Generated pattern:", pattern)
try { try {
const regex = new RegExp(pattern, 'i') const regex = new RegExp(pattern, 'i');
// 'i' for case-insensitive // 'i' for case-insensitive
//Logger.d("Tray", "wildCardMatch - Regex test result:", regex.test(str)) //Logger.d("Tray", "wildCardMatch - Regex test result:", regex.test(str))
return regex.test(str) return regex.test(str);
} catch (e) { } catch (e) {
Logger.w("Tray", "Invalid regex pattern for wildcard match:", rule, e.message) Logger.w("Tray", "Invalid regex pattern for wildcard match:", rule, e.message);
return false // If regex is invalid, it won't match return false; // If regex is invalid, it won't match
} }
} }
function toggleDrawer(button) { function toggleDrawer(button) {
TooltipService.hideImmediately() TooltipService.hideImmediately();
// Close the tray menu if it's open // Close the tray menu if it's open
if (trayMenuWindow && trayMenuWindow.visible) { if (trayMenuWindow && trayMenuWindow.visible) {
trayMenuWindow.close() trayMenuWindow.close();
} }
const panel = PanelService.getPanel("trayDrawerPanel", root.screen) const panel = PanelService.getPanel("trayDrawerPanel", root.screen);
if (panel) { if (panel) {
panel.widgetSection = root.section panel.widgetSection = root.section;
panel.widgetIndex = root.sectionWidgetIndex panel.widgetIndex = root.sectionWidgetIndex;
panel.toggle(this) panel.toggle(this);
} }
} }
function onLoaded() { function onLoaded() {
// When the widget is fully initialized with its props set the screen for the trayMenu // When the widget is fully initialized with its props set the screen for the trayMenu
if (trayMenu && trayMenu.item) { if (trayMenu && trayMenu.item) {
trayMenu.item.screen = screen trayMenu.item.screen = screen;
} }
} }
Connections { Connections {
target: SystemTray.items target: SystemTray.items
function onValuesChanged() { function onValuesChanged() {
root.updateFilteredItems() root.updateFilteredItems();
} }
} }
Connections { Connections {
target: Settings target: Settings
function onSettingsSaved() { function onSettingsSaved() {
root.updateFilteredItems() root.updateFilteredItems();
} }
} }
Component.onCompleted: { Component.onCompleted: {
root.updateFilteredItems() // Initial update root.updateFilteredItems(); // Initial update
} }
visible: filteredItems.length > 0 || dropdownItems.length > 0 visible: filteredItems.length > 0 || dropdownItems.length > 0
@@ -248,14 +248,14 @@ Rectangle {
icon: { icon: {
switch (barPosition) { switch (barPosition) {
case "bottom": case "bottom":
return "caret-up" return "caret-up";
case "left": case "left":
return "caret-right" return "caret-right";
case "right": case "right":
return "caret-left" return "caret-left";
case "top": case "top":
default: default:
return "caret-down" return "caret-down";
} }
} }
onClicked: toggleDrawer(this) onClicked: toggleDrawer(this)
@@ -283,20 +283,20 @@ Rectangle {
property bool menuJustOpened: false property bool menuJustOpened: false
source: { source: {
let icon = modelData?.icon || "" let icon = modelData?.icon || "";
if (!icon) { if (!icon) {
return "" return "";
} }
// Process icon path // Process icon path
if (icon.includes("?path=")) { if (icon.includes("?path=")) {
const chunks = icon.split("?path=") const chunks = icon.split("?path=");
const name = chunks[0] const name = chunks[0];
const path = chunks[1] const path = chunks[1];
const fileName = name.substring(name.lastIndexOf("/") + 1) const fileName = name.substring(name.lastIndexOf("/") + 1);
return `file://${path}/${fileName}` return `file://${path}/${fileName}`;
} }
return icon return icon;
} }
opacity: status === Image.Ready ? 1 : 0 opacity: status === Image.Ready ? 1 : 0
@@ -315,71 +315,71 @@ Rectangle {
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onClicked: mouse => { onClicked: mouse => {
if (!modelData) { if (!modelData) {
return return;
} }
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
// Close any open menu first // Close any open menu first
if (trayMenuWindow) { if (trayMenuWindow) {
trayMenuWindow.close() trayMenuWindow.close();
} }
if (!modelData.onlyMenu) { if (!modelData.onlyMenu) {
modelData.activate() modelData.activate();
} }
} else if (mouse.button === Qt.MiddleButton) { } else if (mouse.button === Qt.MiddleButton) {
// Close the menu if it was visible // Close the menu if it was visible
if (trayMenuWindow && trayMenuWindow.visible) { if (trayMenuWindow && trayMenuWindow.visible) {
trayMenuWindow.close() trayMenuWindow.close();
return return;
} }
modelData.secondaryActivate && modelData.secondaryActivate() modelData.secondaryActivate && modelData.secondaryActivate();
} else if (mouse.button === Qt.RightButton) { } else if (mouse.button === Qt.RightButton) {
TooltipService.hideImmediately() TooltipService.hideImmediately();
// Close the menu if it was visible // Close the menu if it was visible
if (trayMenuWindow && trayMenuWindow.visible) { if (trayMenuWindow && trayMenuWindow.visible) {
trayMenuWindow.close() trayMenuWindow.close();
return return;
} }
// Close any opened panel // Close any opened panel
if ((PanelService.openedPanel !== null) && !PanelService.openedPanel.isClosing) { if ((PanelService.openedPanel !== null) && !PanelService.openedPanel.isClosing) {
PanelService.openedPanel.close() PanelService.openedPanel.close();
} }
if (modelData.hasMenu && modelData.menu && trayMenuWindow && trayMenu && trayMenu.item) { if (modelData.hasMenu && modelData.menu && trayMenuWindow && trayMenu && trayMenu.item) {
trayMenuWindow.open() trayMenuWindow.open();
// Position menu based on bar position // Position menu based on bar position
let menuX, menuY let menuX, menuY;
if (barPosition === "left") { if (barPosition === "left") {
// For left bar: position menu to the right of the bar // For left bar: position menu to the right of the bar
menuX = width + Style.marginM menuX = width + Style.marginM;
menuY = 0 menuY = 0;
} else if (barPosition === "right") { } else if (barPosition === "right") {
// For right bar: position menu to the left of the bar // For right bar: position menu to the left of the bar
menuX = -trayMenu.item.width - Style.marginM menuX = -trayMenu.item.width - Style.marginM;
menuY = 0 menuY = 0;
} else { } else {
// For horizontal bars: center horizontally and position below // For horizontal bars: center horizontally and position below
menuX = (width / 2) - (trayMenu.item.width / 2) menuX = (width / 2) - (trayMenu.item.width / 2);
menuY = (barPosition === "top") ? Style.barHeight : -Style.barHeight menuY = (barPosition === "top") ? Style.barHeight : -Style.barHeight;
} }
trayMenu.item.trayItem = modelData trayMenu.item.trayItem = modelData;
trayMenu.item.widgetSection = root.section trayMenu.item.widgetSection = root.section;
trayMenu.item.widgetIndex = root.sectionWidgetIndex trayMenu.item.widgetIndex = root.sectionWidgetIndex;
trayMenu.item.showAt(parent, menuX, menuY) trayMenu.item.showAt(parent, menuX, menuY);
} else { } 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: { onEntered: {
if (trayMenuWindow) { 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() onExited: TooltipService.hide()
} }
@@ -403,14 +403,14 @@ Rectangle {
icon: { icon: {
switch (barPosition) { switch (barPosition) {
case "bottom": case "bottom":
return "caret-up" return "caret-up";
case "left": case "left":
return "caret-right" return "caret-right";
case "right": case "right":
return "caret-left" return "caret-left";
case "top": case "top":
default: default:
return "caret-down" return "caret-down";
} }
} }
onClicked: toggleDrawer(this) onClicked: toggleDrawer(this)

View File

@@ -5,8 +5,8 @@ import Quickshell.Services.Pipewire
import qs.Commons import qs.Commons
import qs.Modules.Bar.Extras import qs.Modules.Bar.Extras
import qs.Modules.Panels.Settings import qs.Modules.Panels.Settings
import qs.Services.UI
import qs.Services.Media import qs.Services.Media
import qs.Services.UI
import qs.Widgets import qs.Widgets
Item { Item {
@@ -23,12 +23,12 @@ Item {
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: { property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) { if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section] var widgets = Settings.data.bar.widgets[section];
if (widgets && sectionWidgetIndex < widgets.length) { 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" readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
@@ -48,12 +48,12 @@ Item {
// Logger.i("Bar:Volume", "onVolumeChanged") // Logger.i("Bar:Volume", "onVolumeChanged")
if (!firstVolumeReceived) { if (!firstVolumeReceived) {
// Ignore the first volume change // Ignore the first volume change
firstVolumeReceived = true firstVolumeReceived = true;
} else { } else {
// Hide any tooltip while the pill is visible / being updated // Hide any tooltip while the pill is visible / being updated
TooltipService.hide() TooltipService.hide();
pill.show() pill.show();
externalHideTimer.restart() externalHideTimer.restart();
} }
} }
} }
@@ -63,7 +63,7 @@ Item {
running: false running: false
interval: 1500 interval: 1500
onTriggered: { onTriggered: {
pill.hide() pill.hide();
} }
} }
@@ -84,25 +84,25 @@ Item {
onWheel: function (delta) { onWheel: function (delta) {
// Hide tooltip as soon as the user starts scrolling to adjust volume // Hide tooltip as soon as the user starts scrolling to adjust volume
TooltipService.hide() TooltipService.hide();
wheelAccumulator += delta wheelAccumulator += delta;
if (wheelAccumulator >= 120) { if (wheelAccumulator >= 120) {
wheelAccumulator = 0 wheelAccumulator = 0;
AudioService.increaseVolume() AudioService.increaseVolume();
} else if (wheelAccumulator <= -120) { } else if (wheelAccumulator <= -120) {
wheelAccumulator = 0 wheelAccumulator = 0;
AudioService.decreaseVolume() AudioService.decreaseVolume();
} }
} }
onClicked: { onClicked: {
PanelService.getPanel("audioPanel", screen)?.toggle(this) PanelService.getPanel("audioPanel", screen)?.toggle(this);
} }
onRightClicked: { onRightClicked: {
AudioService.setOutputMuted(!AudioService.muted) AudioService.setOutputMuted(!AudioService.muted);
} }
onMiddleClicked: { onMiddleClicked: {
Quickshell.execDetached(["sh", "-c", "pwvucontrol || pavucontrol"]) Quickshell.execDetached(["sh", "-c", "pwvucontrol || pavucontrol"]);
} }
} }
} }

View File

@@ -21,11 +21,11 @@ NIconButton {
colorBorder: Color.transparent colorBorder: Color.transparent
colorBorderHover: Color.transparent colorBorderHover: Color.transparent
onClicked: { onClicked: {
var wallpaperPanel = PanelService.getPanel("wallpaperPanel", screen) var wallpaperPanel = PanelService.getPanel("wallpaperPanel", screen);
if (Settings.data.wallpaper.panelPosition === "follow_bar") { if (Settings.data.wallpaper.panelPosition === "follow_bar") {
wallpaperPanel?.toggle(this) wallpaperPanel?.toggle(this);
} else { } else {
wallpaperPanel?.toggle() wallpaperPanel?.toggle();
} }
} }
} }

View File

@@ -1,9 +1,9 @@
import QtQuick import QtQuick
import Quickshell import Quickshell
import qs.Commons import qs.Commons
import qs.Modules.Bar.Extras
import qs.Services.Networking import qs.Services.Networking
import qs.Services.UI import qs.Services.UI
import qs.Modules.Bar.Extras
Item { Item {
id: root id: root
@@ -19,12 +19,12 @@ Item {
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: { property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) { if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section] var widgets = Settings.data.bar.widgets[section];
if (widgets && sectionWidgetIndex < widgets.length) { 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" readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
@@ -41,37 +41,37 @@ Item {
icon: { icon: {
try { try {
if (NetworkService.ethernetConnected) { if (NetworkService.ethernetConnected) {
return NetworkService.internetConnectivity ? "ethernet" : "ethernet-off" return NetworkService.internetConnectivity ? "ethernet" : "ethernet-off";
} }
let connected = false let connected = false;
let signalStrength = 0 let signalStrength = 0;
for (const net in NetworkService.networks) { for (const net in NetworkService.networks) {
if (NetworkService.networks[net].connected) { if (NetworkService.networks[net].connected) {
connected = true connected = true;
signalStrength = NetworkService.networks[net].signal signalStrength = NetworkService.networks[net].signal;
break break;
} }
} }
return connected ? NetworkService.signalIcon(signalStrength, true) : "wifi-off" return connected ? NetworkService.signalIcon(signalStrength, true) : "wifi-off";
} catch (error) { } catch (error) {
Logger.e("Wi-Fi", "Error getting icon:", error) Logger.e("Wi-Fi", "Error getting icon:", error);
return "wifi-off" return "wifi-off";
} }
} }
text: { text: {
try { try {
if (NetworkService.ethernetConnected) { if (NetworkService.ethernetConnected) {
return "" return "";
} }
for (const net in NetworkService.networks) { for (const net in NetworkService.networks) {
if (NetworkService.networks[net].connected) { if (NetworkService.networks[net].connected) {
return net return net;
} }
} }
return "" return "";
} catch (error) { } catch (error) {
Logger.e("Wi-Fi", "Error getting ssid:", error) Logger.e("Wi-Fi", "Error getting ssid:", error);
return "error" return "error";
} }
} }
autoHide: false autoHide: false
@@ -81,9 +81,9 @@ Item {
onRightClicked: NetworkService.setWifiEnabled(!Settings.data.network.wifiEnabled) onRightClicked: NetworkService.setWifiEnabled(!Settings.data.network.wifiEnabled)
tooltipText: { tooltipText: {
if (pill.text !== "") { if (pill.text !== "") {
return pill.text return pill.text;
} }
return I18n.tr("tooltips.manage-wifi") return I18n.tr("tooltips.manage-wifi");
} }
} }
} }

View File

@@ -1,8 +1,8 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Effects
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Window import QtQuick.Window
import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
import qs.Commons import qs.Commons
@@ -24,23 +24,23 @@ Item {
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: { property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) { if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section] var widgets = Settings.data.bar.widgets[section];
if (widgets && sectionWidgetIndex < widgets.length) { if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex] return widgets[sectionWidgetIndex];
} }
} }
return {} return {};
} }
readonly property string barPosition: Settings.data.bar.position readonly property string barPosition: Settings.data.bar.position
readonly property bool isVertical: barPosition === "left" || barPosition === "right" readonly property bool isVertical: barPosition === "left" || barPosition === "right"
readonly property bool density: Settings.data.bar.density readonly property bool density: Settings.data.bar.density
readonly property real baseDimensionRatio: { readonly property real baseDimensionRatio: {
const b = (density === "compact") ? 0.85 : 0.65 const b = (density === "compact") ? 0.85 : 0.65;
if (widgetSettings.labelMode === "none") { 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 readonly property string labelMode: (widgetSettings.labelMode !== undefined) ? widgetSettings.labelMode : widgetMetadata.labelMode
@@ -68,76 +68,76 @@ Item {
implicitHeight: isVertical ? computeHeight() : Style.barHeight implicitHeight: isVertical ? computeHeight() : Style.barHeight
function getWorkspaceWidth(ws) { function getWorkspaceWidth(ws) {
const d = Style.capsuleHeight * root.baseDimensionRatio const d = Style.capsuleHeight * root.baseDimensionRatio;
const factor = ws.isActive ? 2.2 : 1 const factor = ws.isActive ? 2.2 : 1;
// For name mode, calculate width based on actual text content // For name mode, calculate width based on actual text content
if (labelMode === "name" && ws.name && ws.name.length > 0) { if (labelMode === "name" && ws.name && ws.name.length > 0) {
const displayText = ws.name.substring(0, characterCount) const displayText = ws.name.substring(0, characterCount);
const textWidth = displayText.length * (d * 0.4) // Approximate width per character const textWidth = displayText.length * (d * 0.4); // Approximate width per character
const padding = d * 0.6 // Padding on both sides const padding = d * 0.6; // Padding on both sides
return Math.max(d * factor, textWidth + padding) return Math.max(d * factor, textWidth + padding);
} }
return d * factor return d * factor;
} }
function getWorkspaceHeight(ws) { function getWorkspaceHeight(ws) {
const d = Style.capsuleHeight * root.baseDimensionRatio const d = Style.capsuleHeight * root.baseDimensionRatio;
const factor = ws.isActive ? 2.2 : 1 const factor = ws.isActive ? 2.2 : 1;
return d * factor return d * factor;
} }
function computeWidth() { function computeWidth() {
let total = 0 let total = 0;
for (var i = 0; i < localWorkspaces.count; i++) { for (var i = 0; i < localWorkspaces.count; i++) {
const ws = localWorkspaces.get(i) const ws = localWorkspaces.get(i);
total += getWorkspaceWidth(ws) total += getWorkspaceWidth(ws);
} }
total += Math.max(localWorkspaces.count - 1, 0) * spacingBetweenPills total += Math.max(localWorkspaces.count - 1, 0) * spacingBetweenPills;
total += horizontalPadding * 2 total += horizontalPadding * 2;
return Math.round(total) return Math.round(total);
} }
function computeHeight() { function computeHeight() {
let total = 0 let total = 0;
for (var i = 0; i < localWorkspaces.count; i++) { for (var i = 0; i < localWorkspaces.count; i++) {
const ws = localWorkspaces.get(i) const ws = localWorkspaces.get(i);
total += getWorkspaceHeight(ws) total += getWorkspaceHeight(ws);
} }
total += Math.max(localWorkspaces.count - 1, 0) * spacingBetweenPills total += Math.max(localWorkspaces.count - 1, 0) * spacingBetweenPills;
total += horizontalPadding * 2 total += horizontalPadding * 2;
return Math.round(total) return Math.round(total);
} }
function getFocusedLocalIndex() { function getFocusedLocalIndex() {
for (var i = 0; i < localWorkspaces.count; i++) { for (var i = 0; i < localWorkspaces.count; i++) {
if (localWorkspaces.get(i).isFocused === true) if (localWorkspaces.get(i).isFocused === true)
return i return i;
} }
return -1 return -1;
} }
function switchByOffset(offset) { function switchByOffset(offset) {
if (localWorkspaces.count === 0) if (localWorkspaces.count === 0)
return return;
var current = getFocusedLocalIndex() var current = getFocusedLocalIndex();
if (current < 0) if (current < 0)
current = 0 current = 0;
var next = (current + offset) % localWorkspaces.count var next = (current + offset) % localWorkspaces.count;
if (next < 0) if (next < 0)
next = localWorkspaces.count - 1 next = localWorkspaces.count - 1;
const ws = localWorkspaces.get(next) const ws = localWorkspaces.get(next);
if (ws && ws.idx !== undefined) if (ws && ws.idx !== undefined)
CompositorService.switchToWorkspace(ws) CompositorService.switchToWorkspace(ws);
} }
Component.onCompleted: { Component.onCompleted: {
refreshWorkspaces() refreshWorkspaces();
} }
Component.onDestruction: { Component.onDestruction: {
root.isDestroying = true root.isDestroying = true;
} }
onScreenChanged: refreshWorkspaces() onScreenChanged: refreshWorkspaces()
@@ -146,40 +146,40 @@ Item {
Connections { Connections {
target: CompositorService target: CompositorService
function onWorkspacesChanged() { function onWorkspacesChanged() {
refreshWorkspaces() refreshWorkspaces();
} }
} }
function refreshWorkspaces() { function refreshWorkspaces() {
localWorkspaces.clear() localWorkspaces.clear();
if (screen !== null) { if (screen !== null) {
for (var i = 0; i < CompositorService.workspaces.count; i++) { 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 (ws.output.toLowerCase() === screen.name.toLowerCase()) {
if (hideUnoccupied && !ws.isOccupied && !ws.isFocused) { if (hideUnoccupied && !ws.isOccupied && !ws.isFocused) {
continue continue;
} }
localWorkspaces.append(ws) localWorkspaces.append(ws);
} }
} }
} }
workspaceRepeaterHorizontal.model = localWorkspaces workspaceRepeaterHorizontal.model = localWorkspaces;
workspaceRepeaterVertical.model = localWorkspaces workspaceRepeaterVertical.model = localWorkspaces;
updateWorkspaceFocus() updateWorkspaceFocus();
} }
function triggerUnifiedWave() { function triggerUnifiedWave() {
effectColor = Color.mPrimary effectColor = Color.mPrimary;
masterAnimation.restart() masterAnimation.restart();
} }
function updateWorkspaceFocus() { function updateWorkspaceFocus() {
for (var i = 0; i < localWorkspaces.count; i++) { for (var i = 0; i < localWorkspaces.count; i++) {
const ws = localWorkspaces.get(i) const ws = localWorkspaces.get(i);
if (ws.isFocused === true) { if (ws.isFocused === true) {
root.triggerUnifiedWave() root.triggerUnifiedWave();
root.workspaceChanged(ws.id, Color.mPrimary) root.workspaceChanged(ws.id, Color.mPrimary);
break break;
} }
} }
} }
@@ -228,8 +228,8 @@ Item {
interval: 150 interval: 150
repeat: false repeat: false
onTriggered: { onTriggered: {
root.wheelCooldown = false root.wheelCooldown = false;
root.wheelAccumulatedDelta = 0 root.wheelAccumulatedDelta = 0;
} }
} }
@@ -240,24 +240,24 @@ Item {
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
onWheel: function (event) { onWheel: function (event) {
if (root.wheelCooldown) if (root.wheelCooldown)
return return;
// Prefer vertical delta, fall back to horizontal if needed // Prefer vertical delta, fall back to horizontal if needed
var dy = event.angleDelta.y var dy = event.angleDelta.y;
var dx = event.angleDelta.x var dx = event.angleDelta.x;
var useDy = Math.abs(dy) >= Math.abs(dx) var useDy = Math.abs(dy) >= Math.abs(dx);
var delta = useDy ? dy : dx var delta = useDy ? dy : dx;
// One notch is typically 120 // One notch is typically 120
root.wheelAccumulatedDelta += delta root.wheelAccumulatedDelta += delta;
var step = 120 var step = 120;
if (Math.abs(root.wheelAccumulatedDelta) >= step) { 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 vertical layout, natural mapping: wheel up -> previous, down -> next (already handled by sign)
// For horizontal layout, same mapping using vertical wheel // For horizontal layout, same mapping using vertical wheel
root.switchByOffset(direction) root.switchByOffset(direction);
root.wheelCooldown = true root.wheelCooldown = true;
wheelDebounce.restart() wheelDebounce.restart();
root.wheelAccumulatedDelta = 0 root.wheelAccumulatedDelta = 0;
event.accepted = true event.accepted = true;
} }
} }
} }
@@ -290,9 +290,9 @@ Item {
y: (pill.height - height) / 2 + (height - contentHeight) / 2 y: (pill.height - height) / 2 + (height - contentHeight) / 2
text: { text: {
if (labelMode === "name" && model.name && model.name.length > 0) { if (labelMode === "name" && model.name && model.name.length > 0) {
return model.name.substring(0, characterCount) return model.name.substring(0, characterCount);
} else { } else {
return model.idx.toString() return model.idx.toString();
} }
} }
family: Settings.data.ui.fontFixed family: Settings.data.ui.fontFixed
@@ -303,13 +303,13 @@ Item {
wrapMode: Text.Wrap wrapMode: Text.Wrap
color: { color: {
if (model.isFocused) if (model.isFocused)
return Color.mOnPrimary return Color.mOnPrimary;
if (model.isUrgent) if (model.isUrgent)
return Color.mOnError return Color.mOnError;
if (model.isOccupied) if (model.isOccupied)
return Color.mOnSecondary return Color.mOnSecondary;
return Color.mOnSecondary return Color.mOnSecondary;
} }
} }
} }
@@ -318,13 +318,13 @@ Item {
radius: width * 0.5 radius: width * 0.5
color: { color: {
if (model.isFocused) if (model.isFocused)
return Color.mPrimary return Color.mPrimary;
if (model.isUrgent) if (model.isUrgent)
return Color.mError return Color.mError;
if (model.isOccupied) 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 scale: model.isActive ? 1.0 : 0.9
z: 0 z: 0
@@ -334,7 +334,7 @@ Item {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
CompositorService.switchToWorkspace(model) CompositorService.switchToWorkspace(model);
} }
hoverEnabled: true hoverEnabled: true
} }
@@ -435,9 +435,9 @@ Item {
y: (pillVertical.height - height) / 2 + (height - contentHeight) / 2 y: (pillVertical.height - height) / 2 + (height - contentHeight) / 2
text: { text: {
if (labelMode === "name" && model.name && model.name.length > 0) { if (labelMode === "name" && model.name && model.name.length > 0) {
return model.name.substring(0, characterCount) return model.name.substring(0, characterCount);
} else { } else {
return model.idx.toString() return model.idx.toString();
} }
} }
family: Settings.data.ui.fontFixed family: Settings.data.ui.fontFixed
@@ -448,13 +448,13 @@ Item {
wrapMode: Text.Wrap wrapMode: Text.Wrap
color: { color: {
if (model.isFocused) if (model.isFocused)
return Color.mOnPrimary return Color.mOnPrimary;
if (model.isUrgent) if (model.isUrgent)
return Color.mOnError return Color.mOnError;
if (model.isOccupied) if (model.isOccupied)
return Color.mOnSecondary return Color.mOnSecondary;
return Color.mOnSurface return Color.mOnSurface;
} }
} }
} }
@@ -463,13 +463,13 @@ Item {
radius: width * 0.5 radius: width * 0.5
color: { color: {
if (model.isFocused) if (model.isFocused)
return Color.mPrimary return Color.mPrimary;
if (model.isUrgent) if (model.isUrgent)
return Color.mError return Color.mError;
if (model.isOccupied) if (model.isOccupied)
return Color.mSecondary return Color.mSecondary;
return Color.mOutline return Color.mOutline;
} }
scale: model.isActive ? 1.0 : 0.9 scale: model.isActive ? 1.0 : 0.9
z: 0 z: 0
@@ -479,7 +479,7 @@ Item {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
CompositorService.switchToWorkspace(model) CompositorService.switchToWorkspace(model);
} }
hoverEnabled: true hoverEnabled: true
} }

View File

@@ -1,7 +1,7 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Effects import QtQuick.Effects
import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Widgets import Quickshell.Widgets
@@ -26,7 +26,7 @@ Loader {
target: BarService target: BarService
function onBarReadyChanged(screenName) { function onBarReadyChanged(screenName) {
if (screenName === modelData.name) { if (screenName === modelData.name) {
barIsReady = true barIsReady = true;
} }
} }
} }
@@ -35,7 +35,7 @@ Loader {
Connections { Connections {
target: ToplevelManager ? ToplevelManager.toplevels : null target: ToplevelManager ? ToplevelManager.toplevels : null
function onValuesChanged() { function onValuesChanged() {
updateDockApps() updateDockApps();
} }
} }
@@ -43,17 +43,17 @@ Loader {
Connections { Connections {
target: Settings.data.dock target: Settings.data.dock
function onPinnedAppsChanged() { function onPinnedAppsChanged() {
updateDockApps() updateDockApps();
} }
function onOnlySameOutputChanged() { function onOnlySameOutputChanged() {
updateDockApps() updateDockApps();
} }
} }
// Initial update when component is ready // Initial update when component is ready
Component.onCompleted: { Component.onCompleted: {
if (ToplevelManager) { if (ToplevelManager) {
updateDockApps() updateDockApps();
} }
} }
@@ -93,33 +93,33 @@ Loader {
// Function to close any open context menu // Function to close any open context menu
function closeAllContextMenus() { function closeAllContextMenus() {
if (currentContextMenu && currentContextMenu.visible) { if (currentContextMenu && currentContextMenu.visible) {
currentContextMenu.hide() currentContextMenu.hide();
} }
} }
// Function to update the combined dock apps model // Function to update the combined dock apps model
function updateDockApps() { function updateDockApps() {
const runningApps = ToplevelManager ? (ToplevelManager.toplevels.values || []) : [] const runningApps = ToplevelManager ? (ToplevelManager.toplevels.values || []) : [];
const pinnedApps = Settings.data.dock.pinnedApps || [] const pinnedApps = Settings.data.dock.pinnedApps || [];
const combined = [] const combined = [];
const processedAppIds = new Set() const processedAppIds = new Set();
// Strategy: Maintain app positions as much as possible // Strategy: Maintain app positions as much as possible
// 1. First pass: Add all running apps (both pinned and non-pinned) in their current order // 1. First pass: Add all running apps (both pinned and non-pinned) in their current order
runningApps.forEach(toplevel => { runningApps.forEach(toplevel => {
if (toplevel && toplevel.appId && !(Settings.data.dock.onlySameOutput && toplevel.screens && !toplevel.screens.includes(modelData))) { if (toplevel && toplevel.appId && !(Settings.data.dock.onlySameOutput && toplevel.screens && !toplevel.screens.includes(modelData))) {
const isPinned = pinnedApps.includes(toplevel.appId) const isPinned = pinnedApps.includes(toplevel.appId);
const appType = isPinned ? "pinned-running" : "running" const appType = isPinned ? "pinned-running" : "running";
combined.push({ combined.push({
"type": appType, "type": appType,
"toplevel": toplevel, "toplevel": toplevel,
"appId": toplevel.appId, "appId": toplevel.appId,
"title": toplevel.title "title": toplevel.title
}) });
processedAppIds.add(toplevel.appId) processedAppIds.add(toplevel.appId);
} }
}) });
// 2. Second pass: Add non-running pinned apps at the end // 2. Second pass: Add non-running pinned apps at the end
pinnedApps.forEach(pinnedAppId => { pinnedApps.forEach(pinnedAppId => {
@@ -130,11 +130,11 @@ Loader {
"toplevel": null, "toplevel": null,
"appId": pinnedAppId, "appId": pinnedAppId,
"title": pinnedAppId "title": pinnedAppId
}) });
} }
}) });
dockApps = combined dockApps = combined;
} }
// Timer to unload dock after hide animation completes // Timer to unload dock after hide animation completes
@@ -143,7 +143,7 @@ Loader {
interval: hideAnimationDuration + 50 // Add small buffer interval: hideAnimationDuration + 50 // Add small buffer
onTriggered: { onTriggered: {
if (hidden && autoHide) { if (hidden && autoHide) {
dockLoaded = false dockLoaded = false;
} }
} }
} }
@@ -155,14 +155,14 @@ Loader {
onTriggered: { onTriggered: {
// Force menuHovered to false if no menu is current or visible // Force menuHovered to false if no menu is current or visible
if (!root.currentContextMenu || !root.currentContextMenu.visible) { if (!root.currentContextMenu || !root.currentContextMenu.visible) {
menuHovered = false menuHovered = false;
} }
if (autoHide && !dockHovered && !anyAppHovered && !peekHovered && !menuHovered) { if (autoHide && !dockHovered && !anyAppHovered && !peekHovered && !menuHovered) {
hidden = true hidden = true;
unloadTimer.restart() // Start unload timer when hiding unloadTimer.restart(); // Start unload timer when hiding
} else if (autoHide && !dockHovered && !peekHovered) { } else if (autoHide && !dockHovered && !peekHovered) {
// Restart timer if menu is closing (handles race condition) // Restart timer if menu is closing (handles race condition)
restart() restart();
} }
} }
} }
@@ -173,9 +173,9 @@ Loader {
interval: showDelay interval: showDelay
onTriggered: { onTriggered: {
if (autoHide) { if (autoHide) {
dockLoaded = true // Load dock immediately dockLoaded = true; // Load dock immediately
hidden = false // Then trigger show animation hidden = false; // Then trigger show animation
unloadTimer.stop() // Cancel any pending unload unloadTimer.stop(); // Cancel any pending unload
} }
} }
} }
@@ -183,14 +183,14 @@ Loader {
// Watch for autoHide setting changes // Watch for autoHide setting changes
onAutoHideChanged: { onAutoHideChanged: {
if (!autoHide) { if (!autoHide) {
hidden = false hidden = false;
dockLoaded = true dockLoaded = true;
hideTimer.stop() hideTimer.stop();
showTimer.stop() showTimer.stop();
unloadTimer.stop() unloadTimer.stop();
} else { } else {
hidden = true hidden = true;
unloadTimer.restart() // Schedule unload after animation unloadTimer.restart(); // Schedule unload after animation
} }
} }
@@ -218,16 +218,16 @@ Loader {
hoverEnabled: true hoverEnabled: true
onEntered: { onEntered: {
peekHovered = true peekHovered = true;
if (hidden) { if (hidden) {
showTimer.start() showTimer.start();
} }
} }
onExited: { onExited: {
peekHovered = false peekHovered = false;
if (!hidden && !dockHovered && !anyAppHovered && !menuHovered) { if (!hidden && !dockHovered && !anyAppHovered && !menuHovered) {
hideTimer.restart() hideTimer.restart();
} }
} }
} }
@@ -260,9 +260,9 @@ Loader {
margins.bottom: { margins.bottom: {
switch (Settings.data.bar.position) { switch (Settings.data.bar.position) {
case "bottom": 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: default:
return floatingMargin return floatingMargin;
} }
} }
@@ -313,24 +313,24 @@ Loader {
hoverEnabled: true hoverEnabled: true
onEntered: { onEntered: {
dockHovered = true dockHovered = true;
if (autoHide) { if (autoHide) {
showTimer.stop() showTimer.stop();
hideTimer.stop() hideTimer.stop();
unloadTimer.stop() // Cancel unload if hovering unloadTimer.stop(); // Cancel unload if hovering
} }
} }
onExited: { onExited: {
dockHovered = false dockHovered = false;
if (autoHide && !anyAppHovered && !peekHovered && !menuHovered) { if (autoHide && !anyAppHovered && !peekHovered && !menuHovered) {
hideTimer.restart() hideTimer.restart();
} }
} }
onClicked: { onClicked: {
// Close any open context menu when clicking on the dock background // Close any open context menu when clicking on the dock background
closeAllContextMenus() closeAllContextMenus();
} }
} }
@@ -342,8 +342,8 @@ Loader {
function getAppIcon(appData): string { function getAppIcon(appData): string {
if (!appData || !appData.appId) if (!appData || !appData.appId)
return "" return "";
return ThemeIcons.iconForAppId(appData.appId?.toLowerCase()) return ThemeIcons.iconForAppId(appData.appId?.toLowerCase());
} }
RowLayout { RowLayout {
@@ -371,7 +371,7 @@ Loader {
Connections { Connections {
target: modelData?.toplevel target: modelData?.toplevel
function onClosed() { function onClosed() {
Qt.callLater(root.updateDockApps) Qt.callLater(root.updateDockApps);
} }
} }
@@ -452,9 +452,9 @@ Loader {
onHoveredChanged: { onHoveredChanged: {
// Only update menuHovered if this menu is current and visible // Only update menuHovered if this menu is current and visible
if (root.currentContextMenu === contextMenu && contextMenu.visible) { if (root.currentContextMenu === contextMenu && contextMenu.visible) {
menuHovered = hovered menuHovered = hovered;
} else { } else {
menuHovered = false menuHovered = false;
} }
} }
@@ -462,26 +462,26 @@ Loader {
target: contextMenu target: contextMenu
function onRequestClose() { function onRequestClose() {
// Clear current menu immediately to prevent hover updates // Clear current menu immediately to prevent hover updates
root.currentContextMenu = null root.currentContextMenu = null;
hideTimer.stop() hideTimer.stop();
contextMenu.hide() contextMenu.hide();
menuHovered = false menuHovered = false;
anyAppHovered = false anyAppHovered = false;
} }
} }
onAppClosed: root.updateDockApps // Force immediate dock update when app is closed onAppClosed: root.updateDockApps // Force immediate dock update when app is closed
onVisibleChanged: { onVisibleChanged: {
if (visible) { if (visible) {
root.currentContextMenu = contextMenu root.currentContextMenu = contextMenu;
anyAppHovered = false anyAppHovered = false;
} else if (root.currentContextMenu === contextMenu) { } else if (root.currentContextMenu === contextMenu) {
root.currentContextMenu = null root.currentContextMenu = null;
hideTimer.stop() hideTimer.stop();
menuHovered = false menuHovered = false;
anyAppHovered = false anyAppHovered = false;
// Restart hide timer after menu closes // Restart hide timer after menu closes
if (autoHide && !dockHovered && !anyAppHovered && !peekHovered && !menuHovered) { if (autoHide && !dockHovered && !anyAppHovered && !peekHovered && !menuHovered) {
hideTimer.restart() hideTimer.restart();
} }
} }
} }
@@ -496,28 +496,28 @@ Loader {
acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
onEntered: { onEntered: {
anyAppHovered = true anyAppHovered = true;
const appName = appButton.appTitle || appButton.appId || "Unknown" const appName = appButton.appTitle || appButton.appId || "Unknown";
const tooltipText = appName.length > 40 ? appName.substring(0, 37) + "..." : appName const tooltipText = appName.length > 40 ? appName.substring(0, 37) + "..." : appName;
if (!contextMenu.visible) { if (!contextMenu.visible) {
TooltipService.show(Screen, appButton, tooltipText, "top") TooltipService.show(Screen, appButton, tooltipText, "top");
} }
if (autoHide) { if (autoHide) {
showTimer.stop() showTimer.stop();
hideTimer.stop() hideTimer.stop();
unloadTimer.stop() // Cancel unload if hovering app unloadTimer.stop(); // Cancel unload if hovering app
} }
} }
onExited: { onExited: {
anyAppHovered = false anyAppHovered = false;
TooltipService.hide() TooltipService.hide();
// Clear menuHovered if no current menu or menu not visible // Clear menuHovered if no current menu or menu not visible
if (!root.currentContextMenu || !root.currentContextMenu.visible) { if (!root.currentContextMenu || !root.currentContextMenu.visible) {
menuHovered = false menuHovered = false;
} }
if (autoHide && !dockHovered && !peekHovered && !menuHovered) { if (autoHide && !dockHovered && !peekHovered && !menuHovered) {
hideTimer.restart() hideTimer.restart();
} }
} }
@@ -525,33 +525,33 @@ Loader {
if (mouse.button === Qt.RightButton) { if (mouse.button === Qt.RightButton) {
// If right-clicking on the same app with an open context menu, close it // If right-clicking on the same app with an open context menu, close it
if (root.currentContextMenu === contextMenu && contextMenu.visible) { if (root.currentContextMenu === contextMenu && contextMenu.visible) {
root.closeAllContextMenus() root.closeAllContextMenus();
return return;
} }
// Close any other existing context menu first // Close any other existing context menu first
root.closeAllContextMenus() root.closeAllContextMenus();
// Hide tooltip when showing context menu // Hide tooltip when showing context menu
TooltipService.hideImmediately() TooltipService.hideImmediately();
contextMenu.show(appButton, modelData.toplevel || modelData) contextMenu.show(appButton, modelData.toplevel || modelData);
return return;
} }
// Close any existing context menu for non-right-click actions // Close any existing context menu for non-right-click actions
root.closeAllContextMenus() root.closeAllContextMenus();
// Check if toplevel is still valid (not a stale reference) // 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) { if (mouse.button === Qt.MiddleButton && isValidToplevel && modelData.toplevel.close) {
modelData.toplevel.close() modelData.toplevel.close();
Qt.callLater(root.updateDockApps) // Force immediate dock update Qt.callLater(root.updateDockApps); // Force immediate dock update
} else if (mouse.button === Qt.LeftButton) { } else if (mouse.button === Qt.LeftButton) {
if (isValidToplevel && modelData.toplevel.activate) { if (isValidToplevel && modelData.toplevel.activate) {
// Running app - activate it // Running app - activate it
modelData.toplevel.activate() modelData.toplevel.activate();
} else if (modelData?.appId) { } else if (modelData?.appId) {
// Pinned app not running - launch it // Pinned app not running - launch it
Quickshell.execDetached(["gtk-launch", modelData.appId]) Quickshell.execDetached(["gtk-launch", modelData.appId]);
} }
} }
} }

View File

@@ -31,21 +31,21 @@ PopupWindow {
function initItems() { function initItems() {
// Is this a running app? // 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? // 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) { if (isRunning) {
// Focus item // Focus item
next.push({ next.push({
"icon": "eye", "icon": "eye",
"text": I18n.tr("dock.menu.focus"), "text": I18n.tr("dock.menu.focus"),
"action": function () { "action": function () {
handleFocus() handleFocus();
} }
}) });
} }
// Pin/Unpin item // Pin/Unpin item
@@ -53,9 +53,9 @@ PopupWindow {
"icon": !isPinned ? "pin" : "unpin", "icon": !isPinned ? "pin" : "unpin",
"text": !isPinned ? I18n.tr("dock.menu.pin") : I18n.tr("dock.menu.unpin"), "text": !isPinned ? I18n.tr("dock.menu.pin") : I18n.tr("dock.menu.unpin"),
"action": function () { "action": function () {
handlePin() handlePin();
} }
}) });
if (isRunning) { if (isRunning) {
// Close item // Close item
@@ -63,55 +63,54 @@ PopupWindow {
"icon": "close", "icon": "close",
"text": I18n.tr("dock.menu.close"), "text": I18n.tr("dock.menu.close"),
"action": function () { "action": function () {
handleClose() handleClose();
} }
}) });
} }
// Create a menu entry for each app-specific action definied in its .desktop file // Create a menu entry for each app-specific action definied in its .desktop file
if (typeof DesktopEntries !== 'undefined' && DesktopEntries.byId) { 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) { if (entry != null) {
entry.actions.forEach(function (action) { entry.actions.forEach(function (action) {
next.push({ next.push({
"icon": "", "icon": "",
"text": action.name, "text": action.name,
"action": function () { "action": function () {
action.execute() action.execute();
} }
}) });
}) });
} }
} }
root.items = next root.items = next;
} }
// Helper functions for pin/unpin functionality // Helper functions for pin/unpin functionality
function isAppPinned(appId) { function isAppPinned(appId) {
if (!appId) if (!appId)
return false return false;
const pinnedApps = Settings.data.dock.pinnedApps || [] const pinnedApps = Settings.data.dock.pinnedApps || [];
return pinnedApps.includes(appId) return pinnedApps.includes(appId);
} }
function toggleAppPin(appId) { function toggleAppPin(appId) {
if (!appId) if (!appId)
return return;
let pinnedApps = (Settings.data.dock.pinnedApps || []).slice(); // Create a copy
let pinnedApps = (Settings.data.dock.pinnedApps || []).slice() // Create a copy const isPinned = pinnedApps.includes(appId);
const isPinned = pinnedApps.includes(appId)
if (isPinned) { if (isPinned) {
// Unpin: remove from array // Unpin: remove from array
pinnedApps = pinnedApps.filter(id => id !== appId) pinnedApps = pinnedApps.filter(id => id !== appId);
} else { } else {
// Pin: add to array // Pin: add to array
pinnedApps.push(appId) pinnedApps.push(appId);
} }
// Update the settings // Update the settings
Settings.data.dock.pinnedApps = pinnedApps Settings.data.dock.pinnedApps = pinnedApps;
} }
anchor.item: anchorItem anchor.item: anchorItem
@@ -120,62 +119,62 @@ PopupWindow {
function show(item, toplevelData) { function show(item, toplevelData) {
if (!item) { if (!item) {
return return;
} }
anchorItem = item anchorItem = item;
toplevel = toplevelData toplevel = toplevelData;
initItems() initItems();
visible = true visible = true;
canAutoClose = false canAutoClose = false;
gracePeriodTimer.restart() gracePeriodTimer.restart();
} }
function hide() { function hide() {
visible = false visible = false;
root.items.length = 0 root.items.length = 0;
} }
// Helper function to determine which menu item is under the mouse // Helper function to determine which menu item is under the mouse
function getHoveredItem(mouseY) { function getHoveredItem(mouseY) {
const itemHeight = 32 const itemHeight = 32;
const startY = Style.marginM const startY = Style.marginM;
const relativeY = mouseY - startY const relativeY = mouseY - startY;
if (relativeY < 0) if (relativeY < 0)
return -1 return -1;
const itemIndex = Math.floor(relativeY / itemHeight) const itemIndex = Math.floor(relativeY / itemHeight);
return itemIndex >= 0 && itemIndex < root.items.length ? itemIndex : -1 return itemIndex >= 0 && itemIndex < root.items.length ? itemIndex : -1;
} }
function handleFocus() { function handleFocus() {
if (root.toplevel?.activate) { if (root.toplevel?.activate) {
root.toplevel.activate() root.toplevel.activate();
} }
root.requestClose() root.requestClose();
} }
function handlePin() { function handlePin() {
if (root.toplevel?.appId) { if (root.toplevel?.appId) {
root.toggleAppPin(root.toplevel.appId) root.toggleAppPin(root.toplevel.appId);
} }
root.requestClose() root.requestClose();
} }
function handleClose() { function handleClose() {
// Check if toplevel is still valid before trying to close it // 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) { if (isValidToplevel && root.toplevel.close) {
root.toplevel.close() root.toplevel.close();
// Trigger immediate dock update callback if provided // Trigger immediate dock update callback if provided
if (root.onAppClosed && typeof root.onAppClosed === "function") { if (root.onAppClosed && typeof root.onAppClosed === "function") {
Qt.callLater(root.onAppClosed) Qt.callLater(root.onAppClosed);
} }
} }
root.hide() root.hide();
root.requestClose() root.requestClose();
} }
// Short delay to ignore spurious events // Short delay to ignore spurious events
@@ -184,9 +183,9 @@ PopupWindow {
interval: 1500 interval: 1500
repeat: false repeat: false
onTriggered: { onTriggered: {
root.canAutoClose = true root.canAutoClose = true;
if (!menuMouseArea.containsMouse) { if (!menuMouseArea.containsMouse) {
closeTimer.start() closeTimer.start();
} }
} }
} }
@@ -197,7 +196,7 @@ PopupWindow {
repeat: false repeat: false
running: false running: false
onTriggered: { onTriggered: {
root.hide() root.hide();
} }
} }
@@ -216,25 +215,25 @@ PopupWindow {
cursorShape: root.hoveredItem >= 0 ? Qt.PointingHandCursor : Qt.ArrowCursor cursorShape: root.hoveredItem >= 0 ? Qt.PointingHandCursor : Qt.ArrowCursor
onEntered: { onEntered: {
closeTimer.stop() closeTimer.stop();
} }
onExited: { onExited: {
root.hoveredItem = -1 root.hoveredItem = -1;
if (root.canAutoClose) { if (root.canAutoClose) {
// Only close if grace period has passed // Only close if grace period has passed
closeTimer.start() closeTimer.start();
} }
} }
onPositionChanged: mouse => { onPositionChanged: mouse => {
root.hoveredItem = root.getHoveredItem(mouse.y) root.hoveredItem = root.getHoveredItem(mouse.y);
} }
onClicked: mouse => { onClicked: mouse => {
const clickedItem = root.getHoveredItem(mouse.y) const clickedItem = root.getHoveredItem(mouse.y);
if (clickedItem >= 0) { if (clickedItem >= 0) {
root.items[clickedItem].action.call() root.items[clickedItem].action.call();
} }
} }
} }

View File

@@ -18,24 +18,24 @@ Scope {
onCurrentTextChanged: { onCurrentTextChanged: {
if (currentText !== "") { if (currentText !== "") {
showFailure = false showFailure = false;
errorMessage = "" errorMessage = "";
} }
} }
function tryUnlock() { function tryUnlock() {
if (!pamAvailable) { if (!pamAvailable) {
errorMessage = "PAM not available" errorMessage = "PAM not available";
showFailure = true showFailure = true;
return return;
} }
root.unlockInProgress = true root.unlockInProgress = true;
errorMessage = "" errorMessage = "";
showFailure = false showFailure = false;
Logger.i("LockContext", "Starting PAM authentication for user:", pam.user) Logger.i("LockContext", "Starting PAM authentication for user:", pam.user);
pam.start() pam.start();
} }
PamContext { PamContext {
@@ -44,48 +44,48 @@ Scope {
user: HostService.username user: HostService.username
onPamMessage: { onPamMessage: {
Logger.i("LockContext", "PAM message:", message, "isError:", messageIsError, "responseRequired:", responseRequired) Logger.i("LockContext", "PAM message:", message, "isError:", messageIsError, "responseRequired:", responseRequired);
if (messageIsError) { if (messageIsError) {
errorMessage = message errorMessage = message;
} else { } else {
infoMessage = message infoMessage = message;
} }
if (responseRequired) { if (responseRequired) {
Logger.i("LockContext", "Responding to PAM with password") Logger.i("LockContext", "Responding to PAM with password");
respond(root.currentText) respond(root.currentText);
} }
} }
onResponseRequiredChanged: { onResponseRequiredChanged: {
Logger.i("LockContext", "Response required changed:", responseRequired) Logger.i("LockContext", "Response required changed:", responseRequired);
if (responseRequired && root.unlockInProgress) { if (responseRequired && root.unlockInProgress) {
Logger.i("LockContext", "Automatically responding to PAM") Logger.i("LockContext", "Automatically responding to PAM");
respond(root.currentText) respond(root.currentText);
} }
} }
onCompleted: result => { onCompleted: result => {
Logger.i("LockContext", "PAM completed with result:", result) Logger.i("LockContext", "PAM completed with result:", result);
if (result === PamResult.Success) { if (result === PamResult.Success) {
Logger.i("LockContext", "Authentication successful") Logger.i("LockContext", "Authentication successful");
root.unlocked() root.unlocked();
} else { } else {
Logger.i("LockContext", "Authentication failed") Logger.i("LockContext", "Authentication failed");
errorMessage = "Authentication failed" errorMessage = "Authentication failed";
showFailure = true showFailure = true;
root.failed() root.failed();
} }
root.unlockInProgress = false root.unlockInProgress = false;
} }
onError: { onError: {
Logger.i("LockContext", "PAM error:", error, "message:", message) Logger.i("LockContext", "PAM error:", error, "message:", message);
errorMessage = message || "Authentication error" errorMessage = message || "Authentication error";
showFailure = true showFailure = true;
root.unlockInProgress = false root.unlockInProgress = false;
root.failed() root.failed();
} }
} }
} }

View File

@@ -1,21 +1,21 @@
import QtQuick import QtQuick
import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Effects import QtQuick.Effects
import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Wayland import Quickshell.Io
import Quickshell.Services.Pam import Quickshell.Services.Pam
import Quickshell.Services.UPower import Quickshell.Services.UPower
import Quickshell.Io import Quickshell.Wayland
import Quickshell.Widgets import Quickshell.Widgets
import qs.Commons import qs.Commons
import qs.Services.Compositor
import qs.Services.Hardware import qs.Services.Hardware
import qs.Services.Keyboard import qs.Services.Keyboard
import qs.Services.Location import qs.Services.Location
import qs.Services.Media import qs.Services.Media
import qs.Services.Compositor
import qs.Services.UI
import qs.Services.System import qs.Services.System
import qs.Services.UI
import qs.Widgets import qs.Widgets
import qs.Widgets.AudioSpectrum import qs.Widgets.AudioSpectrum
@@ -31,14 +31,14 @@ Loader {
interval: 250 interval: 250
repeat: false repeat: false
onTriggered: { onTriggered: {
lockScreen.active = false lockScreen.active = false;
// Reset the deprecation flag when unlocking // Reset the deprecation flag when unlocking
lockScreen.triggeredViaDeprecatedCall = false lockScreen.triggeredViaDeprecatedCall = false;
} }
} }
function scheduleUnloadAfterUnlock() { function scheduleUnloadAfterUnlock() {
unloadAfterUnlockTimer.start() unloadAfterUnlockTimer.start();
} }
sourceComponent: Component { sourceComponent: Component {
@@ -48,12 +48,12 @@ Loader {
LockContext { LockContext {
id: lockContext id: lockContext
onUnlocked: { onUnlocked: {
lockSession.locked = false lockSession.locked = false;
lockScreen.scheduleUnloadAfterUnlock() lockScreen.scheduleUnloadAfterUnlock();
lockContext.currentText = "" lockContext.currentText = "";
} }
onFailed: { onFailed: {
lockContext.currentText = "" lockContext.currentText = "";
} }
} }
@@ -130,21 +130,20 @@ Loader {
smooth: false smooth: false
onPaint: { onPaint: {
const ctx = getContext("2d") const ctx = getContext("2d");
if (!ctx) if (!ctx)
return return;
ctx.reset();
ctx.clearRect(0, 0, width, height);
ctx.reset() ctx.fillStyle = parent.cornerColor;
ctx.clearRect(0, 0, width, height) ctx.fillRect(0, 0, width, height);
ctx.fillStyle = parent.cornerColor ctx.globalCompositeOperation = "destination-out";
ctx.fillRect(0, 0, width, height) ctx.fillStyle = "#ffffff";
ctx.beginPath();
ctx.globalCompositeOperation = "destination-out" ctx.arc(width, height, parent.cornerRadius, 0, 2 * Math.PI);
ctx.fillStyle = "#ffffff" ctx.fill();
ctx.beginPath()
ctx.arc(width, height, parent.cornerRadius, 0, 2 * Math.PI)
ctx.fill()
} }
onWidthChanged: if (available) onWidthChanged: if (available)
@@ -164,21 +163,20 @@ Loader {
smooth: true smooth: true
onPaint: { onPaint: {
const ctx = getContext("2d") const ctx = getContext("2d");
if (!ctx) if (!ctx)
return return;
ctx.reset();
ctx.clearRect(0, 0, width, height);
ctx.reset() ctx.fillStyle = parent.cornerColor;
ctx.clearRect(0, 0, width, height) ctx.fillRect(0, 0, width, height);
ctx.fillStyle = parent.cornerColor ctx.globalCompositeOperation = "destination-out";
ctx.fillRect(0, 0, width, height) ctx.fillStyle = "#ffffff";
ctx.beginPath();
ctx.globalCompositeOperation = "destination-out" ctx.arc(0, height, parent.cornerRadius, 0, 2 * Math.PI);
ctx.fillStyle = "#ffffff" ctx.fill();
ctx.beginPath()
ctx.arc(0, height, parent.cornerRadius, 0, 2 * Math.PI)
ctx.fill()
} }
onWidthChanged: if (available) onWidthChanged: if (available)
@@ -198,21 +196,20 @@ Loader {
smooth: true smooth: true
onPaint: { onPaint: {
const ctx = getContext("2d") const ctx = getContext("2d");
if (!ctx) if (!ctx)
return return;
ctx.reset();
ctx.clearRect(0, 0, width, height);
ctx.reset() ctx.fillStyle = parent.cornerColor;
ctx.clearRect(0, 0, width, height) ctx.fillRect(0, 0, width, height);
ctx.fillStyle = parent.cornerColor ctx.globalCompositeOperation = "destination-out";
ctx.fillRect(0, 0, width, height) ctx.fillStyle = "#ffffff";
ctx.beginPath();
ctx.globalCompositeOperation = "destination-out" ctx.arc(width, 0, parent.cornerRadius, 0, 2 * Math.PI);
ctx.fillStyle = "#ffffff" ctx.fill();
ctx.beginPath()
ctx.arc(width, 0, parent.cornerRadius, 0, 2 * Math.PI)
ctx.fill()
} }
onWidthChanged: if (available) onWidthChanged: if (available)
@@ -232,21 +229,20 @@ Loader {
smooth: true smooth: true
onPaint: { onPaint: {
const ctx = getContext("2d") const ctx = getContext("2d");
if (!ctx) if (!ctx)
return return;
ctx.reset();
ctx.clearRect(0, 0, width, height);
ctx.reset() ctx.fillStyle = parent.cornerColor;
ctx.clearRect(0, 0, width, height) ctx.fillRect(0, 0, width, height);
ctx.fillStyle = parent.cornerColor ctx.globalCompositeOperation = "destination-out";
ctx.fillRect(0, 0, width, height) ctx.fillStyle = "#ffffff";
ctx.beginPath();
ctx.globalCompositeOperation = "destination-out" ctx.arc(0, 0, parent.cornerRadius, 0, 2 * Math.PI);
ctx.fillStyle = "#ffffff" ctx.fill();
ctx.beginPath()
ctx.arc(0, 0, parent.cornerRadius, 0, 2 * Math.PI)
ctx.fill()
} }
onWidthChanged: if (available) onWidthChanged: if (available)
@@ -347,7 +343,7 @@ Loader {
// Date below // Date below
NText { NText {
text: { text: {
var lang = I18n.locale.name.split("_")[0] var lang = I18n.locale.name.split("_")[0];
var formats = { var formats = {
"de": "dddd, d. MMMM", "de": "dddd, d. MMMM",
"es": "dddd, d 'de' MMMM", "es": "dddd, d 'de' MMMM",
@@ -356,8 +352,8 @@ Loader {
"zh": "yyyy年M月d日 dddd", "zh": "yyyy年M月d日 dddd",
"uk": "dddd, d MMMM", "uk": "dddd, d MMMM",
"tr": "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 pointSize: Style.fontSizeXL
font.weight: Font.Medium font.weight: Font.Medium
@@ -487,15 +483,15 @@ Loader {
// Compact status indicators container (compact mode only) // Compact status indicators container (compact mode only)
Rectangle { Rectangle {
width: { width: {
var hasBattery = UPower.displayDevice && UPower.displayDevice.ready && UPower.displayDevice.isPresent var hasBattery = UPower.displayDevice && UPower.displayDevice.ready && UPower.displayDevice.isPresent;
var hasKeyboard = keyboardLayout.currentLayout !== "Unknown" var hasKeyboard = keyboardLayout.currentLayout !== "Unknown";
if (hasBattery && hasKeyboard) { if (hasBattery && hasKeyboard) {
return 200 return 200;
} else if (hasBattery || hasKeyboard) { } else if (hasBattery || hasKeyboard) {
return 120 return 120;
} else { } else {
return 0 return 0;
} }
} }
height: 40 height: 40
@@ -708,14 +704,14 @@ Loader {
NText { NText {
text: { text: {
var temp = LocationService.data.weather.current_weather.temperature var temp = LocationService.data.weather.current_weather.temperature;
var suffix = "C" var suffix = "C";
if (Settings.data.location.useFahrenheit) { if (Settings.data.location.useFahrenheit) {
temp = LocationService.celsiusToFahrenheit(temp) temp = LocationService.celsiusToFahrenheit(temp);
suffix = "F" suffix = "F";
} }
temp = Math.round(temp) temp = Math.round(temp);
return temp + "°" + suffix return temp + "°" + suffix;
} }
pointSize: Style.fontSizeXL pointSize: Style.fontSizeXL
font.weight: Style.fontWeightBold font.weight: Style.fontWeightBold
@@ -724,14 +720,14 @@ Loader {
NText { NText {
text: { text: {
var wind = LocationService.data.weather.current_weather.windspeed var wind = LocationService.data.weather.current_weather.windspeed;
var unit = "km/h" var unit = "km/h";
if (Settings.data.location.useFahrenheit) { if (Settings.data.location.useFahrenheit) {
wind = wind * 0.621371 // Convert km/h to mph wind = wind * 0.621371; // Convert km/h to mph
unit = "mph" unit = "mph";
} }
wind = Math.round(wind) wind = Math.round(wind);
return wind + " " + unit return wind + " " + unit;
} }
pointSize: Style.fontSizeM pointSize: Style.fontSizeM
color: Color.mOnSurfaceVariant color: Color.mOnSurfaceVariant
@@ -773,8 +769,8 @@ Loader {
NText { NText {
text: { text: {
var weatherDate = new Date(LocationService.data.weather.daily.time[index].replace(/-/g, "/")) var weatherDate = new Date(LocationService.data.weather.daily.time[index].replace(/-/g, "/"));
return I18n.locale.toString(weatherDate, "ddd") return I18n.locale.toString(weatherDate, "ddd");
} }
pointSize: Style.fontSizeM pointSize: Style.fontSizeM
color: Color.mOnSurfaceVariant color: Color.mOnSurfaceVariant
@@ -791,15 +787,15 @@ Loader {
NText { NText {
text: { text: {
var max = LocationService.data.weather.daily.temperature_2m_max[index] var max = LocationService.data.weather.daily.temperature_2m_max[index];
var min = LocationService.data.weather.daily.temperature_2m_min[index] var min = LocationService.data.weather.daily.temperature_2m_min[index];
if (Settings.data.location.useFahrenheit) { if (Settings.data.location.useFahrenheit) {
max = LocationService.celsiusToFahrenheit(max) max = LocationService.celsiusToFahrenheit(max);
min = LocationService.celsiusToFahrenheit(min) min = LocationService.celsiusToFahrenheit(min);
} }
max = Math.round(max) max = Math.round(max);
min = Math.round(min) min = Math.round(min);
return max + "°/" + min + "°" return max + "°/" + min + "°";
} }
pointSize: Style.fontSizeM pointSize: Style.fontSizeM
font.weight: Style.fontWeightMedium font.weight: Style.fontWeightMedium
@@ -916,7 +912,7 @@ Loader {
Keys.onPressed: function (event) { Keys.onPressed: function (event) {
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
lockContext.tryUnlock() lockContext.tryUnlock();
} }
} }

View File

@@ -3,8 +3,8 @@ import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import qs.Commons import qs.Commons
import qs.Services.UI
import qs.Modules.MainScreen import qs.Modules.MainScreen
import qs.Services.UI
// ------------------------------ // ------------------------------
// MainScreen for each screen (manages bar + all panels) // MainScreen for each screen (manages bar + all panels)
@@ -16,10 +16,10 @@ Variants {
property bool shouldBeActive: { property bool shouldBeActive: {
if (!modelData || !modelData.name) { if (!modelData || !modelData.name) {
return false return false;
} }
Logger.d("Shell", "MainScreen activated for", modelData?.name) Logger.d("Shell", "MainScreen activated for", modelData?.name);
return true return true;
} }
property bool windowLoaded: false property bool windowLoaded: false
@@ -33,7 +33,7 @@ Variants {
onLoaded: { onLoaded: {
// Signal that window is loaded so exclusion zone can be created // Signal that window is loaded so exclusion zone can be created
parent.windowLoaded = true parent.windowLoaded = true;
} }
sourceComponent: MainScreen { sourceComponent: MainScreen {
@@ -45,11 +45,11 @@ Variants {
Loader { Loader {
active: { active: {
if (!parent.windowLoaded || !parent.shouldBeActive || !BarService.isVisible) if (!parent.windowLoaded || !parent.shouldBeActive || !BarService.isVisible)
return false return false;
// Check if bar is configured for this screen // Check if bar is configured for this screen
var monitors = Settings.data.bar.monitors || [] var monitors = Settings.data.bar.monitors || [];
return monitors.length === 0 || monitors.includes(modelData?.name) return monitors.length === 0 || monitors.includes(modelData?.name);
} }
asynchronous: false asynchronous: false
@@ -58,7 +58,7 @@ Variants {
} }
onLoaded: { onLoaded: {
Logger.d("Shell", "BarContentWindow created for", modelData?.name) Logger.d("Shell", "BarContentWindow created for", modelData?.name);
} }
} }
@@ -67,11 +67,11 @@ Variants {
Loader { Loader {
active: { active: {
if (!parent.windowLoaded || !parent.shouldBeActive || !BarService.isVisible) if (!parent.windowLoaded || !parent.shouldBeActive || !BarService.isVisible)
return false return false;
// Check if bar is configured for this screen // Check if bar is configured for this screen
var monitors = Settings.data.bar.monitors || [] var monitors = Settings.data.bar.monitors || [];
return monitors.length === 0 || monitors.includes(modelData?.name) return monitors.length === 0 || monitors.includes(modelData?.name);
} }
asynchronous: false asynchronous: false
@@ -80,7 +80,7 @@ Variants {
} }
onLoaded: { onLoaded: {
Logger.d("Shell", "BarExclusionZone created for", modelData?.name) Logger.d("Shell", "BarExclusionZone created for", modelData?.name);
} }
} }
@@ -95,7 +95,7 @@ Variants {
} }
onLoaded: { onLoaded: {
Logger.d("Shell", "TrayMenuWindow created for", modelData?.name) Logger.d("Shell", "TrayMenuWindow created for", modelData?.name);
} }
} }
} }

View File

@@ -3,17 +3,16 @@ import QtQuick.Shapes
import qs.Commons import qs.Commons
import qs.Widgets import qs.Widgets
/** /**
* AllBackgrounds - Unified Shape container for all bar and panel backgrounds * AllBackgrounds - Unified Shape container for all bar and panel backgrounds
* *
* Unified shadow system. This component contains a single Shape * Unified shadow system. This component contains a single Shape
* with multiple ShapePath children (one for bar, one for each panel type). * with multiple ShapePath children (one for bar, one for each panel type).
* *
* Benefits: * Benefits:
* - Single GPU-accelerated rendering pass for all backgrounds * - Single GPU-accelerated rendering pass for all backgrounds
* - Unified shadow system (one MultiEffect for everything) * - Unified shadow system (one MultiEffect for everything)
*/ */
Item { Item {
id: root id: root
@@ -46,15 +45,14 @@ Item {
enabled: false // Disable mouse input on the Shape itself enabled: false // Disable mouse input on the Shape itself
Component.onCompleted: { Component.onCompleted: {
Logger.d("AllBackgrounds", "AllBackgrounds initialized") Logger.d("AllBackgrounds", "AllBackgrounds initialized");
Logger.d("AllBackgrounds", " bar:", root.bar) Logger.d("AllBackgrounds", " bar:", root.bar);
Logger.d("AllBackgrounds", " windowRoot:", root.windowRoot) Logger.d("AllBackgrounds", " windowRoot:", root.windowRoot);
} }
/** /**
* Bar * Bar
*/ */
BarBackground { BarBackground {
bar: root.bar bar: root.bar
shapeContainer: backgroundsShape shapeContainer: backgroundsShape
@@ -62,10 +60,9 @@ Item {
backgroundColor: Qt.alpha(Color.mSurface, Settings.data.bar.backgroundOpacity) backgroundColor: Qt.alpha(Color.mSurface, Settings.data.bar.backgroundOpacity)
} }
/** /**
* Panels * Panels
*/ */
// Audio // Audio
PanelBackground { PanelBackground {

View File

@@ -1,22 +1,21 @@
import QtQuick import QtQuick
import QtQuick.Shapes import QtQuick.Shapes
import qs.Commons import qs.Commons
import qs.Services.UI
import qs.Modules.MainScreen.Backgrounds import qs.Modules.MainScreen.Backgrounds
import qs.Services.UI
/** /**
* BarBackground - ShapePath component for rendering the bar background * BarBackground - ShapePath component for rendering the bar background
* *
* Unified shadow system. This component is a ShapePath that will be * Unified shadow system. This component is a ShapePath that will be
* a child of the unified AllBackgrounds Shape container. * a child of the unified AllBackgrounds Shape container.
* *
* Uses 4-state per-corner system for flexible corner rendering: * Uses 4-state per-corner system for flexible corner rendering:
* - State -1: No radius (flat/square corner) * - State -1: No radius (flat/square corner)
* - State 0: Normal (inner curve) * - State 0: Normal (inner curve)
* - State 1: Horizontal inversion (outer curve on X-axis) * - State 1: Horizontal inversion (outer curve on X-axis)
* - State 2: Vertical inversion (outer curve on Y-axis) * - State 2: Vertical inversion (outer curve on Y-axis)
*/ */
ShapePath { ShapePath {
id: root id: root
@@ -35,15 +34,15 @@ ShapePath {
readonly property bool shouldShow: { readonly property bool shouldShow: {
// Check global bar visibility // Check global bar visibility
if (!BarService.isVisible) if (!BarService.isVisible)
return false return false;
// Check screen-specific configuration // Check screen-specific configuration
var monitors = Settings.data.bar.monitors || [] var monitors = Settings.data.bar.monitors || [];
var screenName = windowRoot?.screen?.name || "" var screenName = windowRoot?.screen?.name || "";
// If no monitors specified, show on all screens // If no monitors specified, show on all screens
// If monitors specified, only show if this screen is in the list // 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) // Corner radius (from Style)
@@ -65,9 +64,9 @@ ShapePath {
function getCornerRadius(cornerState) { function getCornerRadius(cornerState) {
// State -1 = no radius (flat corner) // State -1 = no radius (flat corner)
if (cornerState === -1) if (cornerState === -1)
return 0 return 0;
// All other states use effectiveRadius // All other states use effectiveRadius
return effectiveRadius return effectiveRadius;
} }
// Per-corner multipliers and radii based on bar's corner states (handle null bar) // Per-corner multipliers and radii based on bar's corner states (handle null bar)

View File

@@ -3,22 +3,21 @@ import QtQuick.Shapes
import qs.Commons import qs.Commons
import qs.Modules.MainScreen.Backgrounds import qs.Modules.MainScreen.Backgrounds
/** /**
* PanelBackground - ShapePath component for rendering a single background * PanelBackground - ShapePath component for rendering a single background
* *
* Unified shadow system. This component is a ShapePath that will * Unified shadow system. This component is a ShapePath that will
* be a child of the unified AllBackgrounds Shape container. * be a child of the unified AllBackgrounds Shape container.
* *
* Reads positioning and geometry from PanelPlaceholder (via panel.panelItem). * Reads positioning and geometry from PanelPlaceholder (via panel.panelItem).
* The actual panel content lives in a separate SmartPanelWindow. * The actual panel content lives in a separate SmartPanelWindow.
* *
* Uses 4-state per-corner system for flexible corner rendering: * Uses 4-state per-corner system for flexible corner rendering:
* - State -1: No radius (flat/square corner) * - State -1: No radius (flat/square corner)
* - State 0: Normal (inner curve) * - State 0: Normal (inner curve)
* - State 1: Horizontal inversion (outer curve on X-axis) * - State 1: Horizontal inversion (outer curve on X-axis)
* - State 2: Vertical inversion (outer curve on Y-axis) * - State 2: Vertical inversion (outer curve on Y-axis)
*/ */
ShapePath { ShapePath {
id: root id: root
@@ -51,9 +50,9 @@ ShapePath {
function getCornerRadius(cornerState) { function getCornerRadius(cornerState) {
// State -1 = no radius (flat corner) // State -1 = no radius (flat corner)
if (cornerState === -1) if (cornerState === -1)
return 0 return 0;
// All other states use effectiveRadius // All other states use effectiveRadius
return effectiveRadius return effectiveRadius;
} }
// Per-corner multipliers and radii based on panelBg's corner states // Per-corner multipliers and radii based on panelBg's corner states

View File

@@ -4,78 +4,71 @@ import QtQuick
import QtQuick.Shapes import QtQuick.Shapes
import Quickshell import Quickshell
/** /**
* ShapeCornerHelper - Utility singleton for shape corner calculations * ShapeCornerHelper - Utility singleton for shape corner calculations
* *
* Uses 4-state per-corner system for flexible corner rendering: * Uses 4-state per-corner system for flexible corner rendering:
* - State -1: No radius (flat/square corner) * - State -1: No radius (flat/square corner)
* - State 0: Normal (inner curve) * - State 0: Normal (inner curve)
* - State 1: Horizontal inversion (outer curve on X-axis) * - State 1: Horizontal inversion (outer curve on X-axis)
* - State 2: Vertical inversion (outer curve on Y-axis) * - State 2: Vertical inversion (outer curve on Y-axis)
* *
* The key technique: Using PathArc direction control (Clockwise vs Counterclockwise) * The key technique: Using PathArc direction control (Clockwise vs Counterclockwise)
* combined with multipliers to create both inner and outer corner curves. * combined with multipliers to create both inner and outer corner curves.
*/ */
Singleton { Singleton {
id: root id: root
/** /**
* Get X-axis multiplier for a corner state * Get X-axis multiplier for a corner state
* State 1 (horizontal invert) returns -1, others return 1 * State 1 (horizontal invert) returns -1, others return 1
*/ */
function getMultX(cornerState) { function getMultX(cornerState) {
return cornerState === 1 ? -1 : 1 return cornerState === 1 ? -1 : 1;
} }
/** /**
* Get Y-axis multiplier for a corner state * Get Y-axis multiplier for a corner state
* State 2 (vertical invert) returns -1, others return 1 * State 2 (vertical invert) returns -1, others return 1
*/ */
function getMultY(cornerState) { function getMultY(cornerState) {
return cornerState === 2 ? -1 : 1 return cornerState === 2 ? -1 : 1;
} }
/** /**
* Get PathArc direction for a corner based on its multipliers * Get PathArc direction for a corner based on its multipliers
* Uses XOR logic: if X inverted differs from Y inverted, use Counterclockwise * Uses XOR logic: if X inverted differs from Y inverted, use Counterclockwise
* This creates the outer curve effect for inverted corners * This creates the outer curve effect for inverted corners
*/ */
function getArcDirection(multX, multY) { 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) { function getArcDirectionFromState(cornerState) {
const multX = getMultX(cornerState) const multX = getMultX(cornerState);
const multY = getMultY(cornerState) const multY = getMultY(cornerState);
return getArcDirection(multX, multY) return getArcDirection(multX, multY);
} }
/** /**
* Get the "flattening" radius when shape dimensions are too small * Get the "flattening" radius when shape dimensions are too small
* Prevents visual artifacts when radius exceeds dimensions * Prevents visual artifacts when radius exceeds dimensions
*/ */
function getFlattenedRadius(dimension, requestedRadius) { function getFlattenedRadius(dimension, requestedRadius) {
if (dimension < requestedRadius * 2) { if (dimension < requestedRadius * 2) {
return dimension / 2 return dimension / 2;
} }
return requestedRadius return requestedRadius;
} }
/** /**
* Check if a shape should use flattened corners * Check if a shape should use flattened corners
* Returns true if width or height is too small for the requested radius * Returns true if width or height is too small for the requested radius
*/ */
function shouldFlatten(width, height, radius) { function shouldFlatten(width, height, radius) {
return width < radius * 2 || height < radius * 2 return width < radius * 2 || height < radius * 2;
} }
} }

View File

@@ -2,19 +2,18 @@ import QtQuick
import Quickshell import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import qs.Commons import qs.Commons
import qs.Services.UI
import qs.Modules.Bar import qs.Modules.Bar
import qs.Services.UI
/** /**
* BarContentWindow - Separate transparent PanelWindow for bar content * BarContentWindow - Separate transparent PanelWindow for bar content
* *
* This window contains only the bar widgets (content), while the background * This window contains only the bar widgets (content), while the background
* is rendered in MainScreen's unified Shape system. This separation prevents * is rendered in MainScreen's unified Shape system. This separation prevents
* fullscreen redraws when bar widgets redraw. * fullscreen redraws when bar widgets redraw.
* *
* This component should be instantiated once per screen by AllScreens.qml * This component should be instantiated once per screen by AllScreens.qml
*/ */
PanelWindow { PanelWindow {
id: barWindow id: barWindow
@@ -22,7 +21,7 @@ PanelWindow {
color: Color.transparent // Transparent - background is in MainScreen below color: Color.transparent // Transparent - background is in MainScreen below
Component.onCompleted: { 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 // Wayland layer configuration

View File

@@ -3,13 +3,12 @@ import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import qs.Commons import qs.Commons
/** /**
* BarExclusionZone - Invisible PanelWindow that reserves exclusive space for the bar * BarExclusionZone - Invisible PanelWindow that reserves exclusive space for the bar
* *
* This is a minimal window that works with the compositor to reserve space, * This is a minimal window that works with the compositor to reserve space,
* while the actual bar UI is rendered in MainScreen. * while the actual bar UI is rendered in MainScreen.
*/ */
PanelWindow { PanelWindow {
id: root id: root
@@ -46,11 +45,11 @@ PanelWindow {
// Vertical bar: reserve bar height + margin on the anchored edge only // Vertical bar: reserve bar height + margin on the anchored edge only
if (barFloating) { if (barFloating) {
// For left bar, reserve left margin; for right bar, reserve right margin // 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: { implicitHeight: {
@@ -58,17 +57,17 @@ PanelWindow {
// Horizontal bar: reserve bar height + margin on the anchored edge only // Horizontal bar: reserve bar height + margin on the anchored edge only
if (barFloating) { if (barFloating) {
// For top bar, reserve top margin; for bottom bar, reserve bottom margin // 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: { Component.onCompleted: {
Logger.d("BarExclusionZone", "Created for screen:", screen?.name) Logger.d("BarExclusionZone", "Created for screen:", screen?.name);
Logger.d("BarExclusionZone", " Position:", barPosition, "Exclusive:", exclusive, "Floating:", barFloating) 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", " 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", " Size:", width, "x", height, "implicitWidth:", implicitWidth, "implicitHeight:", implicitHeight);
} }
} }

View File

@@ -2,10 +2,9 @@ import QtQuick
import QtQuick.Effects import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import "Backgrounds" as Backgrounds
import qs.Commons import qs.Commons
import qs.Services.UI
import "Backgrounds" as Backgrounds
// All panels // All panels
import qs.Modules.Bar import qs.Modules.Bar
@@ -23,11 +22,11 @@ import qs.Modules.Panels.SetupWizard
import qs.Modules.Panels.Tray import qs.Modules.Panels.Tray
import qs.Modules.Panels.Wallpaper import qs.Modules.Panels.Wallpaper
import qs.Modules.Panels.WiFi 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 { PanelWindow {
id: root id: root
@@ -62,7 +61,7 @@ PanelWindow {
readonly property var wifiPanelPlaceholder: wifiPanel.panelPlaceholder readonly property var wifiPanelPlaceholder: wifiPanel.panelPlaceholder
Component.onCompleted: { 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 // Wayland
@@ -85,9 +84,9 @@ PanelWindow {
color: { color: {
if (dimmerOpacity > 0 && isPanelOpen && !isPanelClosing) { 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 { Behavior on color {
@@ -101,15 +100,15 @@ PanelWindow {
readonly property bool barShouldShow: { readonly property bool barShouldShow: {
// Check global bar visibility // Check global bar visibility
if (!BarService.isVisible) if (!BarService.isVisible)
return false return false;
// Check screen-specific configuration // Check screen-specific configuration
var monitors = Settings.data.bar.monitors || [] var monitors = Settings.data.bar.monitors || [];
var screenName = screen?.name || "" var screenName = screen?.name || "";
// If no monitors specified, show on all screens // If no monitors specified, show on all screens
// If monitors specified, only show if this screen is in the list // 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 // Make everything click-through except bar
@@ -166,8 +165,8 @@ PanelWindow {
z: 50 z: 50
Component.onCompleted: { Component.onCompleted: {
objectName = "audioPanel-" + (screen?.name || "unknown") objectName = "audioPanel-" + (screen?.name || "unknown");
PanelService.registerPanel(audioPanel) PanelService.registerPanel(audioPanel);
} }
} }
@@ -177,8 +176,8 @@ PanelWindow {
z: 50 z: 50
Component.onCompleted: { Component.onCompleted: {
objectName = "batteryPanel-" + (screen?.name || "unknown") objectName = "batteryPanel-" + (screen?.name || "unknown");
PanelService.registerPanel(batteryPanel) PanelService.registerPanel(batteryPanel);
} }
} }
@@ -188,8 +187,8 @@ PanelWindow {
z: 50 z: 50
Component.onCompleted: { Component.onCompleted: {
objectName = "bluetoothPanel-" + (screen?.name || "unknown") objectName = "bluetoothPanel-" + (screen?.name || "unknown");
PanelService.registerPanel(bluetoothPanel) PanelService.registerPanel(bluetoothPanel);
} }
} }
@@ -199,8 +198,8 @@ PanelWindow {
z: 50 z: 50
Component.onCompleted: { Component.onCompleted: {
objectName = "controlCenterPanel-" + (screen?.name || "unknown") objectName = "controlCenterPanel-" + (screen?.name || "unknown");
PanelService.registerPanel(controlCenterPanel) PanelService.registerPanel(controlCenterPanel);
} }
} }
@@ -210,8 +209,8 @@ PanelWindow {
z: 50 z: 50
Component.onCompleted: { Component.onCompleted: {
objectName = "calendarPanel-" + (screen?.name || "unknown") objectName = "calendarPanel-" + (screen?.name || "unknown");
PanelService.registerPanel(calendarPanel) PanelService.registerPanel(calendarPanel);
} }
} }
@@ -221,8 +220,8 @@ PanelWindow {
z: 50 z: 50
Component.onCompleted: { Component.onCompleted: {
objectName = "launcherPanel-" + (screen?.name || "unknown") objectName = "launcherPanel-" + (screen?.name || "unknown");
PanelService.registerPanel(launcherPanel) PanelService.registerPanel(launcherPanel);
} }
} }
@@ -232,8 +231,8 @@ PanelWindow {
z: 50 z: 50
Component.onCompleted: { Component.onCompleted: {
objectName = "notificationHistoryPanel-" + (screen?.name || "unknown") objectName = "notificationHistoryPanel-" + (screen?.name || "unknown");
PanelService.registerPanel(notificationHistoryPanel) PanelService.registerPanel(notificationHistoryPanel);
} }
} }
@@ -243,8 +242,8 @@ PanelWindow {
z: 50 z: 50
Component.onCompleted: { Component.onCompleted: {
objectName = "sessionMenuPanel-" + (screen?.name || "unknown") objectName = "sessionMenuPanel-" + (screen?.name || "unknown");
PanelService.registerPanel(sessionMenuPanel) PanelService.registerPanel(sessionMenuPanel);
} }
} }
@@ -254,8 +253,8 @@ PanelWindow {
z: 50 z: 50
Component.onCompleted: { Component.onCompleted: {
objectName = "settingsPanel-" + (screen?.name || "unknown") objectName = "settingsPanel-" + (screen?.name || "unknown");
PanelService.registerPanel(settingsPanel) PanelService.registerPanel(settingsPanel);
} }
} }
@@ -265,8 +264,8 @@ PanelWindow {
z: 50 z: 50
Component.onCompleted: { Component.onCompleted: {
objectName = "setupWizardPanel-" + (screen?.name || "unknown") objectName = "setupWizardPanel-" + (screen?.name || "unknown");
PanelService.registerPanel(setupWizardPanel) PanelService.registerPanel(setupWizardPanel);
} }
} }
@@ -276,8 +275,8 @@ PanelWindow {
z: 50 z: 50
Component.onCompleted: { Component.onCompleted: {
objectName = "trayDrawerPanel-" + (screen?.name || "unknown") objectName = "trayDrawerPanel-" + (screen?.name || "unknown");
PanelService.registerPanel(trayDrawerPanel) PanelService.registerPanel(trayDrawerPanel);
} }
} }
@@ -287,8 +286,8 @@ PanelWindow {
z: 50 z: 50
Component.onCompleted: { Component.onCompleted: {
objectName = "wallpaperPanel-" + (screen?.name || "unknown") objectName = "wallpaperPanel-" + (screen?.name || "unknown");
PanelService.registerPanel(wallpaperPanel) PanelService.registerPanel(wallpaperPanel);
} }
} }
@@ -298,8 +297,8 @@ PanelWindow {
z: 50 z: 50
Component.onCompleted: { Component.onCompleted: {
objectName = "wifiPanel-" + (screen?.name || "unknown") objectName = "wifiPanel-" + (screen?.name || "unknown");
PanelService.registerPanel(wifiPanel) PanelService.registerPanel(wifiPanel);
} }
} }
@@ -326,94 +325,93 @@ PanelWindow {
// Use screen dimensions directly // Use screen dimensions directly
x: { x: {
if (barPosition === "right") if (barPosition === "right")
return screen.width - Style.barHeight - barMarginH - attachmentOverlap // Extend left towards panels return screen.width - Style.barHeight - barMarginH - attachmentOverlap; // Extend left towards panels
return barMarginH return barMarginH;
} }
y: { y: {
if (barPosition === "bottom") if (barPosition === "bottom")
return screen.height - Style.barHeight - barMarginV - attachmentOverlap return screen.height - Style.barHeight - barMarginV - attachmentOverlap;
return barMarginV return barMarginV;
} }
width: { width: {
if (barIsVertical) { if (barIsVertical) {
return Style.barHeight + attachmentOverlap return Style.barHeight + attachmentOverlap;
} }
return screen.width - barMarginH * 2 return screen.width - barMarginH * 2;
} }
height: { height: {
if (barIsVertical) { 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) // Corner states (same as Bar.qml)
readonly property int topLeftCornerState: { readonly property int topLeftCornerState: {
if (barFloating) if (barFloating)
return 0 return 0;
if (barPosition === "top") if (barPosition === "top")
return -1 return -1;
if (barPosition === "left") if (barPosition === "left")
return -1 return -1;
if (Settings.data.bar.outerCorners && (barPosition === "bottom" || barPosition === "right")) { 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: { readonly property int topRightCornerState: {
if (barFloating) if (barFloating)
return 0 return 0;
if (barPosition === "top") if (barPosition === "top")
return -1 return -1;
if (barPosition === "right") if (barPosition === "right")
return -1 return -1;
if (Settings.data.bar.outerCorners && (barPosition === "bottom" || barPosition === "left")) { 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: { readonly property int bottomLeftCornerState: {
if (barFloating) if (barFloating)
return 0 return 0;
if (barPosition === "bottom") if (barPosition === "bottom")
return -1 return -1;
if (barPosition === "left") if (barPosition === "left")
return -1 return -1;
if (Settings.data.bar.outerCorners && (barPosition === "top" || barPosition === "right")) { 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: { readonly property int bottomRightCornerState: {
if (barFloating) if (barFloating)
return 0 return 0;
if (barPosition === "bottom") if (barPosition === "bottom")
return -1 return -1;
if (barPosition === "right") if (barPosition === "right")
return -1 return -1;
if (Settings.data.bar.outerCorners && (barPosition === "top" || barPosition === "left")) { if (Settings.data.bar.outerCorners && (barPosition === "top" || barPosition === "left")) {
return barIsVertical ? 1 : 2 return barIsVertical ? 1 : 2;
} }
return -1 return -1;
} }
Component.onCompleted: { Component.onCompleted: {
Logger.d("MainScreen", "===== Bar placeholder loaded =====") Logger.d("MainScreen", "===== Bar placeholder loaded =====");
Logger.d("MainScreen", " Screen:", screen?.name, "Size:", screen?.width, "x", screen?.height) Logger.d("MainScreen", " Screen:", screen?.name, "Size:", screen?.width, "x", screen?.height);
Logger.d("MainScreen", " Bar position:", barPosition, "| isVertical:", barIsVertical) Logger.d("MainScreen", " Bar position:", barPosition, "| isVertical:", barIsVertical);
Logger.d("MainScreen", " Bar dimensions: x=" + x, "y=" + y, "width=" + width, "height=" + height) Logger.d("MainScreen", " Bar dimensions: x=" + x, "y=" + y, "width=" + width, "height=" + height);
Logger.d("MainScreen", " Style.barHeight =", Style.barHeight) Logger.d("MainScreen", " Style.barHeight =", Style.barHeight);
Logger.d("MainScreen", " Margins: H=" + barMarginH, "V=" + barMarginV, "| Floating:", barFloating) Logger.d("MainScreen", " Margins: H=" + barMarginH, "V=" + barMarginV, "| Floating:", barFloating);
} }
} }
/** /**
* Screen Corners * Screen Corners
*/ */
ScreenCorners {} ScreenCorners {}
} }
} }

View File

@@ -3,14 +3,13 @@ import Quickshell
import qs.Commons import qs.Commons
import qs.Services.UI import qs.Services.UI
/** /**
* PanelPlaceholder - Lightweight positioning logic for panel backgrounds * PanelPlaceholder - Lightweight positioning logic for panel backgrounds
* *
* This component stays in MainScreen and provides geometry for PanelBackground rendering. * This component stays in MainScreen and provides geometry for PanelBackground rendering.
* It contains only positioning calculations and animations, no visual content. * It contains only positioning calculations and animations, no visual content.
* The actual panel content lives in a separate SmartPanelWindow. * The actual panel content lives in a separate SmartPanelWindow.
*/ */
Item { Item {
id: root id: root
@@ -71,13 +70,13 @@ Item {
readonly property bool allowAttach: Settings.data.ui.panelsAttachedToBar || root.forceAttachToBar readonly property bool allowAttach: Settings.data.ui.panelsAttachedToBar || root.forceAttachToBar
readonly property bool allowAttachToBar: { readonly property bool allowAttachToBar: {
if (!(Settings.data.ui.panelsAttachedToBar || root.forceAttachToBar) || Settings.data.bar.backgroundOpacity < 1.0) { 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 // A panel can only be attached to a bar if there is a bar on that screen
var monitors = Settings.data.bar.monitors || [] var monitors = Settings.data.bar.monitors || [];
var result = monitors.length === 0 || monitors.includes(root.screen?.name || "") var result = monitors.length === 0 || monitors.includes(root.screen?.name || "");
return result return result;
} }
// Effective anchor properties (depend on allowAttach) // Effective anchor properties (depend on allowAttach)
@@ -97,17 +96,17 @@ Item {
function onUiScaleRatioChanged() { function onUiScaleRatioChanged() {
if (root.isPanelVisible) { if (root.isPanelVisible) {
root.setPosition() root.setPosition();
} }
} }
} }
// Public function to update content size from SmartPanelWindow // Public function to update content size from SmartPanelWindow
function updateContentSize(w, h) { function updateContentSize(w, h) {
contentPreferredWidth = w contentPreferredWidth = w;
contentPreferredHeight = h contentPreferredHeight = h;
if (isPanelVisible) { if (isPanelVisible) {
setPosition() setPosition();
} }
} }
@@ -115,45 +114,45 @@ Item {
function setPosition() { function setPosition() {
// Don't calculate position if parent dimensions aren't available yet // Don't calculate position if parent dimensions aren't available yet
if (!root.width || !root.height) { if (!root.width || !root.height) {
Logger.d("PanelPlaceholder", "Skipping setPosition - dimensions not ready:", root.width, "x", root.height, panelName) Logger.d("PanelPlaceholder", "Skipping setPosition - dimensions not ready:", root.width, "x", root.height, panelName);
Qt.callLater(setPosition) Qt.callLater(setPosition);
return return;
} }
// Calculate panel dimensions first (needed for positioning) // Calculate panel dimensions first (needed for positioning)
var w var w;
// Priority 1: Content-driven size (dynamic) // Priority 1: Content-driven size (dynamic)
if (contentPreferredWidth > 0) { if (contentPreferredWidth > 0) {
w = contentPreferredWidth w = contentPreferredWidth;
} // Priority 2: Ratio-based size } // Priority 2: Ratio-based size
else if (root.preferredWidthRatio !== undefined) { 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 } // Priority 3: Static preferred width
else { 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) // Priority 1: Content-driven size (dynamic)
if (contentPreferredHeight > 0) { if (contentPreferredHeight > 0) {
h = contentPreferredHeight h = contentPreferredHeight;
} // Priority 2: Ratio-based size } // Priority 2: Ratio-based size
else if (root.preferredHeightRatio !== undefined) { 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 } // Priority 3: Static preferred height
else { 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) // Update panelBackground target size (will be animated)
panelBackground.targetWidth = panelWidth panelBackground.targetWidth = panelWidth;
panelBackground.targetHeight = panelHeight panelBackground.targetHeight = panelHeight;
// Calculate position // Calculate position
var calculatedX var calculatedX;
var calculatedY var calculatedY;
// ===== X POSITIONING ===== // ===== X POSITIONING =====
if (root.useButtonPosition && root.width > 0 && panelWidth > 0) { if (root.useButtonPosition && root.width > 0 && panelWidth > 0) {
@@ -162,111 +161,111 @@ Item {
if (allowAttach) { if (allowAttach) {
// Attached panels: align with bar edge (left or right side) // Attached panels: align with bar edge (left or right side)
if (root.barPosition === "left") { if (root.barPosition === "left") {
var leftBarEdge = root.barMarginH + Style.barHeight var leftBarEdge = root.barMarginH + Style.barHeight;
calculatedX = leftBarEdge calculatedX = leftBarEdge;
} else { } else {
// right // right
var rightBarEdge = root.width - root.barMarginH - Style.barHeight var rightBarEdge = root.width - root.barMarginH - Style.barHeight;
calculatedX = rightBarEdge - panelWidth calculatedX = rightBarEdge - panelWidth;
} }
} else { } else {
// Detached panels: center on button X position // Detached panels: center on button X position
var panelX = root.buttonPosition.x + root.buttonWidth / 2 - panelWidth / 2 var panelX = root.buttonPosition.x + root.buttonWidth / 2 - panelWidth / 2;
var minX = Style.marginL var minX = Style.marginL;
var maxX = root.width - panelWidth - Style.marginL var maxX = root.width - panelWidth - Style.marginL;
// Account for vertical bar taking up space // Account for vertical bar taking up space
if (root.barPosition === "left") { if (root.barPosition === "left") {
minX = root.barMarginH + Style.barHeight + Style.marginL minX = root.barMarginH + Style.barHeight + Style.marginL;
} else if (root.barPosition === "right") { } 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)) panelX = Math.max(minX, Math.min(panelX, maxX));
calculatedX = panelX calculatedX = panelX;
} }
} else { } else {
// For horizontal bars, center panel on button X position // 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) { if (allowAttach) {
var cornerInset = root.barFloating ? Style.radiusL * 2 : 0 var cornerInset = root.barFloating ? Style.radiusL * 2 : 0;
var barLeftEdge = root.barMarginH + cornerInset var barLeftEdge = root.barMarginH + cornerInset;
var barRightEdge = root.width - root.barMarginH - cornerInset var barRightEdge = root.width - root.barMarginH - cornerInset;
panelX = Math.max(barLeftEdge, Math.min(panelX, barRightEdge - panelWidth)) panelX = Math.max(barLeftEdge, Math.min(panelX, barRightEdge - panelWidth));
} else { } 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 { } else {
// Standard anchor positioning // Standard anchor positioning
if (root.panelAnchorHorizontalCenter) { if (root.panelAnchorHorizontalCenter) {
if (root.barIsVertical) { if (root.barIsVertical) {
if (root.barPosition === "left") { if (root.barPosition === "left") {
var availableStart = root.barMarginH + Style.barHeight var availableStart = root.barMarginH + Style.barHeight;
var availableWidth = root.width - availableStart var availableWidth = root.width - availableStart;
calculatedX = availableStart + (availableWidth - panelWidth) / 2 calculatedX = availableStart + (availableWidth - panelWidth) / 2;
} else if (root.barPosition === "right") { } else if (root.barPosition === "right") {
var availableWidth = root.width - root.barMarginH - Style.barHeight var availableWidth = root.width - root.barMarginH - Style.barHeight;
calculatedX = (availableWidth - panelWidth) / 2 calculatedX = (availableWidth - panelWidth) / 2;
} else { } else {
calculatedX = (root.width - panelWidth) / 2 calculatedX = (root.width - panelWidth) / 2;
} }
} else { } else {
calculatedX = (root.width - panelWidth) / 2 calculatedX = (root.width - panelWidth) / 2;
} }
} else if (root.effectivePanelAnchorRight) { } else if (root.effectivePanelAnchorRight) {
if (allowAttach && root.barIsVertical && root.barPosition === "right") { if (allowAttach && root.barIsVertical && root.barPosition === "right") {
var rightBarEdge = root.width - root.barMarginH - Style.barHeight var rightBarEdge = root.width - root.barMarginH - Style.barHeight;
calculatedX = rightBarEdge - panelWidth calculatedX = rightBarEdge - panelWidth;
} else if (allowAttach) { } else if (allowAttach) {
// Account for corner inset when bar is floating, horizontal, AND panel is on same edge as bar // 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) { if (!root.barIsVertical && root.barFloating && panelOnSameEdgeAsBar) {
var rightCornerInset = Style.radiusL * 2 var rightCornerInset = Style.radiusL * 2;
calculatedX = root.width - root.barMarginH - rightCornerInset - panelWidth calculatedX = root.width - root.barMarginH - rightCornerInset - panelWidth;
} else { } else {
calculatedX = root.width - panelWidth calculatedX = root.width - panelWidth;
} }
} else { } else {
calculatedX = root.width - panelWidth - Style.marginL calculatedX = root.width - panelWidth - Style.marginL;
} }
} else if (root.effectivePanelAnchorLeft) { } else if (root.effectivePanelAnchorLeft) {
if (allowAttach && root.barIsVertical && root.barPosition === "left") { if (allowAttach && root.barIsVertical && root.barPosition === "left") {
var leftBarEdge = root.barMarginH + Style.barHeight var leftBarEdge = root.barMarginH + Style.barHeight;
calculatedX = leftBarEdge calculatedX = leftBarEdge;
} else if (allowAttach) { } else if (allowAttach) {
// Account for corner inset when bar is floating, horizontal, AND panel is on same edge as bar // 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) { if (!root.barIsVertical && root.barFloating && panelOnSameEdgeAsBar) {
var leftCornerInset = Style.radiusL * 2 var leftCornerInset = Style.radiusL * 2;
calculatedX = root.barMarginH + leftCornerInset calculatedX = root.barMarginH + leftCornerInset;
} else { } else {
calculatedX = 0 calculatedX = 0;
} }
} else { } else {
calculatedX = Style.marginL calculatedX = Style.marginL;
} }
} else { } else {
// No explicit anchor: default to centering on bar // No explicit anchor: default to centering on bar
if (root.barIsVertical) { if (root.barIsVertical) {
if (root.barPosition === "left") { if (root.barPosition === "left") {
var availableStart = root.barMarginH + Style.barHeight var availableStart = root.barMarginH + Style.barHeight;
var availableWidth = root.width - availableStart - Style.marginL var availableWidth = root.width - availableStart - Style.marginL;
calculatedX = availableStart + (availableWidth - panelWidth) / 2 calculatedX = availableStart + (availableWidth - panelWidth) / 2;
} else { } else {
var availableWidth = root.width - root.barMarginH - Style.barHeight - Style.marginL var availableWidth = root.width - root.barMarginH - Style.barHeight - Style.marginL;
calculatedX = Style.marginL + (availableWidth - panelWidth) / 2 calculatedX = Style.marginL + (availableWidth - panelWidth) / 2;
} }
} else { } else {
if (allowAttach) { if (allowAttach) {
var cornerInset = Style.radiusL + (root.barFloating ? Style.radiusL : 0) var cornerInset = Style.radiusL + (root.barFloating ? Style.radiusL : 0);
var barLeftEdge = root.barMarginH + cornerInset var barLeftEdge = root.barMarginH + cornerInset;
var barRightEdge = root.width - root.barMarginH - cornerInset var barRightEdge = root.width - root.barMarginH - cornerInset;
var centeredX = (root.width - panelWidth) / 2 var centeredX = (root.width - panelWidth) / 2;
calculatedX = Math.max(barLeftEdge, Math.min(centeredX, barRightEdge - panelWidth)) calculatedX = Math.max(barLeftEdge, Math.min(centeredX, barRightEdge - panelWidth));
} else { } else {
calculatedX = (root.width - panelWidth) / 2 calculatedX = (root.width - panelWidth) / 2;
} }
} }
} }
@@ -274,76 +273,76 @@ Item {
// Edge snapping for X // Edge snapping for X
if (allowAttach && !root.barFloating && root.width > 0 && panelWidth > 0) { if (allowAttach && !root.barFloating && root.width > 0 && panelWidth > 0) {
var leftEdgePos = root.barMarginH var leftEdgePos = root.barMarginH;
if (root.barPosition === "left") { 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") { 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 // 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 // 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) { if (shouldSnapToLeft && Math.abs(calculatedX - leftEdgePos) <= root.edgeSnapDistance) {
calculatedX = leftEdgePos calculatedX = leftEdgePos;
} else if (shouldSnapToRight && Math.abs(calculatedX - rightEdgePos) <= root.edgeSnapDistance) { } else if (shouldSnapToRight && Math.abs(calculatedX - rightEdgePos) <= root.edgeSnapDistance) {
calculatedX = rightEdgePos calculatedX = rightEdgePos;
} }
} }
// ===== Y POSITIONING ===== // ===== Y POSITIONING =====
if (root.useButtonPosition && root.height > 0 && panelHeight > 0) { if (root.useButtonPosition && root.height > 0 && panelHeight > 0) {
if (root.barPosition === "top") { if (root.barPosition === "top") {
var topBarEdge = root.barMarginV + Style.barHeight var topBarEdge = root.barMarginV + Style.barHeight;
if (allowAttach) { if (allowAttach) {
calculatedY = topBarEdge calculatedY = topBarEdge;
} else { } else {
calculatedY = topBarEdge + Style.marginM calculatedY = topBarEdge + Style.marginM;
} }
} else if (root.barPosition === "bottom") { } else if (root.barPosition === "bottom") {
var bottomBarEdge = root.height - root.barMarginV - Style.barHeight var bottomBarEdge = root.height - root.barMarginV - Style.barHeight;
if (allowAttach) { if (allowAttach) {
calculatedY = bottomBarEdge - panelHeight calculatedY = bottomBarEdge - panelHeight;
} else { } else {
calculatedY = bottomBarEdge - panelHeight - Style.marginM calculatedY = bottomBarEdge - panelHeight - Style.marginM;
} }
} else if (root.barIsVertical) { } else if (root.barIsVertical) {
var panelY = root.buttonPosition.y + root.buttonHeight / 2 - panelHeight / 2 var panelY = root.buttonPosition.y + root.buttonHeight / 2 - panelHeight / 2;
var extraPadding = (allowAttach && root.barFloating) ? Style.radiusL : 0 var extraPadding = (allowAttach && root.barFloating) ? Style.radiusL : 0;
if (allowAttach) { if (allowAttach) {
var cornerInset = extraPadding + (root.barFloating ? Style.radiusL : 0) var cornerInset = extraPadding + (root.barFloating ? Style.radiusL : 0);
var barTopEdge = root.barMarginV + cornerInset var barTopEdge = root.barMarginV + cornerInset;
var barBottomEdge = root.height - root.barMarginV - cornerInset var barBottomEdge = root.height - root.barMarginV - cornerInset;
panelY = Math.max(barTopEdge, Math.min(panelY, barBottomEdge - panelHeight)) panelY = Math.max(barTopEdge, Math.min(panelY, barBottomEdge - panelHeight));
} else { } 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 { } else {
// Standard anchor positioning // Standard anchor positioning
var barOffset = 0 var barOffset = 0;
if (!allowAttach) { if (!allowAttach) {
if (root.barPosition === "top") { if (root.barPosition === "top") {
barOffset = root.barMarginV + Style.barHeight + Style.marginM barOffset = root.barMarginV + Style.barHeight + Style.marginM;
} else if (root.barPosition === "bottom") { } else if (root.barPosition === "bottom") {
barOffset = root.barMarginV + Style.barHeight + Style.marginM barOffset = root.barMarginV + Style.barHeight + Style.marginM;
} }
} else { } else {
if (root.effectivePanelAnchorTop && root.barPosition === "top") { if (root.effectivePanelAnchorTop && root.barPosition === "top") {
calculatedY = root.barMarginV + Style.barHeight calculatedY = root.barMarginV + Style.barHeight;
} else if (root.effectivePanelAnchorBottom && root.barPosition === "bottom") { } 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) { } else if (!root.hasExplicitVerticalAnchor) {
if (root.barPosition === "top") { if (root.barPosition === "top") {
calculatedY = root.barMarginV + Style.barHeight calculatedY = root.barMarginV + Style.barHeight;
} else if (root.barPosition === "bottom") { } 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.panelAnchorVerticalCenter) {
if (!root.barIsVertical) { if (!root.barIsVertical) {
if (root.barPosition === "top") { if (root.barPosition === "top") {
var availableStart = root.barMarginV + Style.barHeight var availableStart = root.barMarginV + Style.barHeight;
var availableHeight = root.height - availableStart var availableHeight = root.height - availableStart;
calculatedY = availableStart + (availableHeight - panelHeight) / 2 calculatedY = availableStart + (availableHeight - panelHeight) / 2;
} else if (root.barPosition === "bottom") { } else if (root.barPosition === "bottom") {
var availableHeight = root.height - root.barMarginV - Style.barHeight var availableHeight = root.height - root.barMarginV - Style.barHeight;
calculatedY = (availableHeight - panelHeight) / 2 calculatedY = (availableHeight - panelHeight) / 2;
} else { } else {
calculatedY = (root.height - panelHeight) / 2 calculatedY = (root.height - panelHeight) / 2;
} }
} else { } else {
calculatedY = (root.height - panelHeight) / 2 calculatedY = (root.height - panelHeight) / 2;
} }
} else if (root.effectivePanelAnchorTop) { } else if (root.effectivePanelAnchorTop) {
if (allowAttach) { if (allowAttach) {
calculatedY = 0 calculatedY = 0;
} else { } else {
var topBarOffset = (root.barPosition === "top") ? barOffset : 0 var topBarOffset = (root.barPosition === "top") ? barOffset : 0;
calculatedY = topBarOffset + Style.marginL calculatedY = topBarOffset + Style.marginL;
} }
} else if (root.effectivePanelAnchorBottom) { } else if (root.effectivePanelAnchorBottom) {
if (allowAttach) { if (allowAttach) {
calculatedY = root.height - panelHeight calculatedY = root.height - panelHeight;
} else { } else {
var bottomBarOffset = (root.barPosition === "bottom") ? barOffset : 0 var bottomBarOffset = (root.barPosition === "bottom") ? barOffset : 0;
calculatedY = root.height - panelHeight - bottomBarOffset - Style.marginL calculatedY = root.height - panelHeight - bottomBarOffset - Style.marginL;
} }
} else { } else {
if (root.barIsVertical) { if (root.barIsVertical) {
if (allowAttach) { if (allowAttach) {
var cornerInset = root.barFloating ? Style.radiusL * 2 : 0 var cornerInset = root.barFloating ? Style.radiusL * 2 : 0;
var barTopEdge = root.barMarginV + cornerInset var barTopEdge = root.barMarginV + cornerInset;
var barBottomEdge = root.height - root.barMarginV - cornerInset var barBottomEdge = root.height - root.barMarginV - cornerInset;
var centeredY = (root.height - panelHeight) / 2 var centeredY = (root.height - panelHeight) / 2;
calculatedY = Math.max(barTopEdge, Math.min(centeredY, barBottomEdge - panelHeight)) calculatedY = Math.max(barTopEdge, Math.min(centeredY, barBottomEdge - panelHeight));
} else { } else {
calculatedY = (root.height - panelHeight) / 2 calculatedY = (root.height - panelHeight) / 2;
} }
} else { } else {
if (allowAttach && !root.barIsVertical) { if (allowAttach && !root.barIsVertical) {
if (root.barPosition === "top") { if (root.barPosition === "top") {
calculatedY = root.barMarginV + Style.barHeight calculatedY = root.barMarginV + Style.barHeight;
} else if (root.barPosition === "bottom") { } else if (root.barPosition === "bottom") {
calculatedY = root.height - root.barMarginV - Style.barHeight - panelHeight calculatedY = root.height - root.barMarginV - Style.barHeight - panelHeight;
} }
} else { } else {
if (root.barPosition === "top") { if (root.barPosition === "top") {
calculatedY = barOffset + Style.marginL calculatedY = barOffset + Style.marginL;
} else if (root.barPosition === "bottom") { } else if (root.barPosition === "bottom") {
calculatedY = Style.marginL calculatedY = Style.marginL;
} else { } else {
calculatedY = Style.marginL calculatedY = Style.marginL;
} }
} }
} }
@@ -412,34 +411,34 @@ Item {
// Edge snapping for Y // Edge snapping for Y
if (allowAttach && !root.barFloating && root.height > 0 && panelHeight > 0) { if (allowAttach && !root.barFloating && root.height > 0 && panelHeight > 0) {
var topEdgePos = root.barMarginV var topEdgePos = root.barMarginV;
if (root.barPosition === "top") { 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") { 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 // 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 // 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) { if (shouldSnapToTop && Math.abs(calculatedY - topEdgePos) <= root.edgeSnapDistance) {
calculatedY = topEdgePos calculatedY = topEdgePos;
} else if (shouldSnapToBottom && Math.abs(calculatedY - bottomEdgePos) <= root.edgeSnapDistance) { } else if (shouldSnapToBottom && Math.abs(calculatedY - bottomEdgePos) <= root.edgeSnapDistance) {
calculatedY = bottomEdgePos calculatedY = bottomEdgePos;
} }
} }
// Apply calculated positions (set targets for animation) // Apply calculated positions (set targets for animation)
panelBackground.targetX = calculatedX panelBackground.targetX = calculatedX;
panelBackground.targetY = calculatedY panelBackground.targetY = calculatedY;
Logger.d("PanelPlaceholder", "Position calculated:", calculatedX, calculatedY, panelName) Logger.d("PanelPlaceholder", "Position calculated:", calculatedX, calculatedY, panelName);
Logger.d("PanelPlaceholder", " Panel size:", panelWidth, "x", panelHeight) Logger.d("PanelPlaceholder", " Panel size:", panelWidth, "x", panelHeight);
} }
// The panel background geometry item // The panel background geometry item
@@ -469,35 +468,35 @@ Item {
// Animation direction determination (using target position to avoid binding loops) // Animation direction determination (using target position to avoid binding loops)
readonly property bool willTouchTopBar: { readonly property bool willTouchTopBar: {
if (!isPanelVisible) if (!isPanelVisible)
return false return false;
if (!allowAttachToBar || root.barPosition !== "top" || root.barIsVertical) if (!allowAttachToBar || root.barPosition !== "top" || root.barIsVertical)
return false return false;
var targetTopBarY = root.barMarginV + Style.barHeight var targetTopBarY = root.barMarginV + Style.barHeight;
return Math.abs(panelBackground.targetY - targetTopBarY) <= 1 return Math.abs(panelBackground.targetY - targetTopBarY) <= 1;
} }
readonly property bool willTouchBottomBar: { readonly property bool willTouchBottomBar: {
if (!isPanelVisible) if (!isPanelVisible)
return false return false;
if (!allowAttachToBar || root.barPosition !== "bottom" || root.barIsVertical) if (!allowAttachToBar || root.barPosition !== "bottom" || root.barIsVertical)
return false return false;
var targetBottomBarY = root.height - root.barMarginV - Style.barHeight - panelBackground.targetHeight var targetBottomBarY = root.height - root.barMarginV - Style.barHeight - panelBackground.targetHeight;
return Math.abs(panelBackground.targetY - targetBottomBarY) <= 1 return Math.abs(panelBackground.targetY - targetBottomBarY) <= 1;
} }
readonly property bool willTouchLeftBar: { readonly property bool willTouchLeftBar: {
if (!isPanelVisible) if (!isPanelVisible)
return false return false;
if (!allowAttachToBar || root.barPosition !== "left" || !root.barIsVertical) if (!allowAttachToBar || root.barPosition !== "left" || !root.barIsVertical)
return false return false;
var targetLeftBarX = root.barMarginH + Style.barHeight var targetLeftBarX = root.barMarginH + Style.barHeight;
return Math.abs(panelBackground.targetX - targetLeftBarX) <= 1 return Math.abs(panelBackground.targetX - targetLeftBarX) <= 1;
} }
readonly property bool willTouchRightBar: { readonly property bool willTouchRightBar: {
if (!isPanelVisible) if (!isPanelVisible)
return false return false;
if (!allowAttachToBar || root.barPosition !== "right" || !root.barIsVertical) if (!allowAttachToBar || root.barPosition !== "right" || !root.barIsVertical)
return false return false;
var targetRightBarX = root.width - root.barMarginH - Style.barHeight - panelBackground.targetWidth var targetRightBarX = root.width - root.barMarginH - Style.barHeight - panelBackground.targetWidth;
return Math.abs(panelBackground.targetX - targetRightBarX) <= 1 return Math.abs(panelBackground.targetX - targetRightBarX) <= 1;
} }
readonly property bool willTouchTopEdge: isPanelVisible && allowAttach && panelBackground.targetY <= 1 readonly property bool willTouchTopEdge: isPanelVisible && allowAttach && panelBackground.targetY <= 1
readonly property bool willTouchBottomEdge: isPanelVisible && allowAttach && (panelBackground.targetY + panelBackground.targetHeight) >= (root.height - 1) readonly property bool willTouchBottomEdge: isPanelVisible && allowAttach && (panelBackground.targetY + panelBackground.targetHeight) >= (root.height - 1)
@@ -506,59 +505,59 @@ Item {
readonly property bool isActuallyAttachedToAnyEdge: { readonly property bool isActuallyAttachedToAnyEdge: {
if (!isPanelVisible) if (!isPanelVisible)
return false return false;
return willTouchTopBar || willTouchBottomBar || willTouchLeftBar || willTouchRightBar || willTouchTopEdge || willTouchBottomEdge || willTouchLeftEdge || willTouchRightEdge return willTouchTopBar || willTouchBottomBar || willTouchLeftBar || willTouchRightBar || willTouchTopEdge || willTouchBottomEdge || willTouchLeftEdge || willTouchRightEdge;
} }
readonly property bool animateFromTop: { readonly property bool animateFromTop: {
if (!isPanelVisible) if (!isPanelVisible)
return true return true;
if (willTouchTopBar) if (willTouchTopBar)
return true return true;
if (willTouchTopEdge && !willTouchTopBar && !willTouchBottomBar && !willTouchLeftBar && !willTouchRightBar) if (willTouchTopEdge && !willTouchTopBar && !willTouchBottomBar && !willTouchLeftBar && !willTouchRightBar)
return true return true;
if (!isActuallyAttachedToAnyEdge) if (!isActuallyAttachedToAnyEdge)
return true return true;
return false return false;
} }
readonly property bool animateFromBottom: { readonly property bool animateFromBottom: {
if (!isPanelVisible) if (!isPanelVisible)
return false return false;
if (willTouchBottomBar) if (willTouchBottomBar)
return true return true;
if (willTouchBottomEdge && !willTouchTopBar && !willTouchBottomBar && !willTouchLeftBar && !willTouchRightBar) if (willTouchBottomEdge && !willTouchTopBar && !willTouchBottomBar && !willTouchLeftBar && !willTouchRightBar)
return true return true;
return false return false;
} }
readonly property bool animateFromLeft: { readonly property bool animateFromLeft: {
if (!isPanelVisible) if (!isPanelVisible)
return false return false;
if (willTouchTopBar || willTouchBottomBar) if (willTouchTopBar || willTouchBottomBar)
return false return false;
if (willTouchLeftBar) if (willTouchLeftBar)
return true return true;
var touchingTopEdge = isPanelVisible && allowAttach && panelBackground.targetY <= 1 var touchingTopEdge = isPanelVisible && allowAttach && panelBackground.targetY <= 1;
var touchingBottomEdge = isPanelVisible && allowAttach && (panelBackground.targetY + panelBackground.targetHeight) >= (root.height - 1) var touchingBottomEdge = isPanelVisible && allowAttach && (panelBackground.targetY + panelBackground.targetHeight) >= (root.height - 1);
if (touchingTopEdge || touchingBottomEdge) if (touchingTopEdge || touchingBottomEdge)
return false return false;
if (willTouchLeftEdge && !willTouchLeftBar && !willTouchTopBar && !willTouchBottomBar && !willTouchRightBar) if (willTouchLeftEdge && !willTouchLeftBar && !willTouchTopBar && !willTouchBottomBar && !willTouchRightBar)
return true return true;
return false return false;
} }
readonly property bool animateFromRight: { readonly property bool animateFromRight: {
if (!isPanelVisible) if (!isPanelVisible)
return false return false;
if (willTouchTopBar || willTouchBottomBar) if (willTouchTopBar || willTouchBottomBar)
return false return false;
if (willTouchRightBar) if (willTouchRightBar)
return true return true;
var touchingTopEdge = isPanelVisible && allowAttach && panelBackground.targetY <= 1 var touchingTopEdge = isPanelVisible && allowAttach && panelBackground.targetY <= 1;
var touchingBottomEdge = isPanelVisible && allowAttach && (panelBackground.targetY + panelBackground.targetHeight) >= (root.height - 1) var touchingBottomEdge = isPanelVisible && allowAttach && (panelBackground.targetY + panelBackground.targetHeight) >= (root.height - 1);
if (touchingTopEdge || touchingBottomEdge) if (touchingTopEdge || touchingBottomEdge)
return false return false;
if (willTouchRightEdge && !willTouchLeftBar && !willTouchTopBar && !willTouchBottomBar && !willTouchRightBar) if (willTouchRightEdge && !willTouchLeftBar && !willTouchTopBar && !willTouchBottomBar && !willTouchRightBar)
return true return true;
return false return false;
} }
readonly property bool shouldAnimateWidth: !shouldAnimateHeight && (animateFromLeft || animateFromRight) readonly property bool shouldAnimateWidth: !shouldAnimateHeight && (animateFromLeft || animateFromRight)
@@ -567,17 +566,17 @@ Item {
// Current animated width/height // Current animated width/height
readonly property real currentWidth: { readonly property real currentWidth: {
if (isClosing && opacityFadeComplete && shouldAnimateWidth) if (isClosing && opacityFadeComplete && shouldAnimateWidth)
return 0 return 0;
if (isClosing || isPanelVisible) if (isClosing || isPanelVisible)
return targetWidth return targetWidth;
return 0 return 0;
} }
readonly property real currentHeight: { readonly property real currentHeight: {
if (isClosing && opacityFadeComplete && shouldAnimateHeight) if (isClosing && opacityFadeComplete && shouldAnimateHeight)
return 0 return 0;
if (isClosing || isPanelVisible) if (isClosing || isPanelVisible)
return targetHeight return targetHeight;
return 0 return 0;
} }
width: currentWidth width: currentWidth
@@ -586,28 +585,28 @@ Item {
x: { x: {
if (animateFromRight) { if (animateFromRight) {
if (isPanelVisible || isClosing) { if (isPanelVisible || isClosing) {
var targetRightEdge = targetX + targetWidth var targetRightEdge = targetX + targetWidth;
return targetRightEdge - width return targetRightEdge - width;
} }
} }
return targetX return targetX;
} }
y: { y: {
if (animateFromBottom) { if (animateFromBottom) {
if (isPanelVisible || isClosing) { if (isPanelVisible || isClosing) {
var targetBottomEdge = targetY + targetHeight var targetBottomEdge = targetY + targetHeight;
return targetBottomEdge - height return targetBottomEdge - height;
} }
} }
return targetY return targetY;
} }
Behavior on width { Behavior on width {
NumberAnimation { NumberAnimation {
duration: { duration: {
if (!panelBackground.shouldAnimateWidth) if (!panelBackground.shouldAnimateWidth)
return 0 return 0;
return root.isClosing ? Style.animationFast : Style.animationNormal return root.isClosing ? Style.animationFast : Style.animationNormal;
} }
easing.type: Easing.BezierSpline easing.type: Easing.BezierSpline
easing.bezierCurve: panelBackground.bezierCurve easing.bezierCurve: panelBackground.bezierCurve
@@ -618,8 +617,8 @@ Item {
NumberAnimation { NumberAnimation {
duration: { duration: {
if (!panelBackground.shouldAnimateHeight) if (!panelBackground.shouldAnimateHeight)
return 0 return 0;
return root.isClosing ? Style.animationFast : Style.animationNormal return root.isClosing ? Style.animationFast : Style.animationNormal;
} }
easing.type: Easing.BezierSpline easing.type: Easing.BezierSpline
easing.bezierCurve: panelBackground.bezierCurve easing.bezierCurve: panelBackground.bezierCurve
@@ -628,75 +627,75 @@ Item {
// Corner states for PanelBackground to read // Corner states for PanelBackground to read
property int topLeftCornerState: { property int topLeftCornerState: {
var barInverted = allowAttachToBar && ((root.barPosition === "top" && !root.barIsVertical && root.effectivePanelAnchorTop) || (root.barPosition === "left" && root.barIsVertical && root.effectivePanelAnchorLeft)) var barInverted = allowAttachToBar && ((root.barPosition === "top" && !root.barIsVertical && root.effectivePanelAnchorTop) || (root.barPosition === "left" && root.barIsVertical && root.effectivePanelAnchorLeft));
var barTouchInverted = touchingTopBar || touchingLeftBar var barTouchInverted = touchingTopBar || touchingLeftBar;
var edgeInverted = allowAttach && (touchingLeftEdge || touchingTopEdge) var edgeInverted = allowAttach && (touchingLeftEdge || touchingTopEdge);
var oppositeEdgeInverted = allowAttach && (touchingTopEdge && !root.barIsVertical && root.barPosition !== "top") var oppositeEdgeInverted = allowAttach && (touchingTopEdge && !root.barIsVertical && root.barPosition !== "top");
if (barInverted || barTouchInverted || edgeInverted || oppositeEdgeInverted) { if (barInverted || barTouchInverted || edgeInverted || oppositeEdgeInverted) {
if (touchingLeftEdge && touchingTopEdge) if (touchingLeftEdge && touchingTopEdge)
return 0 return 0;
if (touchingLeftEdge) if (touchingLeftEdge)
return 2 return 2;
if (touchingTopEdge) if (touchingTopEdge)
return 1 return 1;
return root.barIsVertical ? 2 : 1 return root.barIsVertical ? 2 : 1;
} }
return 0 return 0;
} }
property int topRightCornerState: { property int topRightCornerState: {
var barInverted = allowAttachToBar && ((root.barPosition === "top" && !root.barIsVertical && root.effectivePanelAnchorTop) || (root.barPosition === "right" && root.barIsVertical && root.effectivePanelAnchorRight)) var barInverted = allowAttachToBar && ((root.barPosition === "top" && !root.barIsVertical && root.effectivePanelAnchorTop) || (root.barPosition === "right" && root.barIsVertical && root.effectivePanelAnchorRight));
var barTouchInverted = touchingTopBar || touchingRightBar var barTouchInverted = touchingTopBar || touchingRightBar;
var edgeInverted = allowAttach && (touchingRightEdge || touchingTopEdge) var edgeInverted = allowAttach && (touchingRightEdge || touchingTopEdge);
var oppositeEdgeInverted = allowAttach && (touchingTopEdge && !root.barIsVertical && root.barPosition !== "top") var oppositeEdgeInverted = allowAttach && (touchingTopEdge && !root.barIsVertical && root.barPosition !== "top");
if (barInverted || barTouchInverted || edgeInverted || oppositeEdgeInverted) { if (barInverted || barTouchInverted || edgeInverted || oppositeEdgeInverted) {
if (touchingRightEdge && touchingTopEdge) if (touchingRightEdge && touchingTopEdge)
return 0 return 0;
if (touchingRightEdge) if (touchingRightEdge)
return 2 return 2;
if (touchingTopEdge) if (touchingTopEdge)
return 1 return 1;
return root.barIsVertical ? 2 : 1 return root.barIsVertical ? 2 : 1;
} }
return 0 return 0;
} }
property int bottomLeftCornerState: { property int bottomLeftCornerState: {
var barInverted = allowAttachToBar && ((root.barPosition === "bottom" && !root.barIsVertical && root.effectivePanelAnchorBottom) || (root.barPosition === "left" && root.barIsVertical && root.effectivePanelAnchorLeft)) var barInverted = allowAttachToBar && ((root.barPosition === "bottom" && !root.barIsVertical && root.effectivePanelAnchorBottom) || (root.barPosition === "left" && root.barIsVertical && root.effectivePanelAnchorLeft));
var barTouchInverted = touchingBottomBar || touchingLeftBar var barTouchInverted = touchingBottomBar || touchingLeftBar;
var edgeInverted = allowAttach && (touchingLeftEdge || touchingBottomEdge) var edgeInverted = allowAttach && (touchingLeftEdge || touchingBottomEdge);
var oppositeEdgeInverted = allowAttach && (touchingBottomEdge && !root.barIsVertical && root.barPosition !== "bottom") var oppositeEdgeInverted = allowAttach && (touchingBottomEdge && !root.barIsVertical && root.barPosition !== "bottom");
if (barInverted || barTouchInverted || edgeInverted || oppositeEdgeInverted) { if (barInverted || barTouchInverted || edgeInverted || oppositeEdgeInverted) {
if (touchingLeftEdge && touchingBottomEdge) if (touchingLeftEdge && touchingBottomEdge)
return 0 return 0;
if (touchingLeftEdge) if (touchingLeftEdge)
return 2 return 2;
if (touchingBottomEdge) if (touchingBottomEdge)
return 1 return 1;
return root.barIsVertical ? 2 : 1 return root.barIsVertical ? 2 : 1;
} }
return 0 return 0;
} }
property int bottomRightCornerState: { property int bottomRightCornerState: {
var barInverted = allowAttachToBar && ((root.barPosition === "bottom" && !root.barIsVertical && root.effectivePanelAnchorBottom) || (root.barPosition === "right" && root.barIsVertical && root.effectivePanelAnchorRight)) var barInverted = allowAttachToBar && ((root.barPosition === "bottom" && !root.barIsVertical && root.effectivePanelAnchorBottom) || (root.barPosition === "right" && root.barIsVertical && root.effectivePanelAnchorRight));
var barTouchInverted = touchingBottomBar || touchingRightBar var barTouchInverted = touchingBottomBar || touchingRightBar;
var edgeInverted = allowAttach && (touchingRightEdge || touchingBottomEdge) var edgeInverted = allowAttach && (touchingRightEdge || touchingBottomEdge);
var oppositeEdgeInverted = allowAttach && (touchingBottomEdge && !root.barIsVertical && root.barPosition !== "bottom") var oppositeEdgeInverted = allowAttach && (touchingBottomEdge && !root.barIsVertical && root.barPosition !== "bottom");
if (barInverted || barTouchInverted || edgeInverted || oppositeEdgeInverted) { if (barInverted || barTouchInverted || edgeInverted || oppositeEdgeInverted) {
if (touchingRightEdge && touchingBottomEdge) if (touchingRightEdge && touchingBottomEdge)
return 0 return 0;
if (touchingRightEdge) if (touchingRightEdge)
return 2 return 2;
if (touchingBottomEdge) if (touchingBottomEdge)
return 1 return 1;
return root.barIsVertical ? 2 : 1 return root.barIsVertical ? 2 : 1;
} }
return 0 return 0;
} }
} }
} }

View File

@@ -2,13 +2,12 @@ import QtQuick
import QtQuick.Shapes import QtQuick.Shapes
import qs.Commons import qs.Commons
/** /**
* ScreenCorners - Shape component for rendering screen corners * ScreenCorners - Shape component for rendering screen corners
* *
* Renders concave corners at the screen edges to create a rounded screen effect. * Renders concave corners at the screen edges to create a rounded screen effect.
* Self-contained Shape component (no shadows). * Self-contained Shape component (no shadows).
*/ */
Item { Item {
id: root id: root

View File

@@ -3,15 +3,14 @@ import Quickshell
import qs.Commons import qs.Commons
import qs.Services.UI import qs.Services.UI
/** /**
* SmartPanel - Wrapper that creates placeholder + content window * SmartPanel - Wrapper that creates placeholder + content window
* *
* This component is a thin wrapper that maintains backward compatibility * This component is a thin wrapper that maintains backward compatibility
* while splitting panel rendering into: * while splitting panel rendering into:
* 1. PanelPlaceholder (in MainScreen, for background rendering) * 1. PanelPlaceholder (in MainScreen, for background rendering)
* 2. SmartPanelWindow (separate window, for content) * 2. SmartPanelWindow (separate window, for content)
*/ */
Item { Item {
id: root id: root
@@ -59,60 +58,74 @@ Item {
// Keyboard event handlers - these can be overridden by panel implementations // Keyboard event handlers - these can be overridden by panel implementations
// Note: SmartPanelWindow directly calls these functions via panelWrapper reference // Note: SmartPanelWindow directly calls these functions via panelWrapper reference
function onEscapePressed() {} function onEscapePressed() {
function onTabPressed() {} }
function onBackTabPressed() {} function onTabPressed() {
function onUpPressed() {} }
function onDownPressed() {} function onBackTabPressed() {
function onLeftPressed() {} }
function onRightPressed() {} function onUpPressed() {
function onReturnPressed() {} }
function onHomePressed() {} function onDownPressed() {
function onEndPressed() {} }
function onPageUpPressed() {} function onLeftPressed() {
function onPageDownPressed() {} }
function onCtrlJPressed() {} function onRightPressed() {
function onCtrlKPressed() {} }
function onReturnPressed() {
}
function onHomePressed() {
}
function onEndPressed() {
}
function onPageUpPressed() {
}
function onPageDownPressed() {
}
function onCtrlJPressed() {
}
function onCtrlKPressed() {
}
// Public control functions // Public control functions
function toggle(buttonItem, buttonName) { function toggle(buttonItem, buttonName) {
// Ensure window is created before toggling // Ensure window is created before toggling
if (!root.windowActive) { if (!root.windowActive) {
root.windowActive = true root.windowActive = true;
Qt.callLater(function () { Qt.callLater(function () {
if (windowLoader.item) { if (windowLoader.item) {
windowLoader.item.toggle(buttonItem, buttonName) windowLoader.item.toggle(buttonItem, buttonName);
} }
}) });
} else if (windowLoader.item) { } else if (windowLoader.item) {
windowLoader.item.toggle(buttonItem, buttonName) windowLoader.item.toggle(buttonItem, buttonName);
} }
} }
function open(buttonItem, buttonName) { function open(buttonItem, buttonName) {
// Ensure window is created before opening // Ensure window is created before opening
if (!root.windowActive) { if (!root.windowActive) {
root.windowActive = true root.windowActive = true;
Qt.callLater(function () { Qt.callLater(function () {
if (windowLoader.item) { if (windowLoader.item) {
windowLoader.item.open(buttonItem, buttonName) windowLoader.item.open(buttonItem, buttonName);
} }
}) });
} else if (windowLoader.item) { } else if (windowLoader.item) {
windowLoader.item.open(buttonItem, buttonName) windowLoader.item.open(buttonItem, buttonName);
} }
} }
function close() { function close() {
if (windowLoader.item) { if (windowLoader.item) {
windowLoader.item.close() windowLoader.item.close();
} }
} }
// Expose setPosition for panels that need to recalculate on settings changes // Expose setPosition for panels that need to recalculate on settings changes
function setPosition() { function setPosition() {
if (panelPlaceholder) { if (panelPlaceholder) {
panelPlaceholder.setPosition() panelPlaceholder.setPosition();
} }
} }
@@ -157,11 +170,11 @@ Item {
// Forward signals // Forward signals
onPanelOpened: root.opened() onPanelOpened: root.opened()
onPanelClosed: { onPanelClosed: {
root.closed() root.closed();
// Destroy the window after close animation completes // Destroy the window after close animation completes
Qt.callLater(function () { Qt.callLater(function () {
root.windowActive = false root.windowActive = false;
}) });
} }
} }
} }
@@ -172,9 +185,9 @@ Item {
// Use Qt.callLater to ensure objectName is set by parent before registering // Use Qt.callLater to ensure objectName is set by parent before registering
Qt.callLater(function () { Qt.callLater(function () {
if (!objectName) { if (!objectName) {
Logger.w("SmartPanel", "Panel created without objectName - PanelService registration may fail") Logger.w("SmartPanel", "Panel created without objectName - PanelService registration may fail");
} }
PanelService.registerPanel(root) PanelService.registerPanel(root);
}) });
} }
} }

View File

@@ -5,13 +5,12 @@ import qs.Commons
import qs.Services.Compositor import qs.Services.Compositor
import qs.Services.UI import qs.Services.UI
/** /**
* SmartPanelWindow - Separate window for panel content * SmartPanelWindow - Separate window for panel content
* *
* This component runs in its own window, separate from MainScreen. * This component runs in its own window, separate from MainScreen.
* It follows the PanelPlaceholder for positioning and contains the actual panel content. * It follows the PanelPlaceholder for positioning and contains the actual panel content.
*/ */
PanelWindow { PanelWindow {
id: root id: root
@@ -63,13 +62,13 @@ PanelWindow {
WlrLayershell.exclusionMode: ExclusionMode.Ignore WlrLayershell.exclusionMode: ExclusionMode.Ignore
WlrLayershell.keyboardFocus: { WlrLayershell.keyboardFocus: {
if (!root.isPanelOpen) { if (!root.isPanelOpen) {
return WlrKeyboardFocus.None return WlrKeyboardFocus.None;
} }
if (CompositorService.isHyprland) { if (CompositorService.isHyprland) {
// Exclusive focus on hyprland is too restrictive. // Exclusive focus on hyprland is too restrictive.
return WlrKeyboardFocus.OnDemand return WlrKeyboardFocus.OnDemand;
} else { } else {
return root.exclusiveKeyboard ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.OnDemand return root.exclusiveKeyboard ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.OnDemand;
} }
} }
@@ -91,95 +90,95 @@ PanelWindow {
// Sync state to placeholder // Sync state to placeholder
onIsPanelVisibleChanged: { onIsPanelVisibleChanged: {
placeholder.isPanelVisible = isPanelVisible placeholder.isPanelVisible = isPanelVisible;
} }
onIsClosingChanged: { onIsClosingChanged: {
placeholder.isClosing = isClosing placeholder.isClosing = isClosing;
} }
onOpacityFadeCompleteChanged: { onOpacityFadeCompleteChanged: {
placeholder.opacityFadeComplete = opacityFadeComplete placeholder.opacityFadeComplete = opacityFadeComplete;
} }
// Panel control functions // Panel control functions
function toggle(buttonItem, buttonName) { function toggle(buttonItem, buttonName) {
if (!isPanelOpen) { if (!isPanelOpen) {
open(buttonItem, buttonName) open(buttonItem, buttonName);
} else { } else {
close() close();
} }
} }
function open(buttonItem, buttonName) { function open(buttonItem, buttonName) {
if (!buttonItem && buttonName) { if (!buttonItem && buttonName) {
buttonItem = BarService.lookupWidget(buttonName, placeholder.screen.name) buttonItem = BarService.lookupWidget(buttonName, placeholder.screen.name);
} }
if (buttonItem) { if (buttonItem) {
placeholder.buttonItem = buttonItem placeholder.buttonItem = buttonItem;
// Map button position to screen coordinates // Map button position to screen coordinates
var buttonPos = buttonItem.mapToItem(null, 0, 0) var buttonPos = buttonItem.mapToItem(null, 0, 0);
placeholder.buttonPosition = Qt.point(buttonPos.x, buttonPos.y) placeholder.buttonPosition = Qt.point(buttonPos.x, buttonPos.y);
placeholder.buttonWidth = buttonItem.width placeholder.buttonWidth = buttonItem.width;
placeholder.buttonHeight = buttonItem.height placeholder.buttonHeight = buttonItem.height;
placeholder.useButtonPosition = true placeholder.useButtonPosition = true;
} else { } else {
// No button provided: reset button position mode // No button provided: reset button position mode
placeholder.buttonItem = null placeholder.buttonItem = null;
placeholder.useButtonPosition = false placeholder.useButtonPosition = false;
} }
// Set isPanelOpen to trigger content loading // Set isPanelOpen to trigger content loading
isPanelOpen = true isPanelOpen = true;
// Notify PanelService // Notify PanelService
PanelService.willOpenPanel(root) PanelService.willOpenPanel(root);
} }
function close() { function close() {
// Start close sequence: fade opacity first // Start close sequence: fade opacity first
isClosing = true isClosing = true;
sizeAnimationComplete = false sizeAnimationComplete = false;
closeFinalized = false closeFinalized = false;
// Stop the open animation timer if it's still running // Stop the open animation timer if it's still running
opacityTrigger.stop() opacityTrigger.stop();
openWatchdogActive = false openWatchdogActive = false;
openWatchdogTimer.stop() openWatchdogTimer.stop();
// Start close watchdog timer // Start close watchdog timer
closeWatchdogActive = true closeWatchdogActive = true;
closeWatchdogTimer.restart() closeWatchdogTimer.restart();
// If opacity is already 0, skip directly to size animation // If opacity is already 0, skip directly to size animation
if (contentWrapper.opacity === 0.0) { if (contentWrapper.opacity === 0.0) {
opacityFadeComplete = true opacityFadeComplete = true;
} else { } else {
opacityFadeComplete = false opacityFadeComplete = false;
} }
Logger.d("SmartPanelWindow", "Closing panel", placeholder.panelName) Logger.d("SmartPanelWindow", "Closing panel", placeholder.panelName);
} }
function finalizeClose() { function finalizeClose() {
// Prevent double-finalization // Prevent double-finalization
if (root.closeFinalized) { if (root.closeFinalized) {
Logger.w("SmartPanelWindow", "finalizeClose called but already finalized - ignoring", placeholder.panelName) Logger.w("SmartPanelWindow", "finalizeClose called but already finalized - ignoring", placeholder.panelName);
return return;
} }
// Complete the close sequence after animations finish // Complete the close sequence after animations finish
root.closeFinalized = true root.closeFinalized = true;
root.closeWatchdogActive = false root.closeWatchdogActive = false;
closeWatchdogTimer.stop() closeWatchdogTimer.stop();
root.isPanelVisible = false root.isPanelVisible = false;
root.isPanelOpen = false root.isPanelOpen = false;
root.isClosing = false root.isClosing = false;
root.opacityFadeComplete = false root.opacityFadeComplete = false;
PanelService.closedPanel(root) PanelService.closedPanel(root);
panelClosed() 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 // Fullscreen container for click-to-close and content
@@ -189,59 +188,59 @@ PanelWindow {
// Handle keyboard events directly via Keys handler // Handle keyboard events directly via Keys handler
Keys.onPressed: event => { 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) { if (event.key === Qt.Key_Escape) {
panelWrapper.onEscapePressed() panelWrapper.onEscapePressed();
if (closeWithEscape) { if (closeWithEscape) {
root.close() root.close();
event.accepted = true event.accepted = true;
} }
} else if (panelWrapper) { } else if (panelWrapper) {
if (event.key === Qt.Key_Up && panelWrapper.onUpPressed) { if (event.key === Qt.Key_Up && panelWrapper.onUpPressed) {
panelWrapper.onUpPressed() panelWrapper.onUpPressed();
event.accepted = true event.accepted = true;
} else if (event.key === Qt.Key_Down && panelWrapper.onDownPressed) { } else if (event.key === Qt.Key_Down && panelWrapper.onDownPressed) {
panelWrapper.onDownPressed() panelWrapper.onDownPressed();
event.accepted = true event.accepted = true;
} else if (event.key === Qt.Key_Left && panelWrapper.onLeftPressed) { } else if (event.key === Qt.Key_Left && panelWrapper.onLeftPressed) {
panelWrapper.onLeftPressed() panelWrapper.onLeftPressed();
event.accepted = true event.accepted = true;
} else if (event.key === Qt.Key_Right && panelWrapper.onRightPressed) { } else if (event.key === Qt.Key_Right && panelWrapper.onRightPressed) {
panelWrapper.onRightPressed() panelWrapper.onRightPressed();
event.accepted = true event.accepted = true;
} else if (event.key === Qt.Key_Tab && panelWrapper.onTabPressed) { } else if (event.key === Qt.Key_Tab && panelWrapper.onTabPressed) {
panelWrapper.onTabPressed() panelWrapper.onTabPressed();
event.accepted = true event.accepted = true;
} else if (event.key === Qt.Key_Backtab && panelWrapper.onBackTabPressed) { } else if (event.key === Qt.Key_Backtab && panelWrapper.onBackTabPressed) {
panelWrapper.onBackTabPressed() panelWrapper.onBackTabPressed();
event.accepted = true event.accepted = true;
} else if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && panelWrapper.onReturnPressed) { } else if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && panelWrapper.onReturnPressed) {
panelWrapper.onReturnPressed() panelWrapper.onReturnPressed();
event.accepted = true event.accepted = true;
} else if (event.key === Qt.Key_Home && panelWrapper.onHomePressed) { } else if (event.key === Qt.Key_Home && panelWrapper.onHomePressed) {
panelWrapper.onHomePressed() panelWrapper.onHomePressed();
event.accepted = true event.accepted = true;
} else if (event.key === Qt.Key_End && panelWrapper.onEndPressed) { } else if (event.key === Qt.Key_End && panelWrapper.onEndPressed) {
panelWrapper.onEndPressed() panelWrapper.onEndPressed();
event.accepted = true event.accepted = true;
} else if (event.key === Qt.Key_PageUp && panelWrapper.onPageUpPressed) { } else if (event.key === Qt.Key_PageUp && panelWrapper.onPageUpPressed) {
panelWrapper.onPageUpPressed() panelWrapper.onPageUpPressed();
event.accepted = true event.accepted = true;
} else if (event.key === Qt.Key_PageDown && panelWrapper.onPageDownPressed) { } else if (event.key === Qt.Key_PageDown && panelWrapper.onPageDownPressed) {
panelWrapper.onPageDownPressed() panelWrapper.onPageDownPressed();
event.accepted = true event.accepted = true;
} else if (event.key === Qt.Key_J && (event.modifiers & Qt.ControlModifier) && panelWrapper.onCtrlJPressed) { } else if (event.key === Qt.Key_J && (event.modifiers & Qt.ControlModifier) && panelWrapper.onCtrlJPressed) {
panelWrapper.onCtrlJPressed() panelWrapper.onCtrlJPressed();
event.accepted = true event.accepted = true;
} else if (event.key === Qt.Key_K && (event.modifiers & Qt.ControlModifier) && panelWrapper.onCtrlKPressed) { } else if (event.key === Qt.Key_K && (event.modifiers & Qt.ControlModifier) && panelWrapper.onCtrlKPressed) {
panelWrapper.onCtrlKPressed() panelWrapper.onCtrlKPressed();
event.accepted = true event.accepted = true;
} else if (event.key === Qt.Key_N && (event.modifiers & Qt.ControlModifier) && panelWrapper.onCtrlNPressed) { } else if (event.key === Qt.Key_N && (event.modifiers & Qt.ControlModifier) && panelWrapper.onCtrlNPressed) {
panelWrapper.onCtrlNPressed() panelWrapper.onCtrlNPressed();
event.accepted = true event.accepted = true;
} else if (event.key === Qt.Key_P && (event.modifiers & Qt.ControlModifier) && panelWrapper.onCtrlPPressed) { } else if (event.key === Qt.Key_P && (event.modifiers & Qt.ControlModifier) && panelWrapper.onCtrlPPressed) {
panelWrapper.onCtrlPPressed() panelWrapper.onCtrlPPressed();
event.accepted = true event.accepted = true;
} }
} }
} }
@@ -252,8 +251,8 @@ PanelWindow {
enabled: root.isPanelOpen && !root.isClosing enabled: root.isPanelOpen && !root.isClosing
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onClicked: mouse => { onClicked: mouse => {
root.close() root.close();
mouse.accepted = true mouse.accepted = true;
} }
z: 0 z: 0
} }
@@ -271,10 +270,10 @@ PanelWindow {
// Opacity animation // Opacity animation
opacity: { opacity: {
if (isClosing) if (isClosing)
return 0.0 return 0.0;
if (isPanelVisible && sizeAnimationComplete) if (isPanelVisible && sizeAnimationComplete)
return 1.0 return 1.0;
return 0.0 return 0.0;
} }
Behavior on opacity { Behavior on opacity {
@@ -287,33 +286,33 @@ PanelWindow {
// Safety: Zero-duration animation handling // Safety: Zero-duration animation handling
if (!running && duration === 0) { if (!running && duration === 0) {
if (root.isClosing && contentWrapper.opacity === 0.0) { if (root.isClosing && contentWrapper.opacity === 0.0) {
root.opacityFadeComplete = true root.opacityFadeComplete = true;
var shouldFinalizeNow = placeholder.panelItem && !placeholder.panelItem.shouldAnimateWidth && !placeholder.panelItem.shouldAnimateHeight var shouldFinalizeNow = placeholder.panelItem && !placeholder.panelItem.shouldAnimateWidth && !placeholder.panelItem.shouldAnimateHeight;
if (shouldFinalizeNow) { if (shouldFinalizeNow) {
Logger.d("SmartPanelWindow", "Zero-duration opacity + no size animation - finalizing", placeholder.panelName) Logger.d("SmartPanelWindow", "Zero-duration opacity + no size animation - finalizing", placeholder.panelName);
Qt.callLater(root.finalizeClose) Qt.callLater(root.finalizeClose);
} }
} else if (root.isPanelVisible && contentWrapper.opacity === 1.0) { } else if (root.isPanelVisible && contentWrapper.opacity === 1.0) {
root.openWatchdogActive = false root.openWatchdogActive = false;
openWatchdogTimer.stop() openWatchdogTimer.stop();
} }
return return;
} }
// When opacity fade completes during close, trigger size animation // When opacity fade completes during close, trigger size animation
if (!running && root.isClosing && contentWrapper.opacity === 0.0) { if (!running && root.isClosing && contentWrapper.opacity === 0.0) {
root.opacityFadeComplete = true root.opacityFadeComplete = true;
var shouldFinalizeNow = placeholder.panelItem && !placeholder.panelItem.shouldAnimateWidth && !placeholder.panelItem.shouldAnimateHeight var shouldFinalizeNow = placeholder.panelItem && !placeholder.panelItem.shouldAnimateWidth && !placeholder.panelItem.shouldAnimateHeight;
if (shouldFinalizeNow) { if (shouldFinalizeNow) {
Logger.d("SmartPanelWindow", "No animation - finalizing immediately", placeholder.panelName) Logger.d("SmartPanelWindow", "No animation - finalizing immediately", placeholder.panelName);
Qt.callLater(root.finalizeClose) Qt.callLater(root.finalizeClose);
} else { } 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 } // When opacity fade completes during open, stop watchdog
else if (!running && root.isPanelVisible && contentWrapper.opacity === 1.0) { else if (!running && root.isPanelVisible && contentWrapper.opacity === 1.0) {
root.openWatchdogActive = false root.openWatchdogActive = false;
openWatchdogTimer.stop() openWatchdogTimer.stop();
} }
} }
} }
@@ -330,31 +329,31 @@ PanelWindow {
onLoaded: { onLoaded: {
// Capture initial content-driven size if available // Capture initial content-driven size if available
if (contentLoader.item) { if (contentLoader.item) {
var hasWidthProp = contentLoader.item.hasOwnProperty('contentPreferredWidth') var hasWidthProp = contentLoader.item.hasOwnProperty('contentPreferredWidth');
var hasHeightProp = contentLoader.item.hasOwnProperty('contentPreferredHeight') var hasHeightProp = contentLoader.item.hasOwnProperty('contentPreferredHeight');
if (hasWidthProp || hasHeightProp) { if (hasWidthProp || hasHeightProp) {
var initialWidth = hasWidthProp ? contentLoader.item.contentPreferredWidth : 0 var initialWidth = hasWidthProp ? contentLoader.item.contentPreferredWidth : 0;
var initialHeight = hasHeightProp ? contentLoader.item.contentPreferredHeight : 0 var initialHeight = hasHeightProp ? contentLoader.item.contentPreferredHeight : 0;
placeholder.updateContentSize(initialWidth, initialHeight) placeholder.updateContentSize(initialWidth, initialHeight);
Logger.d("SmartPanelWindow", "Initial content size:", initialWidth, "x", initialHeight, placeholder.panelName) Logger.d("SmartPanelWindow", "Initial content size:", initialWidth, "x", initialHeight, placeholder.panelName);
} }
} }
// Calculate position in placeholder // Calculate position in placeholder
placeholder.setPosition() placeholder.setPosition();
// Make panel visible on the next frame // Make panel visible on the next frame
Qt.callLater(function () { Qt.callLater(function () {
root.isPanelVisible = true root.isPanelVisible = true;
opacityTrigger.start() opacityTrigger.start();
// Start open watchdog timer // Start open watchdog timer
root.openWatchdogActive = true root.openWatchdogActive = true;
openWatchdogTimer.start() openWatchdogTimer.start();
panelOpened() panelOpened();
}) });
} }
} }
@@ -363,7 +362,7 @@ PanelWindow {
anchors.fill: parent anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onClicked: mouse => { 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 z: -1 // Behind content but above background click-to-close
} }
@@ -375,13 +374,13 @@ PanelWindow {
function onContentPreferredWidthChanged() { function onContentPreferredWidthChanged() {
if (root.isPanelOpen && root.isPanelVisible && contentLoader.item) { if (root.isPanelOpen && root.isPanelVisible && contentLoader.item) {
placeholder.updateContentSize(contentLoader.item.contentPreferredWidth, placeholder.contentPreferredHeight) placeholder.updateContentSize(contentLoader.item.contentPreferredWidth, placeholder.contentPreferredHeight);
} }
} }
function onContentPreferredHeightChanged() { function onContentPreferredHeightChanged() {
if (root.isPanelOpen && root.isPanelVisible && contentLoader.item) { if (root.isPanelOpen && root.isPanelVisible && contentLoader.item) {
placeholder.updateContentSize(placeholder.contentPreferredWidth, contentLoader.item.contentPreferredHeight) placeholder.updateContentSize(placeholder.contentPreferredWidth, contentLoader.item.contentPreferredHeight);
} }
} }
} }
@@ -395,7 +394,7 @@ PanelWindow {
repeat: false repeat: false
onTriggered: { onTriggered: {
if (root.isPanelVisible) { if (root.isPanelVisible) {
root.sizeAnimationComplete = true root.sizeAnimationComplete = true;
} }
} }
} }
@@ -407,11 +406,11 @@ PanelWindow {
repeat: false repeat: false
onTriggered: { onTriggered: {
if (root.openWatchdogActive) { if (root.openWatchdogActive) {
Logger.w("SmartPanelWindow", "Open watchdog timeout - forcing panel visible state", placeholder.panelName) Logger.w("SmartPanelWindow", "Open watchdog timeout - forcing panel visible state", placeholder.panelName);
root.openWatchdogActive = false root.openWatchdogActive = false;
if (root.isPanelOpen && !root.isPanelVisible) { if (root.isPanelOpen && !root.isPanelVisible) {
root.isPanelVisible = true root.isPanelVisible = true;
root.sizeAnimationComplete = true root.sizeAnimationComplete = true;
} }
} }
} }
@@ -424,8 +423,8 @@ PanelWindow {
repeat: false repeat: false
onTriggered: { onTriggered: {
if (root.closeWatchdogActive && !root.closeFinalized) { if (root.closeWatchdogActive && !root.closeFinalized) {
Logger.w("SmartPanelWindow", "Close watchdog timeout - forcing panel close", placeholder.panelName) Logger.w("SmartPanelWindow", "Close watchdog timeout - forcing panel close", placeholder.panelName);
Qt.callLater(root.finalizeClose) Qt.callLater(root.finalizeClose);
} }
} }
} }
@@ -437,14 +436,14 @@ PanelWindow {
function onWidthChanged() { function onWidthChanged() {
// When width shrinks to 0 during close and we're animating width, finalize // When width shrinks to 0 during close and we're animating width, finalize
if (root.isClosing && placeholder.panelItem.width === 0 && placeholder.panelItem.shouldAnimateWidth) { if (root.isClosing && placeholder.panelItem.width === 0 && placeholder.panelItem.shouldAnimateWidth) {
Qt.callLater(root.finalizeClose) Qt.callLater(root.finalizeClose);
} }
} }
function onHeightChanged() { function onHeightChanged() {
// When height shrinks to 0 during close and we're animating height, finalize // When height shrinks to 0 during close and we're animating height, finalize
if (root.isClosing && placeholder.panelItem.height === 0 && placeholder.panelItem.shouldAnimateHeight) { if (root.isClosing && placeholder.panelItem.height === 0 && placeholder.panelItem.shouldAnimateHeight) {
Qt.callLater(root.finalizeClose) Qt.callLater(root.finalizeClose);
} }
} }
} }

View File

@@ -2,8 +2,8 @@ import QtQuick
import Quickshell import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import qs.Commons import qs.Commons
import qs.Services.UI
import qs.Modules.Bar.Extras import qs.Modules.Bar.Extras
import qs.Services.UI
// Separate window for TrayMenu context menus // Separate window for TrayMenu context menus
// This is a top-level PanelWindow (sibling to MainScreen, not nested inside it) // 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 // Register with PanelService so panels can find this window
Component.onCompleted: { Component.onCompleted: {
objectName = "trayMenuWindow-" + (screen?.name || "unknown") objectName = "trayMenuWindow-" + (screen?.name || "unknown");
PanelService.registerTrayMenuWindow(screen, root) PanelService.registerTrayMenuWindow(screen, root);
} }
function open() { function open() {
visible = true visible = true;
} }
function close() { function close() {
visible = false visible = false;
if (trayMenu.item) { if (trayMenu.item) {
trayMenu.item.hideMenu() trayMenu.item.hideMenu();
} }
} }
@@ -56,7 +56,7 @@ PanelWindow {
source: Quickshell.shellDir + "/Modules/Bar/Extras/TrayMenu.qml" source: Quickshell.shellDir + "/Modules/Bar/Extras/TrayMenu.qml"
onLoaded: { onLoaded: {
if (item) { if (item) {
item.screen = root.screen item.screen = root.screen;
} }
} }
} }

View File

@@ -2,9 +2,9 @@ import QtQuick
import QtQuick.Effects import QtQuick.Effects
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Widgets
import Quickshell.Wayland
import Quickshell.Services.Notifications import Quickshell.Services.Notifications
import Quickshell.Wayland
import Quickshell.Widgets
import qs.Commons import qs.Commons
import qs.Services.System import qs.Services.System
import qs.Widgets import qs.Widgets
@@ -35,7 +35,7 @@ Variants {
target: notificationModel target: notificationModel
function onCountChanged() { function onCountChanged() {
if (notificationModel.count === 0 && root.active) { if (notificationModel.count === 0 && root.active) {
delayTimer.restart() delayTimer.restart();
} }
} }
} }
@@ -66,30 +66,30 @@ Variants {
// Calculate bar offsets for each edge separately // Calculate bar offsets for each edge separately
readonly property int barOffsetTop: { readonly property int barOffsetTop: {
if (barPos !== "top") if (barPos !== "top")
return 0 return 0;
const floatMarginV = isFloating ? Settings.data.bar.marginVertical * Style.marginXL : 0 const floatMarginV = isFloating ? Settings.data.bar.marginVertical * Style.marginXL : 0;
return Style.barHeight + floatMarginV return Style.barHeight + floatMarginV;
} }
readonly property int barOffsetBottom: { readonly property int barOffsetBottom: {
if (barPos !== "bottom") if (barPos !== "bottom")
return 0 return 0;
const floatMarginV = isFloating ? Settings.data.bar.marginVertical * Style.marginXL : 0 const floatMarginV = isFloating ? Settings.data.bar.marginVertical * Style.marginXL : 0;
return Style.barHeight + floatMarginV return Style.barHeight + floatMarginV;
} }
readonly property int barOffsetLeft: { readonly property int barOffsetLeft: {
if (barPos !== "left") if (barPos !== "left")
return 0 return 0;
const floatMarginH = isFloating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0 const floatMarginH = isFloating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0;
return floatMarginH return floatMarginH;
} }
readonly property int barOffsetRight: { readonly property int barOffsetRight: {
if (barPos !== "right") if (barPos !== "right")
return 0 return 0;
const floatMarginH = isFloating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0 const floatMarginH = isFloating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0;
return floatMarginH return floatMarginH;
} }
// Anchoring // Anchoring
@@ -111,31 +111,31 @@ Variants {
Component.onCompleted: { Component.onCompleted: {
animateConnection = function (notificationId) { animateConnection = function (notificationId) {
var delegate = null var delegate = null;
if (notificationRepeater) { if (notificationRepeater) {
for (var i = 0; i < notificationRepeater.count; i++) { for (var i = 0; i < notificationRepeater.count; i++) {
var item = notificationRepeater.itemAt(i) var item = notificationRepeater.itemAt(i);
if (item?.notificationId === notificationId) { if (item?.notificationId === notificationId) {
delegate = item delegate = item;
break break;
} }
} }
} }
if (delegate?.animateOut) { if (delegate?.animateOut) {
delegate.animateOut() delegate.animateOut();
} else { } else {
NotificationService.dismissActiveNotification(notificationId) NotificationService.dismissActiveNotification(notificationId);
} }
} };
NotificationService.animateAndRemove.connect(animateConnection) NotificationService.animateAndRemove.connect(animateConnection);
} }
Component.onDestruction: { Component.onDestruction: {
if (animateConnection) { if (animateConnection) {
NotificationService.animateAndRemove.disconnect(animateConnection) NotificationService.animateAndRemove.disconnect(animateConnection);
animateConnection = null animateConnection = null;
} }
} }
@@ -223,8 +223,8 @@ Variants {
width: parent.availableWidth * model.progress width: parent.availableWidth * model.progress
color: { color: {
var baseColor = model.urgency === 2 ? Color.mError : model.urgency === 0 ? Color.mOnSurface : Color.mPrimary var baseColor = model.urgency === 2 ? Color.mError : model.urgency === 0 ? Color.mOnSurface : Color.mPrimary;
return Qt.alpha(baseColor, Settings.data.notifications.backgroundOpacity || 1.0) return Qt.alpha(baseColor, Settings.data.notifications.backgroundOpacity || 1.0);
} }
antialiasing: true antialiasing: true
@@ -257,10 +257,10 @@ Variants {
// Hover handling // Hover handling
onHoverCountChanged: { onHoverCountChanged: {
if (hoverCount > 0) { if (hoverCount > 0) {
resumeTimer.stop() resumeTimer.stop();
NotificationService.pauseTimeout(notificationId) NotificationService.pauseTimeout(notificationId);
} else { } else {
resumeTimer.start() resumeTimer.start();
} }
} }
@@ -270,7 +270,7 @@ Variants {
repeat: false repeat: false
onTriggered: { onTriggered: {
if (hoverCount === 0) { if (hoverCount === 0) {
NotificationService.resumeTimeout(notificationId) NotificationService.resumeTimeout(notificationId);
} }
} }
} }
@@ -284,30 +284,30 @@ Variants {
onExited: parent.hoverCount-- onExited: parent.hoverCount--
onClicked: { onClicked: {
if (mouse.button === Qt.RightButton) { if (mouse.button === Qt.RightButton) {
animateOut() animateOut();
} }
} }
} }
// Animation setup // Animation setup
function triggerEntryAnimation() { function triggerEntryAnimation() {
animInDelayTimer.stop() animInDelayTimer.stop();
removalTimer.stop() removalTimer.stop();
resumeTimer.stop() resumeTimer.stop();
isRemoving = false isRemoving = false;
hoverCount = 0 hoverCount = 0;
if (Settings.data.general.animationDisabled) { if (Settings.data.general.animationDisabled) {
slideOffset = 0 slideOffset = 0;
scaleValue = 1.0 scaleValue = 1.0;
opacityValue = 1.0 opacityValue = 1.0;
return return;
} }
slideOffset = slideInOffset slideOffset = slideInOffset;
scaleValue = 0.8 scaleValue = 0.8;
opacityValue = 0.0 opacityValue = 0.0;
animInDelayTimer.interval = animationDelay animInDelayTimer.interval = animationDelay;
animInDelayTimer.start() animInDelayTimer.start();
} }
Component.onCompleted: triggerEntryAnimation() Component.onCompleted: triggerEntryAnimation()
@@ -320,23 +320,23 @@ Variants {
repeat: false repeat: false
onTriggered: { onTriggered: {
if (card.isRemoving) if (card.isRemoving)
return return;
slideOffset = 0 slideOffset = 0;
scaleValue = 1.0 scaleValue = 1.0;
opacityValue = 1.0 opacityValue = 1.0;
} }
} }
function animateOut() { function animateOut() {
if (isRemoving) if (isRemoving)
return return;
animInDelayTimer.stop() animInDelayTimer.stop();
resumeTimer.stop() resumeTimer.stop();
isRemoving = true isRemoving = true;
if (!Settings.data.general.animationDisabled) { if (!Settings.data.general.animationDisabled) {
slideOffset = slideOutOffset slideOffset = slideOutOffset;
scaleValue = 0.8 scaleValue = 0.8;
opacityValue = 0.0 opacityValue = 0.0;
} }
} }
@@ -345,13 +345,13 @@ Variants {
interval: Style.animationSlow interval: Style.animationSlow
repeat: false repeat: false
onTriggered: { onTriggered: {
NotificationService.dismissActiveNotification(notificationId) NotificationService.dismissActiveNotification(notificationId);
} }
} }
onIsRemovingChanged: { onIsRemovingChanged: {
if (isRemoving) { if (isRemoving) {
removalTimer.start() removalTimer.start();
} }
} }
@@ -478,9 +478,9 @@ Variants {
property string parentNotificationId: notificationId property string parentNotificationId: notificationId
property var parsedActions: { property var parsedActions: {
try { try {
return model.actionsJson ? JSON.parse(model.actionsJson) : [] return model.actionsJson ? JSON.parse(model.actionsJson) : [];
} catch (e) { } catch (e) {
return [] return [];
} }
} }
visible: parsedActions.length > 0 visible: parsedActions.length > 0
@@ -495,11 +495,11 @@ Variants {
onExited: card.hoverCount-- onExited: card.hoverCount--
text: { text: {
var actionText = actionData.text || "Open" var actionText = actionData.text || "Open";
if (actionText.includes(",")) { if (actionText.includes(",")) {
return actionText.split(",")[1] || actionText return actionText.split(",")[1] || actionText;
} }
return actionText return actionText;
} }
fontSize: Style.fontSizeS fontSize: Style.fontSizeS
backgroundColor: Color.mPrimary backgroundColor: Color.mPrimary
@@ -508,7 +508,7 @@ Variants {
outlined: false outlined: false
implicitHeight: 24 implicitHeight: 24
onClicked: { onClicked: {
NotificationService.invokeAction(parent.parentNotificationId, actionData.identifier) NotificationService.invokeAction(parent.parentNotificationId, actionData.identifier);
} }
} }
} }
@@ -528,8 +528,8 @@ Variants {
anchors.rightMargin: Style.marginXL anchors.rightMargin: Style.marginXL
onClicked: { onClicked: {
NotificationService.removeFromHistory(model.id) NotificationService.removeFromHistory(model.id);
animateOut() animateOut();
} }
} }
} }

View File

@@ -1,15 +1,15 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Effects import QtQuick.Effects
import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Wayland
import Quickshell.Services.Pipewire import Quickshell.Services.Pipewire
import Quickshell.Wayland
import qs.Commons import qs.Commons
import qs.Widgets
import qs.Services.Hardware import qs.Services.Hardware
import qs.Services.Media import qs.Services.Media
import qs.Services.System import qs.Services.System
import qs.Widgets
// Unified OSD component that displays volume, input volume, and brightness changes // Unified OSD component that displays volume, input volume, and brightness changes
Variants { Variants {
@@ -52,54 +52,54 @@ Variants {
switch (currentOSDType) { switch (currentOSDType) {
case "volume": case "volume":
if (isMuted) if (isMuted)
return "volume-mute" return "volume-mute";
if (currentVolume <= Number.EPSILON) if (currentVolume <= Number.EPSILON)
return "volume-zero" return "volume-zero";
return currentVolume <= 0.5 ? "volume-low" : "volume-high" return currentVolume <= 0.5 ? "volume-low" : "volume-high";
case "inputVolume": case "inputVolume":
return isInputMuted ? "microphone-off" : "microphone" return isInputMuted ? "microphone-off" : "microphone";
case "brightness": case "brightness":
return currentBrightness <= 0.5 ? "brightness-low" : "brightness-high" return currentBrightness <= 0.5 ? "brightness-low" : "brightness-high";
default: default:
return "" return "";
} }
} }
function getCurrentValue() { function getCurrentValue() {
switch (currentOSDType) { switch (currentOSDType) {
case "volume": case "volume":
return isMuted ? 0 : currentVolume return isMuted ? 0 : currentVolume;
case "inputVolume": case "inputVolume":
return isInputMuted ? 0 : currentInputVolume return isInputMuted ? 0 : currentInputVolume;
case "brightness": case "brightness":
return currentBrightness return currentBrightness;
default: default:
return 0 return 0;
} }
} }
function getMaxValue() { function getMaxValue() {
if (currentOSDType === "volume" || currentOSDType === "inputVolume") { 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() { function getDisplayPercentage() {
const value = getCurrentValue() const value = getCurrentValue();
const max = getMaxValue() const max = getMaxValue();
const pct = Math.round(Math.min(max, value) * 100) const pct = Math.round(Math.min(max, value) * 100);
return pct + "%" return pct + "%";
} }
function getProgressColor() { function getProgressColor() {
const isMutedState = (currentOSDType === "volume" && isMuted) || (currentOSDType === "inputVolume" && isInputMuted) const isMutedState = (currentOSDType === "volume" && isMuted) || (currentOSDType === "inputVolume" && isInputMuted);
return isMutedState ? Color.mError : Color.mPrimary return isMutedState ? Color.mError : Color.mPrimary;
} }
function getIconColor() { function getIconColor() {
const isMutedState = (currentOSDType === "volume" && isMuted) || (currentOSDType === "inputVolume" && isInputMuted) const isMutedState = (currentOSDType === "volume" && isMuted) || (currentOSDType === "inputVolume" && isInputMuted);
return isMutedState ? Color.mError : Color.mOnSurface return isMutedState ? Color.mError : Color.mOnSurface;
} }
// ============================================================================ // ============================================================================
@@ -108,35 +108,35 @@ Variants {
function initializeAudioValues() { function initializeAudioValues() {
// Initialize output volume // Initialize output volume
if (AudioService.sink?.ready && AudioService.sink?.audio && lastKnownVolume < 0) { if (AudioService.sink?.ready && AudioService.sink?.audio && lastKnownVolume < 0) {
const vol = AudioService.volume const vol = AudioService.volume;
if (vol !== undefined && !isNaN(vol)) { if (vol !== undefined && !isNaN(vol)) {
lastKnownVolume = vol lastKnownVolume = vol;
volumeInitialized = true volumeInitialized = true;
muteInitialized = true muteInitialized = true;
} }
} }
// Initialize input volume // Initialize input volume
if (AudioService.hasInput && AudioService.source?.ready && AudioService.source?.audio && lastKnownInputVolume < 0) { if (AudioService.hasInput && AudioService.source?.ready && AudioService.source?.audio && lastKnownInputVolume < 0) {
const inputVol = AudioService.inputVolume const inputVol = AudioService.inputVolume;
if (inputVol !== undefined && !isNaN(inputVol)) { if (inputVol !== undefined && !isNaN(inputVol)) {
lastKnownInputVolume = inputVol lastKnownInputVolume = inputVol;
inputInitialized = true inputInitialized = true;
} }
} }
} }
function resetOutputInit() { function resetOutputInit() {
lastKnownVolume = -1 lastKnownVolume = -1;
volumeInitialized = false volumeInitialized = false;
muteInitialized = false muteInitialized = false;
Qt.callLater(initializeAudioValues) Qt.callLater(initializeAudioValues);
} }
function resetInputInit() { function resetInputInit() {
lastKnownInputVolume = -1 lastKnownInputVolume = -1;
inputInitialized = false inputInitialized = false;
Qt.callLater(initializeAudioValues) Qt.callLater(initializeAudioValues);
} }
// ============================================================================ // ============================================================================
@@ -144,48 +144,48 @@ Variants {
// ============================================================================ // ============================================================================
function connectBrightnessMonitors() { function connectBrightnessMonitors() {
for (var i = 0; i < BrightnessService.monitors.length; i++) { for (var i = 0; i < BrightnessService.monitors.length; i++) {
const monitor = BrightnessService.monitors[i] const monitor = BrightnessService.monitors[i];
monitor.brightnessUpdated.disconnect(onBrightnessChanged) monitor.brightnessUpdated.disconnect(onBrightnessChanged);
monitor.brightnessUpdated.connect(onBrightnessChanged) monitor.brightnessUpdated.connect(onBrightnessChanged);
} }
} }
function onBrightnessChanged(newBrightness) { function onBrightnessChanged(newBrightness) {
lastUpdatedBrightness = newBrightness lastUpdatedBrightness = newBrightness;
if (!brightnessInitialized) { if (!brightnessInitialized) {
brightnessInitialized = true brightnessInitialized = true;
return return;
} }
showOSD("brightness") showOSD("brightness");
} }
// ============================================================================ // ============================================================================
// OSD Display Control // OSD Display Control
// ============================================================================ // ============================================================================
function showOSD(type) { function showOSD(type) {
currentOSDType = type currentOSDType = type;
if (!root.active) { if (!root.active) {
root.active = true root.active = true;
} }
if (root.item) { if (root.item) {
root.item.showOSD() root.item.showOSD();
} else { } else {
Qt.callLater(() => { Qt.callLater(() => {
if (root.item) if (root.item)
root.item.showOSD() root.item.showOSD();
}) });
} }
} }
function hideOSD() { function hideOSD() {
if (root.item?.osdItem) { if (root.item?.osdItem) {
root.item.osdItem.hideImmediately() root.item.osdItem.hideImmediately();
} else if (root.active) { } else if (root.active) {
root.active = false root.active = false;
} }
} }
@@ -199,15 +199,15 @@ Variants {
function onReadyChanged() { function onReadyChanged() {
if (Pipewire.ready) if (Pipewire.ready)
Qt.callLater(initializeAudioValues) Qt.callLater(initializeAudioValues);
} }
function onDefaultAudioSinkChanged() { function onDefaultAudioSinkChanged() {
resetOutputInit() resetOutputInit();
} }
function onDefaultAudioSourceChanged() { function onDefaultAudioSourceChanged() {
resetInputInit() resetInputInit();
} }
} }
@@ -217,72 +217,68 @@ Variants {
function onSinkChanged() { function onSinkChanged() {
if (AudioService.sink?.ready && AudioService.sink?.audio) { if (AudioService.sink?.ready && AudioService.sink?.audio) {
resetOutputInit() resetOutputInit();
} }
} }
function onSourceChanged() { function onSourceChanged() {
if (AudioService.hasInput && AudioService.source?.ready && AudioService.source?.audio) { if (AudioService.hasInput && AudioService.source?.ready && AudioService.source?.audio) {
resetInputInit() resetInputInit();
} }
} }
function onVolumeChanged() { function onVolumeChanged() {
if (lastKnownVolume < 0) { if (lastKnownVolume < 0) {
initializeAudioValues() initializeAudioValues();
if (lastKnownVolume < 0) if (lastKnownVolume < 0)
return return;
} }
if (!volumeInitialized) if (!volumeInitialized)
return return;
if (Math.abs(AudioService.volume - lastKnownVolume) > 0.001) { if (Math.abs(AudioService.volume - lastKnownVolume) > 0.001) {
lastKnownVolume = AudioService.volume lastKnownVolume = AudioService.volume;
showOSD("volume") showOSD("volume");
} }
} }
function onMutedChanged() { function onMutedChanged() {
if (lastKnownVolume < 0) { if (lastKnownVolume < 0) {
initializeAudioValues() initializeAudioValues();
if (lastKnownVolume < 0) if (lastKnownVolume < 0)
return return;
} }
if (!muteInitialized) if (!muteInitialized)
return return;
showOSD("volume") showOSD("volume");
} }
function onInputVolumeChanged() { function onInputVolumeChanged() {
if (!AudioService.hasInput) if (!AudioService.hasInput)
return return;
if (lastKnownInputVolume < 0) { if (lastKnownInputVolume < 0) {
initializeAudioValues() initializeAudioValues();
if (lastKnownInputVolume < 0) if (lastKnownInputVolume < 0)
return return;
} }
if (!inputInitialized) if (!inputInitialized)
return return;
if (Math.abs(AudioService.inputVolume - lastKnownInputVolume) > 0.001) { if (Math.abs(AudioService.inputVolume - lastKnownInputVolume) > 0.001) {
lastKnownInputVolume = AudioService.inputVolume lastKnownInputVolume = AudioService.inputVolume;
showOSD("inputVolume") showOSD("inputVolume");
} }
} }
function onInputMutedChanged() { function onInputMutedChanged() {
if (!AudioService.hasInput) if (!AudioService.hasInput)
return return;
if (lastKnownInputVolume < 0) { if (lastKnownInputVolume < 0) {
initializeAudioValues() initializeAudioValues();
if (lastKnownInputVolume < 0) if (lastKnownInputVolume < 0)
return return;
} }
if (!inputInitialized) if (!inputInitialized)
return return;
showOSD("inputVolume") showOSD("inputVolume");
} }
} }
@@ -290,7 +286,7 @@ Variants {
Connections { Connections {
target: BrightnessService target: BrightnessService
function onMonitorsChanged() { function onMonitorsChanged() {
connectBrightnessMonitors() connectBrightnessMonitors();
} }
} }
@@ -301,9 +297,9 @@ Variants {
running: true running: true
onTriggered: { onTriggered: {
if (Pipewire.ready) if (Pipewire.ready)
initializeAudioValues() initializeAudioValues();
muteInitialized = true muteInitialized = true;
connectBrightnessMonitors() connectBrightnessMonitors();
} }
} }
@@ -314,22 +310,21 @@ Variants {
repeat: true repeat: true
onTriggered: { onTriggered: {
if (!Pipewire.ready) if (!Pipewire.ready)
return return;
const needsOutputInit = lastKnownVolume < 0;
const needsOutputInit = lastKnownVolume < 0 const needsInputInit = AudioService.hasInput && lastKnownInputVolume < 0;
const needsInputInit = AudioService.hasInput && lastKnownInputVolume < 0
if (needsOutputInit || needsInputInit) { if (needsOutputInit || needsInputInit) {
initializeAudioValues() initializeAudioValues();
// Stop timer if both are initialized // Stop timer if both are initialized
const outputDone = lastKnownVolume >= 0 const outputDone = lastKnownVolume >= 0;
const inputDone = !AudioService.hasInput || lastKnownInputVolume >= 0 const inputDone = !AudioService.hasInput || lastKnownInputVolume >= 0;
if (outputDone && inputDone) { if (outputDone && inputDone) {
running = false running = false;
} }
} else { } else {
running = false running = false;
} }
} }
} }
@@ -355,8 +350,8 @@ Variants {
readonly property int vWidth: Math.round(80 * Style.uiScaleRatio) readonly property int vWidth: Math.round(80 * Style.uiScaleRatio)
readonly property int vHeight: Math.round(280 * Style.uiScaleRatio) readonly property int vHeight: Math.round(280 * Style.uiScaleRatio)
readonly property int barThickness: { readonly property int barThickness: {
const base = Math.max(8, Math.round(8 * Style.uiScaleRatio)) const base = Math.max(8, Math.round(8 * Style.uiScaleRatio));
return base % 2 === 0 ? base : base + 1 return base % 2 === 0 ? base : base + 1;
} }
anchors.top: isTop anchors.top: isTop
@@ -366,15 +361,15 @@ Variants {
function calculateMargin(isAnchored, position) { function calculateMargin(isAnchored, position) {
if (!isAnchored) if (!isAnchored)
return 0 return 0;
let base = Style.marginM let base = Style.marginM;
if (Settings.data.bar.position === position) { if (Settings.data.bar.position === position) {
const isVertical = position === "top" || position === "bottom" const isVertical = position === "top" || position === "bottom";
const floatExtra = Settings.data.bar.floating ? (isVertical ? Settings.data.bar.marginVertical : Settings.data.bar.marginHorizontal) * Style.marginXL : 0 const floatExtra = Settings.data.bar.floating ? (isVertical ? Settings.data.bar.marginVertical : Settings.data.bar.marginHorizontal) * Style.marginXL : 0;
return Style.barHeight + base + floatExtra return Style.barHeight + base + floatExtra;
} }
return base return base;
} }
margins.top: calculateMargin(anchors.top, "top") margins.top: calculateMargin(anchors.top, "top")
@@ -422,9 +417,9 @@ Variants {
id: visibilityTimer id: visibilityTimer
interval: Style.animationNormal + 50 interval: Style.animationNormal + 50
onTriggered: { onTriggered: {
osdItem.visible = false osdItem.visible = false;
root.currentOSDType = "" root.currentOSDType = "";
root.active = false root.active = false;
} }
} }
@@ -436,8 +431,8 @@ Variants {
color: Qt.alpha(Color.mSurface, Settings.data.osd.backgroundOpacity || 1.0) color: Qt.alpha(Color.mSurface, Settings.data.osd.backgroundOpacity || 1.0)
border.color: Qt.alpha(Color.mOutline, Settings.data.osd.backgroundOpacity || 1.0) border.color: Qt.alpha(Color.mOutline, Settings.data.osd.backgroundOpacity || 1.0)
border.width: { border.width: {
const bw = Math.max(2, Style.borderM) const bw = Math.max(2, Style.borderM);
return bw % 2 === 0 ? bw : bw + 1 return bw % 2 === 0 ? bw : bw + 1;
} }
} }
@@ -608,39 +603,39 @@ Variants {
} }
function show() { function show() {
hideTimer.stop() hideTimer.stop();
visibilityTimer.stop() visibilityTimer.stop();
osdItem.visible = true osdItem.visible = true;
Qt.callLater(() => { Qt.callLater(() => {
osdItem.opacity = 1 osdItem.opacity = 1;
osdItem.scale = 1.0 osdItem.scale = 1.0;
}) });
hideTimer.start() hideTimer.start();
} }
function hide() { function hide() {
hideTimer.stop() hideTimer.stop();
visibilityTimer.stop() visibilityTimer.stop();
osdItem.opacity = 0 osdItem.opacity = 0;
osdItem.scale = 0.85 osdItem.scale = 0.85;
visibilityTimer.start() visibilityTimer.start();
} }
function hideImmediately() { function hideImmediately() {
hideTimer.stop() hideTimer.stop();
visibilityTimer.stop() visibilityTimer.stop();
osdItem.opacity = 0 osdItem.opacity = 0;
osdItem.scale = 0.85 osdItem.scale = 0.85;
osdItem.visible = false osdItem.visible = false;
root.currentOSDType = "" root.currentOSDType = "";
root.active = false root.active = false;
} }
} }
function showOSD() { function showOSD() {
osdItem.show() osdItem.show();
} }
} }
} }

View File

@@ -1,12 +1,12 @@
import QtQuick import QtQuick
import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Services.Pipewire import Quickshell.Services.Pipewire
import qs.Commons import qs.Commons
import qs.Widgets
import qs.Modules.MainScreen import qs.Modules.MainScreen
import qs.Services.Media import qs.Services.Media
import qs.Widgets
SmartPanel { SmartPanel {
id: root id: root
@@ -25,7 +25,7 @@ SmartPanel {
target: AudioService.sink?.audio ? AudioService.sink?.audio : null target: AudioService.sink?.audio ? AudioService.sink?.audio : null
function onVolumeChanged() { function onVolumeChanged() {
if (!localOutputVolumeChanging) { if (!localOutputVolumeChanging) {
localOutputVolume = AudioService.volume localOutputVolume = AudioService.volume;
} }
} }
} }
@@ -34,7 +34,7 @@ SmartPanel {
target: AudioService.source?.audio ? AudioService.source?.audio : null target: AudioService.source?.audio ? AudioService.source?.audio : null
function onVolumeChanged() { function onVolumeChanged() {
if (!localInputVolumeChanging) { if (!localInputVolumeChanging) {
localInputVolume = AudioService.inputVolume localInputVolume = AudioService.inputVolume;
} }
} }
} }
@@ -46,10 +46,10 @@ SmartPanel {
repeat: true repeat: true
onTriggered: { onTriggered: {
if (Math.abs(localOutputVolume - AudioService.volume) >= 0.01) { if (Math.abs(localOutputVolume - AudioService.volume) >= 0.01) {
AudioService.setVolume(localOutputVolume) AudioService.setVolume(localOutputVolume);
} }
if (Math.abs(localInputVolume - AudioService.inputVolume) >= 0.01) { 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") tooltipText: I18n.tr("tooltips.output-muted")
baseSize: Style.baseWidgetSize * 0.8 baseSize: Style.baseWidgetSize * 0.8
onClicked: { onClicked: {
AudioService.setOutputMuted(!AudioService.muted) AudioService.setOutputMuted(!AudioService.muted);
} }
} }
@@ -104,7 +104,7 @@ SmartPanel {
tooltipText: I18n.tr("tooltips.input-muted") tooltipText: I18n.tr("tooltips.input-muted")
baseSize: Style.baseWidgetSize * 0.8 baseSize: Style.baseWidgetSize * 0.8
onClicked: { onClicked: {
AudioService.setInputMuted(!AudioService.inputMuted) AudioService.setInputMuted(!AudioService.inputMuted);
} }
} }
@@ -113,7 +113,7 @@ SmartPanel {
tooltipText: I18n.tr("tooltips.close") tooltipText: I18n.tr("tooltips.close")
baseSize: Style.baseWidgetSize * 0.8 baseSize: Style.baseWidgetSize * 0.8
onClicked: { onClicked: {
root.close() root.close();
} }
} }
} }
@@ -179,8 +179,8 @@ SmartPanel {
text: modelData.description text: modelData.description
checked: AudioService.sink?.id === modelData.id checked: AudioService.sink?.id === modelData.id
onClicked: { onClicked: {
AudioService.setAudioSink(modelData) AudioService.setAudioSink(modelData);
localOutputVolume = AudioService.volume localOutputVolume = AudioService.volume;
} }
Layout.fillWidth: true Layout.fillWidth: true
} }

View File

@@ -1,12 +1,12 @@
import QtQuick import QtQuick
import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import qs.Commons import qs.Commons
import qs.Modules.MainScreen
import qs.Services.Hardware import qs.Services.Hardware
import qs.Widgets import qs.Widgets
import qs.Modules.MainScreen
SmartPanel { SmartPanel {
id: root id: root
@@ -14,21 +14,25 @@ SmartPanel {
property var optionsModel: [] property var optionsModel: []
function updateOptionsModel() { function updateOptionsModel() {
let newOptions = [{ let newOptions = [
"id": BatteryService.ChargingMode.Full, {
"label": "battery.panel.full" "id": BatteryService.ChargingMode.Full,
}, { "label": "battery.panel.full"
"id": BatteryService.ChargingMode.Balanced, },
"label": "battery.panel.balanced" {
}, { "id": BatteryService.ChargingMode.Balanced,
"id": BatteryService.ChargingMode.Lifespan, "label": "battery.panel.balanced"
"label": "battery.panel.lifespan" },
}] {
root.optionsModel = newOptions "id": BatteryService.ChargingMode.Lifespan,
"label": "battery.panel.lifespan"
}
];
root.optionsModel = newOptions;
} }
onOpened: { onOpened: {
updateOptionsModel() updateOptionsModel();
} }
ButtonGroup { ButtonGroup {
@@ -49,7 +53,7 @@ SmartPanel {
}) })
checked: BatteryService.chargingMode === modelData.id checked: BatteryService.chargingMode === modelData.id
onClicked: { onClicked: {
BatteryService.setChargingMode(modelData.id) BatteryService.setChargingMode(modelData.id);
} }
Layout.fillWidth: true Layout.fillWidth: true
} }
@@ -131,7 +135,7 @@ SmartPanel {
tooltipText: I18n.tr("tooltips.close") tooltipText: I18n.tr("tooltips.close")
baseSize: Style.baseWidgetSize * 0.8 baseSize: Style.baseWidgetSize * 0.8
onClicked: { onClicked: {
root.close() root.close();
} }
} }
} }

View File

@@ -1,6 +1,6 @@
import QtQuick import QtQuick
import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Bluetooth import Quickshell.Bluetooth
import Quickshell.Wayland import Quickshell.Wayland
@@ -13,9 +13,7 @@ NBox {
property string label: "" property string label: ""
property string tooltipText: "" property string tooltipText: ""
property var model: { property var model: {}
}
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: column.implicitHeight + Style.marginM * 2 Layout.preferredHeight: column.implicitHeight + Style.marginM * 2
@@ -52,10 +50,10 @@ NBox {
function getContentColor(defaultColor = Color.mOnSurface) { function getContentColor(defaultColor = Color.mOnSurface) {
if (modelData.pairing || modelData.state === BluetoothDeviceState.Connecting) if (modelData.pairing || modelData.state === BluetoothDeviceState.Connecting)
return Color.mPrimary return Color.mPrimary;
if (modelData.blocked) if (modelData.blocked)
return Color.mError return Color.mError;
return defaultColor return defaultColor;
} }
Layout.fillWidth: true Layout.fillWidth: true
@@ -154,33 +152,33 @@ NBox {
fontWeight: Style.fontWeightMedium fontWeight: Style.fontWeightMedium
backgroundColor: { backgroundColor: {
if (device.canDisconnect && !isBusy) { if (device.canDisconnect && !isBusy) {
return Color.mError return Color.mError;
} }
return Color.mPrimary return Color.mPrimary;
} }
tooltipText: root.tooltipText tooltipText: root.tooltipText
text: { text: {
if (modelData.pairing) { if (modelData.pairing) {
return I18n.tr("bluetooth.panel.pairing") return I18n.tr("bluetooth.panel.pairing");
} }
if (modelData.blocked) { if (modelData.blocked) {
return I18n.tr("bluetooth.panel.blocked") return I18n.tr("bluetooth.panel.blocked");
} }
if (modelData.connected) { 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) icon: (isBusy ? "busy" : null)
onClicked: { onClicked: {
if (modelData.connected) { if (modelData.connected) {
BluetoothService.disconnectDevice(modelData) BluetoothService.disconnectDevice(modelData);
} else { } else {
BluetoothService.connectDeviceWithTrust(modelData) BluetoothService.connectDeviceWithTrust(modelData);
} }
} }
onRightClicked: { onRightClicked: {
BluetoothService.forgetDevice(modelData) BluetoothService.forgetDevice(modelData);
} }
} }
} }

View File

@@ -1,13 +1,13 @@
import QtQuick import QtQuick
import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Bluetooth import Quickshell.Bluetooth
import qs.Commons import qs.Commons
import qs.Services.UI
import qs.Services.Networking
import qs.Widgets
import qs.Modules.MainScreen import qs.Modules.MainScreen
import qs.Services.Networking
import qs.Services.UI
import qs.Widgets
SmartPanel { SmartPanel {
id: root id: root
@@ -65,7 +65,7 @@ SmartPanel {
baseSize: Style.baseWidgetSize * 0.8 baseSize: Style.baseWidgetSize * 0.8
onClicked: { onClicked: {
if (BluetoothService.adapter) { 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") tooltipText: I18n.tr("tooltips.close")
baseSize: Style.baseWidgetSize * 0.8 baseSize: Style.baseWidgetSize * 0.8
onClicked: { onClicked: {
root.close() root.close();
} }
} }
} }
@@ -143,9 +143,9 @@ SmartPanel {
label: I18n.tr("bluetooth.panel.connected-devices") label: I18n.tr("bluetooth.panel.connected-devices")
property var items: { property var items: {
if (!BluetoothService.adapter || !Bluetooth.devices) if (!BluetoothService.adapter || !Bluetooth.devices)
return [] return [];
var filtered = Bluetooth.devices.values.filter(dev => dev && !dev.blocked && dev.connected) var filtered = Bluetooth.devices.values.filter(dev => dev && !dev.blocked && dev.connected);
return BluetoothService.sortDevices(filtered) return BluetoothService.sortDevices(filtered);
} }
model: items model: items
visible: items.length > 0 visible: items.length > 0
@@ -158,9 +158,9 @@ SmartPanel {
tooltipText: I18n.tr("tooltips.connect-disconnect-devices") tooltipText: I18n.tr("tooltips.connect-disconnect-devices")
property var items: { property var items: {
if (!BluetoothService.adapter || !Bluetooth.devices) if (!BluetoothService.adapter || !Bluetooth.devices)
return [] return [];
var filtered = Bluetooth.devices.values.filter(dev => dev && !dev.blocked && !dev.connected && (dev.paired || dev.trusted)) var filtered = Bluetooth.devices.values.filter(dev => dev && !dev.blocked && !dev.connected && (dev.paired || dev.trusted));
return BluetoothService.sortDevices(filtered) return BluetoothService.sortDevices(filtered);
} }
model: items model: items
visible: items.length > 0 visible: items.length > 0
@@ -172,9 +172,9 @@ SmartPanel {
label: I18n.tr("bluetooth.panel.available-devices") label: I18n.tr("bluetooth.panel.available-devices")
property var items: { property var items: {
if (!BluetoothService.adapter || !Bluetooth.devices) if (!BluetoothService.adapter || !Bluetooth.devices)
return [] return [];
var filtered = Bluetooth.devices.values.filter(dev => dev && !dev.blocked && !dev.paired && !dev.trusted) var filtered = Bluetooth.devices.values.filter(dev => dev && !dev.blocked && !dev.paired && !dev.trusted);
return BluetoothService.sortDevices(filtered) return BluetoothService.sortDevices(filtered);
} }
model: items model: items
visible: items.length > 0 visible: items.length > 0
@@ -187,13 +187,13 @@ SmartPanel {
Layout.preferredHeight: columnScanning.implicitHeight + Style.marginM * 2 Layout.preferredHeight: columnScanning.implicitHeight + Style.marginM * 2
visible: { visible: {
if (!BluetoothService.adapter || !BluetoothService.adapter.discovering || !Bluetooth.devices) { if (!BluetoothService.adapter || !BluetoothService.adapter.discovering || !Bluetooth.devices) {
return false return false;
} }
var availableCount = Bluetooth.devices.values.filter(dev => { var availableCount = Bluetooth.devices.values.filter(dev => {
return dev && !dev.paired && !dev.pairing && !dev.blocked && (dev.signalStrength === undefined || dev.signalStrength > 0) return dev && !dev.paired && !dev.pairing && !dev.blocked && (dev.signalStrength === undefined || dev.signalStrength > 0);
}).length }).length;
return (availableCount === 0) return (availableCount === 0);
} }
ColumnLayout { ColumnLayout {

View File

@@ -21,22 +21,22 @@ SmartPanel {
// Helper function to calculate ISO week number // Helper function to calculate ISO week number
function getISOWeekNumber(date) { function getISOWeekNumber(date) {
const target = new Date(date.valueOf()) const target = new Date(date.valueOf());
const dayNr = (date.getDay() + 6) % 7 const dayNr = (date.getDay() + 6) % 7;
target.setDate(target.getDate() - dayNr + 3) target.setDate(target.getDate() - dayNr + 3);
const firstThursday = new Date(target.getFullYear(), 0, 4) const firstThursday = new Date(target.getFullYear(), 0, 4);
const diff = target - firstThursday const diff = target - firstThursday;
const oneWeek = 1000 * 60 * 60 * 24 * 7 const oneWeek = 1000 * 60 * 60 * 24 * 7;
const weekNumber = 1 + Math.round(diff / oneWeek) const weekNumber = 1 + Math.round(diff / oneWeek);
return weekNumber return weekNumber;
} }
// Helper function to check if an event is all-day // Helper function to check if an event is all-day
function isAllDayEvent(event) { function isAllDayEvent(event) {
const duration = event.end - event.start const duration = event.end - event.start;
const startDate = new Date(event.start * 1000) const startDate = new Date(event.start * 1000);
const isAtMidnight = startDate.getHours() === 0 && startDate.getMinutes() === 0 const isAtMidnight = startDate.getHours() === 0 && startDate.getMinutes() === 0;
return duration === 86400 && isAtMidnight return duration === 86400 && isAtMidnight;
} }
panelContent: Item { panelContent: Item {
@@ -50,12 +50,12 @@ SmartPanel {
property real calendarGridHeight: { property real calendarGridHeight: {
// Calculate number of weeks in the calendar grid // 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) // 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 { ColumnLayout {
id: content id: content
@@ -69,17 +69,17 @@ SmartPanel {
readonly property bool weatherReady: Settings.data.location.weatherEnabled && (LocationService.data.weather !== null) readonly property bool weatherReady: Settings.data.location.weatherEnabled && (LocationService.data.weather !== null)
function checkIsCurrentMonth() { function checkIsCurrentMonth() {
return (now.getMonth() === grid.month) && (now.getFullYear() === grid.year) return (now.getMonth() === grid.month) && (now.getFullYear() === grid.year);
} }
Component.onCompleted: { Component.onCompleted: {
isCurrentMonth = checkIsCurrentMonth() isCurrentMonth = checkIsCurrentMonth();
} }
Connections { Connections {
target: Time target: Time
function onNowChanged() { function onNowChanged() {
content.isCurrentMonth = content.checkIsCurrentMonth() content.isCurrentMonth = content.checkIsCurrentMonth();
} }
} }
@@ -87,7 +87,7 @@ SmartPanel {
target: I18n target: I18n
function onLanguageChanged() { function onLanguageChanged() {
// Force update of day names when language changes // Force update of day names when language changes
grid.month = grid.month grid.month = grid.month;
} }
} }
@@ -180,11 +180,11 @@ SmartPanel {
NText { NText {
text: { text: {
if (!Settings.data.location.weatherEnabled) if (!Settings.data.location.weatherEnabled)
return "" return "";
if (!content.weatherReady) if (!content.weatherReady)
return I18n.tr("calendar.weather.loading") return I18n.tr("calendar.weather.loading");
const chunks = Settings.data.location.name.split(",") const chunks = Settings.data.location.name.split(",");
return chunks[0] return chunks[0];
} }
pointSize: Style.fontSizeM pointSize: Style.fontSizeM
font.weight: Style.fontWeightMedium font.weight: Style.fontWeightMedium
@@ -227,11 +227,11 @@ SmartPanel {
id: calendar id: calendar
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: { Layout.preferredHeight: {
const navigationHeight = Style.baseWidgetSize // Navigation buttons row const navigationHeight = Style.baseWidgetSize; // Navigation buttons row
const dayNamesHeight = Style.baseWidgetSize * 0.6 // Day names header row const dayNamesHeight = Style.baseWidgetSize * 0.6; // Day names header row
const innerMargins = Style.marginM * 2 // Top and bottom margins inside NBox const innerMargins = Style.marginM * 2; // Top and bottom margins inside NBox
const innerSpacing = Style.marginS * 2 // Spacing between nav, dayNames, and grid (2 gaps) const innerSpacing = Style.marginS * 2; // Spacing between nav, dayNames, and grid (2 gaps)
return navigationHeight + dayNamesHeight + calendarGridHeight + innerMargins + innerSpacing return navigationHeight + dayNamesHeight + calendarGridHeight + innerMargins + innerSpacing;
} }
Behavior on Layout.preferredWidth { Behavior on Layout.preferredWidth {
@@ -257,46 +257,46 @@ SmartPanel {
NIconButton { NIconButton {
icon: "chevron-left" icon: "chevron-left"
onClicked: { onClicked: {
let newDate = new Date(grid.year, grid.month - 1, 1) let newDate = new Date(grid.year, grid.month - 1, 1);
grid.year = newDate.getFullYear() grid.year = newDate.getFullYear();
grid.month = newDate.getMonth() grid.month = newDate.getMonth();
content.isCurrentMonth = content.checkIsCurrentMonth() content.isCurrentMonth = content.checkIsCurrentMonth();
const now = new Date() const now = new Date();
const monthStart = new Date(grid.year, grid.month, 1) const monthStart = new Date(grid.year, grid.month, 1);
const monthEnd = new Date(grid.year, grid.month + 1, 0) const monthEnd = new Date(grid.year, grid.month + 1, 0);
const daysBehind = Math.max(0, Math.ceil((now - monthStart) / (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))) 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 { NIconButton {
icon: "calendar" icon: "calendar"
onClicked: { onClicked: {
grid.month = now.getMonth() grid.month = now.getMonth();
grid.year = now.getFullYear() grid.year = now.getFullYear();
content.isCurrentMonth = true content.isCurrentMonth = true;
CalendarService.loadEvents() CalendarService.loadEvents();
} }
} }
NIconButton { NIconButton {
icon: "chevron-right" icon: "chevron-right"
onClicked: { onClicked: {
let newDate = new Date(grid.year, grid.month + 1, 1) let newDate = new Date(grid.year, grid.month + 1, 1);
grid.year = newDate.getFullYear() grid.year = newDate.getFullYear();
grid.month = newDate.getMonth() grid.month = newDate.getMonth();
content.isCurrentMonth = content.checkIsCurrentMonth() content.isCurrentMonth = content.checkIsCurrentMonth();
const now = new Date() const now = new Date();
const monthStart = new Date(grid.year, grid.month, 1) const monthStart = new Date(grid.year, grid.month, 1);
const monthEnd = new Date(grid.year, grid.month + 1, 0) const monthEnd = new Date(grid.year, grid.month + 1, 0);
const daysBehind = Math.max(0, Math.ceil((now - monthStart) / (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))) 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 { NText {
anchors.centerIn: parent anchors.centerIn: parent
text: { text: {
let dayIndex = (content.firstDayOfWeek + index) % 7 let dayIndex = (content.firstDayOfWeek + index) % 7;
const dayName = I18n.locale.dayName(dayIndex, Locale.ShortFormat) const dayName = I18n.locale.dayName(dayIndex, Locale.ShortFormat);
return dayName.substring(0, 2).toUpperCase() return dayName.substring(0, 2).toUpperCase();
} }
color: Color.mPrimary color: Color.mPrimary
pointSize: Style.fontSizeS pointSize: Style.fontSizeS
@@ -345,55 +345,55 @@ SmartPanel {
// Helper function to check if a date has events // Helper function to check if a date has events
function hasEventsOnDate(year, month, day) { function hasEventsOnDate(year, month, day) {
if (!CalendarService.available || CalendarService.events.length === 0) if (!CalendarService.available || CalendarService.events.length === 0)
return false return false;
const targetDate = new Date(year, month, day) const targetDate = new Date(year, month, day);
const targetStart = new Date(targetDate.getFullYear(), targetDate.getMonth(), targetDate.getDate()).getTime() / 1000 const targetStart = new Date(targetDate.getFullYear(), targetDate.getMonth(), targetDate.getDate()).getTime() / 1000;
const targetEnd = targetStart + 86400 // +24 hours const targetEnd = targetStart + 86400; // +24 hours
return CalendarService.events.some(event => { return CalendarService.events.some(event => {
// Check if event starts or overlaps with this day // 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 // Helper function to get events for a specific date
function getEventsForDate(year, month, day) { function getEventsForDate(year, month, day) {
if (!CalendarService.available || CalendarService.events.length === 0) if (!CalendarService.available || CalendarService.events.length === 0)
return [] return [];
const targetDate = new Date(year, month, day) const targetDate = new Date(year, month, day);
const targetStart = Math.floor(new Date(targetDate.getFullYear(), targetDate.getMonth(), targetDate.getDate()).getTime() / 1000) const targetStart = Math.floor(new Date(targetDate.getFullYear(), targetDate.getMonth(), targetDate.getDate()).getTime() / 1000);
const targetEnd = targetStart + 86400 // +24 hours const targetEnd = targetStart + 86400; // +24 hours
return CalendarService.events.filter(event => { 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 // Helper function to check if an event is multi-day
function isMultiDayEvent(event) { function isMultiDayEvent(event) {
if (isAllDayEvent(event)) { if (isAllDayEvent(event)) {
return false return false;
} }
const startDate = new Date(event.start * 1000) const startDate = new Date(event.start * 1000);
const endDate = new Date(event.end * 1000) const endDate = new Date(event.end * 1000);
const startDateOnly = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate()) const startDateOnly = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate());
const endDateOnly = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.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 // Helper function to get color for a specific event
function getEventColor(event, isToday) { function getEventColor(event, isToday) {
if (isMultiDayEvent(event)) { if (isMultiDayEvent(event)) {
return isToday ? Color.mOnSecondary : Color.mTertiary return isToday ? Color.mOnSecondary : Color.mTertiary;
} else if (isAllDayEvent(event)) { } else if (isAllDayEvent(event)) {
return isToday ? Color.mOnSecondary : Color.mSecondary return isToday ? Color.mOnSecondary : Color.mSecondary;
} else { } else {
return isToday ? Color.mOnSecondary : Color.mPrimary return isToday ? Color.mOnSecondary : Color.mPrimary;
} }
} }
@@ -402,9 +402,9 @@ SmartPanel {
visible: Settings.data.location.showWeekNumberInCalendar visible: Settings.data.location.showWeekNumberInCalendar
Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 : 0 Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 : 0
Layout.preferredHeight: { Layout.preferredHeight: {
const numWeeks = weekNumbers ? weekNumbers.length : 5 const numWeeks = weekNumbers ? weekNumbers.length : 5;
const rowHeight = Style.baseWidgetSize * 0.9 + Style.marginXXS const rowHeight = Style.baseWidgetSize * 0.9 + Style.marginXXS;
return numWeeks * rowHeight return numWeeks * rowHeight;
} }
spacing: Style.marginXXS spacing: Style.marginXXS
@@ -417,33 +417,33 @@ SmartPanel {
property var weekNumbers: { property var weekNumbers: {
if (!grid.daysModel || grid.daysModel.length === 0) if (!grid.daysModel || grid.daysModel.length === 0)
return [] return [];
const weeks = [] const weeks = [];
const numWeeks = Math.ceil(grid.daysModel.length / 7) const numWeeks = Math.ceil(grid.daysModel.length / 7);
for (var i = 0; i < numWeeks; i++) { for (var i = 0; i < numWeeks; i++) {
const dayIndex = i * 7 const dayIndex = i * 7;
if (dayIndex < grid.daysModel.length) { if (dayIndex < grid.daysModel.length) {
const weekDay = grid.daysModel[dayIndex] const weekDay = grid.daysModel[dayIndex];
const date = new Date(weekDay.year, weekDay.month, weekDay.day) const date = new Date(weekDay.year, weekDay.month, weekDay.day);
// Get Thursday of this week for ISO week calculation // Get Thursday of this week for ISO week calculation
const firstDayOfWeek = content.firstDayOfWeek const firstDayOfWeek = content.firstDayOfWeek;
let thursday = new Date(date) let thursday = new Date(date);
if (firstDayOfWeek === 0) { if (firstDayOfWeek === 0) {
thursday.setDate(date.getDate() + 4) thursday.setDate(date.getDate() + 4);
} else if (firstDayOfWeek === 1) { } else if (firstDayOfWeek === 1) {
thursday.setDate(date.getDate() + 3) thursday.setDate(date.getDate() + 3);
} else { } else {
let daysToThursday = (4 - firstDayOfWeek + 7) % 7 let daysToThursday = (4 - firstDayOfWeek + 7) % 7;
thursday.setDate(date.getDate() + daysToThursday) thursday.setDate(date.getDate() + daysToThursday);
} }
weeks.push(root.getISOWeekNumber(thursday)) weeks.push(root.getISOWeekNumber(thursday));
} }
} }
return weeks return weeks;
} }
Repeater { Repeater {
@@ -466,9 +466,9 @@ SmartPanel {
id: grid id: grid
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: { Layout.preferredHeight: {
const numWeeks = daysModel ? Math.ceil(daysModel.length / 7) : 5 const numWeeks = daysModel ? Math.ceil(daysModel.length / 7) : 5;
const rowHeight = Style.baseWidgetSize * 0.9 + Style.marginXXS const rowHeight = Style.baseWidgetSize * 0.9 + Style.marginXXS;
return numWeeks * rowHeight return numWeeks * rowHeight;
} }
columns: 7 columns: 7
columnSpacing: Style.marginXXS columnSpacing: Style.marginXXS
@@ -486,51 +486,51 @@ SmartPanel {
// Calculate days to display // Calculate days to display
property var daysModel: { property var daysModel: {
const firstOfMonth = new Date(year, month, 1) const firstOfMonth = new Date(year, month, 1);
const lastOfMonth = new Date(year, month + 1, 0) const lastOfMonth = new Date(year, month + 1, 0);
const daysInMonth = lastOfMonth.getDate() const daysInMonth = lastOfMonth.getDate();
// Get first day of week (0 = Sunday, 1 = Monday, etc.) // Get first day of week (0 = Sunday, 1 = Monday, etc.)
const firstDayOfWeek = content.firstDayOfWeek const firstDayOfWeek = content.firstDayOfWeek;
const firstOfMonthDayOfWeek = firstOfMonth.getDay() const firstOfMonthDayOfWeek = firstOfMonth.getDay();
// Calculate days before first of month // 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 // Calculate days after last of month to complete the week
const lastOfMonthDayOfWeek = lastOfMonth.getDay() const lastOfMonthDayOfWeek = lastOfMonth.getDay();
const daysAfter = (firstDayOfWeek - lastOfMonthDayOfWeek - 1 + 7) % 7 const daysAfter = (firstDayOfWeek - lastOfMonthDayOfWeek - 1 + 7) % 7;
// Build array of day objects // Build array of day objects
const days = [] const days = [];
const today = new Date() const today = new Date();
// Previous month days // Previous month days
const prevMonth = new Date(year, month, 0) const prevMonth = new Date(year, month, 0);
const prevMonthDays = prevMonth.getDate() const prevMonthDays = prevMonth.getDate();
for (var i = daysBefore - 1; i >= 0; i--) { for (var i = daysBefore - 1; i >= 0; i--) {
const day = prevMonthDays - i const day = prevMonthDays - i;
const date = new Date(year, month - 1, day) const date = new Date(year, month - 1, day);
days.push({ days.push({
"day": day, "day": day,
"month": month - 1, "month": month - 1,
"year": month === 0 ? year - 1 : year, "year": month === 0 ? year - 1 : year,
"today": false, "today": false,
"currentMonth": false "currentMonth": false
}) });
} }
// Current month days // Current month days
for (var day = 1; day <= daysInMonth; day++) { for (var day = 1; day <= daysInMonth; day++) {
const date = new Date(year, month, day) const date = new Date(year, month, day);
const isToday = date.getFullYear() === today.getFullYear() && date.getMonth() === today.getMonth() && date.getDate() === today.getDate() const isToday = date.getFullYear() === today.getFullYear() && date.getMonth() === today.getMonth() && date.getDate() === today.getDate();
days.push({ days.push({
"day": day, "day": day,
"month": month, "month": month,
"year": year, "year": year,
"today": isToday, "today": isToday,
"currentMonth": true "currentMonth": true
}) });
} }
// Next month days (only if needed to complete the week) // Next month days (only if needed to complete the week)
@@ -541,10 +541,10 @@ SmartPanel {
"year": month === 11 ? year + 1 : year, "year": month === 11 ? year + 1 : year,
"today": false, "today": false,
"currentMonth": false "currentMonth": false
}) });
} }
return days return days;
} }
Repeater { Repeater {
@@ -566,10 +566,10 @@ SmartPanel {
text: modelData.day text: modelData.day
color: { color: {
if (modelData.today) if (modelData.today)
return Color.mOnSecondary return Color.mOnSecondary;
if (modelData.currentMonth) if (modelData.currentMonth)
return Color.mOnSurface return Color.mOnSurface;
return Color.mOnSurfaceVariant return Color.mOnSurfaceVariant;
} }
opacity: modelData.currentMonth ? 1.0 : 0.4 opacity: modelData.currentMonth ? 1.0 : 0.4
pointSize: Style.fontSizeM pointSize: Style.fontSizeM
@@ -602,35 +602,35 @@ SmartPanel {
enabled: Settings.data.location.showCalendarEvents enabled: Settings.data.location.showCalendarEvents
onEntered: { 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) { if (events.length > 0) {
const summaries = events.map(event => { const summaries = events.map(event => {
if (isAllDayEvent(event)) { if (isAllDayEvent(event)) {
return event.summary return event.summary;
} else { } else {
// Always format with '0' padding to ensure proper horizontal alignment // Always format with '0' padding to ensure proper horizontal alignment
const timeFormat = Settings.data.location.use12hourFormat ? "hh:mm AP" : "HH:mm" const timeFormat = Settings.data.location.use12hourFormat ? "hh:mm AP" : "HH:mm";
const start = new Date(event.start * 1000) const start = new Date(event.start * 1000);
const startFormatted = I18n.locale.toString(start, timeFormat) const startFormatted = I18n.locale.toString(start, timeFormat);
const end = new Date(event.end * 1000) const end = new Date(event.end * 1000);
const endFormatted = I18n.locale.toString(end, timeFormat) const endFormatted = I18n.locale.toString(end, timeFormat);
return `${startFormatted}-${endFormatted} ${event.summary}` return `${startFormatted}-${endFormatted} ${event.summary}`;
} }
}).join('\n') }).join('\n');
TooltipService.show(screen, parent, summaries, "auto", Style.tooltipDelay, Settings.data.ui.fontFixed) TooltipService.show(screen, parent, summaries, "auto", Style.tooltipDelay, Settings.data.ui.fontFixed);
} }
} }
onClicked: { 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) { if (ProgramCheckerService.gnomeCalendarAvailable) {
Quickshell.execDetached(["gnome-calendar", "--date", dateWithSlashes]) Quickshell.execDetached(["gnome-calendar", "--date", dateWithSlashes]);
root.close() root.close();
} }
} }
onExited: { onExited: {
TooltipService.hide() TooltipService.hide();
} }
} }

View File

@@ -16,10 +16,10 @@ NBox {
property bool localInputVolumeChanging: false property bool localInputVolumeChanging: false
Component.onCompleted: { Component.onCompleted: {
var vol = AudioService.volume var vol = AudioService.volume;
localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0 localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0;
var inputVol = AudioService.inputVolume var inputVol = AudioService.inputVolume;
localInputVolume = (inputVol !== undefined && !isNaN(inputVol)) ? inputVol : 0 localInputVolume = (inputVol !== undefined && !isNaN(inputVol)) ? inputVol : 0;
} }
// Timer to debounce volume changes // Timer to debounce volume changes
@@ -29,10 +29,10 @@ NBox {
repeat: true repeat: true
onTriggered: { onTriggered: {
if (Math.abs(localOutputVolume - AudioService.volume) >= 0.01) { if (Math.abs(localOutputVolume - AudioService.volume) >= 0.01) {
AudioService.setVolume(localOutputVolume) AudioService.setVolume(localOutputVolume);
} }
if (Math.abs(localInputVolume - AudioService.inputVolume) >= 0.01) { if (Math.abs(localInputVolume - AudioService.inputVolume) >= 0.01) {
AudioService.setInputVolume(localInputVolume) AudioService.setInputVolume(localInputVolume);
} }
} }
} }
@@ -42,8 +42,8 @@ NBox {
target: AudioService target: AudioService
function onVolumeChanged() { function onVolumeChanged() {
if (!localOutputVolumeChanging) { if (!localOutputVolumeChanging) {
var vol = AudioService.volume var vol = AudioService.volume;
localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0 localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0;
} }
} }
} }
@@ -52,8 +52,8 @@ NBox {
target: AudioService.sink?.audio ? AudioService.sink?.audio : null target: AudioService.sink?.audio ? AudioService.sink?.audio : null
function onVolumeChanged() { function onVolumeChanged() {
if (!localOutputVolumeChanging) { if (!localOutputVolumeChanging) {
var vol = AudioService.volume var vol = AudioService.volume;
localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0 localOutputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0;
} }
} }
} }
@@ -62,8 +62,8 @@ NBox {
target: AudioService target: AudioService
function onInputVolumeChanged() { function onInputVolumeChanged() {
if (!localInputVolumeChanging) { if (!localInputVolumeChanging) {
var vol = AudioService.inputVolume var vol = AudioService.inputVolume;
localInputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0 localInputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0;
} }
} }
} }
@@ -72,8 +72,8 @@ NBox {
target: AudioService.source?.audio ? AudioService.source?.audio : null target: AudioService.source?.audio ? AudioService.source?.audio : null
function onVolumeChanged() { function onVolumeChanged() {
if (!localInputVolumeChanging) { if (!localInputVolumeChanging) {
var vol = AudioService.inputVolume var vol = AudioService.inputVolume;
localInputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0 localInputVolume = (vol !== undefined && !isNaN(vol)) ? vol : 0;
} }
} }
} }
@@ -105,7 +105,7 @@ NBox {
colorFgHover: Color.mOnHover colorFgHover: Color.mOnHover
onClicked: { onClicked: {
if (AudioService.sink && AudioService.sink.audio) { if (AudioService.sink && AudioService.sink.audio) {
AudioService.sink.audio.muted = !AudioService.muted AudioService.sink.audio.muted = !AudioService.muted;
} }
} }
} }

View File

@@ -1,7 +1,7 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Effects import QtQuick.Effects
import QtQuick.Layouts
import Quickshell import Quickshell
import qs.Commons import qs.Commons
import qs.Services.Media import qs.Services.Media
@@ -22,7 +22,7 @@ NBox {
target: WallpaperService target: WallpaperService
function onWallpaperChanged(screenName, path) { function onWallpaperChanged(screenName, path) {
if (screenName === screen.name) { if (screenName === screen.name) {
wallpaper = path wallpaper = path;
} }
} }
} }
@@ -48,8 +48,8 @@ NBox {
// Background image that covers everything // Background image that covers everything
Image { Image {
readonly property int dim: Math.round(256 * Style.uiScaleRatio)
id: bgImage id: bgImage
readonly property int dim: Math.round(256 * Style.uiScaleRatio)
anchors.fill: parent anchors.fill: parent
source: MediaService.trackArtUrl || wallpaper source: MediaService.trackArtUrl || wallpaper
sourceSize: Qt.size(dim, dim) sourceSize: Qt.size(dim, dim)
@@ -81,13 +81,13 @@ NBox {
sourceComponent: { sourceComponent: {
switch (Settings.data.audio.visualizerType) { switch (Settings.data.audio.visualizerType) {
case "linear": case "linear":
return linearComponent return linearComponent;
case "mirrored": case "mirrored":
return mirroredComponent return mirroredComponent;
case "wave": case "wave":
return waveComponent return waveComponent;
default: default:
return null return null;
} }
} }
@@ -164,8 +164,8 @@ NBox {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
var menuItems = [] var menuItems = [];
var players = MediaService.getAvailablePlayers() var players = MediaService.getAvailablePlayers();
for (var i = 0; i < players.length; i++) { for (var i = 0; i < players.length; i++) {
menuItems.push({ menuItems.push({
"label": players[i].identity, "label": players[i].identity,
@@ -173,10 +173,10 @@ NBox {
"icon": "disc", "icon": "disc",
"enabled": true, "enabled": true,
"visible": true "visible": true
}) });
} }
playerContextMenu.model = menuItems playerContextMenu.model = menuItems;
playerContextMenu.openAtItem(playerSelectorButton, playerSelectorButton.width - playerContextMenu.width, playerSelectorButton.height) playerContextMenu.openAtItem(playerSelectorButton, playerSelectorButton.width - playerContextMenu.width, playerSelectorButton.height);
} }
} }
@@ -187,9 +187,9 @@ NBox {
verticalPolicy: ScrollBar.AlwaysOff verticalPolicy: ScrollBar.AlwaysOff
onTriggered: function (action) { onTriggered: function (action) {
var index = parseInt(action) var index = parseInt(action);
if (!isNaN(index)) { if (!isNaN(index)) {
MediaService.switchToPlayer(index) MediaService.switchToPlayer(index);
} }
} }
} }
@@ -276,11 +276,11 @@ NBox {
property real seekEpsilon: 0.01 property real seekEpsilon: 0.01
property real progressRatio: { property real progressRatio: {
if (!MediaService.currentPlayer || MediaService.trackLength <= 0) if (!MediaService.currentPlayer || MediaService.trackLength <= 0)
return 0 return 0;
const r = MediaService.currentPosition / MediaService.trackLength const r = MediaService.currentPosition / MediaService.trackLength;
if (isNaN(r) || !isFinite(r)) if (isNaN(r) || !isFinite(r))
return 0 return 0;
return Math.max(0, Math.min(1, r)) return Math.max(0, Math.min(1, r));
} }
property real effectiveRatio: (MediaService.isSeeking && localSeekRatio >= 0) ? Math.max(0, Math.min(1, localSeekRatio)) : progressRatio property real effectiveRatio: (MediaService.isSeeking && localSeekRatio >= 0) ? Math.max(0, Math.min(1, localSeekRatio)) : progressRatio
@@ -290,10 +290,10 @@ NBox {
repeat: false repeat: false
onTriggered: { onTriggered: {
if (MediaService.isSeeking && progressWrapper.localSeekRatio >= 0) { 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) { if (progressWrapper.lastSentSeekRatio < 0 || Math.abs(next - progressWrapper.lastSentSeekRatio) >= progressWrapper.seekEpsilon) {
MediaService.seekByRatio(next) MediaService.seekByRatio(next);
progressWrapper.lastSentSeekRatio = next progressWrapper.lastSentSeekRatio = next;
} }
} }
} }
@@ -310,21 +310,21 @@ NBox {
heightRatio: 0.6 heightRatio: 0.6
onMoved: { onMoved: {
progressWrapper.localSeekRatio = value progressWrapper.localSeekRatio = value;
seekDebounce.restart() seekDebounce.restart();
} }
onPressedChanged: { onPressedChanged: {
if (pressed) { if (pressed) {
MediaService.isSeeking = true MediaService.isSeeking = true;
progressWrapper.localSeekRatio = value progressWrapper.localSeekRatio = value;
MediaService.seekByRatio(value) MediaService.seekByRatio(value);
progressWrapper.lastSentSeekRatio = value progressWrapper.lastSentSeekRatio = value;
} else { } else {
seekDebounce.stop() seekDebounce.stop();
MediaService.seekByRatio(value) MediaService.seekByRatio(value);
MediaService.isSeeking = false MediaService.isSeeking = false;
progressWrapper.localSeekRatio = -1 progressWrapper.localSeekRatio = -1;
progressWrapper.lastSentSeekRatio = -1 progressWrapper.lastSentSeekRatio = -1;
} }
} }
} }

View File

@@ -61,9 +61,9 @@ NBox {
icon: "settings" icon: "settings"
tooltipText: I18n.tr("tooltips.open-settings") tooltipText: I18n.tr("tooltips.open-settings")
onClicked: { onClicked: {
var panel = PanelService.getPanel("settingsPanel", screen) var panel = PanelService.getPanel("settingsPanel", screen);
panel.requestedTab = SettingsPanel.Tab.General panel.requestedTab = SettingsPanel.Tab.General;
panel.open() panel.open();
} }
} }
@@ -71,8 +71,8 @@ NBox {
icon: "power" icon: "power"
tooltipText: I18n.tr("tooltips.session-menu") tooltipText: I18n.tr("tooltips.session-menu")
onClicked: { onClicked: {
PanelService.getPanel("sessionMenuPanel", screen)?.open() PanelService.getPanel("sessionMenuPanel", screen)?.open();
PanelService.getPanel("controlCenterPanel", screen)?.close() PanelService.getPanel("controlCenterPanel", screen)?.close();
} }
} }
@@ -80,7 +80,7 @@ NBox {
icon: "close" icon: "close"
tooltipText: I18n.tr("tooltips.close") tooltipText: I18n.tr("tooltips.close")
onClicked: { onClicked: {
PanelService.getPanel("controlCenterPanel", screen)?.close() PanelService.getPanel("controlCenterPanel", screen)?.close();
} }
} }
} }
@@ -102,14 +102,14 @@ NBox {
stdout: StdioCollector { stdout: StdioCollector {
onStreamFinished: { onStreamFinished: {
var uptimeSeconds = parseFloat(this.text.trim().split(' ')[0]) var uptimeSeconds = parseFloat(this.text.trim().split(' ')[0]);
uptimeText = Time.formatVagueHumanReadableDuration(uptimeSeconds) uptimeText = Time.formatVagueHumanReadableDuration(uptimeSeconds);
uptimeProcess.running = false uptimeProcess.running = false;
} }
} }
} }
function updateSystemInfo() { function updateSystemInfo() {
uptimeProcess.running = true uptimeProcess.running = true;
} }
} }

View File

@@ -3,9 +3,9 @@ import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell import Quickshell
import qs.Commons import qs.Commons
import qs.Widgets
import qs.Modules.Panels.ControlCenter import qs.Modules.Panels.ControlCenter
import qs.Modules.Panels.ControlCenter.Cards import qs.Modules.Panels.ControlCenter.Cards
import qs.Widgets
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true

View File

@@ -28,9 +28,11 @@ NBox {
height: content.widgetHeight height: content.widgetHeight
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
// Highlight color based on thresholds // Highlight color based on thresholds
fillColor: (SystemStatService.cpuUsage > Settings.data.systemMonitor.cpuCriticalThreshold) ? (Settings.data.systemMonitor.useCustomColors ? (Settings.data.systemMonitor.criticalColor 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 ? (
|| Color.mError) : Color.mError) : (SystemStatService.cpuUsage > Settings.data.systemMonitor.cpuWarningThreshold) ? (Settings.data.systemMonitor.useCustomColors ? (Settings.data.systemMonitor.warningColor Settings.data.systemMonitor.warningColor
|| Color.mTertiary) : Color.mTertiary) : Color.mPrimary || 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 textColor: (SystemStatService.cpuUsage > Settings.data.systemMonitor.cpuCriticalThreshold) ? Color.mSurfaceVariant : (SystemStatService.cpuUsage > Settings.data.systemMonitor.cpuWarningThreshold) ? Color.mSurfaceVariant : Color.mOnSurface
} }
NCircleStat { NCircleStat {
@@ -42,9 +44,11 @@ NBox {
height: content.widgetHeight height: content.widgetHeight
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
// Highlight color based on thresholds // Highlight color based on thresholds
fillColor: (SystemStatService.cpuTemp > Settings.data.systemMonitor.tempCriticalThreshold) ? (Settings.data.systemMonitor.useCustomColors ? (Settings.data.systemMonitor.criticalColor 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 ? (
|| Color.mError) : Color.mError) : (SystemStatService.cpuTemp > Settings.data.systemMonitor.tempWarningThreshold) ? (Settings.data.systemMonitor.useCustomColors ? (Settings.data.systemMonitor.warningColor Settings.data.systemMonitor.warningColor
|| Color.mTertiary) : Color.mTertiary) : Color.mPrimary || 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 textColor: (SystemStatService.cpuTemp > Settings.data.systemMonitor.tempCriticalThreshold) ? Color.mSurfaceVariant : (SystemStatService.cpuTemp > Settings.data.systemMonitor.tempWarningThreshold) ? Color.mSurfaceVariant : Color.mOnSurface
} }
NCircleStat { NCircleStat {
@@ -55,9 +59,11 @@ NBox {
height: content.widgetHeight height: content.widgetHeight
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
// Highlight color based on thresholds // Highlight color based on thresholds
fillColor: (SystemStatService.memPercent > Settings.data.systemMonitor.memCriticalThreshold) ? (Settings.data.systemMonitor.useCustomColors ? (Settings.data.systemMonitor.criticalColor 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 ? (
|| Color.mError) : Color.mError) : (SystemStatService.memPercent > Settings.data.systemMonitor.memWarningThreshold) ? (Settings.data.systemMonitor.useCustomColors ? (Settings.data.systemMonitor.warningColor Settings.data.systemMonitor.warningColor
|| Color.mTertiary) : Color.mTertiary) : Color.mPrimary || 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 textColor: (SystemStatService.memPercent > Settings.data.systemMonitor.memCriticalThreshold) ? Color.mSurfaceVariant : (SystemStatService.memPercent > Settings.data.systemMonitor.memWarningThreshold) ? Color.mSurfaceVariant : Color.mOnSurface
} }
NCircleStat { NCircleStat {
@@ -68,11 +74,12 @@ NBox {
height: content.widgetHeight height: content.widgetHeight
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
// Highlight color based on thresholds // Highlight color based on thresholds
fillColor: ((SystemStatService.diskPercents["/"] 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) ? (
?? 0) > Settings.data.systemMonitor.diskCriticalThreshold) ? (Settings.data.systemMonitor.useCustomColors ? (Settings.data.systemMonitor.criticalColor Settings.data.systemMonitor.useCustomColors
|| Color.mError) : Color.mError) : ((SystemStatService.diskPercents["/"] ? (Settings.data.systemMonitor.warningColor
?? 0) > Settings.data.systemMonitor.diskWarningThreshold) ? (Settings.data.systemMonitor.useCustomColors ? (Settings.data.systemMonitor.warningColor || Color.mTertiary) :
|| Color.mTertiary) : Color.mTertiary) : Color.mPrimary 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 textColor: ((SystemStatService.diskPercents["/"] ?? 0) > Settings.data.systemMonitor.diskCriticalThreshold) ? Color.mSurfaceVariant : ((SystemStatService.diskPercents["/"] ?? 0) > Settings.data.systemMonitor.diskWarningThreshold) ? Color.mSurfaceVariant : Color.mOnSurface
} }
} }

View File

@@ -43,8 +43,8 @@ NBox {
NText { NText {
text: { text: {
// Ensure the name is not too long if one had to specify the country // Ensure the name is not too long if one had to specify the country
const chunks = Settings.data.location.name.split(",") const chunks = Settings.data.location.name.split(",");
return chunks[0] return chunks[0];
} }
pointSize: Style.fontSizeL pointSize: Style.fontSizeL
font.weight: Style.fontWeightBold font.weight: Style.fontWeightBold
@@ -56,16 +56,16 @@ NBox {
visible: weatherReady visible: weatherReady
text: { text: {
if (!weatherReady) { if (!weatherReady) {
return "" return "";
} }
var temp = LocationService.data.weather.current_weather.temperature var temp = LocationService.data.weather.current_weather.temperature;
var suffix = "C" var suffix = "C";
if (Settings.data.location.useFahrenheit) { if (Settings.data.location.useFahrenheit) {
temp = LocationService.celsiusToFahrenheit(temp) temp = LocationService.celsiusToFahrenheit(temp);
var suffix = "F" var suffix = "F";
} }
temp = Math.round(temp) temp = Math.round(temp);
return `${temp}°${suffix}` return `${temp}°${suffix}`;
} }
pointSize: showLocation ? Style.fontSizeXL : Style.fontSizeXL * 1.6 pointSize: showLocation ? Style.fontSizeXL : Style.fontSizeXL * 1.6
font.weight: Style.fontWeightBold font.weight: Style.fontWeightBold
@@ -103,8 +103,8 @@ NBox {
NText { NText {
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
text: { text: {
var weatherDate = new Date(LocationService.data.weather.daily.time[index].replace(/-/g, "/")) var weatherDate = new Date(LocationService.data.weather.daily.time[index].replace(/-/g, "/"));
return I18n.locale.toString(weatherDate, "ddd") return I18n.locale.toString(weatherDate, "ddd");
} }
color: Color.mOnSurface color: Color.mOnSurface
} }
@@ -117,15 +117,15 @@ NBox {
NText { NText {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: { text: {
var max = LocationService.data.weather.daily.temperature_2m_max[index] var max = LocationService.data.weather.daily.temperature_2m_max[index];
var min = LocationService.data.weather.daily.temperature_2m_min[index] var min = LocationService.data.weather.daily.temperature_2m_min[index];
if (Settings.data.location.useFahrenheit) { if (Settings.data.location.useFahrenheit) {
max = LocationService.celsiusToFahrenheit(max) max = LocationService.celsiusToFahrenheit(max);
min = LocationService.celsiusToFahrenheit(min) min = LocationService.celsiusToFahrenheit(min);
} }
max = Math.round(max) max = Math.round(max);
min = Math.round(min) min = Math.round(min);
return `${max}°/${min}°` return `${max}°/${min}°`;
} }
pointSize: Style.fontSizeXS pointSize: Style.fontSizeXS
color: Color.mOnSurfaceVariant color: Color.mOnSurfaceVariant

View File

@@ -17,8 +17,8 @@ SmartPanel {
// Check if there's a bar on this screen // Check if there's a bar on this screen
readonly property bool hasBarOnScreen: { readonly property bool hasBarOnScreen: {
var monitors = Settings.data.bar.monitors || [] var monitors = Settings.data.bar.monitors || [];
return monitors.length === 0 || monitors.includes(screen?.name) return monitors.length === 0 || monitors.includes(screen?.name);
} }
// When position is "close_to_bar_button" but there's no bar, fall back to center // 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) preferredWidth: Math.round(460 * Style.uiScaleRatio)
preferredHeight: { preferredHeight: {
var height = 0 var height = 0;
var count = 0 var count = 0;
for (var i = 0; i < Settings.data.controlCenter.cards.length; i++) { 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) if (!card.enabled)
continue continue;
const contributes = (card.id !== "weather-card" || Settings.data.location.weatherEnabled);
const contributes = (card.id !== "weather-card" || Settings.data.location.weatherEnabled)
if (!contributes) if (!contributes)
continue continue;
count++;
count++
switch (card.id) { switch (card.id) {
case "profile-card": case "profile-card":
height += profileHeight height += profileHeight;
break break;
case "shortcuts-card": case "shortcuts-card":
height += shortcutsHeight height += shortcutsHeight;
break break;
case "audio-card": case "audio-card":
height += audioHeight height += audioHeight;
break break;
case "weather-card": case "weather-card":
height += weatherHeight height += weatherHeight;
break break;
case "media-sysmon-card": case "media-sysmon-card":
height += mediaSysMonHeight height += mediaSysMonHeight;
break break;
default: default:
break break;
} }
} }
return height + (count + 1) * Style.marginL return height + (count + 1) * Style.marginL;
} }
readonly property int profileHeight: Math.round(64 * Style.uiScaleRatio) readonly property int profileHeight: Math.round(64 * Style.uiScaleRatio)
@@ -77,11 +75,11 @@ SmartPanel {
property int weatherHeight: Math.round(210 * Style.uiScaleRatio) property int weatherHeight: Math.round(210 * Style.uiScaleRatio)
onOpened: { onOpened: {
MediaService.autoSwitchingPaused = true MediaService.autoSwitchingPaused = true;
} }
onClosed: { onClosed: {
MediaService.autoSwitchingPaused = false MediaService.autoSwitchingPaused = false;
} }
panelContent: Item { panelContent: Item {
@@ -103,31 +101,31 @@ SmartPanel {
Layout.preferredHeight: { Layout.preferredHeight: {
switch (modelData.id) { switch (modelData.id) {
case "profile-card": case "profile-card":
return profileHeight return profileHeight;
case "shortcuts-card": case "shortcuts-card":
return shortcutsHeight return shortcutsHeight;
case "audio-card": case "audio-card":
return audioHeight return audioHeight;
case "weather-card": case "weather-card":
return weatherHeight return weatherHeight;
case "media-sysmon-card": case "media-sysmon-card":
return mediaSysMonHeight return mediaSysMonHeight;
default: default:
return 0 return 0;
} }
} }
sourceComponent: { sourceComponent: {
switch (modelData.id) { switch (modelData.id) {
case "profile-card": case "profile-card":
return profileCard return profileCard;
case "shortcuts-card": case "shortcuts-card":
return shortcutsCard return shortcutsCard;
case "audio-card": case "audio-card":
return audioCard return audioCard;
case "weather-card": case "weather-card":
return weatherCard return weatherCard;
case "media-sysmon-card": case "media-sysmon-card":
return mediaSysMonCard return mediaSysMonCard;
} }
} }
} }
@@ -153,7 +151,7 @@ SmartPanel {
id: weatherCard id: weatherCard
WeatherCard { WeatherCard {
Component.onCompleted: { Component.onCompleted: {
root.weatherHeight = this.height root.weatherHeight = this.height;
} }
} }
} }

View File

@@ -1,7 +1,7 @@
import QtQuick import QtQuick
import Quickshell import Quickshell
import qs.Services.UI
import qs.Commons import qs.Commons
import qs.Services.UI
Item { Item {
id: root id: root
@@ -18,7 +18,7 @@ Item {
implicitHeight: getImplicitSize(loader.item, "implicitHeight") implicitHeight: getImplicitSize(loader.item, "implicitHeight")
function getImplicitSize(item, prop) { function getImplicitSize(item, prop) {
return (item && item.visible) ? item[prop] : 0 return (item && item.visible) ? item[prop] : 0;
} }
Loader { Loader {
@@ -29,36 +29,36 @@ Item {
onLoaded: { onLoaded: {
if (!item) if (!item)
return return;
// Apply properties to loaded widget // Apply properties to loaded widget
for (var prop in widgetProps) { for (var prop in widgetProps) {
if (item.hasOwnProperty(prop)) { if (item.hasOwnProperty(prop)) {
item[prop] = widgetProps[prop] item[prop] = widgetProps[prop];
} }
} }
// Set screen property // Set screen property
if (item.hasOwnProperty("screen")) { if (item.hasOwnProperty("screen")) {
item.screen = widgetScreen item.screen = widgetScreen;
} }
// Call custom onLoaded if it exists // Call custom onLoaded if it exists
if (item.hasOwnProperty("onLoaded")) { if (item.hasOwnProperty("onLoaded")) {
item.onLoaded() item.onLoaded();
} }
} }
Component.onDestruction: { Component.onDestruction: {
// Explicitly clear references // Explicitly clear references
widgetProps = null widgetProps = null;
} }
} }
// Error handling // Error handling
Component.onCompleted: { Component.onCompleted: {
if (!ControlCenterWidgetRegistry.hasWidget(widgetId)) { if (!ControlCenterWidgetRegistry.hasWidget(widgetId)) {
Logger.w("ControlCenterWidgetLoader", "Widget not found in registry:", widgetId) Logger.w("ControlCenterWidgetLoader", "Widget not found in registry:", widgetId);
} }
} }
} }

View File

@@ -33,28 +33,28 @@ Item {
function _updatePropertiesFromSettings() { function _updatePropertiesFromSettings() {
if (!widgetSettings) { if (!widgetSettings) {
return return;
} }
onClickedCommand = widgetSettings.onClicked || "" onClickedCommand = widgetSettings.onClicked || "";
onRightClickedCommand = widgetSettings.onRightClicked || "" onRightClickedCommand = widgetSettings.onRightClicked || "";
onMiddleClickedCommand = widgetSettings.onMiddleClicked || "" onMiddleClickedCommand = widgetSettings.onMiddleClicked || "";
stateChecksJson = widgetSettings.stateChecksJson || "[]" // Populate from widgetSettings stateChecksJson = widgetSettings.stateChecksJson || "[]"; // Populate from widgetSettings
try { try {
_parsedStateChecks = JSON.parse(stateChecksJson) _parsedStateChecks = JSON.parse(stateChecksJson);
} catch (e) { } catch (e) {
console.error("CustomButton: Failed to parse stateChecksJson:", e.message) console.error("CustomButton: Failed to parse stateChecksJson:", e.message);
_parsedStateChecks = [] _parsedStateChecks = [];
} }
generalTooltipText = widgetSettings.generalTooltipText || "Custom Button" generalTooltipText = widgetSettings.generalTooltipText || "Custom Button";
enableOnStateLogic = widgetSettings.enableOnStateLogic || false enableOnStateLogic = widgetSettings.enableOnStateLogic || false;
updateState() updateState();
} }
function onWidgetSettingsChanged() { function onWidgetSettingsChanged() {
if (widgetSettings) { if (widgetSettings) {
_updatePropertiesFromSettings() _updatePropertiesFromSettings();
} }
} }
} }
@@ -64,16 +64,16 @@ Item {
running: false running: false
command: _currentStateCheckIndex !== -1 && _parsedStateChecks.length > _currentStateCheckIndex ? ["sh", "-c", _parsedStateChecks[_currentStateCheckIndex].command] : [] command: _currentStateCheckIndex !== -1 && _parsedStateChecks.length > _currentStateCheckIndex ? ["sh", "-c", _parsedStateChecks[_currentStateCheckIndex].command] : []
onExited: function (exitCode, stdout, stderr) { onExited: function (exitCode, stdout, stderr) {
var currentCheckItem = _parsedStateChecks[_currentStateCheckIndex] var currentCheckItem = _parsedStateChecks[_currentStateCheckIndex];
var currentCommand = currentCheckItem.command var currentCommand = currentCheckItem.command;
if (exitCode === 0) { if (exitCode === 0) {
// Command succeeded, this is the active state // Command succeeded, this is the active state
_isHot = true _isHot = true;
_activeStateIcon = currentCheckItem.icon || widgetSettings.icon || "heart" _activeStateIcon = currentCheckItem.icon || widgetSettings.icon || "heart";
} else { } else {
// Command failed, try next one // Command failed, try next one
_currentStateCheckIndex++ _currentStateCheckIndex++;
_checkNextState() _checkNextState();
} }
} }
} }
@@ -85,53 +85,53 @@ Item {
repeat: false repeat: false
onTriggered: { onTriggered: {
if (enableOnStateLogic && _parsedStateChecks.length > 0) { if (enableOnStateLogic && _parsedStateChecks.length > 0) {
_currentStateCheckIndex = 0 _currentStateCheckIndex = 0;
_checkNextState() _checkNextState();
} else { } else {
_isHot = false _isHot = false;
_activeStateIcon = widgetSettings.icon || "heart" _activeStateIcon = widgetSettings.icon || "heart";
} }
} }
} }
function _checkNextState() { function _checkNextState() {
if (_currentStateCheckIndex < _parsedStateChecks.length) { if (_currentStateCheckIndex < _parsedStateChecks.length) {
var currentCheckItem = _parsedStateChecks[_currentStateCheckIndex] var currentCheckItem = _parsedStateChecks[_currentStateCheckIndex];
if (currentCheckItem && currentCheckItem.command) { if (currentCheckItem && currentCheckItem.command) {
stateCheckProcessExecutor.running = true stateCheckProcessExecutor.running = true;
} else { } else {
_currentStateCheckIndex++ _currentStateCheckIndex++;
_checkNextState() _checkNextState();
} }
} else { } else {
// All checks failed // All checks failed
_isHot = false _isHot = false;
_activeStateIcon = widgetSettings.icon || "heart" _activeStateIcon = widgetSettings.icon || "heart";
} }
} }
function updateState() { function updateState() {
if (!enableOnStateLogic || _parsedStateChecks.length === 0) { if (!enableOnStateLogic || _parsedStateChecks.length === 0) {
_isHot = false _isHot = false;
_activeStateIcon = widgetSettings.icon || "heart" _activeStateIcon = widgetSettings.icon || "heart";
return return;
} }
stateUpdateTimer.restart() stateUpdateTimer.restart();
} }
function _buildTooltipText() { function _buildTooltipText() {
let tooltip = generalTooltipText let tooltip = generalTooltipText;
if (onClickedCommand) { if (onClickedCommand) {
tooltip += `\nLeft click: ${onClickedCommand}` tooltip += `\nLeft click: ${onClickedCommand}`;
} }
if (onRightClickedCommand) { if (onRightClickedCommand) {
tooltip += `\nRight click: ${onRightClickedCommand}` tooltip += `\nRight click: ${onRightClickedCommand}`;
} }
if (onMiddleClickedCommand) { if (onMiddleClickedCommand) {
tooltip += `\nMiddle click: ${onMiddleClickedCommand}` tooltip += `\nMiddle click: ${onMiddleClickedCommand}`;
} }
return tooltip return tooltip;
} }
NIconButtonHot { NIconButtonHot {
@@ -141,20 +141,20 @@ Item {
tooltipText: _buildTooltipText() tooltipText: _buildTooltipText()
onClicked: { onClicked: {
if (onClickedCommand) { if (onClickedCommand) {
Quickshell.execDetached(["sh", "-c", onClickedCommand]) Quickshell.execDetached(["sh", "-c", onClickedCommand]);
updateState() updateState();
} }
} }
onRightClicked: { onRightClicked: {
if (onRightClickedCommand) { if (onRightClickedCommand) {
Quickshell.execDetached(["sh", "-c", onRightClickedCommand]) Quickshell.execDetached(["sh", "-c", onRightClickedCommand]);
updateState() updateState();
} }
} }
onMiddleClicked: { onMiddleClicked: {
if (onMiddleClickedCommand) { if (onMiddleClickedCommand) {
Quickshell.execDetached(["sh", "-c", onMiddleClickedCommand]) Quickshell.execDetached(["sh", "-c", onMiddleClickedCommand]);
updateState() updateState();
} }
} }
} }

View File

@@ -16,19 +16,19 @@ NIconButtonHot {
onClicked: { onClicked: {
if (!Settings.data.nightLight.enabled) { if (!Settings.data.nightLight.enabled) {
Settings.data.nightLight.enabled = true Settings.data.nightLight.enabled = true;
Settings.data.nightLight.forced = false Settings.data.nightLight.forced = false;
} else if (Settings.data.nightLight.enabled && !Settings.data.nightLight.forced) { } else if (Settings.data.nightLight.enabled && !Settings.data.nightLight.forced) {
Settings.data.nightLight.forced = true Settings.data.nightLight.forced = true;
} else { } else {
Settings.data.nightLight.enabled = false Settings.data.nightLight.enabled = false;
Settings.data.nightLight.forced = false Settings.data.nightLight.forced = false;
} }
} }
onRightClicked: { onRightClicked: {
var settingsPanel = PanelService.getPanel("settingsPanel", screen) var settingsPanel = PanelService.getPanel("settingsPanel", screen);
settingsPanel.requestedTab = SettingsPanel.Tab.Display settingsPanel.requestedTab = SettingsPanel.Tab.Display;
settingsPanel.open() settingsPanel.open();
} }
} }

View File

@@ -14,9 +14,9 @@ NIconButtonHot {
hot: ScreenRecorderService.isRecording hot: ScreenRecorderService.isRecording
tooltipText: I18n.tr("quickSettings.screenRecorder.tooltip.action") tooltipText: I18n.tr("quickSettings.screenRecorder.tooltip.action")
onClicked: { onClicked: {
ScreenRecorderService.toggleRecording() ScreenRecorderService.toggleRecording();
if (!ScreenRecorderService.isRecording) { if (!ScreenRecorderService.isRecording) {
PanelService.getPanel("controlCenterPanel", screen)?.close PanelService.getPanel("controlCenterPanel", screen)?.close;
} }
} }
} }

View File

@@ -11,21 +11,21 @@ NIconButtonHot {
icon: { icon: {
try { try {
if (NetworkService.ethernetConnected) { if (NetworkService.ethernetConnected) {
return NetworkService.internetConnectivity ? "ethernet" : "ethernet-off" return NetworkService.internetConnectivity ? "ethernet" : "ethernet-off";
} }
let connected = false let connected = false;
let signalStrength = 0 let signalStrength = 0;
for (const net in NetworkService.networks) { for (const net in NetworkService.networks) {
if (NetworkService.networks[net].connected) { if (NetworkService.networks[net].connected) {
connected = true connected = true;
signalStrength = NetworkService.networks[net].signal signalStrength = NetworkService.networks[net].signal;
break break;
} }
} }
return connected ? NetworkService.signalIcon(signalStrength, true) : "wifi-off" return connected ? NetworkService.signalIcon(signalStrength, true) : "wifi-off";
} catch (error) { } catch (error) {
Logger.e("Wi-Fi", "Error getting icon:", error) Logger.e("Wi-Fi", "Error getting icon:", error);
return "wifi-off" return "wifi-off";
} }
} }

View File

@@ -3,13 +3,13 @@ import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Widgets import Quickshell.Widgets
import "Plugins"
import qs.Commons import qs.Commons
import qs.Modules.MainScreen import qs.Modules.MainScreen
import qs.Services.Keyboard import qs.Services.Keyboard
import qs.Widgets import qs.Widgets
import "Plugins"
SmartPanel { SmartPanel {
id: root id: root
@@ -23,12 +23,12 @@ SmartPanel {
readonly property string panelPosition: { readonly property string panelPosition: {
if (Settings.data.appLauncher.position === "follow_bar") { if (Settings.data.appLauncher.position === "follow_bar") {
if (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") { if (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") {
return `center_${Settings.data.bar.position}` return `center_${Settings.data.bar.position}`;
} else { } else {
return `${Settings.data.bar.position}_center` return `${Settings.data.bar.position}_center`;
} }
} else { } else {
return Settings.data.appLauncher.position return Settings.data.appLauncher.position;
} }
} }
panelAnchorHorizontalCenter: panelPosition === "center" || panelPosition.endsWith("_center") panelAnchorHorizontalCenter: panelPosition === "center" || panelPosition.endsWith("_center")
@@ -55,83 +55,83 @@ SmartPanel {
// They are not coming from SmartPanelWindow as they are consumed by the search field before reaching the panel. // They are not coming from SmartPanelWindow as they are consumed by the search field before reaching the panel.
// They are instead being forwared from the search field NTextInput below. // They are instead being forwared from the search field NTextInput below.
function onTabPressed() { function onTabPressed() {
selectNextWrapped() selectNextWrapped();
} }
function onBackTabPressed() { function onBackTabPressed() {
selectPreviousWrapped() selectPreviousWrapped();
} }
function onUpPressed() { function onUpPressed() {
selectPreviousWrapped() selectPreviousWrapped();
} }
function onDownPressed() { function onDownPressed() {
selectNextWrapped() selectNextWrapped();
} }
function onReturnPressed() { function onReturnPressed() {
activate() activate();
} }
function onHomePressed() { function onHomePressed() {
selectFirst() selectFirst();
} }
function onEndPressed() { function onEndPressed() {
selectLast() selectLast();
} }
function onPageUpPressed() { function onPageUpPressed() {
selectPreviousPage() selectPreviousPage();
} }
function onPageDownPressed() { function onPageDownPressed() {
selectNextPage() selectNextPage();
} }
function onCtrlJPressed() { function onCtrlJPressed() {
selectNextWrapped() selectNextWrapped();
} }
function onCtrlKPressed() { function onCtrlKPressed() {
selectPreviousWrapped() selectPreviousWrapped();
} }
function onCtrlNPressed() { function onCtrlNPressed() {
selectNextWrapped() selectNextWrapped();
} }
function onCtrlPPressed() { function onCtrlPPressed() {
selectPreviousWrapped() selectPreviousWrapped();
} }
// Public API for plugins // Public API for plugins
function setSearchText(text) { function setSearchText(text) {
searchText = text searchText = text;
} }
// Plugin registration // Plugin registration
function registerPlugin(plugin) { function registerPlugin(plugin) {
plugins.push(plugin) plugins.push(plugin);
plugin.launcher = root plugin.launcher = root;
if (plugin.init) if (plugin.init)
plugin.init() plugin.init();
} }
// Search handling // Search handling
function updateResults() { function updateResults() {
results = [] results = [];
activePlugin = null activePlugin = null;
// Check for command mode // Check for command mode
if (searchText.startsWith(">")) { if (searchText.startsWith(">")) {
// Find plugin that handles this command // Find plugin that handles this command
for (let plugin of plugins) { for (let plugin of plugins) {
if (plugin.handleCommand && plugin.handleCommand(searchText)) { if (plugin.handleCommand && plugin.handleCommand(searchText)) {
activePlugin = plugin activePlugin = plugin;
results = plugin.getResults(searchText) results = plugin.getResults(searchText);
break break;
} }
} }
@@ -139,7 +139,7 @@ SmartPanel {
if (searchText === ">" && !activePlugin) { if (searchText === ">" && !activePlugin) {
for (let plugin of plugins) { for (let plugin of plugins) {
if (plugin.commands) { if (plugin.commands) {
results = results.concat(plugin.commands()) results = results.concat(plugin.commands());
} }
} }
} }
@@ -147,43 +147,43 @@ SmartPanel {
// Regular search - let plugins contribute results // Regular search - let plugins contribute results
for (let plugin of plugins) { for (let plugin of plugins) {
if (plugin.handleSearch) { if (plugin.handleSearch) {
const pluginResults = plugin.getResults(searchText) const pluginResults = plugin.getResults(searchText);
results = results.concat(pluginResults) results = results.concat(pluginResults);
} }
} }
} }
selectedIndex = 0 selectedIndex = 0;
} }
onSearchTextChanged: updateResults() onSearchTextChanged: updateResults()
// Lifecycle // Lifecycle
onOpened: { onOpened: {
resultsReady = false resultsReady = false;
ignoreMouseHover = true ignoreMouseHover = true;
// Notify plugins and update results // Notify plugins and update results
// Use Qt.callLater to ensure plugins are registered (Component.onCompleted runs first) // Use Qt.callLater to ensure plugins are registered (Component.onCompleted runs first)
Qt.callLater(() => { Qt.callLater(() => {
for (let plugin of plugins) { for (let plugin of plugins) {
if (plugin.onOpened) if (plugin.onOpened)
plugin.onOpened() plugin.onOpened();
} }
updateResults() updateResults();
resultsReady = true resultsReady = true;
}) });
} }
onClosed: { onClosed: {
// Reset search text // Reset search text
searchText = "" searchText = "";
ignoreMouseHover = true ignoreMouseHover = true;
// Notify plugins // Notify plugins
for (let plugin of plugins) { for (let plugin of plugins) {
if (plugin.onClosed) if (plugin.onClosed)
plugin.onClosed() plugin.onClosed();
} }
} }
@@ -191,16 +191,16 @@ SmartPanel {
ApplicationsPlugin { ApplicationsPlugin {
id: appsPlugin id: appsPlugin
Component.onCompleted: { Component.onCompleted: {
registerPlugin(this) registerPlugin(this);
Logger.d("Launcher", "Registered: ApplicationsPlugin") Logger.d("Launcher", "Registered: ApplicationsPlugin");
} }
} }
CalculatorPlugin { CalculatorPlugin {
id: calcPlugin id: calcPlugin
Component.onCompleted: { Component.onCompleted: {
registerPlugin(this) registerPlugin(this);
Logger.d("Launcher", "Registered: CalculatorPlugin") Logger.d("Launcher", "Registered: CalculatorPlugin");
} }
} }
@@ -208,8 +208,8 @@ SmartPanel {
id: clipPlugin id: clipPlugin
Component.onCompleted: { Component.onCompleted: {
if (Settings.data.appLauncher.enableClipboardHistory) { if (Settings.data.appLauncher.enableClipboardHistory) {
registerPlugin(this) registerPlugin(this);
Logger.d("Launcher", "Registered: ClipboardPlugin") Logger.d("Launcher", "Registered: ClipboardPlugin");
} }
} }
} }
@@ -217,47 +217,47 @@ SmartPanel {
// Navigation functions // Navigation functions
function selectNextWrapped() { function selectNextWrapped() {
if (results.length > 0) { if (results.length > 0) {
selectedIndex = (selectedIndex + 1) % results.length selectedIndex = (selectedIndex + 1) % results.length;
} }
} }
function selectPreviousWrapped() { function selectPreviousWrapped() {
if (results.length > 0) { if (results.length > 0) {
selectedIndex = (((selectedIndex - 1) % results.length) + results.length) % results.length selectedIndex = (((selectedIndex - 1) % results.length) + results.length) % results.length;
} }
} }
function selectFirst() { function selectFirst() {
selectedIndex = 0 selectedIndex = 0;
} }
function selectLast() { function selectLast() {
if (results.length > 0) { if (results.length > 0) {
selectedIndex = results.length - 1 selectedIndex = results.length - 1;
} else { } else {
selectedIndex = 0 selectedIndex = 0;
} }
} }
function selectNextPage() { function selectNextPage() {
if (results.length > 0) { if (results.length > 0) {
const page = Math.max(1, Math.floor(600 / entryHeight)) // Use approximate height const page = Math.max(1, Math.floor(600 / entryHeight)); // Use approximate height
selectedIndex = Math.min(selectedIndex + page, results.length - 1) selectedIndex = Math.min(selectedIndex + page, results.length - 1);
} }
} }
function selectPreviousPage() { function selectPreviousPage() {
if (results.length > 0) { if (results.length > 0) {
const page = Math.max(1, Math.floor(600 / entryHeight)) // Use approximate height const page = Math.max(1, Math.floor(600 / entryHeight)); // Use approximate height
selectedIndex = Math.max(selectedIndex - page, 0) selectedIndex = Math.max(selectedIndex - page, 0);
} }
} }
function activate() { function activate() {
if (results.length > 0 && results[selectedIndex]) { if (results.length > 0 && results[selectedIndex]) {
const item = results[selectedIndex] const item = results[selectedIndex];
if (item.onActivate) { if (item.onActivate) {
item.onActivate() item.onActivate();
} }
} }
} }
@@ -284,19 +284,19 @@ SmartPanel {
onPositionChanged: mouse => { onPositionChanged: mouse => {
// Store initial position // Store initial position
if (!initialized) { if (!initialized) {
lastX = mouse.x lastX = mouse.x;
lastY = mouse.y lastY = mouse.y;
initialized = true initialized = true;
return return;
} }
// Check if mouse actually moved // Check if mouse actually moved
const deltaX = Math.abs(mouse.x - lastX) const deltaX = Math.abs(mouse.x - lastX);
const deltaY = Math.abs(mouse.y - lastY) const deltaY = Math.abs(mouse.y - lastY);
if (deltaX > 1 || deltaY > 1) { if (deltaX > 1 || deltaY > 1) {
root.ignoreMouseHover = false root.ignoreMouseHover = false;
lastX = mouse.x lastX = mouse.x;
lastY = mouse.y lastY = mouse.y;
} }
} }
@@ -304,7 +304,7 @@ SmartPanel {
Connections { Connections {
target: root target: root
function onOpened() { function onOpened() {
mouseMovementDetector.initialized = false mouseMovementDetector.initialized = false;
} }
} }
} }
@@ -316,9 +316,9 @@ SmartPanel {
// Delay focus to ensure window has keyboard focus // Delay focus to ensure window has keyboard focus
Qt.callLater(() => { Qt.callLater(() => {
if (searchInput.inputItem) { if (searchInput.inputItem) {
searchInput.inputItem.forceActiveFocus() searchInput.inputItem.forceActiveFocus();
} }
}) });
} }
} }
@@ -348,17 +348,17 @@ SmartPanel {
Component.onCompleted: { Component.onCompleted: {
if (searchInput.inputItem) { if (searchInput.inputItem) {
searchInput.inputItem.forceActiveFocus() searchInput.inputItem.forceActiveFocus();
// Intercept Tab keys before TextField handles them // Intercept Tab keys before TextField handles them
searchInput.inputItem.Keys.onPressed.connect(function (event) { searchInput.inputItem.Keys.onPressed.connect(function (event) {
if (event.key === Qt.Key_Tab) { if (event.key === Qt.Key_Tab) {
root.onTabPressed() root.onTabPressed();
event.accepted = true event.accepted = true;
} else if (event.key === Qt.Key_Backtab) { } else if (event.key === Qt.Key_Backtab) {
root.onBackTabPressed() root.onBackTabPressed();
event.accepted = true event.accepted = true;
} }
}) });
} }
} }
} }
@@ -377,14 +377,12 @@ SmartPanel {
currentIndex: selectedIndex currentIndex: selectedIndex
cacheBuffer: resultsList.height * 2 cacheBuffer: resultsList.height * 2
onCurrentIndexChanged: { onCurrentIndexChanged: {
cancelFlick() cancelFlick();
if (currentIndex >= 0) { if (currentIndex >= 0) {
positionViewAtIndex(currentIndex, ListView.Contain) positionViewAtIndex(currentIndex, ListView.Contain);
} }
} }
onModelChanged: { onModelChanged: {}
}
delegate: Rectangle { delegate: Rectangle {
id: entry id: entry
@@ -396,19 +394,19 @@ SmartPanel {
// Pin helpers // Pin helpers
function togglePin(appId) { function togglePin(appId) {
if (!appId) if (!appId)
return return;
let arr = (Settings.data.dock.pinnedApps || []).slice() let arr = (Settings.data.dock.pinnedApps || []).slice();
const idx = arr.indexOf(appId) const idx = arr.indexOf(appId);
if (idx >= 0) if (idx >= 0)
arr.splice(idx, 1) arr.splice(idx, 1);
else else
arr.push(appId) arr.push(appId);
Settings.data.dock.pinnedApps = arr Settings.data.dock.pinnedApps = arr;
} }
function isPinned(appId) { function isPinned(appId) {
const arr = Settings.data.dock.pinnedApps || [] const arr = Settings.data.dock.pinnedApps || [];
return appId && arr.indexOf(appId) >= 0 return appId && arr.indexOf(appId) >= 0;
} }
// Property to reliably track the current item's ID. // Property to reliably track the current item's ID.
@@ -419,7 +417,7 @@ SmartPanel {
onCurrentClipboardIdChanged: { onCurrentClipboardIdChanged: {
// Check if it's a valid ID and if the data isn't already cached. // Check if it's a valid ID and if the data isn't already cached.
if (currentClipboardId && !ClipboardService.getImageData(currentClipboardId)) { if (currentClipboardId && !ClipboardService.getImageData(currentClipboardId)) {
ClipboardService.decodeToDataUrl(currentClipboardId, modelData.mime, null) ClipboardService.decodeToDataUrl(currentClipboardId, modelData.mime, null);
} }
} }
@@ -466,8 +464,8 @@ SmartPanel {
// Fetches from the service's cache. // Fetches from the service's cache.
// The dependency on `_rev` ensures this binding is re-evaluated when the cache is updated. // The dependency on `_rev` ensures this binding is re-evaluated when the cache is updated.
imagePath: { imagePath: {
_rev _rev;
return ClipboardService.getImageData(modelData.clipboardId) || "" return ClipboardService.getImageData(modelData.clipboardId) || "";
} }
// Loading indicator // Loading indicator
@@ -487,8 +485,8 @@ SmartPanel {
// Error fallback // Error fallback
onStatusChanged: status => { onStatusChanged: status => {
if (status === Image.Error) { if (status === Image.Error) {
iconLoader.visible = true iconLoader.visible = true;
imagePreview.visible = false imagePreview.visible = false;
} }
} }
} }
@@ -538,10 +536,10 @@ SmartPanel {
anchors.centerIn: parent anchors.centerIn: parent
text: { text: {
if (!modelData.isImage) if (!modelData.isImage)
return "" return "";
const desc = modelData.description || "" const desc = modelData.description || "";
const parts = desc.split(" • ") const parts = desc.split(" • ");
return parts[0] || "IMG" return parts[0] || "IMG";
} }
pointSize: Style.fontSizeXXS pointSize: Style.fontSizeXXS
color: Color.mPrimary color: Color.mPrimary
@@ -592,14 +590,14 @@ SmartPanel {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onEntered: { onEntered: {
if (!root.ignoreMouseHover) { if (!root.ignoreMouseHover) {
selectedIndex = index selectedIndex = index;
} }
} }
onClicked: mouse => { onClicked: mouse => {
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
selectedIndex = index selectedIndex = index;
root.activate() root.activate();
mouse.accepted = true mouse.accepted = true;
} }
} }
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton
@@ -616,9 +614,9 @@ SmartPanel {
Layout.fillWidth: true Layout.fillWidth: true
text: { text: {
if (results.length === 0) if (results.length === 0)
return searchText ? "No results" : "" return searchText ? "No results" : "";
const prefix = activePlugin?.name ? `${activePlugin.name}: ` : "" const prefix = activePlugin?.name ? `${activePlugin.name}: ` : "";
return prefix + `${results.length} result${results.length !== 1 ? 's' : ''}` return prefix + `${results.length} result${results.length !== 1 ? 's' : ''}`;
} }
pointSize: Style.fontSizeXS pointSize: Style.fontSizeXS
color: Color.mOnSurfaceVariant color: Color.mOnSurfaceVariant

View File

@@ -1,8 +1,8 @@
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
import qs.Commons
import "../../../../Helpers/FuzzySort.js" as Fuzzysort import "../../../../Helpers/FuzzySort.js" as Fuzzysort
import qs.Commons
Item { Item {
property var launcher: null property var launcher: null
@@ -29,7 +29,7 @@ Item {
onLoadFailed: function (error) { onLoadFailed: function (error) {
if (error.toString().includes("No such file") || error === 2) { if (error.toString().includes("No such file") || error === 2) {
writeAdapter() writeAdapter();
} }
} }
@@ -43,89 +43,89 @@ Item {
} }
function init() { function init() {
loadApplications() loadApplications();
} }
function onOpened() { function onOpened() {
// Refresh apps when launcher opens // Refresh apps when launcher opens
loadApplications() loadApplications();
} }
function loadApplications() { function loadApplications() {
if (typeof DesktopEntries === 'undefined') { if (typeof DesktopEntries === 'undefined') {
Logger.w("ApplicationsPlugin", "DesktopEntries service not available") Logger.w("ApplicationsPlugin", "DesktopEntries service not available");
return return;
} }
const allApps = DesktopEntries.applications.values || [] const allApps = DesktopEntries.applications.values || [];
entries = allApps.filter(app => app && !app.noDisplay).map(app => { entries = allApps.filter(app => app && !app.noDisplay).map(app => {
// Add executable name property for search // Add executable name property for search
app.executableName = getExecutableName(app) app.executableName = getExecutableName(app);
return app return app;
}) });
Logger.d("ApplicationsPlugin", `Loaded ${entries.length} applications`) Logger.d("ApplicationsPlugin", `Loaded ${entries.length} applications`);
} }
function getExecutableName(app) { function getExecutableName(app) {
if (!app) if (!app)
return "" return "";
// Try to get executable name from command array // Try to get executable name from command array
if (app.command && Array.isArray(app.command) && app.command.length > 0) { if (app.command && Array.isArray(app.command) && app.command.length > 0) {
const cmd = app.command[0] const cmd = app.command[0];
// Extract just the executable name from the full path // Extract just the executable name from the full path
const parts = cmd.split('/') const parts = cmd.split('/');
const executable = parts[parts.length - 1] const executable = parts[parts.length - 1];
// Remove any arguments or parameters // Remove any arguments or parameters
return executable.split(' ')[0] return executable.split(' ')[0];
} }
// Try to get from exec property if available // Try to get from exec property if available
if (app.exec) { if (app.exec) {
const parts = app.exec.split('/') const parts = app.exec.split('/');
const executable = parts[parts.length - 1] const executable = parts[parts.length - 1];
return executable.split(' ')[0] return executable.split(' ')[0];
} }
// Fallback to app id (desktop file name without .desktop) // Fallback to app id (desktop file name without .desktop)
if (app.id) { if (app.id) {
return app.id.replace('.desktop', '') return app.id.replace('.desktop', '');
} }
return "" return "";
} }
function getResults(query) { function getResults(query) {
if (!entries || entries.length === 0) if (!entries || entries.length === 0)
return [] return [];
if (!query || query.trim() === "") { if (!query || query.trim() === "") {
// Return all apps, optionally sorted by usage // Return all apps, optionally sorted by usage
const favoriteApps = Settings.data.appLauncher.favoriteApps || [] const favoriteApps = Settings.data.appLauncher.favoriteApps || [];
let sorted let sorted;
if (Settings.data.appLauncher.sortByMostUsed) { if (Settings.data.appLauncher.sortByMostUsed) {
sorted = entries.slice().sort((a, b) => { sorted = entries.slice().sort((a, b) => {
// Favorites first // Favorites first
const aFav = favoriteApps.includes(getAppKey(a)) const aFav = favoriteApps.includes(getAppKey(a));
const bFav = favoriteApps.includes(getAppKey(b)) const bFav = favoriteApps.includes(getAppKey(b));
if (aFav !== bFav) if (aFav !== bFav)
return aFav ? -1 : 1 return aFav ? -1 : 1;
const ua = getUsageCount(a) const ua = getUsageCount(a);
const ub = getUsageCount(b) const ub = getUsageCount(b);
if (ub !== ua) if (ub !== ua)
return ub - ua return ub - ua;
return (a.name || "").toLowerCase().localeCompare((b.name || "").toLowerCase()) return (a.name || "").toLowerCase().localeCompare((b.name || "").toLowerCase());
}) });
} else { } else {
sorted = entries.slice().sort((a, b) => { sorted = entries.slice().sort((a, b) => {
const aFav = favoriteApps.includes(getAppKey(a)) const aFav = favoriteApps.includes(getAppKey(a));
const bFav = favoriteApps.includes(getAppKey(b)) const bFav = favoriteApps.includes(getAppKey(b));
if (aFav !== bFav) if (aFav !== bFav)
return aFav ? -1 : 1 return aFav ? -1 : 1;
return (a.name || "").toLowerCase().localeCompare((b.name || "").toLowerCase()) return (a.name || "").toLowerCase().localeCompare((b.name || "").toLowerCase());
}) });
} }
return sorted.map(app => createResultEntry(app)) return sorted.map(app => createResultEntry(app));
} }
// Use fuzzy search if available, fallback to simple search // Use fuzzy search if available, fallback to simple search
@@ -134,54 +134,54 @@ Item {
"keys": ["name", "comment", "genericName", "executableName"], "keys": ["name", "comment", "genericName", "executableName"],
"threshold": -1000, "threshold": -1000,
"limit": 20 "limit": 20
}) });
// Sort favorites first within fuzzy results while preserving fuzzysort order otherwise // Sort favorites first within fuzzy results while preserving fuzzysort order otherwise
const favoriteApps = Settings.data.appLauncher.favoriteApps || [] const favoriteApps = Settings.data.appLauncher.favoriteApps || [];
const fav = [] const fav = [];
const nonFav = [] const nonFav = [];
for (const r of fuzzyResults) { for (const r of fuzzyResults) {
const app = r.obj const app = r.obj;
if (favoriteApps.includes(getAppKey(app))) if (favoriteApps.includes(getAppKey(app)))
fav.push(r) fav.push(r);
else else
nonFav.push(r) nonFav.push(r);
} }
return fav.concat(nonFav).map(result => createResultEntry(result.obj)) return fav.concat(nonFav).map(result => createResultEntry(result.obj));
} else { } else {
// Fallback to simple search // Fallback to simple search
const searchTerm = query.toLowerCase() const searchTerm = query.toLowerCase();
return entries.filter(app => { return entries.filter(app => {
const name = (app.name || "").toLowerCase() const name = (app.name || "").toLowerCase();
const comment = (app.comment || "").toLowerCase() const comment = (app.comment || "").toLowerCase();
const generic = (app.genericName || "").toLowerCase() const generic = (app.genericName || "").toLowerCase();
const executable = getExecutableName(app).toLowerCase() const executable = getExecutableName(app).toLowerCase();
return name.includes(searchTerm) || comment.includes(searchTerm) || generic.includes(searchTerm) || executable.includes(searchTerm) return name.includes(searchTerm) || comment.includes(searchTerm) || generic.includes(searchTerm) || executable.includes(searchTerm);
}).sort((a, b) => { }).sort((a, b) => {
// Prioritize name matches, then executable matches // Prioritize name matches, then executable matches
const aName = a.name.toLowerCase() const aName = a.name.toLowerCase();
const bName = b.name.toLowerCase() const bName = b.name.toLowerCase();
const aExecutable = getExecutableName(a).toLowerCase() const aExecutable = getExecutableName(a).toLowerCase();
const bExecutable = getExecutableName(b).toLowerCase() const bExecutable = getExecutableName(b).toLowerCase();
const aStarts = aName.startsWith(searchTerm) const aStarts = aName.startsWith(searchTerm);
const bStarts = bName.startsWith(searchTerm) const bStarts = bName.startsWith(searchTerm);
const aExecStarts = aExecutable.startsWith(searchTerm) const aExecStarts = aExecutable.startsWith(searchTerm);
const bExecStarts = bExecutable.startsWith(searchTerm) const bExecStarts = bExecutable.startsWith(searchTerm);
// Prioritize name matches first // Prioritize name matches first
if (aStarts && !bStarts) if (aStarts && !bStarts)
return -1 return -1;
if (!aStarts && bStarts) if (!aStarts && bStarts)
return 1 return 1;
// Then prioritize executable matches // Then prioritize executable matches
if (aExecStarts && !bExecStarts) if (aExecStarts && !bExecStarts)
return -1 return -1;
if (!aExecStarts && bExecStarts) if (!aExecStarts && bExecStarts)
return 1 return 1;
return aName.localeCompare(bName) return aName.localeCompare(bName);
}).slice(0, 20).map(app => createResultEntry(app)) }).slice(0, 20).map(app => createResultEntry(app));
} }
} }
@@ -195,75 +195,75 @@ Item {
"onActivate": function () { "onActivate": function () {
// Close the launcher/SmartPanel immediately without any animations. // Close the launcher/SmartPanel immediately without any animations.
// Ensures we are not preventing the future focusing of the app // Ensures we are not preventing the future focusing of the app
launcher.close() launcher.close();
Logger.d("ApplicationsPlugin", `Launching: ${app.name}`) Logger.d("ApplicationsPlugin", `Launching: ${app.name}`);
// Record usage and persist asynchronously // Record usage and persist asynchronously
if (Settings.data.appLauncher.sortByMostUsed) if (Settings.data.appLauncher.sortByMostUsed)
recordUsage(app) recordUsage(app);
if (Settings.data.appLauncher.customLaunchPrefixEnabled && Settings.data.appLauncher.customLaunchPrefix) { if (Settings.data.appLauncher.customLaunchPrefixEnabled && Settings.data.appLauncher.customLaunchPrefix) {
// Use custom launch prefix // Use custom launch prefix
const prefix = Settings.data.appLauncher.customLaunchPrefix.split(" ") const prefix = Settings.data.appLauncher.customLaunchPrefix.split(" ");
if (app.runInTerminal) { if (app.runInTerminal) {
const terminal = Settings.data.appLauncher.terminalCommand.split(" ") const terminal = Settings.data.appLauncher.terminalCommand.split(" ");
const command = prefix.concat(terminal.concat(app.command)) const command = prefix.concat(terminal.concat(app.command));
Quickshell.execDetached(command) Quickshell.execDetached(command);
} else { } else {
const command = prefix.concat(app.command) const command = prefix.concat(app.command);
Quickshell.execDetached(command) Quickshell.execDetached(command);
} }
} else if (Settings.data.appLauncher.useApp2Unit && app.id) { } else if (Settings.data.appLauncher.useApp2Unit && app.id) {
Logger.d("ApplicationsPlugin", `Using app2unit for: ${app.id}`) Logger.d("ApplicationsPlugin", `Using app2unit for: ${app.id}`);
if (app.runInTerminal) if (app.runInTerminal)
Quickshell.execDetached(["app2unit", "--", app.id + ".desktop"]) Quickshell.execDetached(["app2unit", "--", app.id + ".desktop"]);
else else
Quickshell.execDetached(["app2unit", "--"].concat(app.command)) Quickshell.execDetached(["app2unit", "--"].concat(app.command));
} else { } else {
// Fallback logic when app2unit is not used // Fallback logic when app2unit is not used
if (app.runInTerminal) { if (app.runInTerminal) {
// If app.execute() fails for terminal apps, we handle it manually. // If app.execute() fails for terminal apps, we handle it manually.
Logger.d("ApplicationsPlugin", "Executing terminal app manually: " + app.name) Logger.d("ApplicationsPlugin", "Executing terminal app manually: " + app.name);
const terminal = Settings.data.appLauncher.terminalCommand.split(" ") const terminal = Settings.data.appLauncher.terminalCommand.split(" ");
const command = terminal.concat(app.command) const command = terminal.concat(app.command);
Quickshell.execDetached(command) Quickshell.execDetached(command);
} else if (app.execute) { } else if (app.execute) {
// Default execution for GUI apps // Default execution for GUI apps
app.execute() app.execute();
} else { } else {
Logger.w("ApplicationsPlugin", `Could not launch: ${app.name}. No valid launch method.`) Logger.w("ApplicationsPlugin", `Could not launch: ${app.name}. No valid launch method.`);
} }
} }
} }
} };
} }
// ------------------------- // -------------------------
// Usage tracking helpers // Usage tracking helpers
function getAppKey(app) { function getAppKey(app) {
if (app && app.id) if (app && app.id)
return String(app.id) return String(app.id);
if (app && app.command && app.command.join) if (app && app.command && app.command.join)
return app.command.join(" ") return app.command.join(" ");
return String(app && app.name ? app.name : "unknown") return String(app && app.name ? app.name : "unknown");
} }
function getUsageCount(app) { function getUsageCount(app) {
const key = getAppKey(app) const key = getAppKey(app);
const m = usageAdapter && usageAdapter.counts ? usageAdapter.counts : null const m = usageAdapter && usageAdapter.counts ? usageAdapter.counts : null;
if (!m) if (!m)
return 0 return 0;
const v = m[key] const v = m[key];
return typeof v === 'number' && isFinite(v) ? v : 0 return typeof v === 'number' && isFinite(v) ? v : 0;
} }
function recordUsage(app) { function recordUsage(app) {
const key = getAppKey(app) const key = getAppKey(app);
if (!usageAdapter.counts) if (!usageAdapter.counts)
usageAdapter.counts = ({}) usageAdapter.counts = ({});
const current = getUsageCount(app) const current = getUsageCount(app);
usageAdapter.counts[key] = current + 1 usageAdapter.counts[key] = current + 1;
// Trigger save via debounced timer // Trigger save via debounced timer
saveTimer.restart() saveTimer.restart();
} }
} }

View File

@@ -1,6 +1,6 @@
import QtQuick import QtQuick
import qs.Commons
import "../../../../Helpers/AdvancedMath.js" as AdvancedMath import "../../../../Helpers/AdvancedMath.js" as AdvancedMath
import qs.Commons
Item { Item {
property var launcher: null property var launcher: null
@@ -8,98 +8,106 @@ Item {
function handleCommand(query) { function handleCommand(query) {
// Handle >calc command or direct math expressions after > // Handle >calc command or direct math expressions after >
return query.startsWith(">calc") || (query.startsWith(">") && query.length > 1 && isMathExpression(query.substring(1))) return query.startsWith(">calc") || (query.startsWith(">") && query.length > 1 && isMathExpression(query.substring(1)));
} }
function commands() { function commands() {
return [{ return [
"name": ">calc", {
"description": I18n.tr("plugins.calculator-description"), "name": ">calc",
"icon": "accessories-calculator", "description": I18n.tr("plugins.calculator-description"),
"isImage": false, "icon": "accessories-calculator",
"onActivate": function () { "isImage": false,
launcher.setSearchText(">calc ") "onActivate": function () {
} launcher.setSearchText(">calc ");
}] }
}
];
} }
function getResults(query) { function getResults(query) {
let expression = "" let expression = "";
if (query.startsWith(">calc")) { if (query.startsWith(">calc")) {
expression = query.substring(5).trim() expression = query.substring(5).trim();
} else if (query.startsWith(">")) { } else if (query.startsWith(">")) {
expression = query.substring(1).trim() expression = query.substring(1).trim();
} else { } else {
return [] return [];
} }
if (!expression) { if (!expression) {
return [{ return [
"name": I18n.tr("plugins.calculator-name"), {
"description": I18n.tr("plugins.calculator-enter-expression"), "name": I18n.tr("plugins.calculator-name"),
"icon": "accessories-calculator", "description": I18n.tr("plugins.calculator-enter-expression"),
"isImage": false, "icon": "accessories-calculator",
"onActivate": function () {} "isImage": false,
}] "onActivate": function () {}
}
];
} }
try { try {
let result = AdvancedMath.evaluate(expression.trim()) let result = AdvancedMath.evaluate(expression.trim());
return [{ return [
"name": AdvancedMath.formatResult(result), {
"description": `${expression} = ${result}`, "name": AdvancedMath.formatResult(result),
"icon": "accessories-calculator", "description": `${expression} = ${result}`,
"isImage": false, "icon": "accessories-calculator",
"onActivate": function () { "isImage": false,
// TODO: copy entry to clipboard via ClipHist "onActivate": function () {
launcher.close() // TODO: copy entry to clipboard via ClipHist
} launcher.close();
}] }
}
];
} catch (error) { } catch (error) {
return [{ return [
"name": I18n.tr("plugins.calculator-error"), {
"description": error.message || "Invalid expression", "name": I18n.tr("plugins.calculator-error"),
"icon": "dialog-error", "description": error.message || "Invalid expression",
"isImage": false, "icon": "dialog-error",
"onActivate": function () {} "isImage": false,
}] "onActivate": function () {}
}
];
} }
} }
function evaluateExpression(expr) { function evaluateExpression(expr) {
// Sanitize input - only allow safe characters // Sanitize input - only allow safe characters
const sanitized = expr.replace(/[^0-9\+\-\*\/\(\)\.\s\%]/g, '') const sanitized = expr.replace(/[^0-9\+\-\*\/\(\)\.\s\%]/g, '');
if (sanitized !== expr) { if (sanitized !== expr) {
throw new Error("Invalid characters in expression") throw new Error("Invalid characters in expression");
} }
// Don't allow empty expressions // Don't allow empty expressions
if (!sanitized.trim()) { if (!sanitized.trim()) {
throw new Error("Empty expression") throw new Error("Empty expression");
} }
try { try {
// Use Function constructor for safe evaluation // Use Function constructor for safe evaluation
// This is safer than eval() but still evaluate math // This is safer than eval() but still evaluate math
const result = Function('"use strict"; return (' + sanitized + ')')() const result = Function('"use strict"; return (' + sanitized + ')')();
// Check for valid result // Check for valid result
if (!isFinite(result)) { if (!isFinite(result)) {
throw new Error("Result is not a finite number") throw new Error("Result is not a finite number");
} }
// Round to reasonable precision to avoid floating point issues // Round to reasonable precision to avoid floating point issues
return Math.round(result * 1000000000) / 1000000000 return Math.round(result * 1000000000) / 1000000000;
} catch (e) { } catch (e) {
throw new Error("Invalid mathematical expression") throw new Error("Invalid mathematical expression");
} }
} }
function isMathExpression(expr) { function isMathExpression(expr) {
// Check if string looks like a math expression // Check if string looks like a math expression
// Allow digits, operators, parentheses, decimal points, and whitespace // Allow digits, operators, parentheses, decimal points, and whitespace
return /^[\d\s\+\-\*\/\(\)\.\%]+$/.test(expr) return /^[\d\s\+\-\*\/\(\)\.\%]+$/.test(expr);
}
} }
}

View File

@@ -25,14 +25,14 @@ Item {
if (gotResults && (lastSearchText === searchText)) { if (gotResults && (lastSearchText === searchText)) {
// Do not update results after the first fetch. // Do not update results after the first fetch.
// This will avoid the list resetting every 2seconds when the service updates. // This will avoid the list resetting every 2seconds when the service updates.
return return;
} }
// Refresh results if we're waiting for data or if clipboard plugin is active // Refresh results if we're waiting for data or if clipboard plugin is active
if (isWaitingForData || (launcher && launcher.searchText.startsWith(">clip"))) { if (isWaitingForData || (launcher && launcher.searchText.startsWith(">clip"))) {
isWaitingForData = false isWaitingForData = false;
gotResults = true gotResults = true;
if (launcher) { if (launcher) {
launcher.updateResults() launcher.updateResults();
} }
} }
} }
@@ -40,142 +40,153 @@ Item {
// Initialize plugin // Initialize plugin
function init() { function init() {
Logger.i("ClipboardPlugin", "Initialized") Logger.i("ClipboardPlugin", "Initialized");
// Pre-load clipboard data if service is active // Pre-load clipboard data if service is active
if (ClipboardService.active) { if (ClipboardService.active) {
ClipboardService.list(100) ClipboardService.list(100);
} }
} }
// Called when launcher opens // Called when launcher opens
function onOpened() { function onOpened() {
isWaitingForData = true isWaitingForData = true;
gotResults = false gotResults = false;
lastSearchText = "" lastSearchText = "";
// Refresh clipboard history when launcher opens // Refresh clipboard history when launcher opens
if (ClipboardService.active) { if (ClipboardService.active) {
ClipboardService.list(100) ClipboardService.list(100);
} }
} }
// Check if this plugin handles the command // Check if this plugin handles the command
function handleCommand(searchText) { function handleCommand(searchText) {
return searchText.startsWith(">clip") return searchText.startsWith(">clip");
} }
// Return available commands when user types ">" // Return available commands when user types ">"
function commands() { function commands() {
return [{ return [
"name": ">clip", {
"description": I18n.tr("plugins.clipboard-search-description"), "name": ">clip",
"icon": "text-x-generic", "description": I18n.tr("plugins.clipboard-search-description"),
"isImage": false, "icon": "text-x-generic",
"onActivate": function () { "isImage": false,
launcher.setSearchText(">clip ") "onActivate": function () {
} launcher.setSearchText(">clip ");
}, { }
"name": ">clip clear", },
"description": I18n.tr("plugins.clipboard-clear-description"), {
"icon": "text-x-generic", "name": ">clip clear",
"isImage": false, "description": I18n.tr("plugins.clipboard-clear-description"),
"onActivate": function () { "icon": "text-x-generic",
ClipboardService.wipeAll() "isImage": false,
launcher.close() "onActivate": function () {
} ClipboardService.wipeAll();
}] launcher.close();
}
}
];
} }
// Get search results // Get search results
function getResults(searchText) { function getResults(searchText) {
if (!searchText.startsWith(">clip")) { if (!searchText.startsWith(">clip")) {
return [] return [];
} }
lastSearchText = searchText lastSearchText = searchText;
const results = [] const results = [];
const query = searchText.slice(5).trim() const query = searchText.slice(5).trim();
// Check if clipboard service is not active // Check if clipboard service is not active
if (!ClipboardService.active) { if (!ClipboardService.active) {
return [{ return [
"name": I18n.tr("plugins.clipboard-history-disabled"), {
"description": I18n.tr("plugins.clipboard-history-disabled-description"), "name": I18n.tr("plugins.clipboard-history-disabled"),
"icon": "view-refresh", "description": I18n.tr("plugins.clipboard-history-disabled-description"),
"isImage": false, "icon": "view-refresh",
"onActivate": function () {} "isImage": false,
}] "onActivate": function () {}
}
];
} }
// Special command: clear // Special command: clear
if (query === "clear") { if (query === "clear") {
return [{ return [
"name": I18n.tr("plugins.clipboard-clear-history"), {
"description": I18n.tr("plugins.clipboard-clear-description-full"), "name": I18n.tr("plugins.clipboard-clear-history"),
"icon": "delete_sweep", "description": I18n.tr("plugins.clipboard-clear-description-full"),
"isImage": false, "icon": "delete_sweep",
"onActivate": function () { "isImage": false,
ClipboardService.wipeAll() "onActivate": function () {
launcher.close() ClipboardService.wipeAll();
} launcher.close();
}] }
}
];
} }
// Show loading state if data is being loaded // Show loading state if data is being loaded
if (ClipboardService.loading || isWaitingForData) { if (ClipboardService.loading || isWaitingForData) {
return [{ return [
"name": I18n.tr("plugins.clipboard-loading"), {
"description": I18n.tr("plugins.clipboard-loading-description"), "name": I18n.tr("plugins.clipboard-loading"),
"icon": "view-refresh", "description": I18n.tr("plugins.clipboard-loading-description"),
"isImage": false, "icon": "view-refresh",
"onActivate": function () {} "isImage": false,
}] "onActivate": function () {}
}
];
} }
// Get clipboard items // Get clipboard items
const items = ClipboardService.items || [] const items = ClipboardService.items || [];
// If no items and we haven't tried loading yet, trigger a load // If no items and we haven't tried loading yet, trigger a load
if (items.count === 0 && !ClipboardService.loading) { if (items.count === 0 && !ClipboardService.loading) {
isWaitingForData = true isWaitingForData = true;
ClipboardService.list(100) ClipboardService.list(100);
return [{ return [
"name": I18n.tr("plugins.clipboard-loading"), {
"description": I18n.tr("plugins.clipboard-loading-description"), "name": I18n.tr("plugins.clipboard-loading"),
"icon": "view-refresh", "description": I18n.tr("plugins.clipboard-loading-description"),
"isImage": false, "icon": "view-refresh",
"onActivate": function () {} "isImage": false,
}] "onActivate": function () {}
}
];
} }
// Search clipboard items // Search clipboard items
const searchTerm = query.toLowerCase() const searchTerm = query.toLowerCase();
// Filter and format results // Filter and format results
items.forEach(function (item) { items.forEach(function (item) {
const preview = (item.preview || "").toLowerCase() const preview = (item.preview || "").toLowerCase();
// Skip if search term doesn't match // Skip if search term doesn't match
if (searchTerm && preview.indexOf(searchTerm) === -1) { if (searchTerm && preview.indexOf(searchTerm) === -1) {
return return;
} }
// Format the result based on type // Format the result based on type
let entry let entry;
if (item.isImage) { if (item.isImage) {
entry = formatImageEntry(item) entry = formatImageEntry(item);
} else { } else {
entry = formatTextEntry(item) entry = formatTextEntry(item);
} }
// Add activation handler // Add activation handler
entry.onActivate = function () { entry.onActivate = function () {
ClipboardService.copyToClipboard(item.id) ClipboardService.copyToClipboard(item.id);
launcher.close() launcher.close();
} };
results.push(entry) results.push(entry);
}) });
// Show empty state if no results // Show empty state if no results
if (results.length === 0) { if (results.length === 0) {
@@ -186,16 +197,16 @@ Item {
"isImage": false, "isImage": false,
"onActivate": function () {// Do nothing "onActivate": function () {// Do nothing
} }
}) });
} }
//Logger.i("ClipboardPlugin", `Returning ${results.length} results for query: "${query}"`) //Logger.i("ClipboardPlugin", `Returning ${results.length} results for query: "${query}"`)
return results return results;
} }
// Helper: Format image clipboard entry // Helper: Format image clipboard entry
function formatImageEntry(item) { function formatImageEntry(item) {
const meta = parseImageMeta(item.preview) const meta = parseImageMeta(item.preview);
// The launcher's delegate will now be responsible for fetching the image data. // The launcher's delegate will now be responsible for fetching the image data.
// This function's role is to provide the necessary metadata for that request. // This function's role is to provide the necessary metadata for that request.
@@ -208,31 +219,31 @@ Item {
"imageHeight": meta ? meta.h : 0, "imageHeight": meta ? meta.h : 0,
"clipboardId": item.id, "clipboardId": item.id,
"mime": item.mime "mime": item.mime
} };
} }
// Helper: Format text clipboard entry with preview // Helper: Format text clipboard entry with preview
function formatTextEntry(item) { function formatTextEntry(item) {
const preview = (item.preview || "").trim() const preview = (item.preview || "").trim();
const lines = preview.split('\n').filter(l => l.trim()) const lines = preview.split('\n').filter(l => l.trim());
// Use first line as title, limit length // Use first line as title, limit length
let title = lines[0] || "Empty text" let title = lines[0] || "Empty text";
if (title.length > 60) { if (title.length > 60) {
title = title.substring(0, 57) + "..." title = title.substring(0, 57) + "...";
} }
// Use second line or character count as description // Use second line or character count as description
let description = "" let description = "";
if (lines.length > 1) { if (lines.length > 1) {
description = lines[1] description = lines[1];
if (description.length > 80) { if (description.length > 80) {
description = description.substring(0, 77) + "..." description = description.substring(0, 77) + "...";
} }
} else { } else {
const chars = preview.length const chars = preview.length;
const words = preview.split(/\s+/).length const words = preview.split(/\s+/).length;
description = `${chars} characters, ${words} word${words !== 1 ? 's' : ''}` description = `${chars} characters, ${words} word${words !== 1 ? 's' : ''}`;
} }
return { return {
@@ -240,16 +251,16 @@ Item {
"description": description, "description": description,
"icon": "text-x-generic", "icon": "text-x-generic",
"isImage": false "isImage": false
} };
} }
// Helper: Parse image metadata from preview string // Helper: Parse image metadata from preview string
function parseImageMeta(preview) { function parseImageMeta(preview) {
const re = /\[\[\s*binary data\s+([\d\.]+\s*(?:KiB|MiB|GiB|B))\s+(\w+)\s+(\d+)x(\d+)\s*\]\]/i const re = /\[\[\s*binary data\s+([\d\.]+\s*(?:KiB|MiB|GiB|B))\s+(\w+)\s+(\d+)x(\d+)\s*\]\]/i;
const match = (preview || "").match(re) const match = (preview || "").match(re);
if (!match) { if (!match) {
return null return null;
} }
return { return {
@@ -257,12 +268,12 @@ Item {
"fmt": (match[2] || "").toUpperCase(), "fmt": (match[2] || "").toUpperCase(),
"w": Number(match[3]), "w": Number(match[3]),
"h": Number(match[4]) "h": Number(match[4])
} };
} }
// Public method to get image data for a clipboard item // Public method to get image data for a clipboard item
// This can be called by the launcher when rendering // This can be called by the launcher when rendering
function getImageForItem(clipboardId) { function getImageForItem(clipboardId) {
return ClipboardService.getImageData ? ClipboardService.getImageData(clipboardId) : null return ClipboardService.getImageData ? ClipboardService.getImageData(clipboardId) : null;
} }
} }

View File

@@ -1,13 +1,13 @@
import QtQuick import QtQuick
import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Wayland
import Quickshell.Services.Notifications import Quickshell.Services.Notifications
import Quickshell.Wayland
import qs.Commons import qs.Commons
import qs.Modules.MainScreen
import qs.Services.System import qs.Services.System
import qs.Widgets import qs.Widgets
import qs.Modules.MainScreen
// Notification History panel // Notification History panel
SmartPanel { SmartPanel {
@@ -17,7 +17,7 @@ SmartPanel {
preferredHeight: Math.round(540 * Style.uiScaleRatio) preferredHeight: Math.round(540 * Style.uiScaleRatio)
onOpened: function () { onOpened: function () {
NotificationService.updateLastSeenTs() NotificationService.updateLastSeenTs();
} }
panelContent: Rectangle { panelContent: Rectangle {
@@ -67,9 +67,9 @@ SmartPanel {
tooltipText: I18n.tr("tooltips.clear-history") tooltipText: I18n.tr("tooltips.clear-history")
baseSize: Style.baseWidgetSize * 0.8 baseSize: Style.baseWidgetSize * 0.8
onClicked: { onClicked: {
NotificationService.clearHistory() NotificationService.clearHistory();
// Close panel as there is nothing more to see. // Close panel as there is nothing more to see.
root.close() root.close();
} }
} }
@@ -177,9 +177,9 @@ SmartPanel {
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: { onClicked: {
if (scrollView.expandedId === notificationId) { if (scrollView.expandedId === notificationId) {
scrollView.expandedId = "" scrollView.expandedId = "";
} else { } else {
scrollView.expandedId = notificationId scrollView.expandedId = notificationId;
} }
} }
} }
@@ -227,11 +227,11 @@ SmartPanel {
visible: model.urgency !== 1 visible: model.urgency !== 1
color: { color: {
if (model.urgency === 2) if (model.urgency === 2)
return Color.mError return Color.mError;
else if (model.urgency === 0) else if (model.urgency === 0)
return Color.mOnSurfaceVariant return Color.mOnSurfaceVariant;
else else
return Color.transparent return Color.transparent;
} }
} }
@@ -312,7 +312,7 @@ SmartPanel {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
onClicked: { onClicked: {
NotificationService.removeFromHistory(notificationId) NotificationService.removeFromHistory(notificationId);
} }
} }
} }

View File

@@ -4,31 +4,31 @@ import QtQuick.Effects
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
import Quickshell.Widgets
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Widgets
import qs.Commons import qs.Commons
import qs.Modules.MainScreen
import qs.Services.Compositor import qs.Services.Compositor
import qs.Services.UI import qs.Services.UI
import qs.Widgets import qs.Widgets
import qs.Modules.MainScreen
SmartPanel { SmartPanel {
id: root id: root
preferredWidth: Math.round(400 * Style.uiScaleRatio) preferredWidth: Math.round(400 * Style.uiScaleRatio)
preferredHeight: { preferredHeight: {
var headerHeight = Settings.data.sessionMenu.showHeader ? Style.baseWidgetSize * 0.6 : 0 var headerHeight = Settings.data.sessionMenu.showHeader ? Style.baseWidgetSize * 0.6 : 0;
var dividerHeight = Settings.data.sessionMenu.showHeader ? Style.marginS : 0 var dividerHeight = Settings.data.sessionMenu.showHeader ? Style.marginS : 0;
var buttonHeight = Style.baseWidgetSize * 1.3 * Style.uiScaleRatio var buttonHeight = Style.baseWidgetSize * 1.3 * Style.uiScaleRatio;
var buttonSpacing = Style.marginS var buttonSpacing = Style.marginS;
var enabledCount = powerOptions.length var enabledCount = powerOptions.length;
var headerSpacing = Settings.data.sessionMenu.showHeader ? (Style.marginL * 2) : 0 var headerSpacing = Settings.data.sessionMenu.showHeader ? (Style.marginL * 2) : 0;
var baseHeight = (Style.marginL * 4) + headerHeight + dividerHeight + headerSpacing var baseHeight = (Style.marginL * 4) + headerHeight + dividerHeight + headerSpacing;
var buttonsHeight = enabledCount > 0 ? (buttonHeight * enabledCount) + (buttonSpacing * (enabledCount - 1)) : 0 var buttonsHeight = enabledCount > 0 ? (buttonHeight * enabledCount) + (buttonSpacing * (enabledCount - 1)) : 0;
return Math.round(baseHeight + buttonsHeight) return Math.round(baseHeight + buttonsHeight);
} }
// Positioning // Positioning
@@ -89,216 +89,216 @@ SmartPanel {
// Build powerOptions from settings, filtering enabled ones and adding metadata // Build powerOptions from settings, filtering enabled ones and adding metadata
property var powerOptions: { property var powerOptions: {
var options = [] var options = [];
var settingsOptions = Settings.data.sessionMenu.powerOptions || [] var settingsOptions = Settings.data.sessionMenu.powerOptions || [];
for (var i = 0; i < settingsOptions.length; i++) { for (var i = 0; i < settingsOptions.length; i++) {
var settingOption = settingsOptions[i] var settingOption = settingsOptions[i];
if (settingOption.enabled && actionMetadata[settingOption.action]) { if (settingOption.enabled && actionMetadata[settingOption.action]) {
var metadata = actionMetadata[settingOption.action] var metadata = actionMetadata[settingOption.action];
options.push({ options.push({
"action": settingOption.action, "action": settingOption.action,
"icon": metadata.icon, "icon": metadata.icon,
"title": metadata.title, "title": metadata.title,
"isShutdown": metadata.isShutdown, "isShutdown": metadata.isShutdown,
"countdownEnabled": settingOption.countdownEnabled !== undefined ? settingOption.countdownEnabled : true "countdownEnabled": settingOption.countdownEnabled !== undefined ? settingOption.countdownEnabled : true
}) });
} }
} }
return options return options;
} }
// Update powerOptions when settings change // Update powerOptions when settings change
Connections { Connections {
target: Settings.data.sessionMenu target: Settings.data.sessionMenu
function onPowerOptionsChanged() { function onPowerOptionsChanged() {
var options = [] var options = [];
var settingsOptions = Settings.data.sessionMenu.powerOptions || [] var settingsOptions = Settings.data.sessionMenu.powerOptions || [];
for (var i = 0; i < settingsOptions.length; i++) { for (var i = 0; i < settingsOptions.length; i++) {
var settingOption = settingsOptions[i] var settingOption = settingsOptions[i];
if (settingOption.enabled && actionMetadata[settingOption.action]) { if (settingOption.enabled && actionMetadata[settingOption.action]) {
var metadata = actionMetadata[settingOption.action] var metadata = actionMetadata[settingOption.action];
options.push({ options.push({
"action": settingOption.action, "action": settingOption.action,
"icon": metadata.icon, "icon": metadata.icon,
"title": metadata.title, "title": metadata.title,
"isShutdown": metadata.isShutdown, "isShutdown": metadata.isShutdown,
"countdownEnabled": settingOption.countdownEnabled !== undefined ? settingOption.countdownEnabled : true "countdownEnabled": settingOption.countdownEnabled !== undefined ? settingOption.countdownEnabled : true
}) });
} }
} }
root.powerOptions = options root.powerOptions = options;
} }
} }
// Lifecycle handlers // Lifecycle handlers
onOpened: { onOpened: {
selectedIndex = 0 selectedIndex = 0;
} }
onClosed: { onClosed: {
cancelTimer() cancelTimer();
selectedIndex = 0 selectedIndex = 0;
} }
// Timer management // Timer management
function startTimer(action) { function startTimer(action) {
// Check if global countdown is disabled // Check if global countdown is disabled
if (!Settings.data.sessionMenu.enableCountdown) { if (!Settings.data.sessionMenu.enableCountdown) {
executeAction(action) executeAction(action);
return return;
} }
// Check per-item countdown setting // Check per-item countdown setting
var option = null var option = null;
for (var i = 0; i < powerOptions.length; i++) { for (var i = 0; i < powerOptions.length; i++) {
if (powerOptions[i].action === action) { if (powerOptions[i].action === action) {
option = powerOptions[i] option = powerOptions[i];
break break;
} }
} }
// If this specific action has countdown disabled, execute immediately // If this specific action has countdown disabled, execute immediately
if (option && option.countdownEnabled === false) { if (option && option.countdownEnabled === false) {
executeAction(action) executeAction(action);
return return;
} }
if (timerActive && pendingAction === action) { if (timerActive && pendingAction === action) {
// Second click - execute immediately // Second click - execute immediately
executeAction(action) executeAction(action);
return return;
} }
pendingAction = action pendingAction = action;
timeRemaining = timerDuration timeRemaining = timerDuration;
timerActive = true timerActive = true;
countdownTimer.start() countdownTimer.start();
} }
function cancelTimer() { function cancelTimer() {
timerActive = false timerActive = false;
pendingAction = "" pendingAction = "";
timeRemaining = 0 timeRemaining = 0;
countdownTimer.stop() countdownTimer.stop();
} }
function executeAction(action) { function executeAction(action) {
// Stop timer but don't reset other properties yet // Stop timer but don't reset other properties yet
countdownTimer.stop() countdownTimer.stop();
switch (action) { switch (action) {
case "lock": case "lock":
// Access lockScreen via PanelService // Access lockScreen via PanelService
if (PanelService.lockScreen && !PanelService.lockScreen.active) { if (PanelService.lockScreen && !PanelService.lockScreen.active) {
PanelService.lockScreen.active = true PanelService.lockScreen.active = true;
} }
break break;
case "suspend": case "suspend":
// Check if we should lock before suspending // Check if we should lock before suspending
if (Settings.data.general.lockOnSuspend) { if (Settings.data.general.lockOnSuspend) {
CompositorService.lockAndSuspend() CompositorService.lockAndSuspend();
} else { } else {
CompositorService.suspend() CompositorService.suspend();
} }
break break;
case "hibernate": case "hibernate":
CompositorService.hibernate() CompositorService.hibernate();
break break;
case "reboot": case "reboot":
CompositorService.reboot() CompositorService.reboot();
break break;
case "logout": case "logout":
CompositorService.logout() CompositorService.logout();
break break;
case "shutdown": case "shutdown":
CompositorService.shutdown() CompositorService.shutdown();
break break;
} }
// Reset timer state and close panel // Reset timer state and close panel
cancelTimer() cancelTimer();
root.close() root.close();
} }
// Navigation functions // Navigation functions
function selectNextWrapped() { function selectNextWrapped() {
if (powerOptions.length > 0) { if (powerOptions.length > 0) {
selectedIndex = (selectedIndex + 1) % powerOptions.length selectedIndex = (selectedIndex + 1) % powerOptions.length;
} }
} }
function selectPreviousWrapped() { function selectPreviousWrapped() {
if (powerOptions.length > 0) { if (powerOptions.length > 0) {
selectedIndex = (((selectedIndex - 1) % powerOptions.length) + powerOptions.length) % powerOptions.length selectedIndex = (((selectedIndex - 1) % powerOptions.length) + powerOptions.length) % powerOptions.length;
} }
} }
function selectFirst() { function selectFirst() {
selectedIndex = 0 selectedIndex = 0;
} }
function selectLast() { function selectLast() {
if (powerOptions.length > 0) { if (powerOptions.length > 0) {
selectedIndex = powerOptions.length - 1 selectedIndex = powerOptions.length - 1;
} else { } else {
selectedIndex = 0 selectedIndex = 0;
} }
} }
function activate() { function activate() {
if (powerOptions.length > 0 && powerOptions[selectedIndex]) { if (powerOptions.length > 0 && powerOptions[selectedIndex]) {
const option = powerOptions[selectedIndex] const option = powerOptions[selectedIndex];
startTimer(option.action) startTimer(option.action);
} }
} }
// Override keyboard handlers from SmartPanel // Override keyboard handlers from SmartPanel
function onEscapePressed() { function onEscapePressed() {
if (timerActive) { if (timerActive) {
cancelTimer() cancelTimer();
} else { } else {
root.close() root.close();
} }
} }
function onTabPressed() { function onTabPressed() {
selectNextWrapped() selectNextWrapped();
} }
function onBackTabPressed() { function onBackTabPressed() {
selectPreviousWrapped() selectPreviousWrapped();
} }
function onUpPressed() { function onUpPressed() {
selectPreviousWrapped() selectPreviousWrapped();
} }
function onDownPressed() { function onDownPressed() {
selectNextWrapped() selectNextWrapped();
} }
function onReturnPressed() { function onReturnPressed() {
activate() activate();
} }
function onHomePressed() { function onHomePressed() {
selectFirst() selectFirst();
} }
function onEndPressed() { function onEndPressed() {
selectLast() selectLast();
} }
function onCtrlJPressed() { function onCtrlJPressed() {
selectNextWrapped() selectNextWrapped();
} }
function onCtrlKPressed() { function onCtrlKPressed() {
selectPreviousWrapped() selectPreviousWrapped();
} }
// Countdown timer // Countdown timer
@@ -307,9 +307,9 @@ SmartPanel {
interval: 100 interval: 100
repeat: true repeat: true
onTriggered: { onTriggered: {
timeRemaining -= interval timeRemaining -= interval;
if (timeRemaining <= 0) { if (timeRemaining <= 0) {
executeAction(pendingAction) executeAction(pendingAction);
} }
} }
} }
@@ -320,23 +320,23 @@ SmartPanel {
// Navigation functions // Navigation functions
function selectFirst() { function selectFirst() {
root.selectFirst() root.selectFirst();
} }
function selectLast() { function selectLast() {
root.selectLast() root.selectLast();
} }
function selectNextWrapped() { function selectNextWrapped() {
root.selectNextWrapped() root.selectNextWrapped();
} }
function selectPreviousWrapped() { function selectPreviousWrapped() {
root.selectPreviousWrapped() root.selectPreviousWrapped();
} }
function activate() { function activate() {
root.activate() root.activate();
} }
NBox { NBox {
@@ -379,10 +379,10 @@ SmartPanel {
colorFg: timerActive ? Color.mError : Color.mOnSurface colorFg: timerActive ? Color.mError : Color.mOnSurface
onClicked: { onClicked: {
if (timerActive) { if (timerActive) {
cancelTimer() cancelTimer();
} else { } else {
cancelTimer() cancelTimer();
root.close() root.close();
} }
} }
} }
@@ -407,8 +407,8 @@ SmartPanel {
isShutdown: modelData.isShutdown || false isShutdown: modelData.isShutdown || false
isSelected: index === selectedIndex isSelected: index === selectedIndex
onClicked: { onClicked: {
selectedIndex = index selectedIndex = index;
startTimer(modelData.action) startTimer(modelData.action);
} }
pending: timerActive && pendingAction === modelData.action pending: timerActive && pendingAction === modelData.action
} }
@@ -434,12 +434,12 @@ SmartPanel {
radius: Style.radiusS radius: Style.radiusS
color: { color: {
if (pending) { if (pending) {
return Qt.alpha(Color.mPrimary, 0.08) return Qt.alpha(Color.mPrimary, 0.08);
} }
if (isSelected || mouseArea.containsMouse) { if (isSelected || mouseArea.containsMouse) {
return Color.mHover return Color.mHover;
} }
return Color.transparent return Color.transparent;
} }
border.width: pending ? Math.max(Style.borderM) : 0 border.width: pending ? Math.max(Style.borderM) : 0
@@ -464,12 +464,12 @@ SmartPanel {
icon: buttonRoot.icon icon: buttonRoot.icon
color: { color: {
if (buttonRoot.pending) if (buttonRoot.pending)
return Color.mPrimary return Color.mPrimary;
if (buttonRoot.isShutdown && !buttonRoot.isSelected && !mouseArea.containsMouse) if (buttonRoot.isShutdown && !buttonRoot.isSelected && !mouseArea.containsMouse)
return Color.mError return Color.mError;
if (buttonRoot.isSelected || mouseArea.containsMouse) if (buttonRoot.isSelected || mouseArea.containsMouse)
return Color.mOnHover return Color.mOnHover;
return Color.mOnSurface return Color.mOnSurface;
} }
pointSize: Style.fontSizeXXL pointSize: Style.fontSizeXXL
width: Style.baseWidgetSize * 0.5 width: Style.baseWidgetSize * 0.5
@@ -499,12 +499,12 @@ SmartPanel {
pointSize: Style.fontSizeM pointSize: Style.fontSizeM
color: { color: {
if (buttonRoot.pending) if (buttonRoot.pending)
return Color.mPrimary return Color.mPrimary;
if (buttonRoot.isShutdown && !buttonRoot.isSelected && !mouseArea.containsMouse) if (buttonRoot.isShutdown && !buttonRoot.isSelected && !mouseArea.containsMouse)
return Color.mError return Color.mError;
if (buttonRoot.isSelected || mouseArea.containsMouse) if (buttonRoot.isSelected || mouseArea.containsMouse)
return Color.mOnHover return Color.mOnHover;
return Color.mOnSurface return Color.mOnSurface;
} }
Behavior on color { Behavior on color {

View File

@@ -3,8 +3,8 @@ import QtQuick.Controls
import QtQuick.Effects import QtQuick.Effects
import QtQuick.Layouts import QtQuick.Layouts
import qs.Commons import qs.Commons
import qs.Widgets
import qs.Services.UI import qs.Services.UI
import qs.Widgets
// Widget Settings Dialog Component // Widget Settings Dialog Component
Popup { Popup {
@@ -27,7 +27,7 @@ Popup {
onOpened: { onOpened: {
// Load settings when popup opens with data // Load settings when popup opens with data
if (widgetData && widgetId) { if (widgetData && widgetId) {
loadWidgetSettings() loadWidgetSettings();
} }
} }
@@ -102,9 +102,9 @@ Popup {
icon: "check" icon: "check"
onClicked: { onClicked: {
if (settingsLoader.item && settingsLoader.item.saveSettings) { if (settingsLoader.item && settingsLoader.item.saveSettings) {
var newSettings = settingsLoader.item.saveSettings() var newSettings = settingsLoader.item.saveSettings();
root.updateWidgetSettings(root.sectionId, root.widgetIndex, newSettings) root.updateWidgetSettings(root.sectionId, root.widgetIndex, newSettings);
root.close() root.close();
} }
} }
} }
@@ -112,13 +112,13 @@ Popup {
} }
function loadWidgetSettings() { function loadWidgetSettings() {
const source = BarWidgetRegistry.widgetSettingsMap[widgetId] const source = BarWidgetRegistry.widgetSettingsMap[widgetId];
if (source) { if (source) {
// Use setSource to pass properties at creation time // Use setSource to pass properties at creation time
settingsLoader.setSource(source, { settingsLoader.setSource(source, {
"widgetData": widgetData, "widgetData": widgetData,
"widgetMetadata": BarWidgetRegistry.widgetMetadata[widgetId] "widgetMetadata": BarWidgetRegistry.widgetMetadata[widgetId]
}) });
} }
} }
} }

View File

@@ -22,35 +22,39 @@ ColumnLayout {
Component.onCompleted: { Component.onCompleted: {
if (widgetData && widgetData.hideMode !== undefined) { if (widgetData && widgetData.hideMode !== undefined) {
valueHideMode = widgetData.hideMode valueHideMode = widgetData.hideMode;
} }
} }
function saveSettings() { function saveSettings() {
var settings = Object.assign({}, widgetData || {}) var settings = Object.assign({}, widgetData || {});
settings.hideMode = valueHideMode settings.hideMode = valueHideMode;
settings.showIcon = valueShowIcon settings.showIcon = valueShowIcon;
settings.scrollingMode = valueScrollingMode settings.scrollingMode = valueScrollingMode;
settings.maxWidth = parseInt(widthInput.text) || widgetMetadata.maxWidth settings.maxWidth = parseInt(widthInput.text) || widgetMetadata.maxWidth;
settings.useFixedWidth = valueUseFixedWidth settings.useFixedWidth = valueUseFixedWidth;
settings.colorizeIcons = valueColorizeIcons settings.colorizeIcons = valueColorizeIcons;
return settings return settings;
} }
NComboBox { NComboBox {
Layout.fillWidth: true Layout.fillWidth: true
label: I18n.tr("bar.widget-settings.active-window.hide-mode.label") label: I18n.tr("bar.widget-settings.active-window.hide-mode.label")
description: I18n.tr("bar.widget-settings.active-window.hide-mode.description") description: I18n.tr("bar.widget-settings.active-window.hide-mode.description")
model: [{ model: [
{
"key": "visible", "key": "visible",
"name": I18n.tr("options.hide-modes.visible") "name": I18n.tr("options.hide-modes.visible")
}, { },
{
"key": "hidden", "key": "hidden",
"name": I18n.tr("options.hide-modes.hidden") "name": I18n.tr("options.hide-modes.hidden")
}, { },
{
"key": "transparent", "key": "transparent",
"name": I18n.tr("options.hide-modes.transparent") "name": I18n.tr("options.hide-modes.transparent")
}] }
]
currentKey: root.valueHideMode currentKey: root.valueHideMode
onSelected: key => root.valueHideMode = key onSelected: key => root.valueHideMode = key
} }
@@ -91,16 +95,20 @@ ColumnLayout {
NComboBox { NComboBox {
label: I18n.tr("bar.widget-settings.active-window.scrolling-mode.label") label: I18n.tr("bar.widget-settings.active-window.scrolling-mode.label")
description: I18n.tr("bar.widget-settings.active-window.scrolling-mode.description") description: I18n.tr("bar.widget-settings.active-window.scrolling-mode.description")
model: [{ model: [
{
"key": "always", "key": "always",
"name": I18n.tr("options.scrolling-modes.always") "name": I18n.tr("options.scrolling-modes.always")
}, { },
{
"key": "hover", "key": "hover",
"name": I18n.tr("options.scrolling-modes.hover") "name": I18n.tr("options.scrolling-modes.hover")
}, { },
{
"key": "never", "key": "never",
"name": I18n.tr("options.scrolling-modes.never") "name": I18n.tr("options.scrolling-modes.never")
}] }
]
currentKey: valueScrollingMode currentKey: valueScrollingMode
onSelected: key => valueScrollingMode = key onSelected: key => valueScrollingMode = key
minimumWidth: 200 minimumWidth: 200

View File

@@ -17,11 +17,11 @@ ColumnLayout {
property string valueColorName: widgetData.colorName !== undefined ? widgetData.colorName : widgetMetadata.colorName property string valueColorName: widgetData.colorName !== undefined ? widgetData.colorName : widgetMetadata.colorName
function saveSettings() { function saveSettings() {
var settings = Object.assign({}, widgetData || {}) var settings = Object.assign({}, widgetData || {});
settings.width = parseInt(widthInput.text) || widgetMetadata.width settings.width = parseInt(widthInput.text) || widgetMetadata.width;
settings.hideWhenIdle = valueHideWhenIdle settings.hideWhenIdle = valueHideWhenIdle;
settings.colorName = valueColorName settings.colorName = valueColorName;
return settings return settings;
} }
NTextInput { NTextInput {
@@ -37,22 +37,28 @@ ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
label: I18n.tr("bar.widget-settings.audio-visualizer.color-name.label") label: I18n.tr("bar.widget-settings.audio-visualizer.color-name.label")
description: I18n.tr("bar.widget-settings.audio-visualizer.color-name.description") description: I18n.tr("bar.widget-settings.audio-visualizer.color-name.description")
model: [{ model: [
{
"key": "primary", "key": "primary",
"name": I18n.tr("options.colors.primary") "name": I18n.tr("options.colors.primary")
}, { },
{
"key": "secondary", "key": "secondary",
"name": I18n.tr("options.colors.secondary") "name": I18n.tr("options.colors.secondary")
}, { },
{
"key": "tertiary", "key": "tertiary",
"name": I18n.tr("options.colors.tertiary") "name": I18n.tr("options.colors.tertiary")
}, { },
{
"key": "onSurface", "key": "onSurface",
"name": I18n.tr("options.colors.onSurface") "name": I18n.tr("options.colors.onSurface")
}, { },
{
"key": "error", "key": "error",
"name": I18n.tr("options.colors.error") "name": I18n.tr("options.colors.error")
}] }
]
currentKey: root.valueColorName currentKey: root.valueColorName
onSelected: key => root.valueColorName = key onSelected: key => root.valueColorName = key
} }

View File

@@ -17,26 +17,30 @@ ColumnLayout {
property int valueWarningThreshold: widgetData.warningThreshold !== undefined ? widgetData.warningThreshold : widgetMetadata.warningThreshold property int valueWarningThreshold: widgetData.warningThreshold !== undefined ? widgetData.warningThreshold : widgetMetadata.warningThreshold
function saveSettings() { function saveSettings() {
var settings = Object.assign({}, widgetData || {}) var settings = Object.assign({}, widgetData || {});
settings.displayMode = valueDisplayMode settings.displayMode = valueDisplayMode;
settings.warningThreshold = valueWarningThreshold settings.warningThreshold = valueWarningThreshold;
return settings return settings;
} }
NComboBox { NComboBox {
label: I18n.tr("bar.widget-settings.battery.display-mode.label") label: I18n.tr("bar.widget-settings.battery.display-mode.label")
description: I18n.tr("bar.widget-settings.battery.display-mode.description") description: I18n.tr("bar.widget-settings.battery.display-mode.description")
minimumWidth: 134 minimumWidth: 134
model: [{ model: [
{
"key": "onhover", "key": "onhover",
"name": I18n.tr("options.display-mode.on-hover") "name": I18n.tr("options.display-mode.on-hover")
}, { },
{
"key": "alwaysShow", "key": "alwaysShow",
"name": I18n.tr("options.display-mode.always-show") "name": I18n.tr("options.display-mode.always-show")
}, { },
{
"key": "alwaysHide", "key": "alwaysHide",
"name": I18n.tr("options.display-mode.always-hide") "name": I18n.tr("options.display-mode.always-hide")
}] }
]
currentKey: root.valueDisplayMode currentKey: root.valueDisplayMode
onSelected: key => root.valueDisplayMode = key onSelected: key => root.valueDisplayMode = key
} }

View File

@@ -15,25 +15,29 @@ ColumnLayout {
property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode
function saveSettings() { function saveSettings() {
var settings = Object.assign({}, widgetData || {}) var settings = Object.assign({}, widgetData || {});
settings.displayMode = valueDisplayMode settings.displayMode = valueDisplayMode;
return settings return settings;
} }
NComboBox { NComboBox {
label: I18n.tr("bar.widget-settings.battery.display-mode.label") label: I18n.tr("bar.widget-settings.battery.display-mode.label")
description: I18n.tr("bar.widget-settings.battery.display-mode.description") description: I18n.tr("bar.widget-settings.battery.display-mode.description")
minimumWidth: 134 minimumWidth: 134
model: [{ model: [
{
"key": "onhover", "key": "onhover",
"name": I18n.tr("options.display-mode.on-hover") "name": I18n.tr("options.display-mode.on-hover")
}, { },
{
"key": "alwaysShow", "key": "alwaysShow",
"name": I18n.tr("options.display-mode.always-show") "name": I18n.tr("options.display-mode.always-show")
}, { },
{
"key": "alwaysHide", "key": "alwaysHide",
"name": I18n.tr("options.display-mode.always-hide") "name": I18n.tr("options.display-mode.always-hide")
}] }
]
currentKey: root.valueDisplayMode currentKey: root.valueDisplayMode
onSelected: key => root.valueDisplayMode = key onSelected: key => root.valueDisplayMode = key
} }

View File

@@ -16,25 +16,29 @@ ColumnLayout {
property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode
function saveSettings() { function saveSettings() {
var settings = Object.assign({}, widgetData || {}) var settings = Object.assign({}, widgetData || {});
settings.displayMode = valueDisplayMode settings.displayMode = valueDisplayMode;
return settings return settings;
} }
NComboBox { NComboBox {
label: I18n.tr("bar.widget-settings.brightness.display-mode.label") label: I18n.tr("bar.widget-settings.brightness.display-mode.label")
description: I18n.tr("bar.widget-settings.brightness.display-mode.description") description: I18n.tr("bar.widget-settings.brightness.display-mode.description")
minimumWidth: 134 minimumWidth: 134
model: [{ model: [
{
"key": "onhover", "key": "onhover",
"name": I18n.tr("options.display-mode.on-hover") "name": I18n.tr("options.display-mode.on-hover")
}, { },
{
"key": "alwaysShow", "key": "alwaysShow",
"name": I18n.tr("options.display-mode.always-show") "name": I18n.tr("options.display-mode.always-show")
}, { },
{
"key": "alwaysHide", "key": "alwaysHide",
"name": I18n.tr("options.display-mode.always-hide") "name": I18n.tr("options.display-mode.always-hide")
}] }
]
currentKey: valueDisplayMode currentKey: valueDisplayMode
onSelected: key => valueDisplayMode = key onSelected: key => valueDisplayMode = key
} }

View File

@@ -2,8 +2,8 @@ import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import qs.Commons import qs.Commons
import qs.Widgets
import qs.Services.System import qs.Services.System
import qs.Widgets
ColumnLayout { ColumnLayout {
id: root id: root
@@ -28,13 +28,13 @@ ColumnLayout {
readonly property var now: Time.now readonly property var now: Time.now
function saveSettings() { function saveSettings() {
var settings = Object.assign({}, widgetData || {}) var settings = Object.assign({}, widgetData || {});
settings.usePrimaryColor = valueUsePrimaryColor settings.usePrimaryColor = valueUsePrimaryColor;
settings.useCustomFont = valueUseCustomFont settings.useCustomFont = valueUseCustomFont;
settings.customFont = valueCustomFont settings.customFont = valueCustomFont;
settings.formatHorizontal = valueFormatHorizontal.trim() settings.formatHorizontal = valueFormatHorizontal.trim();
settings.formatVertical = valueFormatVertical.trim() settings.formatVertical = valueFormatVertical.trim();
return settings return settings;
} }
// Function to insert token at cursor position in the focused input // Function to insert token at cursor position in the focused input
@@ -42,25 +42,25 @@ ColumnLayout {
if (!focusedInput || !focusedInput.inputItem) { if (!focusedInput || !focusedInput.inputItem) {
// If no input is focused, default to horiz // If no input is focused, default to horiz
if (inputHoriz.inputItem) { if (inputHoriz.inputItem) {
inputHoriz.inputItem.focus = true inputHoriz.inputItem.focus = true;
focusedInput = inputHoriz focusedInput = inputHoriz;
} }
} }
if (focusedInput && focusedInput.inputItem) { if (focusedInput && focusedInput.inputItem) {
var input = focusedInput.inputItem var input = focusedInput.inputItem;
var cursorPos = input.cursorPosition var cursorPos = input.cursorPosition;
var currentText = input.text var currentText = input.text;
// Insert token at cursor position // Insert token at cursor position
var newText = currentText.substring(0, cursorPos) + token + currentText.substring(cursorPos) var newText = currentText.substring(0, cursorPos) + token + currentText.substring(cursorPos);
input.text = newText + " " input.text = newText + " ";
// Move cursor after the inserted token // Move cursor after the inserted token
input.cursorPosition = cursorPos + token.length + 1 input.cursorPosition = cursorPos + token.length + 1;
// Ensure the input keeps focus // Ensure the input keeps focus
input.focus = true input.focus = true;
} }
} }
@@ -92,7 +92,7 @@ ColumnLayout {
popupHeight: 420 popupHeight: 420
minimumWidth: 300 minimumWidth: 300
onSelected: function (key) { onSelected: function (key) {
valueCustomFont = key valueCustomFont = key;
} }
} }
@@ -131,9 +131,9 @@ ColumnLayout {
if (inputItem) { if (inputItem) {
inputItem.onActiveFocusChanged.connect(function () { inputItem.onActiveFocusChanged.connect(function () {
if (inputItem.activeFocus) { if (inputItem.activeFocus) {
root.focusedInput = inputHoriz root.focusedInput = inputHoriz;
} }
}) });
} }
} }
} }
@@ -155,9 +155,9 @@ ColumnLayout {
if (inputItem) { if (inputItem) {
inputItem.onActiveFocusChanged.connect(function () { inputItem.onActiveFocusChanged.connect(function () {
if (inputItem.activeFocus) { if (inputItem.activeFocus) {
root.focusedInput = inputVert root.focusedInput = inputVert;
} }
}) });
} }
} }
} }

View File

@@ -20,12 +20,12 @@ ColumnLayout {
property bool valueColorizeDistroLogo: widgetData.colorizeDistroLogo !== undefined ? widgetData.colorizeDistroLogo : (widgetMetadata.colorizeDistroLogo !== undefined ? widgetMetadata.colorizeDistroLogo : false) property bool valueColorizeDistroLogo: widgetData.colorizeDistroLogo !== undefined ? widgetData.colorizeDistroLogo : (widgetMetadata.colorizeDistroLogo !== undefined ? widgetMetadata.colorizeDistroLogo : false)
function saveSettings() { function saveSettings() {
var settings = Object.assign({}, widgetData || {}) var settings = Object.assign({}, widgetData || {});
settings.icon = valueIcon settings.icon = valueIcon;
settings.useDistroLogo = valueUseDistroLogo settings.useDistroLogo = valueUseDistroLogo;
settings.customIconPath = valueCustomIconPath settings.customIconPath = valueCustomIconPath;
settings.colorizeDistroLogo = valueColorizeDistroLogo settings.colorizeDistroLogo = valueColorizeDistroLogo;
return settings return settings;
} }
NToggle { NToggle {
@@ -33,10 +33,10 @@ ColumnLayout {
description: I18n.tr("bar.widget-settings.control-center.use-distro-logo.description") description: I18n.tr("bar.widget-settings.control-center.use-distro-logo.description")
checked: valueUseDistroLogo checked: valueUseDistroLogo
onToggled: function (checked) { onToggled: function (checked) {
valueUseDistroLogo = checked valueUseDistroLogo = checked;
if (checked) { if (checked) {
valueCustomIconPath = "" valueCustomIconPath = "";
valueIcon = "" valueIcon = "";
} }
} }
} }
@@ -47,7 +47,7 @@ ColumnLayout {
description: I18n.tr("bar.widget-settings.control-center.colorize-distro-logo.description") description: I18n.tr("bar.widget-settings.control-center.colorize-distro-logo.description")
checked: valueColorizeDistroLogo checked: valueColorizeDistroLogo
onToggled: function (checked) { onToggled: function (checked) {
valueColorizeDistroLogo = checked valueColorizeDistroLogo = checked;
} }
} }
@@ -94,8 +94,8 @@ ColumnLayout {
id: iconPicker id: iconPicker
initialIcon: valueIcon initialIcon: valueIcon
onIconSelected: iconName => { onIconSelected: iconName => {
valueIcon = iconName valueIcon = iconName;
valueCustomIconPath = "" valueCustomIconPath = "";
} }
} }
@@ -107,7 +107,7 @@ ColumnLayout {
initialPath: Quickshell.env("HOME") initialPath: Quickshell.env("HOME")
onAccepted: paths => { onAccepted: paths => {
if (paths.length > 0) { if (paths.length > 0) {
valueCustomIconPath = paths[0] // Use first selected file valueCustomIconPath = paths[0]; // Use first selected file
} }
} }
} }

View File

@@ -19,21 +19,21 @@ ColumnLayout {
property bool valueHideTextInVerticalBar: widgetData.hideTextInVerticalBar !== undefined ? widgetData.hideTextInVerticalBar : widgetMetadata.hideTextInVerticalBar property bool valueHideTextInVerticalBar: widgetData.hideTextInVerticalBar !== undefined ? widgetData.hideTextInVerticalBar : widgetMetadata.hideTextInVerticalBar
function saveSettings() { function saveSettings() {
var settings = Object.assign({}, widgetData || {}) var settings = Object.assign({}, widgetData || {});
settings.icon = valueIcon settings.icon = valueIcon;
settings.leftClickExec = leftClickExecInput.text settings.leftClickExec = leftClickExecInput.text;
settings.leftClickUpdateText = leftClickUpdateText.checked settings.leftClickUpdateText = leftClickUpdateText.checked;
settings.rightClickExec = rightClickExecInput.text settings.rightClickExec = rightClickExecInput.text;
settings.rightClickUpdateText = rightClickUpdateText.checked settings.rightClickUpdateText = rightClickUpdateText.checked;
settings.middleClickExec = middleClickExecInput.text settings.middleClickExec = middleClickExecInput.text;
settings.middleClickUpdateText = middleClickUpdateText.checked settings.middleClickUpdateText = middleClickUpdateText.checked;
settings.textCommand = textCommandInput.text settings.textCommand = textCommandInput.text;
settings.textCollapse = textCollapseInput.text settings.textCollapse = textCollapseInput.text;
settings.textStream = valueTextStream settings.textStream = valueTextStream;
settings.parseJson = valueParseJson settings.parseJson = valueParseJson;
settings.hideTextInVerticalBar = valueHideTextInVerticalBar settings.hideTextInVerticalBar = valueHideTextInVerticalBar;
settings.textIntervalMs = parseInt(textIntervalInput.text || textIntervalInput.placeholderText, 10) settings.textIntervalMs = parseInt(textIntervalInput.text || textIntervalInput.placeholderText, 10);
return settings return settings;
} }
RowLayout { RowLayout {
@@ -61,7 +61,7 @@ ColumnLayout {
id: iconPicker id: iconPicker
initialIcon: valueIcon initialIcon: valueIcon
onIconSelected: function (iconName) { onIconSelected: function (iconName) {
valueIcon = iconName valueIcon = iconName;
} }
} }

View File

@@ -16,25 +16,29 @@ ColumnLayout {
property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode property string valueDisplayMode: widgetData.displayMode !== undefined ? widgetData.displayMode : widgetMetadata.displayMode
function saveSettings() { function saveSettings() {
var settings = Object.assign({}, widgetData || {}) var settings = Object.assign({}, widgetData || {});
settings.displayMode = valueDisplayMode settings.displayMode = valueDisplayMode;
return settings return settings;
} }
NComboBox { NComboBox {
label: I18n.tr("bar.widget-settings.keyboard-layout.display-mode.label") label: I18n.tr("bar.widget-settings.keyboard-layout.display-mode.label")
description: I18n.tr("bar.widget-settings.keyboard-layout.display-mode.description") description: I18n.tr("bar.widget-settings.keyboard-layout.display-mode.description")
minimumWidth: 134 minimumWidth: 134
model: [{ model: [
{
"key": "onhover", "key": "onhover",
"name": I18n.tr("options.display-mode.on-hover") "name": I18n.tr("options.display-mode.on-hover")
}, { },
{
"key": "forceOpen", "key": "forceOpen",
"name": I18n.tr("options.display-mode.force-open") "name": I18n.tr("options.display-mode.force-open")
}, { },
{
"key": "alwaysHide", "key": "alwaysHide",
"name": I18n.tr("options.display-mode.always-hide") "name": I18n.tr("options.display-mode.always-hide")
}] }
]
currentKey: valueDisplayMode currentKey: valueDisplayMode
onSelected: key => valueDisplayMode = key onSelected: key => valueDisplayMode = key
} }

Some files were not shown because too many files have changed in this diff Show More