1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-05-03 19:12:11 -04:00

Compare commits

...

15 Commits

Author SHA1 Message Date
bbedward
3bc6461e2a sysmon: change spacing of monitor widgets 2025-11-23 23:26:00 -05:00
bbedward
d3194e15e2 dock: hide pin to dock for internal windows 2025-11-23 22:55:47 -05:00
bbedward
2db79ef202 dankbar: de-bounce bar settings 2025-11-23 22:23:18 -05:00
bbedward
b3c07edef6 notifications: fix DnD tooltip 2025-11-23 20:37:08 -05:00
bbedward
b773fdca34 cc: fix brightness tooltip 2025-11-23 20:33:52 -05:00
bbedward
2e9f9f7b7e media: restore tooltips 2025-11-23 20:31:54 -05:00
bbedward
30cbfe729d dank tooltip v2: apply to settings 2025-11-23 20:00:45 -05:00
Álvaro
b036da2446 Added per app volume control (#801)
* Added per app volume control

* format and lint fixes
2025-11-23 19:46:21 -05:00
bbedward
c8a9fb1674 media: make controls more usable since popout change 2025-11-23 19:38:10 -05:00
bbedward
43bea80cad power: disable profile osd by default, ensure dbus activation doesnt
happen
2025-11-23 18:17:35 -05:00
Lucas
23538c0323 bar: fix auto-hide hiding when tray popout is opened (#802) 2025-11-23 18:06:55 -05:00
bbedward
2ae911230d osd: try to optimize power profile osd more 2025-11-23 17:29:56 -05:00
bbedward
5ce1cb87ea power profile: put OSD in a lazyloader 2025-11-23 16:55:22 -05:00
bbedward
2a37028b6a dock: touch of inner padding to dms icon 2025-11-23 16:00:51 -05:00
bbedward
8130feb2a0 paths: show dms icon & title for dms windows 2025-11-23 15:57:03 -05:00
29 changed files with 2817 additions and 2376 deletions

View File

@@ -1,5 +1,4 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtCore
@@ -23,79 +22,79 @@ Singleton {
property string profileLastPath: ""
property var fileBrowserSettings: ({
"wallpaper": {
"lastPath": "",
"viewMode": "grid",
"sortBy": "name",
"sortAscending": true,
"iconSizeIndex": 1,
"showSidebar": true
},
"profile": {
"lastPath": "",
"viewMode": "grid",
"sortBy": "name",
"sortAscending": true,
"iconSizeIndex": 1,
"showSidebar": true
},
"notepad_save": {
"lastPath": "",
"viewMode": "list",
"sortBy": "name",
"sortAscending": true,
"iconSizeIndex": 1,
"showSidebar": true
},
"notepad_load": {
"lastPath": "",
"viewMode": "list",
"sortBy": "name",
"sortAscending": true,
"iconSizeIndex": 1,
"showSidebar": true
},
"generic": {
"lastPath": "",
"viewMode": "list",
"sortBy": "name",
"sortAscending": true,
"iconSizeIndex": 1,
"showSidebar": true
},
"default": {
"lastPath": "",
"viewMode": "list",
"sortBy": "name",
"sortAscending": true,
"iconSizeIndex": 1,
"showSidebar": true
}
})
"wallpaper": {
"lastPath": "",
"viewMode": "grid",
"sortBy": "name",
"sortAscending": true,
"iconSizeIndex": 1,
"showSidebar": true
},
"profile": {
"lastPath": "",
"viewMode": "grid",
"sortBy": "name",
"sortAscending": true,
"iconSizeIndex": 1,
"showSidebar": true
},
"notepad_save": {
"lastPath": "",
"viewMode": "list",
"sortBy": "name",
"sortAscending": true,
"iconSizeIndex": 1,
"showSidebar": true
},
"notepad_load": {
"lastPath": "",
"viewMode": "list",
"sortBy": "name",
"sortAscending": true,
"iconSizeIndex": 1,
"showSidebar": true
},
"generic": {
"lastPath": "",
"viewMode": "list",
"sortBy": "name",
"sortAscending": true,
"iconSizeIndex": 1,
"showSidebar": true
},
"default": {
"lastPath": "",
"viewMode": "list",
"sortBy": "name",
"sortAscending": true,
"iconSizeIndex": 1,
"showSidebar": true
}
})
Component.onCompleted: {
if (!isGreeterMode) {
loadCache()
loadCache();
}
}
function loadCache() {
_loading = true
parseCache(cacheFile.text())
_loading = false
_loading = true;
parseCache(cacheFile.text());
_loading = false;
}
function parseCache(content) {
_loading = true
_loading = true;
try {
if (content && content.trim()) {
const cache = JSON.parse(content)
const cache = JSON.parse(content);
wallpaperLastPath = cache.wallpaperLastPath !== undefined ? cache.wallpaperLastPath : ""
profileLastPath = cache.profileLastPath !== undefined ? cache.profileLastPath : ""
wallpaperLastPath = cache.wallpaperLastPath !== undefined ? cache.wallpaperLastPath : "";
profileLastPath = cache.profileLastPath !== undefined ? cache.profileLastPath : "";
if (cache.fileBrowserSettings !== undefined) {
fileBrowserSettings = cache.fileBrowserSettings
fileBrowserSettings = cache.fileBrowserSettings;
} else if (cache.fileBrowserViewMode !== undefined) {
fileBrowserSettings = {
"wallpaper": {
@@ -122,65 +121,60 @@ Singleton {
"iconSizeIndex": 1,
"showSidebar": true
}
}
};
}
if (cache.configVersion === undefined) {
migrateFromUndefinedToV1(cache)
cleanupUnusedKeys()
saveCache()
migrateFromUndefinedToV1(cache);
cleanupUnusedKeys();
saveCache();
}
}
} catch (e) {
console.warn("CacheData: Failed to parse cache:", e.message)
console.warn("CacheData: Failed to parse cache:", e.message);
} finally {
_loading = false
_loading = false;
}
}
function saveCache() {
if (_loading)
return
return;
cacheFile.setText(JSON.stringify({
"wallpaperLastPath": wallpaperLastPath,
"profileLastPath": profileLastPath,
"fileBrowserSettings": fileBrowserSettings,
"configVersion": cacheConfigVersion
}, null, 2))
"wallpaperLastPath": wallpaperLastPath,
"profileLastPath": profileLastPath,
"fileBrowserSettings": fileBrowserSettings,
"configVersion": cacheConfigVersion
}, null, 2));
}
function migrateFromUndefinedToV1(cache) {
console.info("CacheData: Migrating configuration from undefined to version 1")
console.info("CacheData: Migrating configuration from undefined to version 1");
}
function cleanupUnusedKeys() {
const validKeys = [
"wallpaperLastPath",
"profileLastPath",
"fileBrowserSettings",
"configVersion"
]
const validKeys = ["wallpaperLastPath", "profileLastPath", "fileBrowserSettings", "configVersion"];
try {
const content = cacheFile.text()
if (!content || !content.trim()) return
const cache = JSON.parse(content)
let needsSave = false
const content = cacheFile.text();
if (!content || !content.trim())
return;
const cache = JSON.parse(content);
let needsSave = false;
for (const key in cache) {
if (!validKeys.includes(key)) {
console.log("CacheData: Removing unused key:", key)
delete cache[key]
needsSave = true
console.log("CacheData: Removing unused key:", key);
delete cache[key];
needsSave = true;
}
}
if (needsSave) {
cacheFile.setText(JSON.stringify(cache, null, 2))
cacheFile.setText(JSON.stringify(cache, null, 2));
}
} catch (e) {
console.warn("CacheData: Failed to cleanup unused keys:", e.message)
console.warn("CacheData: Failed to cleanup unused keys:", e.message);
}
}
@@ -194,12 +188,12 @@ Singleton {
watchChanges: !isGreeterMode
onLoaded: {
if (!isGreeterMode) {
parseCache(cacheFile.text())
parseCache(cacheFile.text());
}
}
onLoadFailed: error => {
if (!isGreeterMode) {
console.info("CacheData: No cache file found, starting fresh")
console.info("CacheData: No cache file found, starting fresh");
}
}
}

View File

@@ -1,28 +1,24 @@
import Quickshell
pragma Singleton
pragma ComponentBehavior: Bound
import Quickshell
Singleton {
id: root
// Clear all image cache
function clearImageCache() {
Quickshell.execDetached(["rm", "-rf", Paths.stringify(
Paths.imagecache)])
Paths.mkdir(Paths.imagecache)
Quickshell.execDetached(["rm", "-rf", Paths.stringify(Paths.imagecache)]);
Paths.mkdir(Paths.imagecache);
}
// Clear cache older than specified minutes
function clearOldCache(ageInMinutes) {
Quickshell.execDetached(
["find", Paths.stringify(
Paths.imagecache), "-name", "*.png", "-mmin", `+${ageInMinutes}`, "-delete"])
Quickshell.execDetached(["find", Paths.stringify(Paths.imagecache), "-name", "*.png", "-mmin", `+${ageInMinutes}`, "-delete"]);
}
// Clear cache for specific size
function clearCacheForSize(size) {
Quickshell.execDetached(
["find", Paths.stringify(
Paths.imagecache), "-name", `*@${size}x${size}.png`, "-delete"])
Quickshell.execDetached(["find", Paths.stringify(Paths.imagecache), "-name", `*@${size}x${size}.png`, "-delete"]);
}
// Get cache size in MB
@@ -30,8 +26,7 @@ Singleton {
var process = Qt.createQmlObject(`
import Quickshell.Io
Process {
command: ["du", "-sm", "${Paths.stringify(
Paths.imagecache)}"]
command: ["du", "-sm", "${Paths.stringify(Paths.imagecache)}"]
running: true
stdout: StdioCollector {
onStreamFinished: {
@@ -40,6 +35,6 @@ Singleton {
}
}
}
`, root)
`, root);
}
}

View File

@@ -1,27 +1,26 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Qt.labs.folderlistmodel
import Quickshell
import Quickshell.Io
pragma Singleton
pragma ComponentBehavior: Bound
Singleton {
id: root
readonly property string _rawLocale: Qt.locale().name
readonly property string _lang: _rawLocale.split(/[_-]/)[0]
readonly property var _candidates: {
readonly property var _candidates: {
const fullUnderscore = _rawLocale;
const fullHyphen = _rawLocale.replace("_", "-");
const fullHyphen = _rawLocale.replace("_", "-");
return [fullUnderscore, fullHyphen, _lang].filter(c => c && c !== "en");
}
readonly property url translationsFolder: Qt.resolvedUrl("../translations/poexports")
property string currentLocale: "en"
property var translations: ({})
property bool translationsLoaded: false
property var translations: ({})
property bool translationsLoaded: false
property url _selectedPath: ""
@@ -32,7 +31,8 @@ Singleton {
showDirs: false
showDotAndDotDot: false
onStatusChanged: if (status === FolderListModel.Ready) root._pickTranslation()
onStatusChanged: if (status === FolderListModel.Ready)
root._pickTranslation()
}
FileView {
@@ -41,73 +41,75 @@ Singleton {
onLoaded: {
try {
root.translations = JSON.parse(text())
root.translationsLoaded = true
console.info(`I18n: Loaded translations for '${root.currentLocale}' ` +
`(${Object.keys(root.translations).length} contexts)`)
root.translations = JSON.parse(text());
root.translationsLoaded = true;
console.info(`I18n: Loaded translations for '${root.currentLocale}' ` + `(${Object.keys(root.translations).length} contexts)`);
} catch (e) {
console.warn(`I18n: Error parsing '${root.currentLocale}':`, e,
"- falling back to English")
root._fallbackToEnglish()
console.warn(`I18n: Error parsing '${root.currentLocale}':`, e, "- falling back to English");
root._fallbackToEnglish();
}
}
onLoadFailed: (error) => {
console.warn(`I18n: Failed to load '${root.currentLocale}' (${error}), ` +
"falling back to English")
root._fallbackToEnglish()
onLoadFailed: error => {
console.warn(`I18n: Failed to load '${root.currentLocale}' (${error}), ` + "falling back to English");
root._fallbackToEnglish();
}
}
function _pickTranslation() {
const present = new Set()
const present = new Set();
for (let i = 0; i < dir.count; i++) {
const name = dir.get(i, "fileName") // e.g. "zh_CN.json"
const name = dir.get(i, "fileName"); // e.g. "zh_CN.json"
if (name && name.endsWith(".json")) {
present.add(name.slice(0, -5))
present.add(name.slice(0, -5));
}
}
for (let i = 0; i < _candidates.length; i++) {
const cand = _candidates[i]
const cand = _candidates[i];
if (present.has(cand)) {
_useLocale(cand, dir.folder + "/" + cand + ".json")
return
_useLocale(cand, dir.folder + "/" + cand + ".json");
return;
}
}
_fallbackToEnglish()
_fallbackToEnglish();
}
function _useLocale(localeTag, fileUrl) {
currentLocale = localeTag
_selectedPath = fileUrl
translationsLoaded = false
translations = ({})
console.info(`I18n: Using locale '${localeTag}' from ${fileUrl}`)
currentLocale = localeTag;
_selectedPath = fileUrl;
translationsLoaded = false;
translations = ({});
console.info(`I18n: Using locale '${localeTag}' from ${fileUrl}`);
}
function _fallbackToEnglish() {
currentLocale = "en"
_selectedPath = ""
translationsLoaded = false
translations = ({})
console.warn("I18n: Falling back to built-in English strings")
currentLocale = "en";
_selectedPath = "";
translationsLoaded = false;
translations = ({});
console.warn("I18n: Falling back to built-in English strings");
}
function tr(term, context) {
if (!translationsLoaded || !translations) return term
const ctx = context || term
if (translations[ctx] && translations[ctx][term]) return translations[ctx][term]
if (!translationsLoaded || !translations)
return term;
const ctx = context || term;
if (translations[ctx] && translations[ctx][term])
return translations[ctx][term];
for (const c in translations) {
if (translations[c] && translations[c][term]) return translations[c][term]
if (translations[c] && translations[c][term])
return translations[c][term];
}
return term
return term;
}
function trContext(context, term) {
if (!translationsLoaded || !translations) return term
if (translations[context] && translations[context][term]) return translations[context][term]
return term
if (!translationsLoaded || !translations)
return term;
if (translations[context] && translations[context][term])
return translations[context][term];
return term;
}
}

View File

@@ -1,4 +1,5 @@
pragma Singleton
pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
@@ -10,11 +11,11 @@ Singleton {
function openModal(modal) {
if (!modal.allowStacking) {
closeAllModalsExcept(modal)
closeAllModalsExcept(modal);
}
if (!modal.keepPopoutsOpen) {
PopoutManager.closeAllPopouts()
PopoutManager.closeAllPopouts();
}
TrayMenuManager.closeAllMenus()
TrayMenuManager.closeAllMenus();
}
}

View File

@@ -1,4 +1,5 @@
pragma Singleton
pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
@@ -10,15 +11,14 @@ Singleton {
function showOSD(osd) {
if (!osd || !osd.screen)
return
const screenName = osd.screen.name
const currentOSD = currentOSDsByScreen[screenName]
return;
const screenName = osd.screen.name;
const currentOSD = currentOSDsByScreen[screenName];
if (currentOSD && currentOSD !== osd) {
currentOSD.hide()
currentOSD.hide();
}
currentOSDsByScreen[screenName] = osd
currentOSDsByScreen[screenName] = osd;
}
}

View File

@@ -1,4 +1,5 @@
pragma Singleton
pragma ComponentBehavior: Bound
import Quickshell
import QtCore
@@ -6,60 +7,74 @@ import QtCore
Singleton {
id: root
readonly property url home: StandardPaths.standardLocations(
StandardPaths.HomeLocation)[0]
readonly property url pictures: StandardPaths.standardLocations(
StandardPaths.PicturesLocation)[0]
readonly property url home: StandardPaths.standardLocations(StandardPaths.HomeLocation)[0]
readonly property url pictures: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0]
readonly property url data: `${StandardPaths.standardLocations(
StandardPaths.GenericDataLocation)[0]}/DankMaterialShell`
readonly property url state: `${StandardPaths.standardLocations(
StandardPaths.GenericStateLocation)[0]}/DankMaterialShell`
readonly property url cache: `${StandardPaths.standardLocations(
StandardPaths.GenericCacheLocation)[0]}/DankMaterialShell`
readonly property url config: `${StandardPaths.standardLocations(
StandardPaths.GenericConfigLocation)[0]}/DankMaterialShell`
readonly property url data: `${StandardPaths.standardLocations(StandardPaths.GenericDataLocation)[0]}/DankMaterialShell`
readonly property url state: `${StandardPaths.standardLocations(StandardPaths.GenericStateLocation)[0]}/DankMaterialShell`
readonly property url cache: `${StandardPaths.standardLocations(StandardPaths.GenericCacheLocation)[0]}/DankMaterialShell`
readonly property url config: `${StandardPaths.standardLocations(StandardPaths.GenericConfigLocation)[0]}/DankMaterialShell`
readonly property url imagecache: `${cache}/imagecache`
function stringify(path: url): string {
return path.toString().replace(/%20/g, " ")
return path.toString().replace(/%20/g, " ");
}
function expandTilde(path: string): string {
return strip(path.replace("~", stringify(root.home)))
return strip(path.replace("~", stringify(root.home)));
}
function shortenHome(path: string): string {
return path.replace(strip(root.home), "~")
return path.replace(strip(root.home), "~");
}
function strip(path: url): string {
return stringify(path).replace("file://", "")
return stringify(path).replace("file://", "");
}
function toFileUrl(path: string): string {
return path.startsWith("file://") ? path : "file://" + path
return path.startsWith("file://") ? path : "file://" + path;
}
function mkdir(path: url): void {
Quickshell.execDetached(["mkdir", "-p", strip(path)])
Quickshell.execDetached(["mkdir", "-p", strip(path)]);
}
function copy(from: url, to: url): void {
Quickshell.execDetached(["cp", strip(from), strip(to)])
Quickshell.execDetached(["cp", strip(from), strip(to)]);
}
// ! Spotify and maybe some other apps report the wrong app id in toplevels, hardcode special case
function moddedAppId(appId: string): string {
if (appId === "Spotify")
return "spotify"
return "spotify";
if (appId === "beepertexts")
return "beeper"
return "beeper";
if (appId === "home assistant desktop")
return "homeassistant-desktop"
return "homeassistant-desktop";
if (appId.includes("com.transmissionbt.transmission"))
return "transmission-gtk"
return appId
return "transmission-gtk";
return appId;
}
function getAppIcon(appId: string, desktopEntry: var): string {
if (appId === "org.quickshell") {
return Qt.resolvedUrl("../assets/danklogo.svg");
}
const moddedId = moddedAppId(appId);
if (moddedId.toLowerCase().includes("steam_app")) {
return "";
}
return desktopEntry && desktopEntry.icon ? Quickshell.iconPath(desktopEntry.icon, true) : "";
}
function getAppName(appId: string, desktopEntry: var): string {
if (appId === "org.quickshell") {
return "dms";
}
return desktopEntry && desktopEntry.name ? desktopEntry.name : appId;
}
}

View File

@@ -194,7 +194,7 @@ var SPEC = {
osdIdleInhibitorEnabled: { def: true },
osdMicMuteEnabled: { def: true },
osdCapsLockEnabled: { def: true },
osdPowerProfileEnabled: { def: true },
osdPowerProfileEnabled: { def: false },
powerActionConfirm: { def: true },
powerMenuActions: { def: ["reboot", "logout", "poweroff", "lock", "suspend", "restart"] },

View File

@@ -1,6 +1,5 @@
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Common
import qs.Modals
import qs.Modals.Clipboard
@@ -19,11 +18,9 @@ import qs.Widgets
import qs.Modules.Notifications.Popup
import qs.Modules.OSD
import qs.Modules.ProcessList
import qs.Modules.Settings
import qs.Modules.DankBar
import qs.Modules.DankBar.Popouts
import qs.Modules.WorkspaceOverlays
import qs.Modules.Plugins
import qs.Services
Item {
@@ -41,12 +38,12 @@ Item {
onLoaded: {
if (item) {
item.pluginService = PluginService
item.pluginService = PluginService;
if (item.popoutService !== undefined) {
item.popoutService = PopoutService
item.popoutService = PopoutService;
}
item.pluginId = pluginId
console.info("Daemon plugin loaded:", pluginId)
item.pluginId = pluginId;
console.info("Daemon plugin loaded:", pluginId);
}
}
}
@@ -85,9 +82,9 @@ Item {
onColorPickerRequested: {
if (colorPickerModal.shouldBeVisible) {
colorPickerModal.close()
colorPickerModal.close();
} else {
colorPickerModal.show()
colorPickerModal.show();
}
}
}
@@ -108,21 +105,20 @@ Item {
onLoaded: {
if (item) {
dockContextMenuLoader.active = true
dockContextMenuLoader.active = true;
}
}
Component.onCompleted: {
initialized = true
initialized = true;
}
onCurrentPositionChanged: {
if (!initialized)
return
const comp = sourceComponent
sourceComponent = null
sourceComponent = comp
return;
const comp = sourceComponent;
sourceComponent = null;
sourceComponent = comp;
}
}
@@ -137,7 +133,7 @@ Item {
id: dankDashPopout
Component.onCompleted: {
PopoutService.dankDashPopout = dankDashPopout
PopoutService.dankDashPopout = dankDashPopout;
}
}
}
@@ -162,7 +158,7 @@ Item {
id: notificationCenter
Component.onCompleted: {
PopoutService.notificationCenterPopout = notificationCenter
PopoutService.notificationCenterPopout = notificationCenter;
}
}
}
@@ -189,11 +185,11 @@ Item {
powerMenuModalLoader: controlCenterLoader.powerModalLoaderRef
onLockRequested: {
lock.activate()
lock.activate();
}
Component.onCompleted: {
PopoutService.controlCenterPopout = controlCenterPopout
PopoutService.controlCenterPopout = controlCenterPopout;
}
}
}
@@ -202,7 +198,7 @@ Item {
id: wifiPasswordModal
Component.onCompleted: {
PopoutService.wifiPasswordModal = wifiPasswordModal
PopoutService.wifiPasswordModal = wifiPasswordModal;
}
}
@@ -214,7 +210,7 @@ Item {
id: bluetoothPairingModal
Component.onCompleted: {
PopoutService.bluetoothPairingModal = bluetoothPairingModal
PopoutService.bluetoothPairingModal = bluetoothPairingModal;
}
}
@@ -225,20 +221,20 @@ Item {
target: NetworkService
function onCredentialsNeeded(token, ssid, setting, fields, hints, reason, connType, connName, vpnService) {
const now = Date.now()
const timeSinceLastPrompt = now - lastCredentialsTime
const now = Date.now();
const timeSinceLastPrompt = now - lastCredentialsTime;
if (wifiPasswordModal.shouldBeVisible && timeSinceLastPrompt < 1000) {
NetworkService.cancelCredentials(lastCredentialsToken)
lastCredentialsToken = token
lastCredentialsTime = now
wifiPasswordModal.showFromPrompt(token, ssid, setting, fields, hints, reason, connType, connName, vpnService)
return
NetworkService.cancelCredentials(lastCredentialsToken);
lastCredentialsToken = token;
lastCredentialsTime = now;
wifiPasswordModal.showFromPrompt(token, ssid, setting, fields, hints, reason, connType, connName, vpnService);
return;
}
lastCredentialsToken = token
lastCredentialsTime = now
wifiPasswordModal.showFromPrompt(token, ssid, setting, fields, hints, reason, connType, connName, vpnService)
lastCredentialsToken = token;
lastCredentialsTime = now;
wifiPasswordModal.showFromPrompt(token, ssid, setting, fields, hints, reason, connType, connName, vpnService);
}
}
@@ -251,7 +247,7 @@ Item {
id: networkInfoModal
Component.onCompleted: {
PopoutService.networkInfoModal = networkInfoModal
PopoutService.networkInfoModal = networkInfoModal;
}
}
}
@@ -265,7 +261,7 @@ Item {
id: batteryPopout
Component.onCompleted: {
PopoutService.batteryPopout = batteryPopout
PopoutService.batteryPopout = batteryPopout;
}
}
}
@@ -279,7 +275,7 @@ Item {
id: layoutPopout
Component.onCompleted: {
PopoutService.layoutPopout = layoutPopout
PopoutService.layoutPopout = layoutPopout;
}
}
}
@@ -293,7 +289,7 @@ Item {
id: vpnPopout
Component.onCompleted: {
PopoutService.vpnPopout = vpnPopout
PopoutService.vpnPopout = vpnPopout;
}
}
}
@@ -317,7 +313,7 @@ Item {
id: processListPopout
Component.onCompleted: {
PopoutService.processListPopout = processListPopout
PopoutService.processListPopout = processListPopout;
}
}
}
@@ -326,7 +322,7 @@ Item {
id: settingsModal
Component.onCompleted: {
PopoutService.settingsModal = settingsModal
PopoutService.settingsModal = settingsModal;
}
}
@@ -339,7 +335,7 @@ Item {
id: appDrawerPopout
Component.onCompleted: {
PopoutService.appDrawerPopout = appDrawerPopout
PopoutService.appDrawerPopout = appDrawerPopout;
}
}
}
@@ -348,7 +344,7 @@ Item {
id: spotlightModal
Component.onCompleted: {
PopoutService.spotlightModal = spotlightModal
PopoutService.spotlightModal = spotlightModal;
}
}
@@ -356,7 +352,7 @@ Item {
id: clipboardHistoryModalPopup
Component.onCompleted: {
PopoutService.clipboardHistoryModal = clipboardHistoryModalPopup
PopoutService.clipboardHistoryModal = clipboardHistoryModalPopup;
}
}
@@ -364,7 +360,7 @@ Item {
id: notificationModal
Component.onCompleted: {
PopoutService.notificationModal = notificationModal
PopoutService.notificationModal = notificationModal;
}
}
@@ -372,7 +368,7 @@ Item {
id: colorPickerModal
Component.onCompleted: {
PopoutService.colorPickerModal = colorPickerModal
PopoutService.colorPickerModal = colorPickerModal;
}
}
@@ -385,7 +381,7 @@ Item {
id: processListModal
Component.onCompleted: {
PopoutService.processListModal = processListModal
PopoutService.processListModal = processListModal;
}
}
}
@@ -399,7 +395,7 @@ Item {
id: systemUpdatePopout
Component.onCompleted: {
PopoutService.systemUpdatePopout = systemUpdatePopout
PopoutService.systemUpdatePopout = systemUpdatePopout;
}
}
}
@@ -420,16 +416,16 @@ Item {
content: Component {
Notepad {
onHideRequested: {
notepadSlideout.hide()
notepadSlideout.hide();
}
}
}
function toggle() {
if (isVisible) {
hide()
hide();
} else {
show()
show();
}
}
}
@@ -444,43 +440,43 @@ Item {
id: powerMenuModal
onPowerActionRequested: (action, title, message) => {
if (SettingsData.powerActionConfirm) {
powerConfirmModalLoader.active = true
if (powerConfirmModalLoader.item) {
powerConfirmModalLoader.item.confirmButtonColor = action === "poweroff" ? Theme.error : action === "reboot" ? Theme.warning : Theme.primary
powerConfirmModalLoader.item.show(title, message, () => actionApply(action), function () {})
}
} else {
actionApply(action)
}
}
if (SettingsData.powerActionConfirm) {
powerConfirmModalLoader.active = true;
if (powerConfirmModalLoader.item) {
powerConfirmModalLoader.item.confirmButtonColor = action === "poweroff" ? Theme.error : action === "reboot" ? Theme.warning : Theme.primary;
powerConfirmModalLoader.item.show(title, message, () => actionApply(action), function () {});
}
} else {
actionApply(action);
}
}
onLockRequested: {
lock.activate()
lock.activate();
}
function actionApply(action) {
switch (action) {
case "logout":
SessionService.logout()
break
SessionService.logout();
break;
case "suspend":
SessionService.suspend()
break
SessionService.suspend();
break;
case "hibernate":
SessionService.hibernate()
break
SessionService.hibernate();
break;
case "reboot":
SessionService.reboot()
break
SessionService.reboot();
break;
case "poweroff":
SessionService.poweroff()
break
SessionService.poweroff();
break;
}
}
Component.onCompleted: {
PopoutService.powerMenuModal = powerMenuModal
PopoutService.powerMenuModal = powerMenuModal;
}
}
}
@@ -494,7 +490,7 @@ Item {
id: keybindsModal
Component.onCompleted: {
PopoutService.hyprKeybindsModal = keybindsModal
PopoutService.hyprKeybindsModal = keybindsModal;
}
}
}
@@ -560,8 +556,14 @@ Item {
}
}
Loader {
id: powerProfileWatcherLoader
active: SettingsData.osdPowerProfileEnabled
source: "Services/PowerProfileWatcher.qml"
}
Variants {
model: SettingsData.getFilteredScreens("osd")
model: SettingsData.osdPowerProfileEnabled ? SettingsData.getFilteredScreens("osd") : []
delegate: PowerProfileOSD {
modelData: item

View File

@@ -1,5 +1,4 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Services.Pipewire
import qs.Common
@@ -10,8 +9,8 @@ Rectangle {
id: root
property bool hasVolumeSliderInCC: {
const widgets = SettingsData.controlCenterWidgets || []
return widgets.some(widget => widget.id === "volumeSlider")
const widgets = SettingsData.controlCenterWidgets || [];
return widgets.some(widget => widget.id === "volumeSlider");
}
implicitHeight: headerRow.height + (!hasVolumeSliderInCC ? volumeSlider.height : 0) + audioContent.height + Theme.spacingM
@@ -66,7 +65,7 @@ Rectangle {
cursorShape: Qt.PointingHandCursor
onClicked: {
if (AudioService.sink && AudioService.sink.audio) {
AudioService.sink.audio.muted = !AudioService.sink.audio.muted
AudioService.sink.audio.muted = !AudioService.sink.audio.muted;
}
}
}
@@ -74,13 +73,17 @@ Rectangle {
DankIcon {
anchors.centerIn: parent
name: {
if (!AudioService.sink || !AudioService.sink.audio) return "volume_off"
let muted = AudioService.sink.audio.muted
let volume = AudioService.sink.audio.volume
if (muted || volume === 0.0) return "volume_off"
if (volume <= 0.33) return "volume_down"
if (volume <= 0.66) return "volume_up"
return "volume_up"
if (!AudioService.sink || !AudioService.sink.audio)
return "volume_off";
let muted = AudioService.sink.audio.muted;
let volume = AudioService.sink.audio.volume;
if (muted || volume === 0.0)
return "volume_off";
if (volume <= 0.33)
return "volume_down";
if (volume <= 0.66)
return "volume_up";
return "volume_up";
}
size: Theme.iconSize
color: AudioService.sink && AudioService.sink.audio && !AudioService.sink.audio.muted && AudioService.sink.audio.volume > 0 ? Theme.primary : Theme.surfaceText
@@ -101,13 +104,13 @@ Rectangle {
valueOverride: actualVolumePercent
thumbOutlineColor: Theme.surfaceVariant
onSliderValueChanged: function(newValue) {
onSliderValueChanged: function (newValue) {
if (AudioService.sink && AudioService.sink.audio) {
AudioService.sink.audio.volume = newValue / 100
AudioService.sink.audio.volume = newValue / 100;
if (newValue > 0 && AudioService.sink.audio.muted) {
AudioService.sink.audio.muted = false
AudioService.sink.audio.muted = false;
}
AudioService.volumeChanged()
AudioService.volumeChanged();
}
}
}
@@ -133,22 +136,26 @@ Rectangle {
model: ScriptModel {
values: {
const nodes = Pipewire.nodes.values.filter(node => {
return node.audio && node.isSink && !node.isStream
})
const pins = SettingsData.audioOutputDevicePins || {}
const pinnedName = pins["preferredOutput"]
let sorted = [...nodes]
return node.audio && node.isSink && !node.isStream;
});
const pins = SettingsData.audioOutputDevicePins || {};
const pinnedName = pins["preferredOutput"];
let sorted = [...nodes];
sorted.sort((a, b) => {
// Pinned device first
if (a.name === pinnedName && b.name !== pinnedName) return -1
if (b.name === pinnedName && a.name !== pinnedName) return 1
if (a.name === pinnedName && b.name !== pinnedName)
return -1;
if (b.name === pinnedName && a.name !== pinnedName)
return 1;
// Then active device
if (a === AudioService.sink && b !== AudioService.sink) return -1
if (b === AudioService.sink && a !== AudioService.sink) return 1
return 0
})
return sorted
if (a === AudioService.sink && b !== AudioService.sink)
return -1;
if (b === AudioService.sink && a !== AudioService.sink)
return 1;
return 0;
});
return sorted;
}
}
@@ -172,13 +179,13 @@ Rectangle {
DankIcon {
name: {
if (modelData.name.includes("bluez"))
return "headset"
return "headset";
else if (modelData.name.includes("hdmi"))
return "tv"
return "tv";
else if (modelData.name.includes("usb"))
return "headset"
return "headset";
else
return "speaker"
return "speaker";
}
size: Theme.iconSize - 4
color: modelData === AudioService.sink ? Theme.primary : Theme.surfaceText
@@ -188,9 +195,9 @@ Rectangle {
Column {
anchors.verticalCenter: parent.verticalCenter
width: {
const iconWidth = Theme.iconSize
const pinButtonWidth = pinOutputRow.width + Theme.spacingS * 4 + Theme.spacingM
return parent.parent.width - iconWidth - parent.spacing - pinButtonWidth - Theme.spacingM * 2
const iconWidth = Theme.iconSize;
const pinButtonWidth = pinOutputRow.width + Theme.spacingS * 4 + Theme.spacingM;
return parent.parent.width - iconWidth - parent.spacing - pinButtonWidth - Theme.spacingM * 2;
}
StyledText {
@@ -222,8 +229,8 @@ Rectangle {
height: 28
radius: height / 2
color: {
const isThisDevicePinned = (SettingsData.audioOutputDevicePins || {})["preferredOutput"] === modelData.name
return isThisDevicePinned ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceText, 0.05)
const isThisDevicePinned = (SettingsData.audioOutputDevicePins || {})["preferredOutput"] === modelData.name;
return isThisDevicePinned ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceText, 0.05);
}
Row {
@@ -235,21 +242,21 @@ Rectangle {
name: "push_pin"
size: 16
color: {
const isThisDevicePinned = (SettingsData.audioOutputDevicePins || {})["preferredOutput"] === modelData.name
return isThisDevicePinned ? Theme.primary : Theme.surfaceText
const isThisDevicePinned = (SettingsData.audioOutputDevicePins || {})["preferredOutput"] === modelData.name;
return isThisDevicePinned ? Theme.primary : Theme.surfaceText;
}
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: {
const isThisDevicePinned = (SettingsData.audioOutputDevicePins || {})["preferredOutput"] === modelData.name
return isThisDevicePinned ? "Pinned" : "Pin"
const isThisDevicePinned = (SettingsData.audioOutputDevicePins || {})["preferredOutput"] === modelData.name;
return isThisDevicePinned ? "Pinned" : "Pin";
}
font.pixelSize: Theme.fontSizeSmall
color: {
const isThisDevicePinned = (SettingsData.audioOutputDevicePins || {})["preferredOutput"] === modelData.name
return isThisDevicePinned ? Theme.primary : Theme.surfaceText
const isThisDevicePinned = (SettingsData.audioOutputDevicePins || {})["preferredOutput"] === modelData.name;
return isThisDevicePinned ? Theme.primary : Theme.surfaceText;
}
anchors.verticalCenter: parent.verticalCenter
}
@@ -259,16 +266,16 @@ Rectangle {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
const pins = JSON.parse(JSON.stringify(SettingsData.audioOutputDevicePins || {}))
const isCurrentlyPinned = pins["preferredOutput"] === modelData.name
const pins = JSON.parse(JSON.stringify(SettingsData.audioOutputDevicePins || {}));
const isCurrentlyPinned = pins["preferredOutput"] === modelData.name;
if (isCurrentlyPinned) {
delete pins["preferredOutput"]
delete pins["preferredOutput"];
} else {
pins["preferredOutput"] = modelData.name
pins["preferredOutput"] = modelData.name;
}
SettingsData.set("audioOutputDevicePins", pins)
SettingsData.set("audioOutputDevicePins", pins);
}
}
}
@@ -281,12 +288,186 @@ Rectangle {
cursorShape: Qt.PointingHandCursor
onClicked: {
if (modelData) {
Pipewire.preferredDefaultAudioSink = modelData
Pipewire.preferredDefaultAudioSink = modelData;
}
}
}
}
}
Row {
id: playbackHeaderRow
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: Theme.spacingM
anchors.rightMargin: Theme.spacingM
height: 28
StyledText {
id: playbackHeaderText
text: I18n.tr("Playback")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
}
Repeater {
model: ScriptModel {
values: {
const nodes = Pipewire.nodes.values.filter(node => {
return node.audio && node.isSink && node.isStream;
});
return nodes;
}
}
delegate: Rectangle {
required property var modelData
required property int index
width: parent.width
height: 50
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
border.color: modelData === AudioService.sink ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: 0
Row {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Theme.spacingM
spacing: Theme.spacingS
DankIcon {
name: "album"
size: Theme.iconSize - 4
color: !modelData.audio.muted ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Column {
anchors.verticalCenter: parent.verticalCenter
width: {
const iconWidth = Theme.iconSize;
return parent.parent.width - iconWidth - parent.spacing - Theme.spacingM * 2;
}
StyledText {
text: AudioService.displayName(modelData)
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: modelData === AudioService.sink ? Font.Medium : Font.Normal
elide: Text.ElideRight
width: parent.width
wrapMode: Text.NoWrap
}
}
}
Rectangle {
anchors.right: parent.right
anchors.rightMargin: 120
anchors.verticalCenter: parent.verticalCenter
width: appVolumeRow.width
height: 28
radius: height / 2
Item {
id: appVolumeRow
property color sliderTrackColor: "transparent"
anchors.centerIn: parent
height: 40
width: parent.width
Rectangle {
anchors.right: parent.right
anchors.rightMargin: Theme.spacingM
width: Theme.iconSize + Theme.spacingS * 2
height: Theme.iconSize + Theme.spacingS * 2
anchors.verticalCenter: parent.verticalCenter
radius: Theme.cornerRadius
color: appIconArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.primary, 0)
MouseArea {
id: appIconArea
anchors.fill: parent
visible: modelData !== null
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (modelData) {
AudioService.suppressOSD = true;
modelData.audio.muted = !modelData.audio.muted;
AudioService.suppressOSD = false;
}
}
}
DankIcon {
anchors.centerIn: parent
name: {
if (!modelData)
return "volume_off";
let volume = modelData.audio.volume;
let muted = modelData.audio.muted;
if (muted || volume === 0.0)
return "volume_off";
if (volume <= 0.33)
return "volume_down";
if (volume <= 0.66)
return "volume_up";
return "volume_up";
}
size: Theme.iconSize
color: modelData && !modelData.audio.muted && modelData.audio.volume > 0 ? Theme.primary : Theme.surfaceText
}
}
DankSlider {
readonly property real actualVolumePercent: modelData ? Math.round(modelData.audio.volume * 100) : 0
anchors.verticalCenter: parent.verticalCenter
width: 100
enabled: modelData !== null
minimum: 0
maximum: 100
value: modelData ? Math.min(100, Math.round(modelData.audio.volume * 100)) : 0
showValue: true
unit: "%"
valueOverride: actualVolumePercent
thumbOutlineColor: Theme.surfaceContainer
trackColor: appVolumeRow.sliderTrackColor.a > 0 ? appVolumeRow.sliderTrackColor : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
onIsDraggingChanged: {
if (isDragging) {
AudioService.suppressOSD = true;
} else {
Qt.callLater(() => {
AudioService.suppressOSD = false;
});
}
}
onSliderValueChanged: function (newValue) {
if (modelData) {
modelData.audio.volume = newValue / 100.0;
if (newValue > 0 && modelData.audio.muted) {
modelData.audio.muted = false;
}
AudioService.playVolumeChangeSoundIfEnabled();
}
}
}
}
PwObjectTracker {
objects: [modelData]
}
}
}
}
}
}
}
}

View File

@@ -1,6 +1,4 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
@@ -18,65 +16,69 @@ Row {
height: 40
spacing: 0
DankTooltipV2 {
id: sharedTooltip
}
property string targetDeviceName: {
if (!DisplayService.brightnessAvailable || !DisplayService.devices || DisplayService.devices.length === 0) {
return ""
return "";
}
if (screenName && screenName.length > 0) {
const pins = SettingsData.brightnessDevicePins || {}
const pinnedDevice = pins[screenName]
const pins = SettingsData.brightnessDevicePins || {};
const pinnedDevice = pins[screenName];
if (pinnedDevice && pinnedDevice.length > 0) {
const found = DisplayService.devices.find(dev => dev.name === pinnedDevice)
const found = DisplayService.devices.find(dev => dev.name === pinnedDevice);
if (found) {
return found.name
return found.name;
}
}
}
if (deviceName && deviceName.length > 0) {
const found = DisplayService.devices.find(dev => dev.name === deviceName)
const found = DisplayService.devices.find(dev => dev.name === deviceName);
if (found) {
return found.name
return found.name;
}
}
const currentDeviceName = DisplayService.currentDevice
const currentDeviceName = DisplayService.currentDevice;
if (currentDeviceName) {
const found = DisplayService.devices.find(dev => dev.name === currentDeviceName)
const found = DisplayService.devices.find(dev => dev.name === currentDeviceName);
if (found) {
return found.name
return found.name;
}
}
const backlight = DisplayService.devices.find(d => d.class === "backlight")
const backlight = DisplayService.devices.find(d => d.class === "backlight");
if (backlight) {
return backlight.name
return backlight.name;
}
const ddc = DisplayService.devices.find(d => d.class === "ddc")
const ddc = DisplayService.devices.find(d => d.class === "ddc");
if (ddc) {
return ddc.name
return ddc.name;
}
return DisplayService.devices.length > 0 ? DisplayService.devices[0].name : ""
return DisplayService.devices.length > 0 ? DisplayService.devices[0].name : "";
}
property var targetDevice: {
if (!targetDeviceName || !DisplayService.devices) {
return null
return null;
}
return DisplayService.devices.find(dev => dev.name === targetDeviceName) || null
return DisplayService.devices.find(dev => dev.name === targetDeviceName) || null;
}
property real targetBrightness: {
DisplayService.brightnessVersion
DisplayService.brightnessVersion;
if (!targetDeviceName) {
return 0
return 0;
}
return DisplayService.getDeviceBrightness(targetDeviceName)
return DisplayService.getDeviceBrightness(targetDeviceName);
}
Rectangle {
@@ -94,46 +96,37 @@ Row {
onClicked: {
if (DisplayService.devices && DisplayService.devices.length > 1) {
root.iconClicked()
root.iconClicked();
}
}
onEntered: {
tooltipLoader.active = true
if (tooltipLoader.item) {
const tooltipText = targetDevice ? "bl device: " + targetDevice.name : "Backlight Control"
const globalPos = iconArea.mapToGlobal(iconArea.width / 2, iconArea.height / 2)
const screenY = root.parentScreen?.y ?? 0
const relativeY = globalPos.y - screenY - 55
tooltipLoader.item.show(tooltipText, globalPos.x, relativeY, root.parentScreen)
}
const tooltipText = targetDevice ? "bl device: " + targetDevice.name : "Backlight Control";
sharedTooltip.show(tooltipText, iconArea, 0, 0, "bottom");
}
onExited: {
if (tooltipLoader.item) {
tooltipLoader.item.hide()
}
tooltipLoader.active = false
sharedTooltip.hide();
}
DankIcon {
anchors.centerIn: parent
name: {
if (!DisplayService.brightnessAvailable || !targetDevice) {
return "brightness_low"
return "brightness_low";
}
if (targetDevice.class === "backlight" || targetDevice.class === "ddc") {
const brightness = targetBrightness
const brightness = targetBrightness;
if (brightness <= 33)
return "brightness_low"
return "brightness_low";
if (brightness <= 66)
return "brightness_medium"
return "brightness_high"
return "brightness_medium";
return "brightness_high";
} else if (targetDevice.name.includes("kbd")) {
return "keyboard"
return "keyboard";
} else {
return "lightbulb"
return "lightbulb";
}
}
size: Theme.iconSize
@@ -149,43 +142,40 @@ Row {
width: parent.width - (Theme.iconSize + Theme.spacingS * 2)
enabled: DisplayService.brightnessAvailable && targetDeviceName.length > 0
minimum: {
if (!targetDevice) return 1
const isExponential = SessionData.getBrightnessExponential(targetDevice.id)
if (!targetDevice)
return 1;
const isExponential = SessionData.getBrightnessExponential(targetDevice.id);
if (isExponential) {
return 1
return 1;
}
return (targetDevice.class === "backlight" || targetDevice.class === "ddc") ? 1 : 0
return (targetDevice.class === "backlight" || targetDevice.class === "ddc") ? 1 : 0;
}
maximum: {
if (!targetDevice) return 100
const isExponential = SessionData.getBrightnessExponential(targetDevice.id)
if (!targetDevice)
return 100;
const isExponential = SessionData.getBrightnessExponential(targetDevice.id);
if (isExponential) {
return 100
return 100;
}
return targetDevice.displayMax || 100
return targetDevice.displayMax || 100;
}
value: !isDragging ? targetBrightness : value
showValue: true
unit: {
if (!targetDevice) return "%"
const isExponential = SessionData.getBrightnessExponential(targetDevice.id)
if (!targetDevice)
return "%";
const isExponential = SessionData.getBrightnessExponential(targetDevice.id);
if (isExponential) {
return "%"
return "%";
}
return targetDevice.class === "ddc" ? "" : "%"
return targetDevice.class === "ddc" ? "" : "%";
}
onSliderValueChanged: function (newValue) {
if (DisplayService.brightnessAvailable && targetDeviceName) {
DisplayService.setBrightness(newValue, targetDeviceName, true)
DisplayService.setBrightness(newValue, targetDeviceName, true);
}
}
thumbOutlineColor: Theme.surfaceContainer
trackColor: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
}
Loader {
id: tooltipLoader
active: false
sourceComponent: DankTooltip {}
}
}

View File

@@ -445,16 +445,27 @@ PanelWindow {
onHasActivePopoutChanged: evaluateReveal()
function updateActivePopoutState() {
const screenName = barWindow.screen.name;
const activePopout = PopoutManager.currentPopoutsByScreen[screenName];
const activeTrayMenu = TrayMenuManager.activeTrayMenus[screenName];
const trayOpen = rootWindow.systemTrayMenuOpen;
const hasVisiblePopout = activePopout && activePopout.shouldBeVisible;
topBarCore.hasActivePopout = !!(hasVisiblePopout || activeTrayMenu || trayOpen);
}
Connections {
target: PopoutManager
function onPopoutChanged() {
const screenName = barWindow.screen.name;
const activePopout = PopoutManager.currentPopoutsByScreen[screenName];
const activeTrayMenu = TrayMenuManager.activeTrayMenus[screenName];
const trayOpen = rootWindow.systemTrayMenuOpen;
topBarCore.updateActivePopoutState();
}
}
const hasVisiblePopout = activePopout && activePopout.shouldBeVisible;
topBarCore.hasActivePopout = !!(hasVisiblePopout || activeTrayMenu || trayOpen);
Connections {
target: TrayMenuManager
function onActiveTrayMenusChanged() {
topBarCore.updateActivePopoutState();
}
}

View File

@@ -1,5 +1,4 @@
import QtQuick
import QtQuick.Controls
import qs.Common
import qs.Modules.Plugins
import qs.Services
@@ -15,7 +14,7 @@ BasePill {
property var widgetData: null
property bool minimumWidth: (widgetData && widgetData.minimumWidth !== undefined) ? widgetData.minimumWidth : true
signal cpuClicked()
signal cpuClicked
Component.onCompleted: {
DgopService.addRef(["cpu"]);
@@ -26,7 +25,7 @@ BasePill {
content: Component {
Item {
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : cpuContent.implicitWidth
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : cpuContentRoot.implicitWidth
implicitHeight: root.isVerticalOrientation ? cpuColumn.implicitHeight : (root.widgetThickness - root.horizontalPadding * 2)
Column {
@@ -66,55 +65,78 @@ BasePill {
}
}
Row {
id: cpuContent
Item {
id: cpuContentRoot
visible: !root.isVerticalOrientation
anchors.centerIn: parent
spacing: 3
DankIcon {
name: "memory"
size: Theme.barIconSize(root.barThickness)
color: {
if (DgopService.cpuUsage > 80) {
return Theme.tempDanger;
implicitWidth: cpuRow.implicitWidth
implicitHeight: cpuRow.implicitHeight
Row {
id: cpuRow
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
id: cpuIcon
name: "memory"
size: Theme.barIconSize(root.barThickness)
color: {
if (DgopService.cpuUsage > 80) {
return Theme.tempDanger;
}
if (DgopService.cpuUsage > 60) {
return Theme.tempWarning;
}
return Theme.widgetIconColor;
}
if (DgopService.cpuUsage > 60) {
return Theme.tempWarning;
implicitWidth: size
implicitHeight: size
width: size
height: size
}
Item {
id: textBox
implicitWidth: root.minimumWidth ? Math.max(cpuBaseline.width, cpuText.paintedWidth) : cpuText.paintedWidth
implicitHeight: cpuText.implicitHeight
width: implicitWidth
height: implicitHeight
Behavior on width {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Easing.OutCubic
}
}
return Theme.widgetIconColor;
}
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: {
if (DgopService.cpuUsage === undefined || DgopService.cpuUsage === null || DgopService.cpuUsage === 0) {
return "--%";
StyledTextMetrics {
id: cpuBaseline
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
text: "88%"
}
return DgopService.cpuUsage.toFixed(0) + "%";
}
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
color: Theme.widgetTextColor
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
elide: Text.ElideNone
StyledText {
id: cpuText
text: {
const v = DgopService.cpuUsage;
if (v === undefined || v === null || v === 0) {
return "--%";
}
return v.toFixed(0) + "%";
}
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
color: Theme.widgetTextColor
StyledTextMetrics {
id: cpuBaseline
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
text: "100%"
}
width: root.minimumWidth ? Math.max(cpuBaseline.width, paintedWidth) : paintedWidth
Behavior on width {
NumberAnimation {
duration: 120
easing.type: Easing.OutCubic
anchors.fill: parent
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
elide: Text.ElideNone
}
}
}
@@ -127,8 +149,8 @@ BasePill {
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onPressed: {
DgopService.setSortBy("cpu")
cpuClicked()
DgopService.setSortBy("cpu");
cpuClicked();
}
}
}

View File

@@ -1,5 +1,4 @@
import QtQuick
import QtQuick.Controls
import qs.Common
import qs.Modules.Plugins
import qs.Services
@@ -15,7 +14,7 @@ BasePill {
property var widgetData: null
property bool minimumWidth: (widgetData && widgetData.minimumWidth !== undefined) ? widgetData.minimumWidth : true
signal cpuTempClicked()
signal cpuTempClicked
Component.onCompleted: {
DgopService.addRef(["cpu"]);
@@ -26,7 +25,7 @@ BasePill {
content: Component {
Item {
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : cpuTempContent.implicitWidth
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : cpuTempContentRoot.implicitWidth
implicitHeight: root.isVerticalOrientation ? cpuTempColumn.implicitHeight : (root.widgetThickness - root.horizontalPadding * 2)
Column {
@@ -66,55 +65,78 @@ BasePill {
}
}
Row {
id: cpuTempContent
Item {
id: cpuTempContentRoot
visible: !root.isVerticalOrientation
anchors.centerIn: parent
spacing: 3
DankIcon {
name: "device_thermostat"
size: Theme.barIconSize(root.barThickness)
color: {
if (DgopService.cpuTemperature > 85) {
return Theme.tempDanger;
implicitWidth: cpuTempRow.implicitWidth
implicitHeight: cpuTempRow.implicitHeight
Row {
id: cpuTempRow
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
id: cpuTempIcon
name: "device_thermostat"
size: Theme.barIconSize(root.barThickness)
color: {
if (DgopService.cpuTemperature > 85) {
return Theme.tempDanger;
}
if (DgopService.cpuTemperature > 69) {
return Theme.tempWarning;
}
return Theme.widgetIconColor;
}
if (DgopService.cpuTemperature > 69) {
return Theme.tempWarning;
implicitWidth: size
implicitHeight: size
width: size
height: size
}
Item {
id: textBox
implicitWidth: root.minimumWidth ? Math.max(tempBaseline.width, cpuTempText.paintedWidth) : cpuTempText.paintedWidth
implicitHeight: cpuTempText.implicitHeight
width: implicitWidth
height: implicitHeight
Behavior on width {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Easing.OutCubic
}
}
return Theme.widgetIconColor;
}
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: {
if (DgopService.cpuTemperature === undefined || DgopService.cpuTemperature === null || DgopService.cpuTemperature < 0) {
return "--°";
StyledTextMetrics {
id: tempBaseline
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
text: "88°"
}
return Math.round(DgopService.cpuTemperature) + "°";
}
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
color: Theme.widgetTextColor
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
elide: Text.ElideNone
StyledText {
id: cpuTempText
text: {
if (DgopService.cpuTemperature === undefined || DgopService.cpuTemperature === null || DgopService.cpuTemperature < 0) {
return "--°";
}
StyledTextMetrics {
id: tempBaseline
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
text: "100°"
}
return Math.round(DgopService.cpuTemperature) + "°";
}
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
color: Theme.widgetTextColor
width: root.minimumWidth ? Math.max(tempBaseline.width, paintedWidth) : paintedWidth
Behavior on width {
NumberAnimation {
duration: 120
easing.type: Easing.OutCubic
anchors.fill: parent
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
elide: Text.ElideNone
}
}
}
@@ -127,8 +149,8 @@ BasePill {
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onPressed: {
DgopService.setSortBy("cpu")
cpuTempClicked()
DgopService.setSortBy("cpu");
cpuTempClicked();
}
}
}

View File

@@ -1,4 +1,5 @@
import QtQuick
import QtQuick.Effects
import Quickshell
import Quickshell.Wayland
import Quickshell.Widgets
@@ -22,86 +23,87 @@ BasePill {
readonly property real minTooltipY: {
if (!parentScreen || !isVerticalOrientation) {
return 0
return 0;
}
if (isAutoHideBar) {
return 0
return 0;
}
if (parentScreen.y > 0) {
return barThickness + (barSpacing || 4)
return barThickness + (barSpacing || 4);
}
return 0
return 0;
}
Component.onCompleted: {
updateDesktopEntry()
updateDesktopEntry();
}
Connections {
target: DesktopEntries
function onApplicationsChanged() {
root.updateDesktopEntry()
root.updateDesktopEntry();
}
}
Connections {
target: root
function onActiveWindowChanged() {
root.updateDesktopEntry()
root.updateDesktopEntry();
}
}
function updateDesktopEntry() {
if (activeWindow && activeWindow.appId) {
const moddedId = Paths.moddedAppId(activeWindow.appId)
activeDesktopEntry = DesktopEntries.heuristicLookup(moddedId)
const moddedId = Paths.moddedAppId(activeWindow.appId);
activeDesktopEntry = DesktopEntries.heuristicLookup(moddedId);
} else {
activeDesktopEntry = null
activeDesktopEntry = null;
}
}
readonly property bool hasWindowsOnCurrentWorkspace: {
if (CompositorService.isNiri) {
let currentWorkspaceId = null
let currentWorkspaceId = null;
for (var i = 0; i < NiriService.allWorkspaces.length; i++) {
const ws = NiriService.allWorkspaces[i]
const ws = NiriService.allWorkspaces[i];
if (ws.is_focused) {
currentWorkspaceId = ws.id
break
currentWorkspaceId = ws.id;
break;
}
}
if (!currentWorkspaceId) {
return false
return false;
}
const workspaceWindows = NiriService.windows.filter(w => w.workspace_id === currentWorkspaceId)
return workspaceWindows.length > 0 && activeWindow && activeWindow.title
const workspaceWindows = NiriService.windows.filter(w => w.workspace_id === currentWorkspaceId);
return workspaceWindows.length > 0 && activeWindow && activeWindow.title;
}
if (CompositorService.isHyprland) {
if (!Hyprland.focusedWorkspace || !activeWindow || !activeWindow.title) {
return false
return false;
}
try {
if (!Hyprland.toplevels) return false
const hyprlandToplevels = Array.from(Hyprland.toplevels.values)
const activeHyprToplevel = hyprlandToplevels.find(t => t?.wayland === activeWindow)
if (!Hyprland.toplevels)
return false;
const hyprlandToplevels = Array.from(Hyprland.toplevels.values);
const activeHyprToplevel = hyprlandToplevels.find(t => t?.wayland === activeWindow);
if (!activeHyprToplevel || !activeHyprToplevel.workspace) {
return false
return false;
}
return activeHyprToplevel.workspace.id === Hyprland.focusedWorkspace.id
return activeHyprToplevel.workspace.id === Hyprland.focusedWorkspace.id;
} catch (e) {
return false
return false;
}
}
return activeWindow && activeWindow.title
return activeWindow && activeWindow.title;
}
width: hasWindowsOnCurrentWorkspace ? (isVerticalOrientation ? barThickness : visualWidth) : 0
@@ -111,10 +113,12 @@ BasePill {
content: Component {
Item {
implicitWidth: {
if (!root.hasWindowsOnCurrentWorkspace) return 0
if (root.isVerticalOrientation) return root.widgetThickness - root.horizontalPadding * 2
const baseWidth = contentRow.implicitWidth
return compactMode ? Math.min(baseWidth, maxCompactWidth - root.horizontalPadding * 2) : Math.min(baseWidth, maxNormalWidth - root.horizontalPadding * 2)
if (!root.hasWindowsOnCurrentWorkspace)
return 0;
if (root.isVerticalOrientation)
return root.widgetThickness - root.horizontalPadding * 2;
const baseWidth = contentRow.implicitWidth;
return compactMode ? Math.min(baseWidth, maxCompactWidth - root.horizontalPadding * 2) : Math.min(baseWidth, maxNormalWidth - root.horizontalPadding * 2);
}
implicitHeight: root.widgetThickness - root.horizontalPadding * 2
clip: false
@@ -126,14 +130,21 @@ BasePill {
height: 18
visible: root.isVerticalOrientation && activeWindow && status === Image.Ready
source: {
if (!activeWindow || !activeWindow.appId) return ""
const moddedId = Paths.moddedAppId(activeWindow.appId)
if (moddedId.toLowerCase().includes("steam_app")) return ""
return Quickshell.iconPath(activeDesktopEntry?.icon, true)
if (!activeWindow || !activeWindow.appId)
return "";
return Paths.getAppIcon(activeWindow.appId, activeDesktopEntry);
}
smooth: true
mipmap: true
asynchronous: true
layer.enabled: activeWindow && activeWindow.appId === "org.quickshell"
layer.smooth: true
layer.mipmap: true
layer.effect: MultiEffect {
saturation: 0
colorization: 1
colorizationColor: Theme.primary
}
}
DankIcon {
@@ -142,26 +153,28 @@ BasePill {
name: "sports_esports"
color: Theme.widgetTextColor
visible: {
if (!root.isVerticalOrientation || !activeWindow || !activeWindow.appId) return false
const moddedId = Paths.moddedAppId(activeWindow.appId)
return moddedId.toLowerCase().includes("steam_app")
if (!root.isVerticalOrientation || !activeWindow || !activeWindow.appId)
return false;
const moddedId = Paths.moddedAppId(activeWindow.appId);
return moddedId.toLowerCase().includes("steam_app");
}
}
Text {
anchors.centerIn: parent
visible: {
if (!root.isVerticalOrientation || !activeWindow || !activeWindow.appId) return false
if (appIcon.status === Image.Ready) return false
const moddedId = Paths.moddedAppId(activeWindow.appId)
return !moddedId.toLowerCase().includes("steam_app")
if (!root.isVerticalOrientation || !activeWindow || !activeWindow.appId)
return false;
if (appIcon.status === Image.Ready)
return false;
const moddedId = Paths.moddedAppId(activeWindow.appId);
return !moddedId.toLowerCase().includes("steam_app");
}
text: {
if (!activeWindow || !activeWindow.appId) return "?"
if (activeDesktopEntry && activeDesktopEntry.name) {
return activeDesktopEntry.name.charAt(0).toUpperCase()
}
return activeWindow.appId.charAt(0).toUpperCase()
if (!activeWindow || !activeWindow.appId)
return "?";
const appName = Paths.getAppName(activeWindow.appId, activeDesktopEntry);
return appName.charAt(0).toUpperCase();
}
font.pixelSize: 10
color: Theme.widgetTextColor
@@ -176,12 +189,9 @@ BasePill {
StyledText {
id: appText
text: {
if (!activeWindow || !activeWindow.appId) {
if (!activeWindow || !activeWindow.appId)
return "";
}
const desktopEntry = DesktopEntries.heuristicLookup(activeWindow.appId);
return desktopEntry && desktopEntry.name ? desktopEntry.name : activeWindow.appId;
return Paths.getAppName(activeWindow.appId, activeDesktopEntry);
}
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
color: Theme.widgetTextColor
@@ -238,31 +248,31 @@ BasePill {
acceptedButtons: Qt.NoButton
onEntered: {
if (root.isVerticalOrientation && activeWindow && activeWindow.appId && root.parentScreen) {
tooltipLoader.active = true
tooltipLoader.active = true;
if (tooltipLoader.item) {
const globalPos = mapToGlobal(width / 2, height / 2)
const currentScreen = root.parentScreen
const screenX = currentScreen ? currentScreen.x : 0
const screenY = currentScreen ? currentScreen.y : 0
const relativeY = globalPos.y - screenY
const globalPos = mapToGlobal(width / 2, height / 2);
const currentScreen = root.parentScreen;
const screenX = currentScreen ? currentScreen.x : 0;
const screenY = currentScreen ? currentScreen.y : 0;
const relativeY = globalPos.y - screenY;
// Add minTooltipY offset to account for top bar
const adjustedY = relativeY + root.minTooltipY
const tooltipX = root.axis?.edge === "left" ? (Theme.barHeight + (barConfig?.spacing ?? 4) + Theme.spacingXS) : (currentScreen.width - Theme.barHeight - (barConfig?.spacing ?? 4) - Theme.spacingXS)
const adjustedY = relativeY + root.minTooltipY;
const tooltipX = root.axis?.edge === "left" ? (Theme.barHeight + (barConfig?.spacing ?? 4) + Theme.spacingXS) : (currentScreen.width - Theme.barHeight - (barConfig?.spacing ?? 4) - Theme.spacingXS);
const appName = activeDesktopEntry && activeDesktopEntry.name ? activeDesktopEntry.name : activeWindow.appId
const title = activeWindow.title || ""
const tooltipText = appName + (title ? " • " + title : "")
const appName = Paths.getAppName(activeWindow.appId, activeDesktopEntry);
const title = activeWindow.title || "";
const tooltipText = appName + (title ? " • " + title : "");
const isLeft = root.axis?.edge === "left"
tooltipLoader.item.show(tooltipText, screenX + tooltipX, adjustedY, currentScreen, isLeft, !isLeft)
const isLeft = root.axis?.edge === "left";
tooltipLoader.item.show(tooltipText, screenX + tooltipX, adjustedY, currentScreen, isLeft, !isLeft);
}
}
}
onExited: {
if (tooltipLoader.item) {
tooltipLoader.item.hide()
tooltipLoader.item.hide();
}
tooltipLoader.active = false
tooltipLoader.active = false;
}
}

View File

@@ -1,5 +1,4 @@
import QtQuick
import QtQuick.Controls
import qs.Common
import qs.Modules.Plugins
import qs.Services
@@ -16,7 +15,7 @@ BasePill {
property int selectedGpuIndex: (widgetData && widgetData.selectedGpuIndex !== undefined) ? widgetData.selectedGpuIndex : 0
property bool minimumWidth: (widgetData && widgetData.minimumWidth !== undefined) ? widgetData.minimumWidth : true
signal gpuTempClicked()
signal gpuTempClicked
property real displayTemp: {
if (!DgopService.availableGpus || DgopService.availableGpus.length === 0) {
@@ -32,9 +31,9 @@ BasePill {
function updateWidgetPciId(pciId) {
const sections = ["left", "center", "right"];
const defaultBar = SettingsData.barConfigs[0] || SettingsData.getBarConfig("default")
if (!defaultBar) return
const defaultBar = SettingsData.barConfigs[0] || SettingsData.getBarConfig("default");
if (!defaultBar)
return;
for (let s = 0; s < sections.length; s++) {
const sectionId = sections[s];
let widgets = [];
@@ -61,7 +60,7 @@ BasePill {
} else if (sectionId === "right") {
SettingsData.setDankBarRightWidgets(widgets);
}
return ;
return;
}
}
}
@@ -94,7 +93,7 @@ BasePill {
content: Component {
Item {
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : gpuTempContent.implicitWidth
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : gpuTempContentRoot.implicitWidth
implicitHeight: root.isVerticalOrientation ? gpuTempColumn.implicitHeight : (root.widgetThickness - root.horizontalPadding * 2)
Column {
@@ -134,55 +133,78 @@ BasePill {
}
}
Row {
id: gpuTempContent
Item {
id: gpuTempContentRoot
visible: !root.isVerticalOrientation
anchors.centerIn: parent
spacing: 3
DankIcon {
name: "auto_awesome_mosaic"
size: Theme.barIconSize(root.barThickness)
color: {
if (root.displayTemp > 80) {
return Theme.tempDanger;
implicitWidth: gpuTempRow.implicitWidth
implicitHeight: gpuTempRow.implicitHeight
Row {
id: gpuTempRow
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
id: gpuTempIcon
name: "auto_awesome_mosaic"
size: Theme.barIconSize(root.barThickness)
color: {
if (root.displayTemp > 80) {
return Theme.tempDanger;
}
if (root.displayTemp > 65) {
return Theme.tempWarning;
}
return Theme.widgetIconColor;
}
if (root.displayTemp > 65) {
return Theme.tempWarning;
implicitWidth: size
implicitHeight: size
width: size
height: size
}
Item {
id: textBox
implicitWidth: root.minimumWidth ? Math.max(gpuTempBaseline.width, gpuTempText.paintedWidth) : gpuTempText.paintedWidth
implicitHeight: gpuTempText.implicitHeight
width: implicitWidth
height: implicitHeight
Behavior on width {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Easing.OutCubic
}
}
return Theme.widgetIconColor;
}
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: {
if (root.displayTemp === undefined || root.displayTemp === null || root.displayTemp === 0) {
return "--°";
StyledTextMetrics {
id: gpuTempBaseline
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
text: "88°"
}
return Math.round(root.displayTemp) + "°";
}
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
color: Theme.widgetTextColor
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
elide: Text.ElideNone
StyledText {
id: gpuTempText
text: {
if (root.displayTemp === undefined || root.displayTemp === null || root.displayTemp === 0) {
return "--°";
}
StyledTextMetrics {
id: gpuTempBaseline
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
text: "100°"
}
return Math.round(root.displayTemp) + "°";
}
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
color: Theme.widgetTextColor
width: root.minimumWidth ? Math.max(gpuTempBaseline.width, paintedWidth) : paintedWidth
Behavior on width {
NumberAnimation {
duration: 120
easing.type: Easing.OutCubic
anchors.fill: parent
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
elide: Text.ElideNone
}
}
}
@@ -195,8 +217,8 @@ BasePill {
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onPressed: {
DgopService.setSortBy("cpu")
gpuTempClicked()
DgopService.setSortBy("cpu");
gpuTempClicked();
}
}

View File

@@ -1,5 +1,4 @@
import QtQuick
import QtQuick.Controls
import qs.Common
import qs.Modules.Plugins
import qs.Services
@@ -17,7 +16,7 @@ BasePill {
property bool showSwap: (widgetData && widgetData.showSwap !== undefined) ? widgetData.showSwap : false
readonly property real swapUsage: DgopService.totalSwapKB > 0 ? (DgopService.usedSwapKB / DgopService.totalSwapKB) * 100 : 0
signal ramClicked()
signal ramClicked
Component.onCompleted: {
DgopService.addRef(["memory"]);
@@ -28,7 +27,7 @@ BasePill {
content: Component {
Item {
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : ramContent.implicitWidth
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : ramContentRoot.implicitWidth
implicitHeight: root.isVerticalOrientation ? ramColumn.implicitHeight : (root.widgetThickness - root.horizontalPadding * 2)
Column {
@@ -76,68 +75,91 @@ BasePill {
}
}
Row {
id: ramContent
Item {
id: ramContentRoot
visible: !root.isVerticalOrientation
anchors.centerIn: parent
spacing: 3
DankIcon {
name: "developer_board"
size: Theme.barIconSize(root.barThickness)
color: {
if (DgopService.memoryUsage > 90) {
return Theme.tempDanger;
}
implicitWidth: ramRow.implicitWidth
implicitHeight: ramRow.implicitHeight
if (DgopService.memoryUsage > 75) {
return Theme.tempWarning;
}
Row {
id: ramRow
anchors.centerIn: parent
spacing: Theme.spacingXS
return Theme.widgetIconColor;
}
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: {
if (DgopService.memoryUsage === undefined || DgopService.memoryUsage === null || DgopService.memoryUsage === 0) {
return "--%";
}
let ramText = DgopService.memoryUsage.toFixed(0) + "%";
if (root.showSwap && DgopService.totalSwapKB > 0) {
return ramText + " · " + root.swapUsage.toFixed(0) + "%";
}
return ramText;
}
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
color: Theme.widgetTextColor
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
elide: Text.ElideNone
wrapMode: Text.NoWrap
StyledTextMetrics {
id: ramBaseline
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
text: {
if (!root.showSwap) {
return "100%";
DankIcon {
id: ramIcon
name: "developer_board"
size: Theme.barIconSize(root.barThickness)
color: {
if (DgopService.memoryUsage > 90) {
return Theme.tempDanger;
}
if (root.swapUsage < 10) {
return "100% · 0%";
if (DgopService.memoryUsage > 75) {
return Theme.tempWarning;
}
return "100% · 100%";
return Theme.widgetIconColor;
}
implicitWidth: size
implicitHeight: size
width: size
height: size
}
width: root.minimumWidth ? Math.max(ramBaseline.width, paintedWidth) : paintedWidth
Item {
id: textBox
Behavior on width {
NumberAnimation {
duration: 120
easing.type: Easing.OutCubic
implicitWidth: root.minimumWidth ? Math.max(ramBaseline.width, ramText.paintedWidth) : ramText.paintedWidth
implicitHeight: ramText.implicitHeight
width: implicitWidth
height: implicitHeight
Behavior on width {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Easing.OutCubic
}
}
StyledTextMetrics {
id: ramBaseline
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
text: {
if (!root.showSwap) {
return "88%";
}
if (root.swapUsage < 10) {
return "88% · 0%";
}
return "88% · 88%";
}
}
StyledText {
id: ramText
text: {
if (DgopService.memoryUsage === undefined || DgopService.memoryUsage === null || DgopService.memoryUsage === 0) {
return "--%";
}
let ramText = DgopService.memoryUsage.toFixed(0) + "%";
if (root.showSwap && DgopService.totalSwapKB > 0) {
return ramText + " · " + root.swapUsage.toFixed(0) + "%";
}
return ramText;
}
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale)
color: Theme.widgetTextColor
anchors.fill: parent
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
elide: Text.ElideNone
wrapMode: Text.NoWrap
}
}
}
@@ -150,8 +172,8 @@ BasePill {
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onPressed: {
DgopService.setSortBy("memory")
ramClicked()
DgopService.setSortBy("memory");
ramClicked();
}
}
}

View File

@@ -1,7 +1,7 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import Quickshell
import Quickshell.Hyprland
import Quickshell.Wayland
import Quickshell.Widgets
import qs.Common
@@ -27,105 +27,112 @@ Item {
readonly property real effectiveBarThickness: {
if (barThickness > 0 && barSpacing > 0) {
return barThickness + barSpacing
return barThickness + barSpacing;
}
const innerPadding = barConfig?.innerPadding ?? 4
const spacing = barConfig?.spacing ?? 4
return Math.max(26 + innerPadding * 0.6, Theme.barHeight - 4 - (8 - innerPadding)) + spacing
const innerPadding = barConfig?.innerPadding ?? 4;
const spacing = barConfig?.spacing ?? 4;
return Math.max(26 + innerPadding * 0.6, Theme.barHeight - 4 - (8 - innerPadding)) + spacing;
}
readonly property var barBounds: {
if (!parentScreen || !barConfig) {
return { "x": 0, "y": 0, "width": 0, "height": 0, "wingSize": 0 }
return {
"x": 0,
"y": 0,
"width": 0,
"height": 0,
"wingSize": 0
};
}
const barPosition = axis.edge === "left" ? 2 : (axis.edge === "right" ? 3 : (axis.edge === "top" ? 0 : 1))
return SettingsData.getBarBounds(parentScreen, effectiveBarThickness, barPosition, barConfig)
const barPosition = axis.edge === "left" ? 2 : (axis.edge === "right" ? 3 : (axis.edge === "top" ? 0 : 1));
return SettingsData.getBarBounds(parentScreen, effectiveBarThickness, barPosition, barConfig);
}
readonly property real barY: barBounds.y
readonly property real minTooltipY: {
if (!parentScreen || !isVertical) {
return 0
return 0;
}
if (isAutoHideBar) {
return 0
return 0;
}
if (parentScreen.y > 0) {
return effectiveBarThickness
return effectiveBarThickness;
}
return 0
return 0;
}
property int _desktopEntriesUpdateTrigger: 0
property int _toplevelsUpdateTrigger: 0
readonly property var sortedToplevels: {
_toplevelsUpdateTrigger
const toplevels = CompositorService.sortedToplevels
if (!toplevels || toplevels.length === 0) return []
_toplevelsUpdateTrigger;
const toplevels = CompositorService.sortedToplevels;
if (!toplevels || toplevels.length === 0)
return [];
if (SettingsData.runningAppsCurrentWorkspace) {
return CompositorService.filterCurrentWorkspace(toplevels, parentScreen?.name) || []
return CompositorService.filterCurrentWorkspace(toplevels, parentScreen?.name) || [];
}
return toplevels
return toplevels;
}
Connections {
target: CompositorService
function onToplevelsChanged() {
_toplevelsUpdateTrigger++
_toplevelsUpdateTrigger++;
}
}
Connections {
target: DesktopEntries
function onApplicationsChanged() {
_desktopEntriesUpdateTrigger++
_desktopEntriesUpdateTrigger++;
}
}
readonly property var groupedWindows: {
if (!SettingsData.runningAppsGroupByApp) {
return []
return [];
}
try {
if (!sortedToplevels || sortedToplevels.length === 0) {
return []
return [];
}
const appGroups = new Map()
const appGroups = new Map();
sortedToplevels.forEach((toplevel, index) => {
if (!toplevel)
return
const appId = toplevel?.appId || "unknown"
if (!appGroups.has(appId)) {
appGroups.set(appId, {
"appId": appId,
"windows": []
})
}
appGroups.get(appId).windows.push({
"toplevel": toplevel,
"windowId": index,
"windowTitle": toplevel?.title || "(Unnamed)"
})
})
return Array.from(appGroups.values())
if (!toplevel)
return;
const appId = toplevel?.appId || "unknown";
if (!appGroups.has(appId)) {
appGroups.set(appId, {
"appId": appId,
"windows": []
});
}
appGroups.get(appId).windows.push({
"toplevel": toplevel,
"windowId": index,
"windowTitle": toplevel?.title || "(Unnamed)"
});
});
return Array.from(appGroups.values());
} catch (e) {
return []
return [];
}
}
readonly property int windowCount: SettingsData.runningAppsGroupByApp ? (groupedWindows?.length || 0) : (sortedToplevels?.length || 0)
readonly property int calculatedSize: {
if (windowCount === 0) {
return 0
return 0;
}
if (SettingsData.runningAppsCompactMode) {
return windowCount * 24 + (windowCount - 1) * Theme.spacingXS + horizontalPadding * 2
return windowCount * 24 + (windowCount - 1) * Theme.spacingXS + horizontalPadding * 2;
} else {
return windowCount * (24 + Theme.spacingXS + 120) + (windowCount - 1) * Theme.spacingXS + horizontalPadding * 2
return windowCount * (24 + Theme.spacingXS + 120) + (windowCount - 1) * Theme.spacingXS + horizontalPadding * 2;
}
}
@@ -133,7 +140,6 @@ Item {
height: windowCount > 0 ? (isVertical ? calculatedSize : barThickness) : 0
visible: windowCount > 0
Rectangle {
id: visualBackground
width: root.isVertical ? root.widgetThickness : root.calculatedSize
@@ -143,19 +149,19 @@ Item {
clip: false
color: {
if (windowCount === 0) {
return "transparent"
return "transparent";
}
if ((barConfig?.noBackground ?? false)) {
return "transparent"
return "transparent";
}
const baseColor = Theme.widgetBaseBackgroundColor
const baseColor = Theme.widgetBaseBackgroundColor;
if (Theme.widgetBackgroundHasAlpha) {
return baseColor
return baseColor;
}
const transparency = (root.barConfig && root.barConfig.widgetTransparency !== undefined) ? root.barConfig.widgetTransparency : 1.0
return Theme.withAlpha(baseColor, transparency)
const transparency = (root.barConfig && root.barConfig.widgetTransparency !== undefined) ? root.barConfig.widgetTransparency : 1.0;
return Theme.withAlpha(baseColor, transparency);
}
}
@@ -168,82 +174,82 @@ Item {
property real touchpadThreshold: 500
onWheel: wheel => {
const deltaY = wheel.angleDelta.y
const isMouseWheel = Math.abs(deltaY) >= 120 && (Math.abs(deltaY) % 120) === 0
const deltaY = wheel.angleDelta.y;
const isMouseWheel = Math.abs(deltaY) >= 120 && (Math.abs(deltaY) % 120) === 0;
const windows = root.sortedToplevels
if (windows.length < 2) {
return
}
const windows = root.sortedToplevels;
if (windows.length < 2) {
return;
}
if (isMouseWheel) {
// Direct mouse wheel action
let currentIndex = -1
for (var i = 0; i < windows.length; i++) {
if (windows[i].activated) {
currentIndex = i
break
}
}
if (isMouseWheel) {
// Direct mouse wheel action
let currentIndex = -1;
for (var i = 0; i < windows.length; i++) {
if (windows[i].activated) {
currentIndex = i;
break;
}
}
let nextIndex
if (deltaY < 0) {
if (currentIndex === -1) {
nextIndex = 0
} else {
nextIndex = Math.min(currentIndex + 1, windows.length - 1)
}
} else {
if (currentIndex === -1) {
nextIndex = windows.length - 1
} else {
nextIndex = Math.max(currentIndex - 1, 0)
}
}
let nextIndex;
if (deltaY < 0) {
if (currentIndex === -1) {
nextIndex = 0;
} else {
nextIndex = Math.min(currentIndex + 1, windows.length - 1);
}
} else {
if (currentIndex === -1) {
nextIndex = windows.length - 1;
} else {
nextIndex = Math.max(currentIndex - 1, 0);
}
}
const nextWindow = windows[nextIndex]
if (nextWindow) {
nextWindow.activate()
}
} else {
// Touchpad - accumulate small deltas
scrollAccumulator += deltaY
const nextWindow = windows[nextIndex];
if (nextWindow) {
nextWindow.activate();
}
} else {
// Touchpad - accumulate small deltas
scrollAccumulator += deltaY;
if (Math.abs(scrollAccumulator) >= touchpadThreshold) {
let currentIndex = -1
for (var i = 0; i < windows.length; i++) {
if (windows[i].activated) {
currentIndex = i
break
}
}
if (Math.abs(scrollAccumulator) >= touchpadThreshold) {
let currentIndex = -1;
for (var i = 0; i < windows.length; i++) {
if (windows[i].activated) {
currentIndex = i;
break;
}
}
let nextIndex
if (scrollAccumulator < 0) {
if (currentIndex === -1) {
nextIndex = 0
} else {
nextIndex = Math.min(currentIndex + 1, windows.length - 1)
}
} else {
if (currentIndex === -1) {
nextIndex = windows.length - 1
} else {
nextIndex = Math.max(currentIndex - 1, 0)
}
}
let nextIndex;
if (scrollAccumulator < 0) {
if (currentIndex === -1) {
nextIndex = 0;
} else {
nextIndex = Math.min(currentIndex + 1, windows.length - 1);
}
} else {
if (currentIndex === -1) {
nextIndex = windows.length - 1;
} else {
nextIndex = Math.max(currentIndex - 1, 0);
}
}
const nextWindow = windows[nextIndex]
if (nextWindow) {
nextWindow.activate()
}
const nextWindow = windows[nextIndex];
if (nextWindow) {
nextWindow.activate();
}
scrollAccumulator = 0
}
}
scrollAccumulator = 0;
}
}
wheel.accepted = true
}
wheel.accepted = true;
}
}
Loader {
@@ -276,16 +282,14 @@ Item {
property var toplevelObject: toplevelData
property int windowCount: isGrouped ? modelData.windows.length : 1
property string tooltipText: {
root._desktopEntriesUpdateTrigger
let appName = "Unknown"
if (appId) {
const desktopEntry = DesktopEntries.heuristicLookup(appId)
appName = desktopEntry && desktopEntry.name ? desktopEntry.name : appId
}
root._desktopEntriesUpdateTrigger;
const desktopEntry = appId ? DesktopEntries.heuristicLookup(appId) : null;
const appName = appId ? Paths.getAppName(appId, desktopEntry) : "Unknown";
if (isGrouped && windowCount > 1) {
return appName + " (" + windowCount + " windows)"
return appName + " (" + windowCount + " windows)";
}
return appName + (windowTitle ? " • " + windowTitle : "")
return appName + (windowTitle ? " • " + windowTitle : "");
}
readonly property real visualWidth: SettingsData.runningAppsCompactMode ? 24 : (24 + Theme.spacingXS + 120)
@@ -300,9 +304,9 @@ Item {
radius: Theme.cornerRadius
color: {
if (isFocused) {
return mouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3) : Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2)
return mouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3) : Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2);
} else {
return mouseArea.containsMouse ? Qt.rgba(Theme.primaryHover.r, Theme.primaryHover.g, Theme.primaryHover.b, 0.1) : "transparent"
return mouseArea.containsMouse ? Qt.rgba(Theme.primaryHover.r, Theme.primaryHover.g, Theme.primaryHover.b, 0.1) : "transparent";
}
}
@@ -315,17 +319,24 @@ Item {
width: Theme.barIconSize(root.barThickness)
height: Theme.barIconSize(root.barThickness)
source: {
root._desktopEntriesUpdateTrigger
const moddedId = Paths.moddedAppId(appId)
if (moddedId.toLowerCase().includes("steam_app")) {
return ""
}
return Quickshell.iconPath(DesktopEntries.heuristicLookup(moddedId)?.icon, true)
root._desktopEntriesUpdateTrigger;
if (!appId)
return "";
const desktopEntry = DesktopEntries.heuristicLookup(appId);
return Paths.getAppIcon(appId, desktopEntry);
}
smooth: true
mipmap: true
asynchronous: true
visible: status === Image.Ready
layer.enabled: appId === "org.quickshell"
layer.smooth: true
layer.mipmap: true
layer.effect: MultiEffect {
saturation: 0
colorization: 1
colorizationColor: Theme.primary
}
}
DankIcon {
@@ -336,8 +347,8 @@ Item {
name: "sports_esports"
color: Theme.widgetTextColor
visible: {
const moddedId = Paths.moddedAppId(appId)
return moddedId.toLowerCase().includes("steam_app")
const moddedId = Paths.moddedAppId(appId);
return moddedId.toLowerCase().includes("steam_app");
}
}
@@ -345,22 +356,18 @@ Item {
Text {
anchors.centerIn: parent
visible: {
const moddedId = Paths.moddedAppId(appId)
const isSteamApp = moddedId.toLowerCase().includes("steam_app")
return !iconImg.visible && !isSteamApp
const moddedId = Paths.moddedAppId(appId);
const isSteamApp = moddedId.toLowerCase().includes("steam_app");
return !iconImg.visible && !isSteamApp;
}
text: {
root._desktopEntriesUpdateTrigger
if (!appId) {
return "?"
}
root._desktopEntriesUpdateTrigger;
if (!appId)
return "?";
const desktopEntry = DesktopEntries.heuristicLookup(appId)
if (desktopEntry && desktopEntry.name) {
return desktopEntry.name.charAt(0).toUpperCase()
}
return appId.charAt(0).toUpperCase()
const desktopEntry = DesktopEntries.heuristicLookup(appId);
const appName = Paths.getAppName(appId, desktopEntry);
return appName.charAt(0).toUpperCase();
}
font.pixelSize: 10
color: Theme.widgetTextColor
@@ -409,86 +416,84 @@ Item {
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: mouse => {
if (mouse.button === Qt.LeftButton) {
if (isGrouped && windowCount > 1) {
let currentIndex = -1
for (var i = 0; i < groupData.windows.length; i++) {
if (groupData.windows[i].toplevel.activated) {
currentIndex = i
break
}
}
const nextIndex = (currentIndex + 1) % groupData.windows.length
groupData.windows[nextIndex].toplevel.activate()
} else if (toplevelObject) {
toplevelObject.activate()
}
} else if (mouse.button === Qt.RightButton) {
if (tooltipLoader.item) {
tooltipLoader.item.hide()
}
tooltipLoader.active = false
if (mouse.button === Qt.LeftButton) {
if (isGrouped && windowCount > 1) {
let currentIndex = -1;
for (var i = 0; i < groupData.windows.length; i++) {
if (groupData.windows[i].toplevel.activated) {
currentIndex = i;
break;
}
}
const nextIndex = (currentIndex + 1) % groupData.windows.length;
groupData.windows[nextIndex].toplevel.activate();
} else if (toplevelObject) {
toplevelObject.activate();
}
} else if (mouse.button === Qt.RightButton) {
if (tooltipLoader.item) {
tooltipLoader.item.hide();
}
tooltipLoader.active = false;
windowContextMenuLoader.active = true
if (windowContextMenuLoader.item) {
windowContextMenuLoader.item.currentWindow = toplevelObject
// Pass bar context
windowContextMenuLoader.item.triggerBarConfig = root.barConfig
windowContextMenuLoader.item.triggerBarPosition = root.axis.edge === "left" ? 2 : (root.axis.edge === "right" ? 3 : (root.axis.edge === "top" ? 0 : 1))
windowContextMenuLoader.item.triggerBarThickness = root.barThickness
windowContextMenuLoader.item.triggerBarSpacing = root.barSpacing
if (root.isVertical) {
const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.height / 2)
const screenX = root.parentScreen ? root.parentScreen.x : 0
const screenY = root.parentScreen ? root.parentScreen.y : 0
const relativeY = globalPos.y - screenY
// Add minTooltipY offset to account for top bar
const adjustedY = relativeY + root.minTooltipY
const xPos = root.axis?.edge === "left" ? (root.barThickness + root.barSpacing + Theme.spacingXS) : (root.parentScreen.width - root.barThickness - root.barSpacing - Theme.spacingXS)
windowContextMenuLoader.item.showAt(xPos, adjustedY, true, root.axis?.edge)
} else {
const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, 0)
const screenX = root.parentScreen ? root.parentScreen.x : 0
const relativeX = globalPos.x - screenX
const yPos = root.barThickness + root.barSpacing - 7
windowContextMenuLoader.item.showAt(relativeX, yPos, false, "top")
}
}
}
}
windowContextMenuLoader.active = true;
if (windowContextMenuLoader.item) {
windowContextMenuLoader.item.currentWindow = toplevelObject;
// Pass bar context
windowContextMenuLoader.item.triggerBarConfig = root.barConfig;
windowContextMenuLoader.item.triggerBarPosition = root.axis.edge === "left" ? 2 : (root.axis.edge === "right" ? 3 : (root.axis.edge === "top" ? 0 : 1));
windowContextMenuLoader.item.triggerBarThickness = root.barThickness;
windowContextMenuLoader.item.triggerBarSpacing = root.barSpacing;
if (root.isVertical) {
const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.height / 2);
const screenX = root.parentScreen ? root.parentScreen.x : 0;
const screenY = root.parentScreen ? root.parentScreen.y : 0;
const relativeY = globalPos.y - screenY;
// Add minTooltipY offset to account for top bar
const adjustedY = relativeY + root.minTooltipY;
const xPos = root.axis?.edge === "left" ? (root.barThickness + root.barSpacing + Theme.spacingXS) : (root.parentScreen.width - root.barThickness - root.barSpacing - Theme.spacingXS);
windowContextMenuLoader.item.showAt(xPos, adjustedY, true, root.axis?.edge);
} else {
const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, 0);
const screenX = root.parentScreen ? root.parentScreen.x : 0;
const relativeX = globalPos.x - screenX;
const yPos = root.barThickness + root.barSpacing - 7;
windowContextMenuLoader.item.showAt(relativeX, yPos, false, "top");
}
}
}
}
onEntered: {
root.hoveredItem = delegateItem
tooltipLoader.active = true
root.hoveredItem = delegateItem;
tooltipLoader.active = true;
if (tooltipLoader.item) {
if (root.isVertical) {
const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.height / 2)
const screenX = root.parentScreen ? root.parentScreen.x : 0
const screenY = root.parentScreen ? root.parentScreen.y : 0
const relativeY = globalPos.y - screenY
const tooltipX = root.axis?.edge === "left" ? (Theme.barHeight + (barConfig?.spacing ?? 4) + Theme.spacingXS) : (root.parentScreen.width - Theme.barHeight - (barConfig?.spacing ?? 4) - Theme.spacingXS)
const isLeft = root.axis?.edge === "left"
const adjustedY = relativeY + root.minTooltipY
const finalX = screenX + tooltipX
tooltipLoader.item.show(delegateItem.tooltipText, finalX, adjustedY, root.parentScreen, isLeft, !isLeft)
const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.height / 2);
const screenX = root.parentScreen ? root.parentScreen.x : 0;
const screenY = root.parentScreen ? root.parentScreen.y : 0;
const relativeY = globalPos.y - screenY;
const tooltipX = root.axis?.edge === "left" ? (Theme.barHeight + (barConfig?.spacing ?? 4) + Theme.spacingXS) : (root.parentScreen.width - Theme.barHeight - (barConfig?.spacing ?? 4) - Theme.spacingXS);
const isLeft = root.axis?.edge === "left";
const adjustedY = relativeY + root.minTooltipY;
const finalX = screenX + tooltipX;
tooltipLoader.item.show(delegateItem.tooltipText, finalX, adjustedY, root.parentScreen, isLeft, !isLeft);
} else {
const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.height)
const screenHeight = root.parentScreen ? root.parentScreen.height : Screen.height
const isBottom = root.axis?.edge === "bottom"
const tooltipY = isBottom
? (screenHeight - Theme.barHeight - (barConfig?.spacing ?? 4) - Theme.spacingXS - 35)
: (Theme.barHeight + (barConfig?.spacing ?? 4) + Theme.spacingXS)
tooltipLoader.item.show(delegateItem.tooltipText, globalPos.x, tooltipY, root.parentScreen, false, false)
const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.height);
const screenHeight = root.parentScreen ? root.parentScreen.height : Screen.height;
const isBottom = root.axis?.edge === "bottom";
const tooltipY = isBottom ? (screenHeight - Theme.barHeight - (barConfig?.spacing ?? 4) - Theme.spacingXS - 35) : (Theme.barHeight + (barConfig?.spacing ?? 4) + Theme.spacingXS);
tooltipLoader.item.show(delegateItem.tooltipText, globalPos.x, tooltipY, root.parentScreen, false, false);
}
}
}
onExited: {
if (root.hoveredItem === delegateItem) {
root.hoveredItem = null
root.hoveredItem = null;
if (tooltipLoader.item) {
tooltipLoader.item.hide()
tooltipLoader.item.hide();
}
tooltipLoader.active = false
tooltipLoader.active = false;
}
}
}
@@ -521,16 +526,14 @@ Item {
property var toplevelObject: toplevelData
property int windowCount: isGrouped ? modelData.windows.length : 1
property string tooltipText: {
root._desktopEntriesUpdateTrigger
let appName = "Unknown"
if (appId) {
const desktopEntry = DesktopEntries.heuristicLookup(appId)
appName = desktopEntry && desktopEntry.name ? desktopEntry.name : appId
}
root._desktopEntriesUpdateTrigger;
const desktopEntry = appId ? DesktopEntries.heuristicLookup(appId) : null;
const appName = appId ? Paths.getAppName(appId, desktopEntry) : "Unknown";
if (isGrouped && windowCount > 1) {
return appName + " (" + windowCount + " windows)"
return appName + " (" + windowCount + " windows)";
}
return appName + (windowTitle ? " • " + windowTitle : "")
return appName + (windowTitle ? " • " + windowTitle : "");
}
readonly property real visualWidth: SettingsData.runningAppsCompactMode ? 24 : (24 + Theme.spacingXS + 120)
@@ -545,9 +548,9 @@ Item {
radius: Theme.cornerRadius
color: {
if (isFocused) {
return mouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3) : Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2)
return mouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3) : Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2);
} else {
return mouseArea.containsMouse ? Qt.rgba(Theme.primaryHover.r, Theme.primaryHover.g, Theme.primaryHover.b, 0.1) : "transparent"
return mouseArea.containsMouse ? Qt.rgba(Theme.primaryHover.r, Theme.primaryHover.g, Theme.primaryHover.b, 0.1) : "transparent";
}
}
@@ -559,17 +562,24 @@ Item {
width: Theme.barIconSize(root.barThickness)
height: Theme.barIconSize(root.barThickness)
source: {
root._desktopEntriesUpdateTrigger
const moddedId = Paths.moddedAppId(appId)
if (moddedId.toLowerCase().includes("steam_app")) {
return ""
}
return Quickshell.iconPath(DesktopEntries.heuristicLookup(moddedId)?.icon, true)
root._desktopEntriesUpdateTrigger;
if (!appId)
return "";
const desktopEntry = DesktopEntries.heuristicLookup(appId);
return Paths.getAppIcon(appId, desktopEntry);
}
smooth: true
mipmap: true
asynchronous: true
visible: status === Image.Ready
layer.enabled: appId === "org.quickshell"
layer.smooth: true
layer.mipmap: true
layer.effect: MultiEffect {
saturation: 0
colorization: 1
colorizationColor: Theme.primary
}
}
DankIcon {
@@ -580,30 +590,26 @@ Item {
name: "sports_esports"
color: Theme.widgetTextColor
visible: {
const moddedId = Paths.moddedAppId(appId)
return moddedId.toLowerCase().includes("steam_app")
const moddedId = Paths.moddedAppId(appId);
return moddedId.toLowerCase().includes("steam_app");
}
}
Text {
anchors.centerIn: parent
visible: {
const moddedId = Paths.moddedAppId(appId)
const isSteamApp = moddedId.toLowerCase().includes("steam_app")
return !iconImg.visible && !isSteamApp
const moddedId = Paths.moddedAppId(appId);
const isSteamApp = moddedId.toLowerCase().includes("steam_app");
return !iconImg.visible && !isSteamApp;
}
text: {
root._desktopEntriesUpdateTrigger
if (!appId) {
return "?"
}
root._desktopEntriesUpdateTrigger;
if (!appId)
return "?";
const desktopEntry = DesktopEntries.heuristicLookup(appId)
if (desktopEntry && desktopEntry.name) {
return desktopEntry.name.charAt(0).toUpperCase()
}
return appId.charAt(0).toUpperCase()
const desktopEntry = DesktopEntries.heuristicLookup(appId);
const appName = Paths.getAppName(appId, desktopEntry);
return appName.charAt(0).toUpperCase();
}
font.pixelSize: 10
color: Theme.widgetTextColor
@@ -651,86 +657,84 @@ Item {
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: mouse => {
if (mouse.button === Qt.LeftButton) {
if (isGrouped && windowCount > 1) {
let currentIndex = -1
for (var i = 0; i < groupData.windows.length; i++) {
if (groupData.windows[i].toplevel.activated) {
currentIndex = i
break
}
}
const nextIndex = (currentIndex + 1) % groupData.windows.length
groupData.windows[nextIndex].toplevel.activate()
} else if (toplevelObject) {
toplevelObject.activate()
}
} else if (mouse.button === Qt.RightButton) {
if (tooltipLoader.item) {
tooltipLoader.item.hide()
}
tooltipLoader.active = false
if (mouse.button === Qt.LeftButton) {
if (isGrouped && windowCount > 1) {
let currentIndex = -1;
for (var i = 0; i < groupData.windows.length; i++) {
if (groupData.windows[i].toplevel.activated) {
currentIndex = i;
break;
}
}
const nextIndex = (currentIndex + 1) % groupData.windows.length;
groupData.windows[nextIndex].toplevel.activate();
} else if (toplevelObject) {
toplevelObject.activate();
}
} else if (mouse.button === Qt.RightButton) {
if (tooltipLoader.item) {
tooltipLoader.item.hide();
}
tooltipLoader.active = false;
windowContextMenuLoader.active = true
if (windowContextMenuLoader.item) {
windowContextMenuLoader.item.currentWindow = toplevelObject
// Pass bar context
windowContextMenuLoader.item.triggerBarConfig = root.barConfig
windowContextMenuLoader.item.triggerBarPosition = root.axis.edge === "left" ? 2 : (root.axis.edge === "right" ? 3 : (root.axis.edge === "top" ? 0 : 1))
windowContextMenuLoader.item.triggerBarThickness = root.barThickness
windowContextMenuLoader.item.triggerBarSpacing = root.barSpacing
if (root.isVertical) {
const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.height / 2)
const screenX = root.parentScreen ? root.parentScreen.x : 0
const screenY = root.parentScreen ? root.parentScreen.y : 0
const relativeY = globalPos.y - screenY
// Add minTooltipY offset to account for top bar
const adjustedY = relativeY + root.minTooltipY
const xPos = root.axis?.edge === "left" ? (root.barThickness + root.barSpacing + Theme.spacingXS) : (root.parentScreen.width - root.barThickness - root.barSpacing - Theme.spacingXS)
windowContextMenuLoader.item.showAt(xPos, adjustedY, true, root.axis?.edge)
} else {
const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, 0)
const screenX = root.parentScreen ? root.parentScreen.x : 0
const relativeX = globalPos.x - screenX
const yPos = root.barThickness + root.barSpacing - 7
windowContextMenuLoader.item.showAt(relativeX, yPos, false, "top")
}
}
}
}
windowContextMenuLoader.active = true;
if (windowContextMenuLoader.item) {
windowContextMenuLoader.item.currentWindow = toplevelObject;
// Pass bar context
windowContextMenuLoader.item.triggerBarConfig = root.barConfig;
windowContextMenuLoader.item.triggerBarPosition = root.axis.edge === "left" ? 2 : (root.axis.edge === "right" ? 3 : (root.axis.edge === "top" ? 0 : 1));
windowContextMenuLoader.item.triggerBarThickness = root.barThickness;
windowContextMenuLoader.item.triggerBarSpacing = root.barSpacing;
if (root.isVertical) {
const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.height / 2);
const screenX = root.parentScreen ? root.parentScreen.x : 0;
const screenY = root.parentScreen ? root.parentScreen.y : 0;
const relativeY = globalPos.y - screenY;
// Add minTooltipY offset to account for top bar
const adjustedY = relativeY + root.minTooltipY;
const xPos = root.axis?.edge === "left" ? (root.barThickness + root.barSpacing + Theme.spacingXS) : (root.parentScreen.width - root.barThickness - root.barSpacing - Theme.spacingXS);
windowContextMenuLoader.item.showAt(xPos, adjustedY, true, root.axis?.edge);
} else {
const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, 0);
const screenX = root.parentScreen ? root.parentScreen.x : 0;
const relativeX = globalPos.x - screenX;
const yPos = root.barThickness + root.barSpacing - 7;
windowContextMenuLoader.item.showAt(relativeX, yPos, false, "top");
}
}
}
}
onEntered: {
root.hoveredItem = delegateItem
tooltipLoader.active = true
root.hoveredItem = delegateItem;
tooltipLoader.active = true;
if (tooltipLoader.item) {
if (root.isVertical) {
const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.height / 2)
const screenX = root.parentScreen ? root.parentScreen.x : 0
const screenY = root.parentScreen ? root.parentScreen.y : 0
const relativeY = globalPos.y - screenY
const tooltipX = root.axis?.edge === "left" ? (root.barThickness + root.barSpacing + Theme.spacingXS) : (root.parentScreen.width - root.barThickness - root.barSpacing - Theme.spacingXS)
const isLeft = root.axis?.edge === "left"
const adjustedY = relativeY + root.minTooltipY
const finalX = screenX + tooltipX
tooltipLoader.item.show(delegateItem.tooltipText, finalX, adjustedY, root.parentScreen, isLeft, !isLeft)
const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.height / 2);
const screenX = root.parentScreen ? root.parentScreen.x : 0;
const screenY = root.parentScreen ? root.parentScreen.y : 0;
const relativeY = globalPos.y - screenY;
const tooltipX = root.axis?.edge === "left" ? (root.barThickness + root.barSpacing + Theme.spacingXS) : (root.parentScreen.width - root.barThickness - root.barSpacing - Theme.spacingXS);
const isLeft = root.axis?.edge === "left";
const adjustedY = relativeY + root.minTooltipY;
const finalX = screenX + tooltipX;
tooltipLoader.item.show(delegateItem.tooltipText, finalX, adjustedY, root.parentScreen, isLeft, !isLeft);
} else {
const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.height)
const screenHeight = root.parentScreen ? root.parentScreen.height : Screen.height
const isBottom = root.axis?.edge === "bottom"
const tooltipY = isBottom
? (screenHeight - root.barThickness - root.barSpacing - Theme.spacingXS - 35)
: (root.barThickness + root.barSpacing + Theme.spacingXS)
tooltipLoader.item.show(delegateItem.tooltipText, globalPos.x, tooltipY, root.parentScreen, false, false)
const globalPos = delegateItem.mapToGlobal(delegateItem.width / 2, delegateItem.height);
const screenHeight = root.parentScreen ? root.parentScreen.height : Screen.height;
const isBottom = root.axis?.edge === "bottom";
const tooltipY = isBottom ? (screenHeight - root.barThickness - root.barSpacing - Theme.spacingXS - 35) : (root.barThickness + root.barSpacing + Theme.spacingXS);
tooltipLoader.item.show(delegateItem.tooltipText, globalPos.x, tooltipY, root.parentScreen, false, false);
}
}
}
onExited: {
if (root.hoveredItem === delegateItem) {
root.hoveredItem = null
root.hoveredItem = null;
if (tooltipLoader.item) {
tooltipLoader.item.hide()
tooltipLoader.item.hide();
}
tooltipLoader.active = false
tooltipLoader.active = false;
}
}
}
@@ -767,33 +771,39 @@ Item {
readonly property real effectiveBarThickness: {
if (triggerBarThickness > 0 && triggerBarSpacing > 0) {
return triggerBarThickness + triggerBarSpacing
return triggerBarThickness + triggerBarSpacing;
}
return Math.max(26 + (barConfig?.innerPadding ?? 4) * 0.6, Theme.barHeight - 4 - (8 - (barConfig?.innerPadding ?? 4))) + (barConfig?.spacing ?? 4)
return Math.max(26 + (barConfig?.innerPadding ?? 4) * 0.6, Theme.barHeight - 4 - (8 - (barConfig?.innerPadding ?? 4))) + (barConfig?.spacing ?? 4);
}
property var barBounds: {
if (!contextMenuWindow.screen || !triggerBarConfig) {
return { "x": 0, "y": 0, "width": 0, "height": 0, "wingSize": 0 }
return {
"x": 0,
"y": 0,
"width": 0,
"height": 0,
"wingSize": 0
};
}
return SettingsData.getBarBounds(contextMenuWindow.screen, effectiveBarThickness, triggerBarPosition, triggerBarConfig)
return SettingsData.getBarBounds(contextMenuWindow.screen, effectiveBarThickness, triggerBarPosition, triggerBarConfig);
}
property real barY: barBounds.y
function showAt(x, y, vertical, barEdge) {
screen = root.parentScreen
anchorPos = Qt.point(x, y)
isVertical = vertical ?? false
edge = barEdge ?? "top"
isVisible = true
visible = true
screen = root.parentScreen;
anchorPos = Qt.point(x, y);
isVertical = vertical ?? false;
edge = barEdge ?? "top";
isVisible = true;
visible = true;
}
function close() {
isVisible = false
visible = false
windowContextMenuLoader.active = false
isVisible = false;
visible = false;
windowContextMenuLoader.active = false;
}
implicitWidth: 100
@@ -821,25 +831,25 @@ Item {
x: {
if (contextMenuWindow.isVertical) {
if (contextMenuWindow.edge === "left") {
return Math.min(contextMenuWindow.width - width - 10, contextMenuWindow.anchorPos.x)
return Math.min(contextMenuWindow.width - width - 10, contextMenuWindow.anchorPos.x);
} else {
return Math.max(10, contextMenuWindow.anchorPos.x - width)
return Math.max(10, contextMenuWindow.anchorPos.x - width);
}
} else {
const left = 10
const right = contextMenuWindow.width - width - 10
const want = contextMenuWindow.anchorPos.x - width / 2
return Math.max(left, Math.min(right, want))
const left = 10;
const right = contextMenuWindow.width - width - 10;
const want = contextMenuWindow.anchorPos.x - width / 2;
return Math.max(left, Math.min(right, want));
}
}
y: {
if (contextMenuWindow.isVertical) {
const top = Math.max(barY, 10)
const bottom = contextMenuWindow.height - height - 10
const want = contextMenuWindow.anchorPos.y - height / 2
return Math.max(top, Math.min(bottom, want))
const top = Math.max(barY, 10);
const bottom = contextMenuWindow.height - height - 10;
const want = contextMenuWindow.anchorPos.y - height / 2;
return Math.max(top, Math.min(bottom, want));
} else {
return contextMenuWindow.anchorPos.y
return contextMenuWindow.anchorPos.y;
}
}
width: 100
@@ -869,9 +879,9 @@ Item {
cursorShape: Qt.PointingHandCursor
onClicked: {
if (contextMenuWindow.currentWindow) {
contextMenuWindow.currentWindow.close()
contextMenuWindow.currentWindow.close();
}
contextMenuWindow.close()
contextMenuWindow.close();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,7 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import Quickshell
import Quickshell.Wayland
import Quickshell.Hyprland
import Quickshell.Widgets
import qs.Common
import qs.Services
@@ -32,11 +31,11 @@ Item {
function updateDesktopEntry() {
if (!appData || appData.appId === "__SEPARATOR__") {
cachedDesktopEntry = null
return
cachedDesktopEntry = null;
return;
}
const moddedId = Paths.moddedAppId(appData.appId)
cachedDesktopEntry = DesktopEntries.heuristicLookup(moddedId)
const moddedId = Paths.moddedAppId(appData.appId);
cachedDesktopEntry = DesktopEntries.heuristicLookup(moddedId);
}
Component.onCompleted: updateDesktopEntry()
@@ -46,80 +45,81 @@ Item {
Connections {
target: DesktopEntries
function onApplicationsChanged() {
updateDesktopEntry()
updateDesktopEntry();
}
}
property bool isWindowFocused: {
if (!appData) {
return false
return false;
}
if (appData.type === "window") {
const toplevel = getToplevelObject()
const toplevel = getToplevelObject();
if (!toplevel) {
return false
return false;
}
return toplevel.activated
return toplevel.activated;
} else if (appData.type === "grouped") {
// For grouped apps, check if any window is focused
const allToplevels = ToplevelManager.toplevels.values
const allToplevels = ToplevelManager.toplevels.values;
for (let i = 0; i < allToplevels.length; i++) {
const toplevel = allToplevels[i]
const toplevel = allToplevels[i];
if (toplevel.appId === appData.appId && toplevel.activated) {
return true
return true;
}
}
}
return false
return false;
}
property string tooltipText: {
if (!appData) {
return ""
if (!appData || !appData.appId) {
return "";
}
const appName = Paths.getAppName(appData.appId, cachedDesktopEntry);
if ((appData.type === "window" && showWindowTitle) || (appData.type === "grouped" && appData.windowTitle)) {
const appName = cachedDesktopEntry && cachedDesktopEntry.name ? cachedDesktopEntry.name : appData.appId
const title = appData.type === "window" ? windowTitle : appData.windowTitle
return appName + (title ? " • " + title : "")
const title = appData.type === "window" ? windowTitle : appData.windowTitle;
return appName + (title ? "" + title : "");
}
if (!appData.appId) {
return ""
}
return cachedDesktopEntry && cachedDesktopEntry.name ? cachedDesktopEntry.name : appData.appId
return appName;
}
function getToplevelObject() {
return appData?.toplevel || null
return appData?.toplevel || null;
}
function getGroupedToplevels() {
return appData?.allWindows?.map(w => w.toplevel).filter(t => t !== null) || []
return appData?.allWindows?.map(w => w.toplevel).filter(t => t !== null) || [];
}
onIsHoveredChanged: {
if (mouseArea.pressed) return
if (mouseArea.pressed)
return;
if (isHovered) {
exitAnimation.stop()
exitAnimation.stop();
if (!bounceAnimation.running) {
bounceAnimation.restart()
bounceAnimation.restart();
}
} else {
bounceAnimation.stop()
exitAnimation.restart()
bounceAnimation.stop();
exitAnimation.restart();
}
}
readonly property bool animateX: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right
readonly property real animationDistance: actualIconSize
readonly property real animationDirection: {
if (SettingsData.dockPosition === SettingsData.Position.Bottom) return -1
if (SettingsData.dockPosition === SettingsData.Position.Top) return 1
if (SettingsData.dockPosition === SettingsData.Position.Right) return -1
if (SettingsData.dockPosition === SettingsData.Position.Left) return 1
return -1
if (SettingsData.dockPosition === SettingsData.Position.Bottom)
return -1;
if (SettingsData.dockPosition === SettingsData.Position.Top)
return 1;
if (SettingsData.dockPosition === SettingsData.Position.Right)
return -1;
if (SettingsData.dockPosition === SettingsData.Position.Left)
return 1;
return -1;
}
SequentialAnimation {
@@ -165,7 +165,7 @@ Item {
repeat: false
onTriggered: {
if (appData && appData.isPinned) {
longPressing = true
longPressing = true;
}
}
}
@@ -180,134 +180,137 @@ Item {
cursorShape: longPressing ? Qt.DragMoveCursor : Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onPressed: mouse => {
if (mouse.button === Qt.LeftButton && appData && appData.isPinned) {
dragStartPos = Qt.point(mouse.x, mouse.y)
longPressTimer.start()
}
}
if (mouse.button === Qt.LeftButton && appData && appData.isPinned) {
dragStartPos = Qt.point(mouse.x, mouse.y);
longPressTimer.start();
}
}
onReleased: mouse => {
longPressTimer.stop()
if (longPressing) {
if (dragging && targetIndex >= 0 && targetIndex !== originalIndex && dockApps) {
dockApps.movePinnedApp(originalIndex, targetIndex)
}
longPressTimer.stop();
if (longPressing) {
if (dragging && targetIndex >= 0 && targetIndex !== originalIndex && dockApps) {
dockApps.movePinnedApp(originalIndex, targetIndex);
}
longPressing = false
dragging = false
dragOffset = Qt.point(0, 0)
targetIndex = -1
originalIndex = -1
longPressing = false;
dragging = false;
dragOffset = Qt.point(0, 0);
targetIndex = -1;
originalIndex = -1;
}
}
onPositionChanged: mouse => {
if (longPressing && !dragging) {
const distance = Math.sqrt(Math.pow(mouse.x - dragStartPos.x, 2) + Math.pow(mouse.y - dragStartPos.y, 2));
if (distance > 5) {
dragging = true;
targetIndex = index;
originalIndex = index;
}
}
if (dragging) {
dragOffset = Qt.point(mouse.x - dragStartPos.x, mouse.y - dragStartPos.y);
if (dockApps) {
const threshold = actualIconSize;
let newTargetIndex = targetIndex;
if (dragOffset.x > threshold && targetIndex < dockApps.pinnedAppCount - 1) {
newTargetIndex = targetIndex + 1;
} else if (dragOffset.x < -threshold && targetIndex > 0) {
newTargetIndex = targetIndex - 1;
}
if (newTargetIndex !== targetIndex) {
targetIndex = newTargetIndex;
dragStartPos = Qt.point(mouse.x, mouse.y);
}
}
}
}
onClicked: mouse => {
if (!appData || longPressing) {
return;
}
if (mouse.button === Qt.LeftButton) {
if (appData.type === "pinned") {
if (appData && appData.appId) {
const desktopEntry = cachedDesktopEntry;
if (desktopEntry) {
AppUsageHistoryData.addAppUsage({
"id": appData.appId,
"name": desktopEntry.name || appData.appId,
"icon": desktopEntry.icon || "",
"exec": desktopEntry.exec || "",
"comment": desktopEntry.comment || ""
});
}
SessionService.launchDesktopEntry(desktopEntry);
}
} else if (appData.type === "window") {
const toplevel = getToplevelObject();
if (toplevel) {
toplevel.activate();
}
} else if (appData.type === "grouped") {
if (appData.windowCount === 0) {
if (appData && appData.appId) {
const desktopEntry = cachedDesktopEntry;
if (desktopEntry) {
AppUsageHistoryData.addAppUsage({
"id": appData.appId,
"name": desktopEntry.name || appData.appId,
"icon": desktopEntry.icon || "",
"exec": desktopEntry.exec || "",
"comment": desktopEntry.comment || ""
});
}
SessionService.launchDesktopEntry(desktopEntry);
}
} else if (appData.windowCount === 1) {
// For single window, activate directly
const toplevel = getToplevelObject();
if (toplevel) {
console.log("Activating grouped app window:", appData.windowTitle);
toplevel.activate();
} else {
console.warn("No toplevel found for grouped app");
}
} else {
if (contextMenu) {
const shouldHidePin = appData.appId === "org.quickshell";
contextMenu.showForButton(root, appData, root.height + 25, shouldHidePin, cachedDesktopEntry, parentDockScreen);
}
}
onPositionChanged: mouse => {
if (longPressing && !dragging) {
const distance = Math.sqrt(Math.pow(mouse.x - dragStartPos.x, 2) + Math.pow(mouse.y - dragStartPos.y, 2))
if (distance > 5) {
dragging = true
targetIndex = index
originalIndex = index
}
}
if (dragging) {
dragOffset = Qt.point(mouse.x - dragStartPos.x, mouse.y - dragStartPos.y)
if (dockApps) {
const threshold = actualIconSize
let newTargetIndex = targetIndex
if (dragOffset.x > threshold && targetIndex < dockApps.pinnedAppCount - 1) {
newTargetIndex = targetIndex + 1
} else if (dragOffset.x < -threshold && targetIndex > 0) {
newTargetIndex = targetIndex - 1
}
if (newTargetIndex !== targetIndex) {
targetIndex = newTargetIndex
dragStartPos = Qt.point(mouse.x, mouse.y)
}
}
}
}
onClicked: mouse => {
if (!appData || longPressing) {
return
}
if (mouse.button === Qt.LeftButton) {
if (appData.type === "pinned") {
if (appData && appData.appId) {
const desktopEntry = cachedDesktopEntry
if (desktopEntry) {
AppUsageHistoryData.addAppUsage({
"id": appData.appId,
"name": desktopEntry.name || appData.appId,
"icon": desktopEntry.icon || "",
"exec": desktopEntry.exec || "",
"comment": desktopEntry.comment || ""
})
}
SessionService.launchDesktopEntry(desktopEntry)
}
} else if (appData.type === "window") {
const toplevel = getToplevelObject()
if (toplevel) {
toplevel.activate()
}
} else if (appData.type === "grouped") {
if (appData.windowCount === 0) {
if (appData && appData.appId) {
const desktopEntry = cachedDesktopEntry
if (desktopEntry) {
AppUsageHistoryData.addAppUsage({
"id": appData.appId,
"name": desktopEntry.name || appData.appId,
"icon": desktopEntry.icon || "",
"exec": desktopEntry.exec || "",
"comment": desktopEntry.comment || ""
})
}
SessionService.launchDesktopEntry(desktopEntry)
}
} else if (appData.windowCount === 1) {
// For single window, activate directly
const toplevel = getToplevelObject()
if (toplevel) {
console.log("Activating grouped app window:", appData.windowTitle)
toplevel.activate()
} else {
console.warn("No toplevel found for grouped app")
}
} else {
if (contextMenu) {
contextMenu.showForButton(root, appData, root.height + 25, true, cachedDesktopEntry, parentDockScreen)
}
}
}
} else if (mouse.button === Qt.MiddleButton) {
if (appData?.type === "window") {
appData?.toplevel?.close()
} else if (appData?.type === "grouped") {
if (contextMenu) {
contextMenu.showForButton(root, appData, root.height, false, cachedDesktopEntry, parentDockScreen)
}
} else if (appData && appData.appId) {
const desktopEntry = cachedDesktopEntry
if (desktopEntry) {
AppUsageHistoryData.addAppUsage({
"id": appData.appId,
"name": desktopEntry.name || appData.appId,
"icon": desktopEntry.icon || "",
"exec": desktopEntry.exec || "",
"comment": desktopEntry.comment || ""
})
}
SessionService.launchDesktopEntry(desktopEntry)
}
} else if (mouse.button === Qt.RightButton) {
if (contextMenu && appData) {
contextMenu.showForButton(root, appData, root.height, false, cachedDesktopEntry, parentDockScreen)
} else {
console.warn("No context menu or appData available")
}
}
}
}
} else if (mouse.button === Qt.MiddleButton) {
if (appData?.type === "window") {
appData?.toplevel?.close();
} else if (appData?.type === "grouped") {
if (contextMenu) {
const shouldHidePin = appData.appId === "org.quickshell";
contextMenu.showForButton(root, appData, root.height, shouldHidePin, cachedDesktopEntry, parentDockScreen);
}
} else if (appData && appData.appId) {
const desktopEntry = cachedDesktopEntry;
if (desktopEntry) {
AppUsageHistoryData.addAppUsage({
"id": appData.appId,
"name": desktopEntry.name || appData.appId,
"icon": desktopEntry.icon || "",
"exec": desktopEntry.exec || "",
"comment": desktopEntry.comment || ""
});
}
SessionService.launchDesktopEntry(desktopEntry);
}
} else if (mouse.button === Qt.RightButton) {
if (contextMenu && appData) {
const shouldHidePin = appData.appId === "org.quickshell";
contextMenu.showForButton(root, appData, root.height, shouldHidePin, cachedDesktopEntry, parentDockScreen);
} else {
console.warn("No context menu or appData available");
}
}
}
}
Item {
@@ -334,21 +337,25 @@ Item {
id: iconImg
anchors.centerIn: parent
implicitSize: actualIconSize
implicitSize: appData && appData.appId === "org.quickshell" ? actualIconSize * 0.85 : actualIconSize
source: {
if (appData.appId === "__SEPARATOR__") {
return ""
if (!appData || appData.appId === "__SEPARATOR__") {
return "";
}
const moddedId = Paths.moddedAppId(appData.appId)
if (moddedId.toLowerCase().includes("steam_app")) {
return ""
}
return cachedDesktopEntry && cachedDesktopEntry.icon ? Quickshell.iconPath(cachedDesktopEntry.icon, true) : ""
return Paths.getAppIcon(appData.appId, cachedDesktopEntry);
}
mipmap: true
smooth: true
asynchronous: true
visible: status === Image.Ready
layer.enabled: appData && appData.appId === "org.quickshell"
layer.smooth: true
layer.mipmap: true
layer.effect: MultiEffect {
saturation: 0
colorization: 1
colorizationColor: Theme.primary
}
}
DankIcon {
@@ -358,10 +365,10 @@ Item {
color: Theme.surfaceText
visible: {
if (!appData || !appData.appId || appData.appId === "__SEPARATOR__") {
return false
return false;
}
const moddedId = Paths.moddedAppId(appData.appId)
return moddedId.toLowerCase().includes("steam_app")
const moddedId = Paths.moddedAppId(appData.appId);
return moddedId.toLowerCase().includes("steam_app");
}
}
@@ -379,15 +386,11 @@ Item {
anchors.centerIn: parent
text: {
if (!appData || !appData.appId) {
return "?"
return "?";
}
const desktopEntry = cachedDesktopEntry
if (desktopEntry && desktopEntry.name) {
return desktopEntry.name.charAt(0).toUpperCase()
}
return appData.appId.charAt(0).toUpperCase()
const appName = Paths.getAppName(appData.appId, cachedDesktopEntry);
return appName.charAt(0).toUpperCase();
}
font.pixelSize: Math.max(8, parent.width * 0.35)
color: Theme.primary
@@ -410,10 +413,13 @@ Item {
sourceComponent: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right ? columnIndicator : rowIndicator
visible: {
if (!appData) return false
if (appData.type === "window") return true
if (appData.type === "grouped") return appData.windowCount > 0
return appData.isRunning
if (!appData)
return false;
if (appData.type === "window")
return true;
if (appData.type === "grouped")
return appData.windowCount > 0;
return appData.isRunning;
}
}
}
@@ -426,49 +432,50 @@ Item {
Repeater {
model: {
if (!appData) return 0
if (!appData)
return 0;
if (appData.type === "grouped") {
return Math.min(appData.windowCount, 4)
return Math.min(appData.windowCount, 4);
} else if (appData.type === "window" || appData.isRunning) {
return 1
return 1;
}
return 0
return 0;
}
Rectangle {
width: {
if (SettingsData.dockIndicatorStyle === "circle") {
return Math.max(4, actualIconSize * 0.1)
return Math.max(4, actualIconSize * 0.1);
}
return appData && appData.type === "grouped" && appData.windowCount > 1 ? Math.max(3, actualIconSize * 0.1) : Math.max(6, actualIconSize * 0.2)
return appData && appData.type === "grouped" && appData.windowCount > 1 ? Math.max(3, actualIconSize * 0.1) : Math.max(6, actualIconSize * 0.2);
}
height: {
if (SettingsData.dockIndicatorStyle === "circle") {
return Math.max(4, actualIconSize * 0.1)
return Math.max(4, actualIconSize * 0.1);
}
return Math.max(2, actualIconSize * 0.05)
return Math.max(2, actualIconSize * 0.05);
}
radius: SettingsData.dockIndicatorStyle === "circle" ? width / 2 : Theme.cornerRadius
color: {
if (!appData) {
return "transparent"
return "transparent";
}
if (appData.type !== "grouped" || appData.windowCount === 1) {
if (isWindowFocused) {
return Theme.primary
return Theme.primary;
}
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6);
}
if (appData.type === "grouped" && appData.windowCount > 1) {
const groupToplevels = getGroupedToplevels()
const groupToplevels = getGroupedToplevels();
if (index < groupToplevels.length && groupToplevels[index].activated) {
return Theme.primary
return Theme.primary;
}
}
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6);
}
}
}
@@ -483,49 +490,50 @@ Item {
Repeater {
model: {
if (!appData) return 0
if (!appData)
return 0;
if (appData.type === "grouped") {
return Math.min(appData.windowCount, 4)
return Math.min(appData.windowCount, 4);
} else if (appData.type === "window" || appData.isRunning) {
return 1
return 1;
}
return 0
return 0;
}
Rectangle {
width: {
if (SettingsData.dockIndicatorStyle === "circle") {
return Math.max(4, actualIconSize * 0.1)
return Math.max(4, actualIconSize * 0.1);
}
return Math.max(2, actualIconSize * 0.05)
return Math.max(2, actualIconSize * 0.05);
}
height: {
if (SettingsData.dockIndicatorStyle === "circle") {
return Math.max(4, actualIconSize * 0.1)
return Math.max(4, actualIconSize * 0.1);
}
return appData && appData.type === "grouped" && appData.windowCount > 1 ? Math.max(3, actualIconSize * 0.1) : Math.max(6, actualIconSize * 0.2)
return appData && appData.type === "grouped" && appData.windowCount > 1 ? Math.max(3, actualIconSize * 0.1) : Math.max(6, actualIconSize * 0.2);
}
radius: SettingsData.dockIndicatorStyle === "circle" ? width / 2 : Theme.cornerRadius
color: {
if (!appData) {
return "transparent"
return "transparent";
}
if (appData.type !== "grouped" || appData.windowCount === 1) {
if (isWindowFocused) {
return Theme.primary
return Theme.primary;
}
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6);
}
if (appData.type === "grouped" && appData.windowCount > 1) {
const groupToplevels = getGroupedToplevels()
const groupToplevels = getGroupedToplevels();
if (index < groupToplevels.length && groupToplevels[index].activated) {
return Theme.primary
return Theme.primary;
}
}
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6);
}
}
}

View File

@@ -18,6 +18,7 @@ PanelWindow {
property int margin: 10
property bool hidePin: false
property var desktopEntry: null
property bool isDmsWindow: appData?.appId === "org.quickshell"
function showForButton(button, data, dockHeight, hidePinOption, entry, dockScreen) {
if (dockScreen) {
@@ -351,7 +352,12 @@ PanelWindow {
}
Rectangle {
visible: root.desktopEntry && root.desktopEntry.actions && root.desktopEntry.actions.length > 0
visible: {
if (!root.desktopEntry?.actions || root.desktopEntry.actions.length === 0) {
return false
}
return !root.hidePin || (!root.isDmsWindow && root.desktopEntry && SessionService.hasPrimeRun)
}
width: parent.width
height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
@@ -398,14 +404,20 @@ PanelWindow {
}
Rectangle {
visible: (root.appData && root.appData.type === "window") || (root.desktopEntry && SessionService.hasPrimeRun)
visible: {
const hasPrimeRun = !root.isDmsWindow && root.desktopEntry && SessionService.hasPrimeRun
const hasWindow = root.appData && (root.appData.type === "window" || (root.appData.type === "grouped" && root.appData.windowCount > 0))
const hasPinOption = !root.hidePin
const hasContentAbove = hasPinOption || hasPrimeRun
return hasContentAbove && hasWindow
}
width: parent.width
height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
}
Rectangle {
visible: root.desktopEntry && SessionService.hasPrimeRun
visible: !root.isDmsWindow && root.desktopEntry && SessionService.hasPrimeRun
width: parent.width
height: 28
radius: Theme.cornerRadius
@@ -439,13 +451,6 @@ PanelWindow {
}
}
Rectangle {
visible: root.appData && (root.appData.type === "window" || (root.appData.type === "grouped" && root.appData.windowCount > 0))
width: parent.width
height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
}
Rectangle {
visible: root.appData && (root.appData.type === "window" || (root.appData.type === "grouped" && root.appData.windowCount > 0))
width: parent.width

View File

@@ -1,5 +1,4 @@
import QtQuick
import QtQuick.Controls
import qs.Common
import qs.Services
import qs.Widgets
@@ -13,6 +12,10 @@ Item {
width: parent.width
height: 32
DankTooltipV2 {
id: sharedTooltip
}
Row {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
@@ -35,17 +38,10 @@ Item {
anchors.verticalCenter: parent.verticalCenter
onClicked: SessionData.setDoNotDisturb(!SessionData.doNotDisturb)
onEntered: {
tooltipLoader.active = true
if (tooltipLoader.item) {
const p = mapToItem(null, width / 2, 0)
tooltipLoader.item.show(I18n.tr("Do Not Disturb"), p.x, p.y - 40, null)
}
sharedTooltip.show(I18n.tr("Do Not Disturb"), doNotDisturbButton, 0, 0, "bottom");
}
onExited: {
if (tooltipLoader.item) {
tooltipLoader.item.hide()
}
tooltipLoader.active = false
sharedTooltip.hide();
}
}
}
@@ -64,7 +60,7 @@ Item {
anchors.verticalCenter: parent.verticalCenter
onClicked: {
if (keyboardController) {
keyboardController.showKeyboardHints = !keyboardController.showKeyboardHints
keyboardController.showKeyboardHints = !keyboardController.showKeyboardHints;
}
}
}
@@ -115,14 +111,6 @@ Item {
cursorShape: Qt.PointingHandCursor
onClicked: NotificationService.clearAllNotifications()
}
}
}
Loader {
id: tooltipLoader
active: false
sourceComponent: DankTooltip {}
}
}

View File

@@ -7,33 +7,36 @@ import qs.Widgets
DankOSD {
id: root
property int currentProfile: 0
property string profileIcon: "settings"
osdWidth: Theme.iconSize + Theme.spacingS * 2
osdHeight: Theme.iconSize + Theme.spacingS * 2
autoHideInterval: 2000
enableMouseInteraction: false
property int lastProfile: -1
Connections {
target: typeof PowerProfiles !== "undefined" ? PowerProfiles : null
target: PowerProfileWatcher
function onProfileChanged() {
if (lastProfile !== -1 && lastProfile !== PowerProfiles.profile && SettingsData.osdPowerProfileEnabled) {
root.show()
function onProfileChanged(profile) {
if (SettingsData.osdPowerProfileEnabled) {
root.currentProfile = profile;
root.profileIcon = Theme.getPowerProfileIcon(profile);
root.show();
}
lastProfile = PowerProfiles.profile
}
}
Component.onCompleted: {
if (typeof PowerProfiles !== "undefined") {
lastProfile = PowerProfiles.profile
if (SettingsData.osdPowerProfileEnabled && typeof PowerProfileWatcher !== "undefined") {
root.currentProfile = PowerProfileWatcher.currentProfile;
root.profileIcon = Theme.getPowerProfileIcon(PowerProfileWatcher.currentProfile);
}
}
content: DankIcon {
anchors.centerIn: parent
name: typeof PowerProfiles !== "undefined" ? Theme.getPowerProfileIcon(PowerProfiles.profile) : "settings"
name: root.profileIcon
size: Theme.iconSize
color: Theme.primary
}

View File

@@ -11,6 +11,10 @@ Item {
property var parentModal: null
property string selectedBarId: "default"
DankTooltipV2 {
id: sharedTooltip
}
property var selectedBarConfig: {
selectedBarId;
SettingsData.barConfigs;
@@ -49,6 +53,120 @@ Item {
}
}
Timer {
id: edgeSpacingDebounce
interval: 100
repeat: false
property real pendingValue: 4
onTriggered: {
SettingsData.updateBarConfig(selectedBarId, {
spacing: pendingValue
});
notifyHorizontalBarChange();
}
}
Timer {
id: exclusiveZoneDebounce
interval: 100
repeat: false
property real pendingValue: 0
onTriggered: {
SettingsData.updateBarConfig(selectedBarId, {
bottomGap: pendingValue
});
notifyHorizontalBarChange();
}
}
Timer {
id: sizeDebounce
interval: 100
repeat: false
property real pendingValue: 4
onTriggered: {
SettingsData.updateBarConfig(selectedBarId, {
innerPadding: pendingValue
});
notifyHorizontalBarChange();
}
}
Timer {
id: popupGapsManualDebounce
interval: 100
repeat: false
property real pendingValue: 4
onTriggered: {
SettingsData.updateBarConfig(selectedBarId, {
popupGapsManual: pendingValue
});
notifyHorizontalBarChange();
}
}
Timer {
id: gothCornerRadiusDebounce
interval: 100
repeat: false
property real pendingValue: 12
onTriggered: {
SettingsData.updateBarConfig(selectedBarId, {
gothCornerRadiusValue: pendingValue
});
}
}
Timer {
id: borderOpacityDebounce
interval: 100
repeat: false
property real pendingValue: 1.0
onTriggered: {
SettingsData.updateBarConfig(selectedBarId, {
borderOpacity: pendingValue
});
}
}
Timer {
id: borderThicknessDebounce
interval: 100
repeat: false
property real pendingValue: 1
onTriggered: {
SettingsData.updateBarConfig(selectedBarId, {
borderThickness: pendingValue
});
}
}
Timer {
id: barTransparencyDebounce
interval: 100
repeat: false
property real pendingValue: 1.0
onTriggered: {
SettingsData.updateBarConfig(selectedBarId, {
transparency: pendingValue
});
notifyHorizontalBarChange();
}
}
Timer {
id: widgetTransparencyDebounce
interval: 100
repeat: false
property real pendingValue: 1.0
onTriggered: {
SettingsData.updateBarConfig(selectedBarId, {
widgetTransparency: pendingValue
});
notifyHorizontalBarChange();
}
}
// ! Hacky workaround because we want to re-register any vertical bars after changing a hBar
// ! That allows them to re-make with the right exclusiveZone
function notifyHorizontalBarChange() {
@@ -1706,10 +1824,8 @@ Item {
wheelEnabled: false
thumbOutlineColor: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
onSliderValueChanged: newValue => {
SettingsData.updateBarConfig(selectedBarId, {
spacing: newValue
});
notifyHorizontalBarChange();
edgeSpacingDebounce.pendingValue = newValue;
edgeSpacingDebounce.restart();
}
Binding {
@@ -1782,10 +1898,8 @@ Item {
wheelEnabled: false
thumbOutlineColor: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
onSliderValueChanged: newValue => {
SettingsData.updateBarConfig(selectedBarId, {
bottomGap: newValue
});
notifyHorizontalBarChange();
exclusiveZoneDebounce.pendingValue = newValue;
exclusiveZoneDebounce.restart();
}
Binding {
@@ -1858,10 +1972,8 @@ Item {
wheelEnabled: false
thumbOutlineColor: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
onSliderValueChanged: newValue => {
SettingsData.updateBarConfig(selectedBarId, {
innerPadding: newValue
});
notifyHorizontalBarChange();
sizeDebounce.pendingValue = newValue;
sizeDebounce.restart();
}
Binding {
@@ -1964,10 +2076,8 @@ Item {
wheelEnabled: false
thumbOutlineColor: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
onSliderValueChanged: newValue => {
SettingsData.updateBarConfig(selectedBarId, {
popupGapsManual: newValue
});
notifyHorizontalBarChange();
popupGapsManualDebounce.pendingValue = newValue;
popupGapsManualDebounce.restart();
}
Binding {
@@ -2096,9 +2206,8 @@ Item {
wheelEnabled: false
thumbOutlineColor: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
onSliderValueChanged: newValue => {
SettingsData.updateBarConfig(selectedBarId, {
gothCornerRadiusValue: newValue
});
gothCornerRadiusDebounce.pendingValue = newValue;
gothCornerRadiusDebounce.restart();
}
Binding {
@@ -2263,9 +2372,8 @@ Item {
wheelEnabled: false
thumbOutlineColor: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
onSliderValueChanged: newValue => {
SettingsData.updateBarConfig(selectedBarId, {
borderOpacity: newValue / 100
});
borderOpacityDebounce.pendingValue = newValue / 100;
borderOpacityDebounce.restart();
}
Binding {
@@ -2338,9 +2446,8 @@ Item {
wheelEnabled: false
thumbOutlineColor: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
onSliderValueChanged: newValue => {
SettingsData.updateBarConfig(selectedBarId, {
borderThickness: newValue
});
borderThicknessDebounce.pendingValue = newValue;
borderThicknessDebounce.restart();
}
Binding {
@@ -2422,10 +2529,8 @@ Item {
wheelEnabled: false
thumbOutlineColor: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
onSliderValueChanged: newValue => {
SettingsData.updateBarConfig(selectedBarId, {
transparency: newValue / 100
});
notifyHorizontalBarChange()
barTransparencyDebounce.pendingValue = newValue / 100;
barTransparencyDebounce.restart();
}
Binding {
@@ -2477,7 +2582,7 @@ Item {
SettingsData.updateBarConfig(selectedBarId, {
widgetTransparency: 1.0
});
notifyHorizontalBarChange()
notifyHorizontalBarChange();
}
}
@@ -2499,10 +2604,8 @@ Item {
wheelEnabled: false
thumbOutlineColor: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
onSliderValueChanged: newValue => {
SettingsData.updateBarConfig(selectedBarId, {
widgetTransparency: newValue / 100
});
notifyHorizontalBarChange()
widgetTransparencyDebounce.pendingValue = newValue / 100;
widgetTransparencyDebounce.restart();
}
Binding {
@@ -2573,7 +2676,7 @@ Item {
SettingsData.updateBarConfig(selectedBarId, {
fontScale: newScale
});
notifyHorizontalBarChange()
notifyHorizontalBarChange();
}
}
@@ -2607,7 +2710,7 @@ Item {
SettingsData.updateBarConfig(selectedBarId, {
fontScale: newScale
});
notifyHorizontalBarChange()
notifyHorizontalBarChange();
}
}
}

View File

@@ -1,6 +1,5 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import qs.Common
import qs.Modals.Common
@@ -18,108 +17,108 @@ DankModal {
property bool isLoading: false
property var parentModal: null
width: 600
height: 650
modalWidth: 600
modalHeight: 650
allowStacking: true
backgroundOpacity: 0
closeOnEscapeKey: false
function updateFilteredPlugins() {
var filtered = []
var query = searchQuery ? searchQuery.toLowerCase() : ""
var filtered = [];
var query = searchQuery ? searchQuery.toLowerCase() : "";
for (var i = 0; i < allPlugins.length; i++) {
var plugin = allPlugins[i]
var isFirstParty = plugin.firstParty || false
var plugin = allPlugins[i];
var isFirstParty = plugin.firstParty || false;
if (!SessionData.showThirdPartyPlugins && !isFirstParty) {
continue
continue;
}
if (query.length > 0) {
var name = plugin.name ? plugin.name.toLowerCase() : ""
var description = plugin.description ? plugin.description.toLowerCase() : ""
var author = plugin.author ? plugin.author.toLowerCase() : ""
var name = plugin.name ? plugin.name.toLowerCase() : "";
var description = plugin.description ? plugin.description.toLowerCase() : "";
var author = plugin.author ? plugin.author.toLowerCase() : "";
if (name.indexOf(query) !== -1 ||
description.indexOf(query) !== -1 ||
author.indexOf(query) !== -1) {
filtered.push(plugin)
if (name.indexOf(query) !== -1 || description.indexOf(query) !== -1 || author.indexOf(query) !== -1) {
filtered.push(plugin);
}
} else {
filtered.push(plugin)
filtered.push(plugin);
}
}
filteredPlugins = filtered
selectedIndex = -1
keyboardNavigationActive = false
filteredPlugins = filtered;
selectedIndex = -1;
keyboardNavigationActive = false;
}
function selectNext() {
if (filteredPlugins.length === 0) return
keyboardNavigationActive = true
selectedIndex = Math.min(selectedIndex + 1, filteredPlugins.length - 1)
if (filteredPlugins.length === 0)
return;
keyboardNavigationActive = true;
selectedIndex = Math.min(selectedIndex + 1, filteredPlugins.length - 1);
}
function selectPrevious() {
if (filteredPlugins.length === 0) return
keyboardNavigationActive = true
selectedIndex = Math.max(selectedIndex - 1, -1)
if (filteredPlugins.length === 0)
return;
keyboardNavigationActive = true;
selectedIndex = Math.max(selectedIndex - 1, -1);
if (selectedIndex === -1) {
keyboardNavigationActive = false
keyboardNavigationActive = false;
}
}
function installPlugin(pluginName) {
ToastService.showInfo("Installing plugin: " + pluginName)
ToastService.showInfo("Installing plugin: " + pluginName);
DMSService.install(pluginName, response => {
if (response.error) {
ToastService.showError("Install failed: " + response.error)
ToastService.showError("Install failed: " + response.error);
} else {
ToastService.showInfo("Plugin installed: " + pluginName)
PluginService.scanPlugins()
refreshPlugins()
ToastService.showInfo("Plugin installed: " + pluginName);
PluginService.scanPlugins();
refreshPlugins();
}
})
});
}
function refreshPlugins() {
isLoading = true
DMSService.listPlugins()
isLoading = true;
DMSService.listPlugins();
if (DMSService.apiVersion >= 8) {
DMSService.listInstalled()
DMSService.listInstalled();
}
}
function show() {
if (parentModal) {
parentModal.shouldHaveFocus = false
parentModal.shouldHaveFocus = false;
}
open()
open();
Qt.callLater(() => {
if (contentLoader.item && contentLoader.item.searchField) {
contentLoader.item.searchField.forceActiveFocus()
contentLoader.item.searchField.forceActiveFocus();
}
})
});
}
function hide() {
close()
close();
if (parentModal) {
parentModal.shouldHaveFocus = Qt.binding(() => {
return parentModal.shouldBeVisible
})
return parentModal.shouldBeVisible;
});
Qt.callLater(() => {
if (parentModal.modalFocusScope) {
parentModal.modalFocusScope.forceActiveFocus()
parentModal.modalFocusScope.forceActiveFocus();
}
})
});
}
}
onOpened: {
refreshPlugins()
refreshPlugins();
}
Connections {
@@ -127,23 +126,23 @@ DankModal {
function onLoaded() {
Qt.callLater(() => {
if (contentLoader.item && contentLoader.item.searchField) {
contentLoader.item.searchField.forceActiveFocus()
contentLoader.item.searchField.forceActiveFocus();
}
})
});
}
}
onDialogClosed: () => {
allPlugins = []
searchQuery = ""
filteredPlugins = []
selectedIndex = -1
keyboardNavigationActive = false
isLoading = false
allPlugins = [];
searchQuery = "";
filteredPlugins = [];
selectedIndex = -1;
keyboardNavigationActive = false;
isLoading = false;
}
onBackgroundClicked: () => {
hide()
hide();
}
content: Component {
@@ -155,19 +154,19 @@ DankModal {
focus: true
Component.onCompleted: {
browserSearchField.forceActiveFocus()
browserSearchField.forceActiveFocus();
}
Keys.onPressed: event => {
if (event.key === Qt.Key_Escape) {
root.close()
event.accepted = true
root.close();
event.accepted = true;
} else if (event.key === Qt.Key_Down) {
root.selectNext()
event.accepted = true
root.selectNext();
event.accepted = true;
} else if (event.key === Qt.Key_Up) {
root.selectPrevious()
event.accepted = true
root.selectPrevious();
event.accepted = true;
}
}
@@ -215,10 +214,10 @@ DankModal {
height: 28
onClicked: {
if (SessionData.showThirdPartyPlugins) {
SessionData.setShowThirdPartyPlugins(false)
root.updateFilteredPlugins()
SessionData.setShowThirdPartyPlugins(false);
root.updateFilteredPlugins();
} else {
thirdPartyConfirmModal.open()
thirdPartyConfirmModal.open();
}
}
}
@@ -278,8 +277,8 @@ DankModal {
ignoreLeftRightKeys: true
keyForwardTargets: [browserKeyHandler]
onTextEdited: {
root.searchQuery = text
root.updateFilteredPlugins()
root.searchQuery = text;
root.updateFilteredPlugins();
}
}
@@ -350,13 +349,8 @@ DankModal {
property bool isSelected: root.keyboardNavigationActive && index === root.selectedIndex
property bool isInstalled: modelData.installed || false
property bool isFirstParty: modelData.firstParty || false
color: isSelected ? Theme.primarySelected :
Qt.rgba(Theme.surfaceVariant.r,
Theme.surfaceVariant.g,
Theme.surfaceVariant.b,
0.3)
border.color: isSelected ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g,
Theme.outline.b, 0.2)
color: isSelected ? Theme.primarySelected : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
border.color: isSelected ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: isSelected ? 2 : 1
Column {
@@ -435,9 +429,9 @@ DankModal {
StyledText {
text: {
const author = "by " + (modelData.author || "Unknown")
const source = modelData.repo ? ` <a href="${modelData.repo}" style="text-decoration:none; color:${Theme.primary};">source</a>` : ""
return author + source
const author = "by " + (modelData.author || "Unknown");
const source = modelData.repo ? ` <a href="${modelData.repo}" style="text-decoration:none; color:${Theme.primary};">source</a>` : "";
return author + source;
}
font.pixelSize: Theme.fontSizeSmall
color: Theme.outline
@@ -502,7 +496,7 @@ DankModal {
enabled: !isInstalled
onClicked: {
if (!isInstalled) {
root.installPlugin(modelData.name)
root.installPlugin(modelData.name);
}
}
}
@@ -563,8 +557,8 @@ DankModal {
DankModal {
id: thirdPartyConfirmModal
width: 500
height: 300
modalWidth: 500
modalHeight: 300
allowStacking: true
backgroundOpacity: 0.4
closeOnEscapeKey: true
@@ -576,8 +570,8 @@ DankModal {
Keys.onPressed: event => {
if (event.key === Qt.Key_Escape) {
thirdPartyConfirmModal.close()
event.accepted = true
thirdPartyConfirmModal.close();
event.accepted = true;
}
}
@@ -656,9 +650,9 @@ DankModal {
text: I18n.tr("I Understand")
iconName: "check"
onClicked: {
SessionData.setShowThirdPartyPlugins(true)
root.updateFilteredPlugins()
thirdPartyConfirmModal.close()
SessionData.setShowThirdPartyPlugins(true);
root.updateFilteredPlugins();
thirdPartyConfirmModal.close();
}
}
}

View File

@@ -20,327 +20,318 @@ DankModal {
function updateFilteredWidgets() {
if (!searchQuery || searchQuery.length === 0) {
filteredWidgets = allWidgets.slice()
return
filteredWidgets = allWidgets.slice();
return;
}
var filtered = []
var query = searchQuery.toLowerCase()
var filtered = [];
var query = searchQuery.toLowerCase();
for (var i = 0; i < allWidgets.length; i++) {
var widget = allWidgets[i]
var text = widget.text ? widget.text.toLowerCase() : ""
var description = widget.description ? widget.description.toLowerCase() : ""
var id = widget.id ? widget.id.toLowerCase() : ""
var widget = allWidgets[i];
var text = widget.text ? widget.text.toLowerCase() : "";
var description = widget.description ? widget.description.toLowerCase() : "";
var id = widget.id ? widget.id.toLowerCase() : "";
if (text.indexOf(query) !== -1 ||
description.indexOf(query) !== -1 ||
id.indexOf(query) !== -1) {
filtered.push(widget)
if (text.indexOf(query) !== -1 || description.indexOf(query) !== -1 || id.indexOf(query) !== -1) {
filtered.push(widget);
}
}
filteredWidgets = filtered
selectedIndex = -1
keyboardNavigationActive = false
filteredWidgets = filtered;
selectedIndex = -1;
keyboardNavigationActive = false;
}
onAllWidgetsChanged: {
updateFilteredWidgets()
updateFilteredWidgets();
}
function selectNext() {
if (filteredWidgets.length === 0) return
keyboardNavigationActive = true
selectedIndex = Math.min(selectedIndex + 1, filteredWidgets.length - 1)
if (filteredWidgets.length === 0)
return;
keyboardNavigationActive = true;
selectedIndex = Math.min(selectedIndex + 1, filteredWidgets.length - 1);
}
function selectPrevious() {
if (filteredWidgets.length === 0) return
keyboardNavigationActive = true
selectedIndex = Math.max(selectedIndex - 1, -1)
if (filteredWidgets.length === 0)
return;
keyboardNavigationActive = true;
selectedIndex = Math.max(selectedIndex - 1, -1);
if (selectedIndex === -1) {
keyboardNavigationActive = false
keyboardNavigationActive = false;
}
}
function selectWidget() {
if (selectedIndex >= 0 && selectedIndex < filteredWidgets.length) {
var widget = filteredWidgets[selectedIndex]
root.widgetSelected(widget.id, root.targetSection)
root.close()
var widget = filteredWidgets[selectedIndex];
root.widgetSelected(widget.id, root.targetSection);
root.close();
}
}
function show() {
if (parentModal) {
parentModal.shouldHaveFocus = false
parentModal.shouldHaveFocus = false;
}
open()
open();
Qt.callLater(() => {
if (contentLoader.item && contentLoader.item.searchField) {
contentLoader.item.searchField.forceActiveFocus()
contentLoader.item.searchField.forceActiveFocus();
}
})
});
}
function hide() {
close()
close();
if (parentModal) {
parentModal.shouldHaveFocus = Qt.binding(() => {
return parentModal.shouldBeVisible
})
return parentModal.shouldBeVisible;
});
Qt.callLater(() => {
if (parentModal && parentModal.modalFocusScope) {
parentModal.modalFocusScope.forceActiveFocus()
parentModal.modalFocusScope.forceActiveFocus();
}
})
});
}
}
width: 500
height: 550
modalWidth: 500
modalHeight: 550
allowStacking: true
backgroundOpacity: 0
closeOnEscapeKey: false
onDialogClosed: () => {
allWidgets = []
targetSection = ""
searchQuery = ""
filteredWidgets = []
selectedIndex = -1
keyboardNavigationActive = false
allWidgets = [];
targetSection = "";
searchQuery = "";
filteredWidgets = [];
selectedIndex = -1;
keyboardNavigationActive = false;
if (parentModal) {
parentModal.shouldHaveFocus = Qt.binding(() => {
return parentModal.shouldBeVisible
})
return parentModal.shouldBeVisible;
});
Qt.callLater(() => {
if (parentModal && parentModal.modalFocusScope) {
parentModal.modalFocusScope.forceActiveFocus()
parentModal.modalFocusScope.forceActiveFocus();
}
})
});
}
}
onBackgroundClicked: () => {
return hide()
return hide();
}
content: widgetSelectionContent
widgetSelectionContent: Component {
FocusScope {
id: widgetKeyHandler
property alias searchField: searchField
id: widgetKeyHandler
property alias searchField: searchField
anchors.fill: parent
focus: true
Keys.onPressed: event => {
if (event.key === Qt.Key_Escape) {
root.close()
event.accepted = true
} else if (event.key === Qt.Key_Down) {
root.selectNext()
event.accepted = true
} else if (event.key === Qt.Key_Up) {
root.selectPrevious()
event.accepted = true
} else if (event.key === Qt.Key_N && event.modifiers & Qt.ControlModifier) {
root.selectNext()
event.accepted = true
} else if (event.key === Qt.Key_P && event.modifiers & Qt.ControlModifier) {
root.selectPrevious()
event.accepted = true
} else if (event.key === Qt.Key_J && event.modifiers & Qt.ControlModifier) {
root.selectNext()
event.accepted = true
} else if (event.key === Qt.Key_K && event.modifiers & Qt.ControlModifier) {
root.selectPrevious()
event.accepted = true
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
if (root.keyboardNavigationActive) {
root.selectWidget()
} else if (root.filteredWidgets.length > 0) {
var firstWidget = root.filteredWidgets[0]
root.widgetSelected(firstWidget.id, root.targetSection)
root.close()
}
event.accepted = true
}
}
DankActionButton {
iconName: "close"
iconSize: Theme.iconSize - 2
iconColor: Theme.outline
anchors.top: parent.top
anchors.topMargin: Theme.spacingM
anchors.right: parent.right
anchors.rightMargin: Theme.spacingM
onClicked: root.close()
}
Column {
id: contentColumn
spacing: Theme.spacingM
anchors.fill: parent
anchors.margins: Theme.spacingL
anchors.topMargin: Theme.spacingL + 30 // Space for close button
focus: true
Keys.onPressed: event => {
if (event.key === Qt.Key_Escape) {
root.close();
event.accepted = true;
} else if (event.key === Qt.Key_Down) {
root.selectNext();
event.accepted = true;
} else if (event.key === Qt.Key_Up) {
root.selectPrevious();
event.accepted = true;
} else if (event.key === Qt.Key_N && event.modifiers & Qt.ControlModifier) {
root.selectNext();
event.accepted = true;
} else if (event.key === Qt.Key_P && event.modifiers & Qt.ControlModifier) {
root.selectPrevious();
event.accepted = true;
} else if (event.key === Qt.Key_J && event.modifiers & Qt.ControlModifier) {
root.selectNext();
event.accepted = true;
} else if (event.key === Qt.Key_K && event.modifiers & Qt.ControlModifier) {
root.selectPrevious();
event.accepted = true;
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
if (root.keyboardNavigationActive) {
root.selectWidget();
} else if (root.filteredWidgets.length > 0) {
var firstWidget = root.filteredWidgets[0];
root.widgetSelected(firstWidget.id, root.targetSection);
root.close();
}
event.accepted = true;
}
}
DankActionButton {
iconName: "close"
iconSize: Theme.iconSize - 2
iconColor: Theme.outline
anchors.top: parent.top
anchors.topMargin: Theme.spacingM
anchors.right: parent.right
anchors.rightMargin: Theme.spacingM
onClicked: root.close()
}
Column {
id: contentColumn
Row {
width: parent.width
spacing: Theme.spacingM
anchors.fill: parent
anchors.margins: Theme.spacingL
anchors.topMargin: Theme.spacingL + 30 // Space for close button
DankIcon {
name: "add_circle"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
Row {
width: parent.width
spacing: Theme.spacingM
DankIcon {
name: "add_circle"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("Add Widget to ") + root.targetSection + " Section"
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
StyledText {
text: I18n.tr("Add Widget to ") + root.targetSection + " Section"
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
text: I18n.tr("Select a widget to add to the ") + root.targetSection.toLowerCase() + " section of the top bar. You can add multiple instances of the same widget if needed."
font.pixelSize: Theme.fontSizeSmall
color: Theme.outline
width: parent.width
wrapMode: Text.WordWrap
}
}
StyledText {
text: I18n.tr("Select a widget to add to the ") + root.targetSection.toLowerCase(
) + " section of the top bar. You can add multiple instances of the same widget if needed."
font.pixelSize: Theme.fontSizeSmall
color: Theme.outline
width: parent.width
wrapMode: Text.WordWrap
}
DankTextField {
id: searchField
width: parent.width
height: 48
cornerRadius: Theme.cornerRadius
backgroundColor: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
normalBorderColor: Theme.outlineMedium
focusedBorderColor: Theme.primary
leftIconName: "search"
leftIconSize: Theme.iconSize
leftIconColor: Theme.surfaceVariantText
leftIconFocusedColor: Theme.primary
showClearButton: true
textColor: Theme.surfaceText
font.pixelSize: Theme.fontSizeMedium
placeholderText: ""
text: root.searchQuery
focus: true
ignoreLeftRightKeys: true
keyForwardTargets: [widgetKeyHandler]
onTextEdited: {
root.searchQuery = text
updateFilteredWidgets()
}
Keys.onPressed: event => {
if (event.key === Qt.Key_Escape) {
root.close()
event.accepted = true
} else if (event.key === Qt.Key_Down || event.key === Qt.Key_Up ||
((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && text.length === 0)) {
event.accepted = false
DankTextField {
id: searchField
width: parent.width
height: 48
cornerRadius: Theme.cornerRadius
backgroundColor: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
normalBorderColor: Theme.outlineMedium
focusedBorderColor: Theme.primary
leftIconName: "search"
leftIconSize: Theme.iconSize
leftIconColor: Theme.surfaceVariantText
leftIconFocusedColor: Theme.primary
showClearButton: true
textColor: Theme.surfaceText
font.pixelSize: Theme.fontSizeMedium
placeholderText: ""
text: root.searchQuery
focus: true
ignoreLeftRightKeys: true
keyForwardTargets: [widgetKeyHandler]
onTextEdited: {
root.searchQuery = text;
updateFilteredWidgets();
}
Keys.onPressed: event => {
if (event.key === Qt.Key_Escape) {
root.close();
event.accepted = true;
} else if (event.key === Qt.Key_Down || event.key === Qt.Key_Up || ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && text.length === 0)) {
event.accepted = false;
}
}
}
}
DankListView {
id: widgetList
DankListView {
id: widgetList
width: parent.width
height: parent.height - y
spacing: Theme.spacingS
model: root.filteredWidgets
clip: true
width: parent.width
height: parent.height - y
spacing: Theme.spacingS
model: root.filteredWidgets
clip: true
delegate: Rectangle {
width: widgetList.width
height: 60
radius: Theme.cornerRadius
property bool isSelected: root.keyboardNavigationActive && index === root.selectedIndex
color: isSelected ? Theme.primarySelected :
widgetArea.containsMouse ? Theme.primaryHover : Qt.rgba(
Theme.surfaceVariant.r,
Theme.surfaceVariant.g,
Theme.surfaceVariant.b,
0.3)
border.color: isSelected ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g,
Theme.outline.b, 0.2)
border.width: isSelected ? 2 : 1
delegate: Rectangle {
width: widgetList.width
height: 60
radius: Theme.cornerRadius
property bool isSelected: root.keyboardNavigationActive && index === root.selectedIndex
color: isSelected ? Theme.primarySelected : widgetArea.containsMouse ? Theme.primaryHover : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
border.color: isSelected ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: isSelected ? 2 : 1
Row {
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingM
Row {
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingM
DankIcon {
name: modelData.icon
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: 2
width: parent.width - Theme.iconSize - Theme.spacingM * 3
StyledText {
text: modelData.text
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
elide: Text.ElideRight
width: parent.width
DankIcon {
name: modelData.icon
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: modelData.description
font.pixelSize: Theme.fontSizeSmall
color: Theme.outline
elide: Text.ElideRight
width: parent.width
wrapMode: Text.WordWrap
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: 2
width: parent.width - Theme.iconSize - Theme.spacingM * 3
StyledText {
text: modelData.text
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
elide: Text.ElideRight
width: parent.width
}
StyledText {
text: modelData.description
font.pixelSize: Theme.fontSizeSmall
color: Theme.outline
elide: Text.ElideRight
width: parent.width
wrapMode: Text.WordWrap
}
}
DankIcon {
name: "add"
size: Theme.iconSize - 4
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
}
DankIcon {
name: "add"
size: Theme.iconSize - 4
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
MouseArea {
id: widgetArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.widgetSelected(modelData.id, root.targetSection);
root.close();
}
}
}
MouseArea {
id: widgetArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.widgetSelected(modelData.id,
root.targetSection)
root.close()
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
}
}
}
}
}
}

View File

@@ -13,6 +13,10 @@ Column {
property string titleIcon: "widgets"
property string sectionId: ""
DankTooltipV2 {
id: sharedTooltip
}
signal itemEnabledChanged(string sectionId, string itemId, bool enabled)
signal itemOrderChanged(var newOrder)
signal addWidget(string sectionId)
@@ -80,11 +84,8 @@ Column {
anchors.fill: parent
anchors.margins: 2
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceContainer.r,
Theme.surfaceContainer.g,
Theme.surfaceContainer.b, 0.8)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
Theme.outline.b, 0.2)
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.8)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: 0
DankIcon {
@@ -126,10 +127,7 @@ Column {
StyledText {
text: modelData.description
font.pixelSize: Theme.fontSizeSmall
color: modelData.enabled ? Theme.outline : Qt.rgba(
Theme.outline.r,
Theme.outline.g,
Theme.outline.b, 0.6)
color: modelData.enabled ? Theme.outline : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.6)
elide: Text.ElideRight
width: parent.width
wrapMode: Text.WordWrap
@@ -154,41 +152,29 @@ Column {
anchors.fill: parent
popupWidth: -1
currentValue: {
var selectedIndex = modelData.selectedGpuIndex
!== undefined ? modelData.selectedGpuIndex : 0
if (DgopService.availableGpus
&& DgopService.availableGpus.length > selectedIndex
&& selectedIndex >= 0) {
var gpu = DgopService.availableGpus[selectedIndex]
return gpu.driver.toUpperCase()
var selectedIndex = modelData.selectedGpuIndex !== undefined ? modelData.selectedGpuIndex : 0;
if (DgopService.availableGpus && DgopService.availableGpus.length > selectedIndex && selectedIndex >= 0) {
var gpu = DgopService.availableGpus[selectedIndex];
return gpu.driver.toUpperCase();
}
return DgopService.availableGpus
&& DgopService.availableGpus.length
> 0 ? DgopService.availableGpus[0].driver.toUpperCase(
) : ""
return DgopService.availableGpus && DgopService.availableGpus.length > 0 ? DgopService.availableGpus[0].driver.toUpperCase() : "";
}
options: {
var gpuOptions = []
if (DgopService.availableGpus
&& DgopService.availableGpus.length > 0) {
var gpuOptions = [];
if (DgopService.availableGpus && DgopService.availableGpus.length > 0) {
for (var i = 0; i < DgopService.availableGpus.length; i++) {
var gpu = DgopService.availableGpus[i]
gpuOptions.push(
gpu.driver.toUpperCase(
))
var gpu = DgopService.availableGpus[i];
gpuOptions.push(gpu.driver.toUpperCase());
}
}
return gpuOptions
return gpuOptions;
}
onValueChanged: value => {
var gpuIndex = options.indexOf(
value)
if (gpuIndex >= 0) {
root.gpuSelectionChanged(
root.sectionId,
index, gpuIndex)
}
}
var gpuIndex = options.indexOf(value);
if (gpuIndex >= 0) {
root.gpuSelectionChanged(root.sectionId, index, gpuIndex);
}
}
}
}
@@ -200,26 +186,26 @@ Column {
id: diskMountDropdown
anchors.fill: parent
currentValue: {
const mountPath = modelData.mountPath || "/"
const mountPath = modelData.mountPath || "/";
if (mountPath === "/") {
return "root (/)"
return "root (/)";
}
return mountPath
return mountPath;
}
options: {
if (!DgopService.diskMounts || DgopService.diskMounts.length === 0) {
return ["root (/)"]
return ["root (/)"];
}
return DgopService.diskMounts.map(mount => {
if (mount.mount === "/") {
return "root (/)"
return "root (/)";
}
return mount.mount
})
return mount.mount;
});
}
onValueChanged: value => {
const newPath = value === "root (/)" ? "/" : value
root.diskMountSelectionChanged(root.sectionId, index, newPath)
const newPath = value === "root (/)" ? "/" : value;
root.diskMountSelectionChanged(root.sectionId, index, newPath);
}
}
}
@@ -247,20 +233,15 @@ Column {
Rectangle {
id: warningTooltip
property string warningText: (modelData.warning !== undefined
&& modelData.warning
!== "") ? modelData.warning : ""
property string warningText: (modelData.warning !== undefined && modelData.warning !== "") ? modelData.warning : ""
width: Math.min(
250,
warningTooltipText.implicitWidth) + Theme.spacingM * 2
width: Math.min(250, warningTooltipText.implicitWidth) + Theme.spacingM * 2
height: warningTooltipText.implicitHeight + Theme.spacingS * 2
radius: Theme.cornerRadius
color: Theme.surfaceContainer
border.color: Theme.outline
border.width: 0
visible: warningArea.containsMouse
&& warningText !== ""
visible: warningArea.containsMouse && warningText !== ""
opacity: visible ? 1 : 0
x: -width - Theme.spacingS
y: (parent.height - height) / 2
@@ -289,31 +270,21 @@ Column {
DankActionButton {
id: minimumWidthButton
buttonSize: 28
visible: modelData.id === "cpuUsage"
|| modelData.id === "memUsage"
|| modelData.id === "cpuTemp"
|| modelData.id === "gpuTemp"
visible: modelData.id === "cpuUsage" || modelData.id === "memUsage" || modelData.id === "cpuTemp" || modelData.id === "gpuTemp"
iconName: "straighten"
iconSize: 16
iconColor: (modelData.minimumWidth !== undefined ? modelData.minimumWidth : true) ? Theme.primary : Theme.outline
onClicked: {
var currentEnabled = modelData.minimumWidth !== undefined ? modelData.minimumWidth : true
root.minimumWidthChanged(root.sectionId, index, !currentEnabled)
var currentEnabled = modelData.minimumWidth !== undefined ? modelData.minimumWidth : true;
root.minimumWidthChanged(root.sectionId, index, !currentEnabled);
}
onEntered: {
minimumWidthTooltipLoader.active = true
if (minimumWidthTooltipLoader.item) {
var currentEnabled = modelData.minimumWidth !== undefined ? modelData.minimumWidth : true
const tooltipText = currentEnabled ? "Force Padding" : "Dynamic Width"
const p = minimumWidthButton.mapToItem(null, minimumWidthButton.width / 2, 0)
minimumWidthTooltipLoader.item.show(tooltipText, p.x, p.y - 40, null)
}
var currentEnabled = modelData.minimumWidth !== undefined ? modelData.minimumWidth : true;
const tooltipText = currentEnabled ? "Force Padding" : "Dynamic Width";
sharedTooltip.show(tooltipText, minimumWidthButton, 0, 0, "bottom");
}
onExited: {
if (minimumWidthTooltipLoader.item) {
minimumWidthTooltipLoader.item.hide()
}
minimumWidthTooltipLoader.active = false
sharedTooltip.hide();
}
}
@@ -325,33 +296,22 @@ Column {
iconSize: 16
iconColor: (modelData.showSwap !== undefined ? modelData.showSwap : false) ? Theme.primary : Theme.outline
onClicked: {
var currentEnabled = modelData.showSwap !== undefined ? modelData.showSwap : false
root.showSwapChanged(root.sectionId, index, !currentEnabled)
var currentEnabled = modelData.showSwap !== undefined ? modelData.showSwap : false;
root.showSwapChanged(root.sectionId, index, !currentEnabled);
}
onEntered: {
showSwapTooltipLoader.active = true
if (showSwapTooltipLoader.item) {
var currentEnabled = modelData.showSwap !== undefined ? modelData.showSwap : false
const tooltipText = currentEnabled ? "Hide Swap" : "Show Swap"
const p = showSwapButton.mapToItem(null, showSwapButton.width / 2, 0)
showSwapTooltipLoader.item.show(tooltipText, p.x, p.y - 40, null)
}
var currentEnabled = modelData.showSwap !== undefined ? modelData.showSwap : false;
const tooltipText = currentEnabled ? "Hide Swap" : "Show Swap";
sharedTooltip.show(tooltipText, showSwapButton, 0, 0, "bottom");
}
onExited: {
if (showSwapTooltipLoader.item) {
showSwapTooltipLoader.item.hide()
}
showSwapTooltipLoader.active = false
sharedTooltip.hide();
}
}
Row {
spacing: Theme.spacingXS
visible: modelData.id === "clock"
|| modelData.id === "music"
|| modelData.id === "focusedWindow"
|| modelData.id === "runningApps"
|| modelData.id === "keyboard_layout_name"
visible: modelData.id === "clock" || modelData.id === "music" || modelData.id === "focusedWindow" || modelData.id === "runningApps" || modelData.id === "keyboard_layout_name"
DankActionButton {
id: smallSizeButton
@@ -359,23 +319,15 @@ Column {
visible: modelData.id === "music"
iconName: "photo_size_select_small"
iconSize: 16
iconColor: SettingsData.mediaSize
=== 0 ? Theme.primary : Theme.outline
iconColor: SettingsData.mediaSize === 0 ? Theme.primary : Theme.outline
onClicked: {
root.compactModeChanged("music", 0)
root.compactModeChanged("music", 0);
}
onEntered: {
smallTooltipLoader.active = true
if (smallTooltipLoader.item) {
const p = smallSizeButton.mapToItem(null, smallSizeButton.width / 2, 0)
smallTooltipLoader.item.show("Small", p.x, p.y - 40, null)
}
sharedTooltip.show("Small", smallSizeButton, 0, 0, "bottom");
}
onExited: {
if (smallTooltipLoader.item) {
smallTooltipLoader.item.hide()
}
smallTooltipLoader.active = false
sharedTooltip.hide();
}
}
@@ -385,23 +337,15 @@ Column {
visible: modelData.id === "music"
iconName: "photo_size_select_actual"
iconSize: 16
iconColor: SettingsData.mediaSize
=== 1 ? Theme.primary : Theme.outline
iconColor: SettingsData.mediaSize === 1 ? Theme.primary : Theme.outline
onClicked: {
root.compactModeChanged("music", 1)
root.compactModeChanged("music", 1);
}
onEntered: {
mediumTooltipLoader.active = true
if (mediumTooltipLoader.item) {
const p = mediumSizeButton.mapToItem(null, mediumSizeButton.width / 2, 0)
mediumTooltipLoader.item.show("Medium", p.x, p.y - 40, null)
}
sharedTooltip.show("Medium", mediumSizeButton, 0, 0, "bottom");
}
onExited: {
if (mediumTooltipLoader.item) {
mediumTooltipLoader.item.hide()
}
mediumTooltipLoader.active = false
sharedTooltip.hide();
}
}
@@ -411,97 +355,71 @@ Column {
visible: modelData.id === "music"
iconName: "photo_size_select_large"
iconSize: 16
iconColor: SettingsData.mediaSize
=== 2 ? Theme.primary : Theme.outline
iconColor: SettingsData.mediaSize === 2 ? Theme.primary : Theme.outline
onClicked: {
root.compactModeChanged("music", 2)
root.compactModeChanged("music", 2);
}
onEntered: {
largeTooltipLoader.active = true
if (largeTooltipLoader.item) {
const p = largeSizeButton.mapToItem(null, largeSizeButton.width / 2, 0)
largeTooltipLoader.item.show("Large", p.x, p.y - 40, null)
}
sharedTooltip.show("Large", largeSizeButton, 0, 0, "bottom");
}
onExited: {
if (largeTooltipLoader.item) {
largeTooltipLoader.item.hide()
}
largeTooltipLoader.active = false
sharedTooltip.hide();
}
}
DankActionButton {
id: compactModeButton
buttonSize: 28
visible: modelData.id === "clock"
|| modelData.id === "focusedWindow"
|| modelData.id === "runningApps"
|| modelData.id === "keyboard_layout_name"
visible: modelData.id === "clock" || modelData.id === "focusedWindow" || modelData.id === "runningApps" || modelData.id === "keyboard_layout_name"
iconName: {
if (modelData.id === "clock")
return SettingsData.clockCompactMode ? "zoom_out" : "zoom_in"
return SettingsData.clockCompactMode ? "zoom_out" : "zoom_in";
if (modelData.id === "focusedWindow")
return SettingsData.focusedWindowCompactMode ? "zoom_out" : "zoom_in"
return SettingsData.focusedWindowCompactMode ? "zoom_out" : "zoom_in";
if (modelData.id === "runningApps")
return SettingsData.runningAppsCompactMode ? "zoom_out" : "zoom_in"
return SettingsData.runningAppsCompactMode ? "zoom_out" : "zoom_in";
if (modelData.id === "keyboard_layout_name")
return SettingsData.keyboardLayoutNameCompactMode ? "zoom_out" : "zoom_in"
return "zoom_in"
return SettingsData.keyboardLayoutNameCompactMode ? "zoom_out" : "zoom_in";
return "zoom_in";
}
iconSize: 16
iconColor: {
if (modelData.id === "clock")
return SettingsData.clockCompactMode ? Theme.primary : Theme.outline
return SettingsData.clockCompactMode ? Theme.primary : Theme.outline;
if (modelData.id === "focusedWindow")
return SettingsData.focusedWindowCompactMode ? Theme.primary : Theme.outline
return SettingsData.focusedWindowCompactMode ? Theme.primary : Theme.outline;
if (modelData.id === "runningApps")
return SettingsData.runningAppsCompactMode ? Theme.primary : Theme.outline
return SettingsData.runningAppsCompactMode ? Theme.primary : Theme.outline;
if (modelData.id === "keyboard_layout_name")
return SettingsData.keyboardLayoutNameCompactMode ? Theme.primary : Theme.outline
return Theme.outline
return SettingsData.keyboardLayoutNameCompactMode ? Theme.primary : Theme.outline;
return Theme.outline;
}
onClicked: {
if (modelData.id === "clock") {
root.compactModeChanged(
"clock",
!SettingsData.clockCompactMode)
root.compactModeChanged("clock", !SettingsData.clockCompactMode);
} else if (modelData.id === "focusedWindow") {
root.compactModeChanged(
"focusedWindow",
!SettingsData.focusedWindowCompactMode)
root.compactModeChanged("focusedWindow", !SettingsData.focusedWindowCompactMode);
} else if (modelData.id === "runningApps") {
root.compactModeChanged(
"runningApps",
!SettingsData.runningAppsCompactMode)
root.compactModeChanged("runningApps", !SettingsData.runningAppsCompactMode);
} else if (modelData.id === "keyboard_layout_name") {
root.compactModeChanged(
"keyboard_layout_name",
!SettingsData.keyboardLayoutNameCompactMode)
root.compactModeChanged("keyboard_layout_name", !SettingsData.keyboardLayoutNameCompactMode);
}
}
onEntered: {
compactTooltipLoader.active = true
if (compactTooltipLoader.item) {
let tooltipText = "Toggle Compact Mode"
if (modelData.id === "clock") {
tooltipText = SettingsData.clockCompactMode ? "Full Size" : "Compact"
} else if (modelData.id === "focusedWindow") {
tooltipText = SettingsData.focusedWindowCompactMode ? "Full Size" : "Compact"
} else if (modelData.id === "runningApps") {
tooltipText = SettingsData.runningAppsCompactMode ? "Full Size" : "Compact"
} else if (modelData.id === "keyboard_layout_name") {
tooltipText = SettingsData.keyboardLayoutNameCompactMode ? "Full Size" : "Compact"
}
const p = compactModeButton.mapToItem(null, compactModeButton.width / 2, 0)
compactTooltipLoader.item.show(tooltipText, p.x, p.y - 40, null)
let tooltipText = "Toggle Compact Mode";
if (modelData.id === "clock") {
tooltipText = SettingsData.clockCompactMode ? "Full Size" : "Compact";
} else if (modelData.id === "focusedWindow") {
tooltipText = SettingsData.focusedWindowCompactMode ? "Full Size" : "Compact";
} else if (modelData.id === "runningApps") {
tooltipText = SettingsData.runningAppsCompactMode ? "Full Size" : "Compact";
} else if (modelData.id === "keyboard_layout_name") {
tooltipText = SettingsData.keyboardLayoutNameCompactMode ? "Full Size" : "Compact";
}
sharedTooltip.show(tooltipText, compactModeButton, 0, 0, "bottom");
}
onExited: {
if (compactTooltipLoader.item) {
compactTooltipLoader.item.hide()
}
compactTooltipLoader.active = false
sharedTooltip.hide();
}
}
@@ -513,21 +431,14 @@ Column {
iconSize: 16
iconColor: SettingsData.runningAppsGroupByApp ? Theme.primary : Theme.outline
onClicked: {
SettingsData.set("runningAppsGroupByApp", !SettingsData.runningAppsGroupByApp)
SettingsData.set("runningAppsGroupByApp", !SettingsData.runningAppsGroupByApp);
}
onEntered: {
groupByAppTooltipLoader.active = true
if (groupByAppTooltipLoader.item) {
const tooltipText = SettingsData.runningAppsGroupByApp ? "Ungroup" : "Group by App"
const p = groupByAppButton.mapToItem(null, groupByAppButton.width / 2, 0)
groupByAppTooltipLoader.item.show(tooltipText, p.x, p.y - 40, null)
}
const tooltipText = SettingsData.runningAppsGroupByApp ? "Ungroup" : "Group by App";
sharedTooltip.show(tooltipText, groupByAppButton, 0, 0, "bottom");
}
onExited: {
if (groupByAppTooltipLoader.item) {
groupByAppTooltipLoader.item.hide()
}
groupByAppTooltipLoader.active = false
sharedTooltip.hide();
}
}
@@ -569,15 +480,15 @@ Column {
iconSize: 18
iconColor: Theme.outline
onClicked: {
console.log("Control Center three-dot button clicked for widget:", modelData.id)
controlCenterContextMenu.widgetData = modelData
controlCenterContextMenu.sectionId = root.sectionId
controlCenterContextMenu.widgetIndex = index
console.log("Control Center three-dot button clicked for widget:", modelData.id);
controlCenterContextMenu.widgetData = modelData;
controlCenterContextMenu.sectionId = root.sectionId;
controlCenterContextMenu.widgetIndex = index;
// Position relative to the action buttons row, not the specific button
var parentPos = parent.mapToItem(root, 0, 0)
controlCenterContextMenu.x = parentPos.x - 210 // Position to the left with margin
controlCenterContextMenu.y = parentPos.y - 10 // Slightly above
controlCenterContextMenu.open()
var parentPos = parent.mapToItem(root, 0, 0);
controlCenterContextMenu.x = parentPos.x - 210; // Position to the left with margin
controlCenterContextMenu.y = parentPos.y - 10; // Slightly above
controlCenterContextMenu.open();
}
}
@@ -588,15 +499,15 @@ Column {
iconSize: 18
iconColor: Theme.outline
onClicked: {
console.log("Privacy three-dot button clicked for widget:", modelData.id)
privacyContextMenu.widgetData = modelData
privacyContextMenu.sectionId = root.sectionId
privacyContextMenu.widgetIndex = index
console.log("Privacy three-dot button clicked for widget:", modelData.id);
privacyContextMenu.widgetData = modelData;
privacyContextMenu.sectionId = root.sectionId;
privacyContextMenu.widgetIndex = index;
// Position relative to the action buttons row, not the specific button
var parentPos = parent.mapToItem(root, 0, 0)
privacyContextMenu.x = parentPos.x - 210 // Position to the left with margin
privacyContextMenu.y = parentPos.y - 10 // Slightly above
privacyContextMenu.open()
var parentPos = parent.mapToItem(root, 0, 0);
privacyContextMenu.x = parentPos.x - 210; // Position to the left with margin
privacyContextMenu.y = parentPos.y - 10; // Slightly above
privacyContextMenu.open();
}
}
@@ -608,23 +519,14 @@ Column {
iconSize: 18
iconColor: modelData.enabled ? Theme.primary : Theme.outline
onClicked: {
root.itemEnabledChanged(root.sectionId,
modelData.id,
!modelData.enabled)
root.itemEnabledChanged(root.sectionId, modelData.id, !modelData.enabled);
}
onEntered: {
visibilityTooltipLoader.active = true
if (visibilityTooltipLoader.item) {
const tooltipText = modelData.enabled ? "Hide" : "Show"
const p = visibilityButton.mapToItem(null, visibilityButton.width / 2, 0)
visibilityTooltipLoader.item.show(tooltipText, p.x, p.y - 40, null)
}
const tooltipText = modelData.enabled ? "Hide" : "Show";
sharedTooltip.show(tooltipText, visibilityButton, 0, 0, "bottom");
}
onExited: {
if (visibilityTooltipLoader.item) {
visibilityTooltipLoader.item.hide()
}
visibilityTooltipLoader.active = false
sharedTooltip.hide();
}
}
@@ -639,11 +541,9 @@ Column {
iconSize: 14
iconColor: Theme.outline
onClicked: {
var currentSize = modelData.size || 20
var newSize = Math.max(5, currentSize - 5)
root.spacerSizeChanged(root.sectionId,
index,
newSize)
var currentSize = modelData.size || 20;
var newSize = Math.max(5, currentSize - 5);
root.spacerSizeChanged(root.sectionId, index, newSize);
}
}
@@ -660,12 +560,9 @@ Column {
iconSize: 14
iconColor: Theme.outline
onClicked: {
var currentSize = modelData.size || 20
var newSize = Math.min(5000,
currentSize + 5)
root.spacerSizeChanged(root.sectionId,
index,
newSize)
var currentSize = modelData.size || 20;
var newSize = Math.min(5000, currentSize + 5);
root.spacerSizeChanged(root.sectionId, index, newSize);
}
}
}
@@ -676,7 +573,7 @@ Column {
iconSize: 18
iconColor: Theme.error
onClicked: {
root.removeWidget(root.sectionId, index)
root.removeWidget(root.sectionId, index);
}
}
}
@@ -696,34 +593,29 @@ Column {
drag.maximumY: itemsList.height
preventStealing: true
onPressed: {
delegateItem.z = 2
delegateItem.originalY = delegateItem.y
delegateItem.z = 2;
delegateItem.originalY = delegateItem.y;
}
onReleased: {
delegateItem.z = 1
delegateItem.z = 1;
if (drag.active) {
var newIndex = Math.round(
delegateItem.y / (delegateItem.height
+ itemsList.spacing))
newIndex = Math.max(
0, Math.min(newIndex,
root.items.length - 1))
var newIndex = Math.round(delegateItem.y / (delegateItem.height + itemsList.spacing));
newIndex = Math.max(0, Math.min(newIndex, root.items.length - 1));
if (newIndex !== index) {
var newItems = root.items.slice()
var draggedItem = newItems.splice(index,
1)[0]
newItems.splice(newIndex, 0, draggedItem)
var newItems = root.items.slice();
var draggedItem = newItems.splice(index, 1)[0];
newItems.splice(newIndex, 0, draggedItem);
root.itemOrderChanged(newItems.map(item => {
return ({
"id": item.id,
"enabled": item.enabled,
"size": item.size
})
}))
return ({
"id": item.id,
"enabled": item.enabled,
"size": item.size
});
}));
}
}
delegateItem.x = 0
delegateItem.y = delegateItem.originalY
delegateItem.x = 0;
delegateItem.y = delegateItem.originalY;
}
}
@@ -744,12 +636,8 @@ Column {
width: 200
height: 40
radius: Theme.cornerRadius
color: addButtonArea.containsMouse ? Theme.primaryContainer : Qt.rgba(
Theme.surfaceVariant.r,
Theme.surfaceVariant.g,
Theme.surfaceVariant.b, 0.3)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
Theme.outline.b, 0.2)
color: addButtonArea.containsMouse ? Theme.primaryContainer : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: 0
anchors.horizontalCenter: parent.horizontalCenter
@@ -769,7 +657,7 @@ Column {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.addWidget(root.sectionId)
root.addWidget(root.sectionId);
}
}
@@ -788,7 +676,6 @@ Column {
property string sectionId: ""
property int widgetIndex: -1
width: 200
height: 120
padding: 0
@@ -797,11 +684,11 @@ Column {
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
onOpened: {
console.log("Control Center context menu opened")
console.log("Control Center context menu opened");
}
onClosed: {
console.log("Control Center context menu closed")
console.log("Control Center context menu closed");
}
background: Rectangle {
@@ -856,7 +743,7 @@ Column {
height: 20
checked: SettingsData.controlCenterShowNetworkIcon
onToggled: {
root.controlCenterSettingChanged(controlCenterContextMenu.sectionId, controlCenterContextMenu.widgetIndex, "showNetworkIcon", toggled)
root.controlCenterSettingChanged(controlCenterContextMenu.sectionId, controlCenterContextMenu.widgetIndex, "showNetworkIcon", toggled);
}
}
@@ -866,8 +753,8 @@ Column {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: {
networkToggle.checked = !networkToggle.checked
root.controlCenterSettingChanged(controlCenterContextMenu.sectionId, controlCenterContextMenu.widgetIndex, "showNetworkIcon", networkToggle.checked)
networkToggle.checked = !networkToggle.checked;
root.controlCenterSettingChanged(controlCenterContextMenu.sectionId, controlCenterContextMenu.widgetIndex, "showNetworkIcon", networkToggle.checked);
}
}
}
@@ -909,7 +796,7 @@ Column {
height: 20
checked: SettingsData.controlCenterShowBluetoothIcon
onToggled: {
root.controlCenterSettingChanged(controlCenterContextMenu.sectionId, controlCenterContextMenu.widgetIndex, "showBluetoothIcon", toggled)
root.controlCenterSettingChanged(controlCenterContextMenu.sectionId, controlCenterContextMenu.widgetIndex, "showBluetoothIcon", toggled);
}
}
@@ -919,8 +806,8 @@ Column {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: {
bluetoothToggle.checked = !bluetoothToggle.checked
root.controlCenterSettingChanged(controlCenterContextMenu.sectionId, controlCenterContextMenu.widgetIndex, "showBluetoothIcon", bluetoothToggle.checked)
bluetoothToggle.checked = !bluetoothToggle.checked;
root.controlCenterSettingChanged(controlCenterContextMenu.sectionId, controlCenterContextMenu.widgetIndex, "showBluetoothIcon", bluetoothToggle.checked);
}
}
}
@@ -962,7 +849,7 @@ Column {
height: 20
checked: SettingsData.controlCenterShowAudioIcon
onToggled: {
root.controlCenterSettingChanged(controlCenterContextMenu.sectionId, controlCenterContextMenu.widgetIndex, "showAudioIcon", toggled)
root.controlCenterSettingChanged(controlCenterContextMenu.sectionId, controlCenterContextMenu.widgetIndex, "showAudioIcon", toggled);
}
}
@@ -972,13 +859,12 @@ Column {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: {
audioToggle.checked = !audioToggle.checked
root.controlCenterSettingChanged(controlCenterContextMenu.sectionId, controlCenterContextMenu.widgetIndex, "showAudioIcon", audioToggle.checked)
audioToggle.checked = !audioToggle.checked;
root.controlCenterSettingChanged(controlCenterContextMenu.sectionId, controlCenterContextMenu.widgetIndex, "showAudioIcon", audioToggle.checked);
}
}
}
}
}
}
@@ -989,7 +875,6 @@ Column {
property string sectionId: ""
property int widgetIndex: -1
width: 200
height: 160
padding: 0
@@ -998,11 +883,11 @@ Column {
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
onOpened: {
console.log("Privacy context menu opened")
console.log("Privacy context menu opened");
}
onClosed: {
console.log("Privacy Center context menu closed")
console.log("Privacy Center context menu closed");
}
background: Rectangle {
@@ -1079,7 +964,7 @@ Column {
height: 20
checked: SettingsData.privacyShowMicIcon
onToggled: toggled => {
root.privacySettingChanged(privacyContextMenu.sectionId, privacyContextMenu.widgetIndex, "showMicIcon", toggled)
root.privacySettingChanged(privacyContextMenu.sectionId, privacyContextMenu.widgetIndex, "showMicIcon", toggled);
}
}
@@ -1089,8 +974,8 @@ Column {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: {
micToggle.checked = !micToggle.checked
root.privacySettingChanged(privacyContextMenu.sectionId, privacyContextMenu.widgetIndex, "showMicIcon", micToggle.checked)
micToggle.checked = !micToggle.checked;
root.privacySettingChanged(privacyContextMenu.sectionId, privacyContextMenu.widgetIndex, "showMicIcon", micToggle.checked);
}
}
}
@@ -1131,7 +1016,7 @@ Column {
height: 20
checked: SettingsData.privacyShowCameraIcon
onToggled: toggled => {
root.privacySettingChanged(privacyContextMenu.sectionId, privacyContextMenu.widgetIndex, "showCameraIcon", toggled)
root.privacySettingChanged(privacyContextMenu.sectionId, privacyContextMenu.widgetIndex, "showCameraIcon", toggled);
}
}
@@ -1141,8 +1026,8 @@ Column {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: {
cameraToggle.checked = !cameraToggle.checked
root.privacySettingChanged(privacyContextMenu.sectionId, privacyContextMenu.widgetIndex, "showCameraIcon", cameraToggle.checked)
cameraToggle.checked = !cameraToggle.checked;
root.privacySettingChanged(privacyContextMenu.sectionId, privacyContextMenu.widgetIndex, "showCameraIcon", cameraToggle.checked);
}
}
}
@@ -1183,7 +1068,7 @@ Column {
height: 20
checked: SettingsData.privacyShowScreenShareIcon
onToggled: toggled => {
root.privacySettingChanged(privacyContextMenu.sectionId, privacyContextMenu.widgetIndex, "showScreenSharingIcon", toggled)
root.privacySettingChanged(privacyContextMenu.sectionId, privacyContextMenu.widgetIndex, "showScreenSharingIcon", toggled);
}
}
@@ -1193,61 +1078,12 @@ Column {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: {
screenshareToggle.checked = !screenshareToggle.checked
root.privacySettingChanged(privacyContextMenu.sectionId, privacyContextMenu.widgetIndex, "showScreenSharingIcon", screenshareToggle.checked)
screenshareToggle.checked = !screenshareToggle.checked;
root.privacySettingChanged(privacyContextMenu.sectionId, privacyContextMenu.widgetIndex, "showScreenSharingIcon", screenshareToggle.checked);
}
}
}
}
}
}
Loader {
id: smallTooltipLoader
active: false
sourceComponent: DankTooltip {}
}
Loader {
id: mediumTooltipLoader
active: false
sourceComponent: DankTooltip {}
}
Loader {
id: largeTooltipLoader
active: false
sourceComponent: DankTooltip {}
}
Loader {
id: compactTooltipLoader
active: false
sourceComponent: DankTooltip {}
}
Loader {
id: visibilityTooltipLoader
active: false
sourceComponent: DankTooltip {}
}
Loader {
id: minimumWidthTooltipLoader
active: false
sourceComponent: DankTooltip {}
}
Loader {
id: groupByAppTooltipLoader
active: false
sourceComponent: DankTooltip {}
}
Loader {
id: showSwapTooltipLoader
active: false
sourceComponent: DankTooltip {}
}
}

View File

@@ -1,10 +1,8 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Services.UPower
import qs.Common
@@ -31,25 +29,28 @@ Singleton {
// Main battery (for backward compatibility)
readonly property UPowerDevice device: {
var preferredDev
var preferredDev;
if (usePreferred) {
preferredDev = batteries.find(dev => dev.nativePath.toLowerCase().includes(preferredBatteryOverride.toLowerCase()))
preferredDev = batteries.find(dev => dev.nativePath.toLowerCase().includes(preferredBatteryOverride.toLowerCase()));
}
return preferredDev || batteries[0] || null
return preferredDev || batteries[0] || null;
}
// Whether at least one battery is available
readonly property bool batteryAvailable: batteries.length > 0
// Aggregated charge level (percentage)
readonly property real batteryLevel: {
if (!batteryAvailable) return 0
if (!batteryAvailable)
return 0;
if (batteryCapacity === 0) {
if (usePreferred && device && device.ready) return Math.round(device.percentage * 100)
const validBatteries = batteries.filter(b => b.ready && b.percentage >= 0)
if (validBatteries.length === 0) return 0
const avgPercentage = validBatteries.reduce((sum, b) => sum + b.percentage, 0) / validBatteries.length
return Math.round(avgPercentage * 100)
if (usePreferred && device && device.ready)
return Math.round(device.percentage * 100);
const validBatteries = batteries.filter(b => b.ready && b.percentage >= 0);
if (validBatteries.length === 0)
return 0;
const avgPercentage = validBatteries.reduce((sum, b) => sum + b.percentage, 0) / validBatteries.length;
return Math.round(avgPercentage * 100);
}
return Math.round((batteryEnergy * 100) / batteryCapacity)
return Math.round((batteryEnergy * 100) / batteryCapacity);
}
readonly property bool isCharging: batteryAvailable && batteries.some(b => b.state === UPowerDeviceState.Charging)
@@ -59,170 +60,182 @@ Singleton {
onIsPluggedInChanged: {
if (suppressSound || !batteryAvailable) {
previousPluggedState = isPluggedIn
return
previousPluggedState = isPluggedIn;
return;
}
if (SettingsData.soundsEnabled && SettingsData.soundPluggedIn) {
if (isPluggedIn && !previousPluggedState) {
AudioService.playPowerPlugSound()
AudioService.playPowerPlugSound();
} else if (!isPluggedIn && previousPluggedState) {
AudioService.playPowerUnplugSound()
AudioService.playPowerUnplugSound();
}
}
previousPluggedState = isPluggedIn
previousPluggedState = isPluggedIn;
}
// Aggregated charge/discharge rate
readonly property real changeRate: {
if (!batteryAvailable) return 0
if (usePreferred && device && device.ready) return device.changeRate
return batteries.length > 0 ? batteries.reduce((sum, b) => sum + b.changeRate, 0) : 0
if (!batteryAvailable)
return 0;
if (usePreferred && device && device.ready)
return device.changeRate;
return batteries.length > 0 ? batteries.reduce((sum, b) => sum + b.changeRate, 0) : 0;
}
// Aggregated battery health
readonly property string batteryHealth: {
if (!batteryAvailable) return "N/A"
if (!batteryAvailable)
return "N/A";
// If a preferred battery is selected and ready
if (usePreferred && device && device.ready && device.healthSupported) return `${Math.round(device.healthPercentage)}%`
if (usePreferred && device && device.ready && device.healthSupported)
return `${Math.round(device.healthPercentage)}%`;
// Otherwise, calculate the average health of all laptop batteries
const validBatteries = batteries.filter(b => b.healthSupported && b.healthPercentage > 0)
if (validBatteries.length === 0) return "N/A"
const validBatteries = batteries.filter(b => b.healthSupported && b.healthPercentage > 0);
if (validBatteries.length === 0)
return "N/A";
const avgHealth = validBatteries.reduce((sum, b) => sum + b.healthPercentage, 0) / validBatteries.length
return `${Math.round(avgHealth)}%`
const avgHealth = validBatteries.reduce((sum, b) => sum + b.healthPercentage, 0) / validBatteries.length;
return `${Math.round(avgHealth)}%`;
}
readonly property real batteryEnergy: {
if (!batteryAvailable) return 0
if (usePreferred && device && device.ready) return device.energy
return batteries.length > 0 ? batteries.reduce((sum, b) => sum + b.energy, 0) : 0
if (!batteryAvailable)
return 0;
if (usePreferred && device && device.ready)
return device.energy;
return batteries.length > 0 ? batteries.reduce((sum, b) => sum + b.energy, 0) : 0;
}
// Total battery capacity (Wh)
readonly property real batteryCapacity: {
if (!batteryAvailable) return 0
if (usePreferred && device && device.ready) return device.energyCapacity
return batteries.length > 0 ? batteries.reduce((sum, b) => sum + b.energyCapacity, 0) : 0
if (!batteryAvailable)
return 0;
if (usePreferred && device && device.ready)
return device.energyCapacity;
return batteries.length > 0 ? batteries.reduce((sum, b) => sum + b.energyCapacity, 0) : 0;
}
// Aggregated battery status
readonly property string batteryStatus: {
if (!batteryAvailable) {
return "No Battery"
return "No Battery";
}
if (isCharging && !batteries.some(b => b.changeRate > 0)) return "Plugged In"
if (isCharging && !batteries.some(b => b.changeRate > 0))
return "Plugged In";
const states = batteries.map(b => b.state)
if (states.every(s => s === states[0])) return UPowerDeviceState.toString(states[0])
const states = batteries.map(b => b.state);
if (states.every(s => s === states[0]))
return UPowerDeviceState.toString(states[0]);
return isCharging ? "Charging" : (isPluggedIn ? "Plugged In" : "Discharging")
return isCharging ? "Charging" : (isPluggedIn ? "Plugged In" : "Discharging");
}
readonly property bool suggestPowerSaver: batteryAvailable && isLowBattery && UPower.onBattery && (typeof PowerProfiles !== "undefined" && PowerProfiles.profile !== PowerProfile.PowerSaver)
readonly property bool suggestPowerSaver: false
readonly property var bluetoothDevices: {
const btDevices = []
const bluetoothTypes = [UPowerDeviceType.BluetoothGeneric, UPowerDeviceType.Headphones, UPowerDeviceType.Headset, UPowerDeviceType.Keyboard, UPowerDeviceType.Mouse, UPowerDeviceType.Speakers]
const btDevices = [];
const bluetoothTypes = [UPowerDeviceType.BluetoothGeneric, UPowerDeviceType.Headphones, UPowerDeviceType.Headset, UPowerDeviceType.Keyboard, UPowerDeviceType.Mouse, UPowerDeviceType.Speakers];
for (var i = 0; i < UPower.devices.count; i++) {
const dev = UPower.devices.get(i)
const dev = UPower.devices.get(i);
if (dev && dev.ready && bluetoothTypes.includes(dev.type)) {
btDevices.push({
"name": dev.model || UPowerDeviceType.toString(dev.type),
"percentage": Math.round(dev.percentage * 100),
"type": dev.type
})
"name": dev.model || UPowerDeviceType.toString(dev.type),
"percentage": Math.round(dev.percentage * 100),
"type": dev.type
});
}
}
return btDevices
return btDevices;
}
// Format time remaining for charge/discharge
function formatTimeRemaining() {
if (!batteryAvailable) {
return "Unknown"
return "Unknown";
}
let totalTime = 0
totalTime = (isCharging) ? ((batteryCapacity - batteryEnergy) / changeRate) : (batteryEnergy / changeRate)
const avgTime = Math.abs(totalTime * 3600)
if (!avgTime || avgTime <= 0 || avgTime > 86400) return "Unknown"
let totalTime = 0;
totalTime = (isCharging) ? ((batteryCapacity - batteryEnergy) / changeRate) : (batteryEnergy / changeRate);
const avgTime = Math.abs(totalTime * 3600);
if (!avgTime || avgTime <= 0 || avgTime > 86400)
return "Unknown";
const hours = Math.floor(avgTime / 3600)
const minutes = Math.floor((avgTime % 3600) / 60)
return hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`
const hours = Math.floor(avgTime / 3600);
const minutes = Math.floor((avgTime % 3600) / 60);
return hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
}
function getBatteryIcon() {
if (!batteryAvailable) {
return "power"
return "power";
}
if (isCharging) {
if (batteryLevel >= 90) {
return "battery_charging_full"
return "battery_charging_full";
}
if (batteryLevel >= 80) {
return "battery_charging_90"
return "battery_charging_90";
}
if (batteryLevel >= 60) {
return "battery_charging_80"
return "battery_charging_80";
}
if (batteryLevel >= 50) {
return "battery_charging_60"
return "battery_charging_60";
}
if (batteryLevel >= 30) {
return "battery_charging_50"
return "battery_charging_50";
}
if (batteryLevel >= 20) {
return "battery_charging_30"
return "battery_charging_30";
}
return "battery_charging_20"
return "battery_charging_20";
}
if (isPluggedIn) {
if (batteryLevel >= 90) {
return "battery_charging_full"
return "battery_charging_full";
}
if (batteryLevel >= 80) {
return "battery_charging_90"
return "battery_charging_90";
}
if (batteryLevel >= 60) {
return "battery_charging_80"
return "battery_charging_80";
}
if (batteryLevel >= 50) {
return "battery_charging_60"
return "battery_charging_60";
}
if (batteryLevel >= 30) {
return "battery_charging_50"
return "battery_charging_50";
}
if (batteryLevel >= 20) {
return "battery_charging_30"
return "battery_charging_30";
}
return "battery_charging_20"
return "battery_charging_20";
}
if (batteryLevel >= 95) {
return "battery_full"
return "battery_full";
}
if (batteryLevel >= 85) {
return "battery_6_bar"
return "battery_6_bar";
}
if (batteryLevel >= 70) {
return "battery_5_bar"
return "battery_5_bar";
}
if (batteryLevel >= 55) {
return "battery_4_bar"
return "battery_4_bar";
}
if (batteryLevel >= 40) {
return "battery_3_bar"
return "battery_3_bar";
}
if (batteryLevel >= 25) {
return "battery_2_bar"
return "battery_2_bar";
}
return "battery_1_bar"
return "battery_1_bar";
}
}

View File

@@ -0,0 +1,36 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Services.UPower
Singleton {
id: root
property int currentProfile: -1
property int previousProfile: -1
signal profileChanged(int profile)
Connections {
target: typeof PowerProfiles !== "undefined" ? PowerProfiles : null
function onProfileChanged() {
if (typeof PowerProfiles !== "undefined") {
root.previousProfile = root.currentProfile;
root.currentProfile = PowerProfiles.profile;
if (root.previousProfile !== -1) {
root.profileChanged(root.currentProfile);
}
}
}
}
Component.onCompleted: {
if (typeof PowerProfiles !== "undefined") {
root.currentProfile = PowerProfiles.profile;
root.previousProfile = PowerProfiles.profile;
}
}
}

View File

@@ -0,0 +1,145 @@
import QtQuick
import QtQuick.Controls
import qs.Common
Item {
id: root
property string text: ""
function show(text, item, offsetX, offsetY, preferredSide) {
if (!item)
return;
const windowContentItem = item.Window.window?.contentItem;
if (!windowContentItem)
return;
tooltip.parent = windowContentItem;
tooltip.text = text;
const itemPos = item.mapToItem(windowContentItem, 0, 0);
const parentWidth = windowContentItem.width;
const parentHeight = windowContentItem.height;
const tooltipWidth = tooltip.implicitWidth;
const tooltipHeight = tooltip.implicitHeight;
const side = preferredSide || _determineBestSide(itemPos, item, parentWidth, parentHeight, tooltipWidth, tooltipHeight);
let targetX = 0;
let targetY = 0;
switch (side) {
case "left":
targetX = itemPos.x - tooltipWidth - 8;
targetY = itemPos.y + (item.height - tooltipHeight) / 2;
break;
case "right":
targetX = itemPos.x + item.width + 8;
targetY = itemPos.y + (item.height - tooltipHeight) / 2;
break;
case "top":
targetX = itemPos.x + (item.width - tooltipWidth) / 2;
targetY = itemPos.y - tooltipHeight - 8;
break;
case "bottom":
default:
targetX = itemPos.x + (item.width - tooltipWidth) / 2;
targetY = itemPos.y + item.height + 8;
break;
}
tooltip.x = Math.max(4, Math.min(parentWidth - tooltipWidth - 4, targetX + (offsetX || 0)));
tooltip.y = Math.max(4, Math.min(parentHeight - tooltipHeight - 4, targetY + (offsetY || 0)));
tooltip.open();
}
function _determineBestSide(itemPos, item, parentWidth, parentHeight, tooltipWidth, tooltipHeight) {
const itemCenterX = itemPos.x + item.width / 2;
const itemCenterY = itemPos.y + item.height / 2;
const spaceLeft = itemPos.x;
const spaceRight = parentWidth - (itemPos.x + item.width);
const spaceTop = itemPos.y;
const spaceBottom = parentHeight - (itemPos.y + item.height);
if (spaceRight >= tooltipWidth + 16) {
return "right";
}
if (spaceLeft >= tooltipWidth + 16) {
return "left";
}
if (spaceBottom >= tooltipHeight + 16) {
return "bottom";
}
if (spaceTop >= tooltipHeight + 16) {
return "top";
}
if (itemCenterX > parentWidth / 2) {
return "left";
}
return "right";
}
function hide() {
tooltip.close();
}
Popup {
id: tooltip
property string text: ""
implicitWidth: Math.min(300, Math.max(120, textContent.implicitWidth + Theme.spacingM * 2))
implicitHeight: textContent.implicitHeight + Theme.spacingS * 2
width: implicitWidth
height: implicitHeight
padding: 0
closePolicy: Popup.NoAutoClose
modal: false
dim: false
background: Rectangle {
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
radius: Theme.cornerRadius
border.width: 1
border.color: Theme.outlineMedium
}
contentItem: Text {
id: textContent
text: tooltip.text
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
wrapMode: Text.NoWrap
maximumLineCount: 1
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
enter: Transition {
NumberAnimation {
property: "opacity"
from: 0
to: 1
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
exit: Transition {
NumberAnimation {
property: "opacity"
from: 1
to: 0
duration: Theme.shorterDuration
easing.type: Theme.standardEasing
}
}
}
}