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
# QML Formatter Script
# Uses: https://github.com/jesperhh/qmlfmt
# Install: AUR package "qmlfmt-git" (requires qt6-5compat)
if command -v qmlfmt &>/dev/null; then
echo "Using 'qmlfmt' for formatting."
format_file() { qmlfmt -e -b 360 -t 2 -i 2 -w "$1" || { echo "Failed: $1" >&2; return 1; }; }
# Find qmlformat binary
if command -v qmlformat &>/dev/null; then
QMLFORMAT="qmlformat"
elif [ -x "/usr/lib/qt6/bin/qmlformat" ]; then
QMLFORMAT="/usr/lib/qt6/bin/qmlformat"
else
echo "No 'qmlfmt' found in PATH." >&2
echo "No 'qmlformat' found in PATH or /usr/lib/qt6/bin." >&2
exit 1
fi
echo "Using 'qmlformat' for formatting: $QMLFORMAT"
export QMLFORMAT
format_file() { "$QMLFORMAT" -w 2 -W 360 -S --semicolon-rule always -i "$1" || { echo "Failed: $1" >&2; return 1; }; }
export -f format_file
# Find all .qml files
mapfile -t all_files < <(find "${1:-.}" -name "*.qml" -type f)
[ ${#all_files[@]} -eq 0 ] && { echo "No QML files found"; exit 0; }
echo "Scanning ${#all_files[@]} files for array destructuring..."
safe_files=()
for file in "${all_files[@]}"; do
# Checks for a comma inside brackets followed by an equals sign aka "array destructuring"
# as this ES6 syntax is not supported by qmlfmt and will result in breakage.
if grep -qE '\[.*,.*\]\s*=' "$file"; then
echo "-> Skipping (Array destructuring detected): $file" >&2
else
safe_files+=("$file")
fi
done
[ ${#safe_files[@]} -eq 0 ] && { echo "No safe files to format after filtering."; exit 0; }
echo "Formatting ${#safe_files[@]} files..."
printf '%s\0' "${safe_files[@]}" | \
echo "Formatting ${#all_files[@]} files..."
printf '%s\0' "${all_files[@]}" | \
xargs -0 -P "${QMLFMT_JOBS:-$(nproc)}" -I {} bash -c 'format_file "$@"' _ {} \
&& echo "Done" || { echo "Errors occurred" >&2; exit 1; }

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,11 +7,6 @@ import qs.Services.Power
Singleton {
id: root
/*
Preset sizes for font, radii, ?
*/
// Font size
readonly property real fontSizeXXS: 8
readonly property real fontSizeXS: 9
@@ -86,29 +81,27 @@ Singleton {
readonly property real barHeight: {
switch (Settings.data.bar.density) {
case "mini":
return (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 22 : 20
return (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 22 : 20;
case "compact":
return (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 27 : 25
return (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 27 : 25;
case "comfortable":
return (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 39 : 37
return (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 39 : 37;
default:
case "default":
return (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 33 : 31
return (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? 33 : 31;
}
}
readonly property real capsuleHeight: {
switch (Settings.data.bar.density) {
case "mini":
return Math.round(barHeight * 1.0)
return Math.round(barHeight * 1.0);
case "compact":
return Math.round(barHeight * 0.85)
return Math.round(barHeight * 0.85);
case "comfortable":
return Math.round(barHeight * 0.73)
return Math.round(barHeight * 0.73);
default:
case "default":
return Math.round(barHeight * 0.82)
return Math.round(barHeight * 0.82);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -20,12 +20,12 @@ Rectangle {
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section]
var widgets = Settings.data.bar.widgets[section];
if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex]
return widgets[sectionWidgetIndex];
}
}
return {}
return {};
}
readonly property string barPosition: Settings.data.bar.position
@@ -70,9 +70,9 @@ Rectangle {
Binding on pointSize {
value: {
if (repeater.model.length == 1) {
return Style.fontSizeS * scaling
return Style.fontSizeS * scaling;
} else {
return (index == 0) ? Style.fontSizeXS * scaling : Style.fontSizeXXS * scaling
return (index == 0) ? Style.fontSizeXS * scaling : Style.fontSizeXXS * scaling;
}
}
}
@@ -122,15 +122,15 @@ Rectangle {
hoverEnabled: true
onEntered: {
if (!PanelService.getPanel("calendarPanel", screen)?.active) {
TooltipService.show(Screen, root, I18n.tr("clock.tooltip"), BarService.getTooltipDirection())
TooltipService.show(Screen, root, I18n.tr("clock.tooltip"), BarService.getTooltipDirection());
}
}
onExited: {
TooltipService.hide()
TooltipService.hide();
}
onClicked: {
TooltipService.hide()
PanelService.getPanel("calendarPanel", screen)?.toggle(this)
TooltipService.hide();
PanelService.getPanel("calendarPanel", screen)?.toggle(this);
}
}
}

View File

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

View File

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

View File

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

View File

@@ -2,10 +2,10 @@ import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Modules.Bar.Extras
import qs.Services.Power
import qs.Services.UI
import qs.Widgets
import qs.Modules.Bar.Extras
Item {
id: root
@@ -21,12 +21,12 @@ Item {
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: {
if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section]
var widgets = Settings.data.bar.widgets[section];
if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex]
return widgets[sectionWidgetIndex];
}
}
return {}
return {};
}
readonly property bool isBarVertical: Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
@@ -47,20 +47,20 @@ Item {
forceOpen: IdleInhibitorService.timeout !== null
forceClose: IdleInhibitorService.timeout == null
onWheel: function (delta) {
var sign = delta > 0 ? 1 : -1
var sign = delta > 0 ? 1 : -1;
// the offset makes scrolling down feel symmetrical to scrolling up
var timeout = IdleInhibitorService.timeout - (delta < 0 ? 60 : 0)
var timeout = IdleInhibitorService.timeout - (delta < 0 ? 60 : 0);
if (timeout == null || timeout < 600) {
delta = 60 // <= 10m, increment at 1m interval
delta = 60; // <= 10m, increment at 1m interval
} else if (timeout >= 600 && timeout < 1800) {
delta = 300 // >= 10m, increment at 5m interval
delta = 300; // >= 10m, increment at 5m interval
} else if (timeout >= 1800 && timeout < 3600) {
delta = 600 // >= 30m, increment at 10m interval
delta = 600; // >= 30m, increment at 10m interval
} else if (timeout >= 3600) {
delta = 1800 // > 1h, increment at 30m interval
delta = 1800; // > 1h, increment at 30m interval
}
IdleInhibitorService.changeTimeout(delta * sign)
IdleInhibitorService.changeTimeout(delta * sign);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,8 +3,8 @@ import Quickshell
import Quickshell.Wayland
import qs.Commons
import qs.Services.UI
import qs.Modules.MainScreen
import qs.Services.UI
// ------------------------------
// MainScreen for each screen (manages bar + all panels)
@@ -16,10 +16,10 @@ Variants {
property bool shouldBeActive: {
if (!modelData || !modelData.name) {
return false
return false;
}
Logger.d("Shell", "MainScreen activated for", modelData?.name)
return true
Logger.d("Shell", "MainScreen activated for", modelData?.name);
return true;
}
property bool windowLoaded: false
@@ -33,7 +33,7 @@ Variants {
onLoaded: {
// Signal that window is loaded so exclusion zone can be created
parent.windowLoaded = true
parent.windowLoaded = true;
}
sourceComponent: MainScreen {
@@ -45,11 +45,11 @@ Variants {
Loader {
active: {
if (!parent.windowLoaded || !parent.shouldBeActive || !BarService.isVisible)
return false
return false;
// Check if bar is configured for this screen
var monitors = Settings.data.bar.monitors || []
return monitors.length === 0 || monitors.includes(modelData?.name)
var monitors = Settings.data.bar.monitors || [];
return monitors.length === 0 || monitors.includes(modelData?.name);
}
asynchronous: false
@@ -58,7 +58,7 @@ Variants {
}
onLoaded: {
Logger.d("Shell", "BarContentWindow created for", modelData?.name)
Logger.d("Shell", "BarContentWindow created for", modelData?.name);
}
}
@@ -67,11 +67,11 @@ Variants {
Loader {
active: {
if (!parent.windowLoaded || !parent.shouldBeActive || !BarService.isVisible)
return false
return false;
// Check if bar is configured for this screen
var monitors = Settings.data.bar.monitors || []
return monitors.length === 0 || monitors.includes(modelData?.name)
var monitors = Settings.data.bar.monitors || [];
return monitors.length === 0 || monitors.includes(modelData?.name);
}
asynchronous: false
@@ -80,7 +80,7 @@ Variants {
}
onLoaded: {
Logger.d("Shell", "BarExclusionZone created for", modelData?.name)
Logger.d("Shell", "BarExclusionZone created for", modelData?.name);
}
}
@@ -95,7 +95,7 @@ Variants {
}
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.Widgets
/**
* AllBackgrounds - Unified Shape container for all bar and panel backgrounds
*
* Unified shadow system. This component contains a single Shape
* with multiple ShapePath children (one for bar, one for each panel type).
*
* Benefits:
* - Single GPU-accelerated rendering pass for all backgrounds
* - Unified shadow system (one MultiEffect for everything)
*/
* AllBackgrounds - Unified Shape container for all bar and panel backgrounds
*
* Unified shadow system. This component contains a single Shape
* with multiple ShapePath children (one for bar, one for each panel type).
*
* Benefits:
* - Single GPU-accelerated rendering pass for all backgrounds
* - Unified shadow system (one MultiEffect for everything)
*/
Item {
id: root
@@ -46,15 +45,14 @@ Item {
enabled: false // Disable mouse input on the Shape itself
Component.onCompleted: {
Logger.d("AllBackgrounds", "AllBackgrounds initialized")
Logger.d("AllBackgrounds", " bar:", root.bar)
Logger.d("AllBackgrounds", " windowRoot:", root.windowRoot)
Logger.d("AllBackgrounds", "AllBackgrounds initialized");
Logger.d("AllBackgrounds", " bar:", root.bar);
Logger.d("AllBackgrounds", " windowRoot:", root.windowRoot);
}
/**
* Bar
*/
* Bar
*/
BarBackground {
bar: root.bar
shapeContainer: backgroundsShape
@@ -62,10 +60,9 @@ Item {
backgroundColor: Qt.alpha(Color.mSurface, Settings.data.bar.backgroundOpacity)
}
/**
* Panels
*/
* Panels
*/
// Audio
PanelBackground {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
import QtQuick
import qs.Commons
import "../../../../Helpers/AdvancedMath.js" as AdvancedMath
import qs.Commons
Item {
property var launcher: null
@@ -8,98 +8,106 @@ Item {
function handleCommand(query) {
// 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() {
return [{
"name": ">calc",
"description": I18n.tr("plugins.calculator-description"),
"icon": "accessories-calculator",
"isImage": false,
"onActivate": function () {
launcher.setSearchText(">calc ")
}
}]
return [
{
"name": ">calc",
"description": I18n.tr("plugins.calculator-description"),
"icon": "accessories-calculator",
"isImage": false,
"onActivate": function () {
launcher.setSearchText(">calc ");
}
}
];
}
function getResults(query) {
let expression = ""
let expression = "";
if (query.startsWith(">calc")) {
expression = query.substring(5).trim()
expression = query.substring(5).trim();
} else if (query.startsWith(">")) {
expression = query.substring(1).trim()
expression = query.substring(1).trim();
} else {
return []
return [];
}
if (!expression) {
return [{
"name": I18n.tr("plugins.calculator-name"),
"description": I18n.tr("plugins.calculator-enter-expression"),
"icon": "accessories-calculator",
"isImage": false,
"onActivate": function () {}
}]
return [
{
"name": I18n.tr("plugins.calculator-name"),
"description": I18n.tr("plugins.calculator-enter-expression"),
"icon": "accessories-calculator",
"isImage": false,
"onActivate": function () {}
}
];
}
try {
let result = AdvancedMath.evaluate(expression.trim())
let result = AdvancedMath.evaluate(expression.trim());
return [{
"name": AdvancedMath.formatResult(result),
"description": `${expression} = ${result}`,
"icon": "accessories-calculator",
"isImage": false,
"onActivate": function () {
// TODO: copy entry to clipboard via ClipHist
launcher.close()
}
}]
return [
{
"name": AdvancedMath.formatResult(result),
"description": `${expression} = ${result}`,
"icon": "accessories-calculator",
"isImage": false,
"onActivate": function () {
// TODO: copy entry to clipboard via ClipHist
launcher.close();
}
}
];
} catch (error) {
return [{
"name": I18n.tr("plugins.calculator-error"),
"description": error.message || "Invalid expression",
"icon": "dialog-error",
"isImage": false,
"onActivate": function () {}
}]
return [
{
"name": I18n.tr("plugins.calculator-error"),
"description": error.message || "Invalid expression",
"icon": "dialog-error",
"isImage": false,
"onActivate": function () {}
}
];
}
}
function evaluateExpression(expr) {
// 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) {
throw new Error("Invalid characters in expression")
throw new Error("Invalid characters in expression");
}
// Don't allow empty expressions
if (!sanitized.trim()) {
throw new Error("Empty expression")
throw new Error("Empty expression");
}
try {
// Use Function constructor for safe evaluation
// 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
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
return Math.round(result * 1000000000) / 1000000000
return Math.round(result * 1000000000) / 1000000000;
} catch (e) {
throw new Error("Invalid mathematical expression")
throw new Error("Invalid mathematical expression");
}
}
function isMathExpression(expr) {
// Check if string looks like a math expression
// 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)) {
// Do not update results after the first fetch.
// 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
if (isWaitingForData || (launcher && launcher.searchText.startsWith(">clip"))) {
isWaitingForData = false
gotResults = true
isWaitingForData = false;
gotResults = true;
if (launcher) {
launcher.updateResults()
launcher.updateResults();
}
}
}
@@ -40,142 +40,153 @@ Item {
// Initialize plugin
function init() {
Logger.i("ClipboardPlugin", "Initialized")
Logger.i("ClipboardPlugin", "Initialized");
// Pre-load clipboard data if service is active
if (ClipboardService.active) {
ClipboardService.list(100)
ClipboardService.list(100);
}
}
// Called when launcher opens
function onOpened() {
isWaitingForData = true
gotResults = false
lastSearchText = ""
isWaitingForData = true;
gotResults = false;
lastSearchText = "";
// Refresh clipboard history when launcher opens
if (ClipboardService.active) {
ClipboardService.list(100)
ClipboardService.list(100);
}
}
// Check if this plugin handles the command
function handleCommand(searchText) {
return searchText.startsWith(">clip")
return searchText.startsWith(">clip");
}
// Return available commands when user types ">"
function commands() {
return [{
"name": ">clip",
"description": I18n.tr("plugins.clipboard-search-description"),
"icon": "text-x-generic",
"isImage": false,
"onActivate": function () {
launcher.setSearchText(">clip ")
}
}, {
"name": ">clip clear",
"description": I18n.tr("plugins.clipboard-clear-description"),
"icon": "text-x-generic",
"isImage": false,
"onActivate": function () {
ClipboardService.wipeAll()
launcher.close()
}
}]
return [
{
"name": ">clip",
"description": I18n.tr("plugins.clipboard-search-description"),
"icon": "text-x-generic",
"isImage": false,
"onActivate": function () {
launcher.setSearchText(">clip ");
}
},
{
"name": ">clip clear",
"description": I18n.tr("plugins.clipboard-clear-description"),
"icon": "text-x-generic",
"isImage": false,
"onActivate": function () {
ClipboardService.wipeAll();
launcher.close();
}
}
];
}
// Get search results
function getResults(searchText) {
if (!searchText.startsWith(">clip")) {
return []
return [];
}
lastSearchText = searchText
const results = []
const query = searchText.slice(5).trim()
lastSearchText = searchText;
const results = [];
const query = searchText.slice(5).trim();
// Check if clipboard service is not active
if (!ClipboardService.active) {
return [{
"name": I18n.tr("plugins.clipboard-history-disabled"),
"description": I18n.tr("plugins.clipboard-history-disabled-description"),
"icon": "view-refresh",
"isImage": false,
"onActivate": function () {}
}]
return [
{
"name": I18n.tr("plugins.clipboard-history-disabled"),
"description": I18n.tr("plugins.clipboard-history-disabled-description"),
"icon": "view-refresh",
"isImage": false,
"onActivate": function () {}
}
];
}
// Special command: clear
if (query === "clear") {
return [{
"name": I18n.tr("plugins.clipboard-clear-history"),
"description": I18n.tr("plugins.clipboard-clear-description-full"),
"icon": "delete_sweep",
"isImage": false,
"onActivate": function () {
ClipboardService.wipeAll()
launcher.close()
}
}]
return [
{
"name": I18n.tr("plugins.clipboard-clear-history"),
"description": I18n.tr("plugins.clipboard-clear-description-full"),
"icon": "delete_sweep",
"isImage": false,
"onActivate": function () {
ClipboardService.wipeAll();
launcher.close();
}
}
];
}
// Show loading state if data is being loaded
if (ClipboardService.loading || isWaitingForData) {
return [{
"name": I18n.tr("plugins.clipboard-loading"),
"description": I18n.tr("plugins.clipboard-loading-description"),
"icon": "view-refresh",
"isImage": false,
"onActivate": function () {}
}]
return [
{
"name": I18n.tr("plugins.clipboard-loading"),
"description": I18n.tr("plugins.clipboard-loading-description"),
"icon": "view-refresh",
"isImage": false,
"onActivate": function () {}
}
];
}
// 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 (items.count === 0 && !ClipboardService.loading) {
isWaitingForData = true
ClipboardService.list(100)
return [{
"name": I18n.tr("plugins.clipboard-loading"),
"description": I18n.tr("plugins.clipboard-loading-description"),
"icon": "view-refresh",
"isImage": false,
"onActivate": function () {}
}]
isWaitingForData = true;
ClipboardService.list(100);
return [
{
"name": I18n.tr("plugins.clipboard-loading"),
"description": I18n.tr("plugins.clipboard-loading-description"),
"icon": "view-refresh",
"isImage": false,
"onActivate": function () {}
}
];
}
// Search clipboard items
const searchTerm = query.toLowerCase()
const searchTerm = query.toLowerCase();
// Filter and format results
items.forEach(function (item) {
const preview = (item.preview || "").toLowerCase()
const preview = (item.preview || "").toLowerCase();
// Skip if search term doesn't match
if (searchTerm && preview.indexOf(searchTerm) === -1) {
return
return;
}
// Format the result based on type
let entry
let entry;
if (item.isImage) {
entry = formatImageEntry(item)
entry = formatImageEntry(item);
} else {
entry = formatTextEntry(item)
entry = formatTextEntry(item);
}
// Add activation handler
entry.onActivate = function () {
ClipboardService.copyToClipboard(item.id)
launcher.close()
}
ClipboardService.copyToClipboard(item.id);
launcher.close();
};
results.push(entry)
})
results.push(entry);
});
// Show empty state if no results
if (results.length === 0) {
@@ -186,16 +197,16 @@ Item {
"isImage": false,
"onActivate": function () {// Do nothing
}
})
});
}
//Logger.i("ClipboardPlugin", `Returning ${results.length} results for query: "${query}"`)
return results
return results;
}
// Helper: Format image clipboard entry
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.
// This function's role is to provide the necessary metadata for that request.
@@ -208,31 +219,31 @@ Item {
"imageHeight": meta ? meta.h : 0,
"clipboardId": item.id,
"mime": item.mime
}
};
}
// Helper: Format text clipboard entry with preview
function formatTextEntry(item) {
const preview = (item.preview || "").trim()
const lines = preview.split('\n').filter(l => l.trim())
const preview = (item.preview || "").trim();
const lines = preview.split('\n').filter(l => l.trim());
// Use first line as title, limit length
let title = lines[0] || "Empty text"
let title = lines[0] || "Empty text";
if (title.length > 60) {
title = title.substring(0, 57) + "..."
title = title.substring(0, 57) + "...";
}
// Use second line or character count as description
let description = ""
let description = "";
if (lines.length > 1) {
description = lines[1]
description = lines[1];
if (description.length > 80) {
description = description.substring(0, 77) + "..."
description = description.substring(0, 77) + "...";
}
} else {
const chars = preview.length
const words = preview.split(/\s+/).length
description = `${chars} characters, ${words} word${words !== 1 ? 's' : ''}`
const chars = preview.length;
const words = preview.split(/\s+/).length;
description = `${chars} characters, ${words} word${words !== 1 ? 's' : ''}`;
}
return {
@@ -240,16 +251,16 @@ Item {
"description": description,
"icon": "text-x-generic",
"isImage": false
}
};
}
// Helper: Parse image metadata from preview string
function parseImageMeta(preview) {
const re = /\[\[\s*binary data\s+([\d\.]+\s*(?:KiB|MiB|GiB|B))\s+(\w+)\s+(\d+)x(\d+)\s*\]\]/i
const match = (preview || "").match(re)
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);
if (!match) {
return null
return null;
}
return {
@@ -257,12 +268,12 @@ Item {
"fmt": (match[2] || "").toUpperCase(),
"w": Number(match[3]),
"h": Number(match[4])
}
};
}
// Public method to get image data for a clipboard item
// This can be called by the launcher when rendering
function getImageForItem(clipboardId) {
return ClipboardService.getImageData ? ClipboardService.getImageData(clipboardId) : null
return ClipboardService.getImageData ? ClipboardService.getImageData(clipboardId) : null;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,8 +2,8 @@ import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Widgets
import qs.Services.System
import qs.Widgets
ColumnLayout {
id: root
@@ -28,13 +28,13 @@ ColumnLayout {
readonly property var now: Time.now
function saveSettings() {
var settings = Object.assign({}, widgetData || {})
settings.usePrimaryColor = valueUsePrimaryColor
settings.useCustomFont = valueUseCustomFont
settings.customFont = valueCustomFont
settings.formatHorizontal = valueFormatHorizontal.trim()
settings.formatVertical = valueFormatVertical.trim()
return settings
var settings = Object.assign({}, widgetData || {});
settings.usePrimaryColor = valueUsePrimaryColor;
settings.useCustomFont = valueUseCustomFont;
settings.customFont = valueCustomFont;
settings.formatHorizontal = valueFormatHorizontal.trim();
settings.formatVertical = valueFormatVertical.trim();
return settings;
}
// Function to insert token at cursor position in the focused input
@@ -42,25 +42,25 @@ ColumnLayout {
if (!focusedInput || !focusedInput.inputItem) {
// If no input is focused, default to horiz
if (inputHoriz.inputItem) {
inputHoriz.inputItem.focus = true
focusedInput = inputHoriz
inputHoriz.inputItem.focus = true;
focusedInput = inputHoriz;
}
}
if (focusedInput && focusedInput.inputItem) {
var input = focusedInput.inputItem
var cursorPos = input.cursorPosition
var currentText = input.text
var input = focusedInput.inputItem;
var cursorPos = input.cursorPosition;
var currentText = input.text;
// Insert token at cursor position
var newText = currentText.substring(0, cursorPos) + token + currentText.substring(cursorPos)
input.text = newText + " "
var newText = currentText.substring(0, cursorPos) + token + currentText.substring(cursorPos);
input.text = newText + " ";
// Move cursor after the inserted token
input.cursorPosition = cursorPos + token.length + 1
input.cursorPosition = cursorPos + token.length + 1;
// Ensure the input keeps focus
input.focus = true
input.focus = true;
}
}
@@ -92,7 +92,7 @@ ColumnLayout {
popupHeight: 420
minimumWidth: 300
onSelected: function (key) {
valueCustomFont = key
valueCustomFont = key;
}
}
@@ -131,9 +131,9 @@ ColumnLayout {
if (inputItem) {
inputItem.onActiveFocusChanged.connect(function () {
if (inputItem.activeFocus) {
root.focusedInput = inputHoriz
root.focusedInput = inputHoriz;
}
})
});
}
}
}
@@ -155,9 +155,9 @@ ColumnLayout {
if (inputItem) {
inputItem.onActiveFocusChanged.connect(function () {
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)
function saveSettings() {
var settings = Object.assign({}, widgetData || {})
settings.icon = valueIcon
settings.useDistroLogo = valueUseDistroLogo
settings.customIconPath = valueCustomIconPath
settings.colorizeDistroLogo = valueColorizeDistroLogo
return settings
var settings = Object.assign({}, widgetData || {});
settings.icon = valueIcon;
settings.useDistroLogo = valueUseDistroLogo;
settings.customIconPath = valueCustomIconPath;
settings.colorizeDistroLogo = valueColorizeDistroLogo;
return settings;
}
NToggle {
@@ -33,10 +33,10 @@ ColumnLayout {
description: I18n.tr("bar.widget-settings.control-center.use-distro-logo.description")
checked: valueUseDistroLogo
onToggled: function (checked) {
valueUseDistroLogo = checked
valueUseDistroLogo = checked;
if (checked) {
valueCustomIconPath = ""
valueIcon = ""
valueCustomIconPath = "";
valueIcon = "";
}
}
}
@@ -47,7 +47,7 @@ ColumnLayout {
description: I18n.tr("bar.widget-settings.control-center.colorize-distro-logo.description")
checked: valueColorizeDistroLogo
onToggled: function (checked) {
valueColorizeDistroLogo = checked
valueColorizeDistroLogo = checked;
}
}
@@ -94,8 +94,8 @@ ColumnLayout {
id: iconPicker
initialIcon: valueIcon
onIconSelected: iconName => {
valueIcon = iconName
valueCustomIconPath = ""
valueIcon = iconName;
valueCustomIconPath = "";
}
}
@@ -107,7 +107,7 @@ ColumnLayout {
initialPath: Quickshell.env("HOME")
onAccepted: paths => {
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
function saveSettings() {
var settings = Object.assign({}, widgetData || {})
settings.icon = valueIcon
settings.leftClickExec = leftClickExecInput.text
settings.leftClickUpdateText = leftClickUpdateText.checked
settings.rightClickExec = rightClickExecInput.text
settings.rightClickUpdateText = rightClickUpdateText.checked
settings.middleClickExec = middleClickExecInput.text
settings.middleClickUpdateText = middleClickUpdateText.checked
settings.textCommand = textCommandInput.text
settings.textCollapse = textCollapseInput.text
settings.textStream = valueTextStream
settings.parseJson = valueParseJson
settings.hideTextInVerticalBar = valueHideTextInVerticalBar
settings.textIntervalMs = parseInt(textIntervalInput.text || textIntervalInput.placeholderText, 10)
return settings
var settings = Object.assign({}, widgetData || {});
settings.icon = valueIcon;
settings.leftClickExec = leftClickExecInput.text;
settings.leftClickUpdateText = leftClickUpdateText.checked;
settings.rightClickExec = rightClickExecInput.text;
settings.rightClickUpdateText = rightClickUpdateText.checked;
settings.middleClickExec = middleClickExecInput.text;
settings.middleClickUpdateText = middleClickUpdateText.checked;
settings.textCommand = textCommandInput.text;
settings.textCollapse = textCollapseInput.text;
settings.textStream = valueTextStream;
settings.parseJson = valueParseJson;
settings.hideTextInVerticalBar = valueHideTextInVerticalBar;
settings.textIntervalMs = parseInt(textIntervalInput.text || textIntervalInput.placeholderText, 10);
return settings;
}
RowLayout {
@@ -61,7 +61,7 @@ ColumnLayout {
id: iconPicker
initialIcon: valueIcon
onIconSelected: function (iconName) {
valueIcon = iconName
valueIcon = iconName;
}
}

View File

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

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