mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-30 15:07:49 -04:00
lock: allow customization, including any desktop widget, to be added and
arranged on lock screen
This commit is contained in:
1 parent
375aa2f492
commit
d7dc8ae5d2
56 files changed
+4244
-2159
No files matched your search
+1
-1
@@ -82,7 +82,7 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/AvengeMedia/dankgo v1.6.3-0.20261002194355-b7285561e19f
|
||||
github.com/AvengeMedia/dankgo v1.6.3-0.20261004193546-f560632e8bd2
|
||||
github.com/atotto/clipboard v0.1.4 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.4.3 // indirect
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
||||
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
||||
github.com/AvengeMedia/dankgo v1.6.3-0.20261002194355-b7285561e19f h1:9n423TuyY2cF0/sZg0JjGSPx9yW6zSdgsVFoSVEHmek=
|
||||
github.com/AvengeMedia/dankgo v1.6.3-0.20261002194355-b7285561e19f/go.mod h1:Fj+FTCumlv8YjKmdv75DREJuGmqU9EnGpBroZuCWc/k=
|
||||
github.com/AvengeMedia/dankgo v1.6.3-0.20261004193546-f560632e8bd2 h1:RX0Dy4ays41fEEN86JVf7d4zYoCeq49ttgv7Ynjy56c=
|
||||
github.com/AvengeMedia/dankgo v1.6.3-0.20261004193546-f560632e8bd2/go.mod h1:Fj+FTCumlv8YjKmdv75DREJuGmqU9EnGpBroZuCWc/k=
|
||||
github.com/AvengeMedia/dgop v1.6.3-0.20260920163359-59c7d81ae3b9 h1:ssoiAng7ng500BgtZa1riJH6kKudQVM8Uk9a9kF7mDA=
|
||||
github.com/AvengeMedia/dgop v1.6.3-0.20260920163359-59c7d81ae3b9/go.mod h1:8TFIP72NLxCbogF2hHU6sXj3RoTbHQW8beeCrisHs/Y=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
func TestLockScreenPasswordFieldBypassesTextInputIME(t *testing.T) {
|
||||
data, err := os.ReadFile("../../../quickshell/Modules/Lock/LockScreenContent.qml")
|
||||
data, err := os.ReadFile("../../../quickshell/Modules/Lock/Widgets/LockAuthWidget.qml")
|
||||
if err != nil {
|
||||
t.Fatalf("read lock screen QML: %v", err)
|
||||
}
|
||||
|
||||
+1
-1
Submodule dank-qml-common updated: 13470a9b54...07d634a3fc.
Generated
+3
-3
@@ -3,11 +3,11 @@
|
||||
"dank-qml-common": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1790974203,
|
||||
"narHash": "sha256-C2DAGa/NkSye4NaANuxxkSeNb4xX1g3gZBsKO5KRIzk=",
|
||||
"lastModified": 1791142152,
|
||||
"narHash": "sha256-tVzNbgalwjVVyzYm5d6FjKkCaZ5DbZPw9uyvnTNC2EA=",
|
||||
"owner": "AvengeMedia",
|
||||
"repo": "dank-qml-common",
|
||||
"rev": "13470a9b545f4c2c2fb8f237db6de2b5f7c26b41",
|
||||
"rev": "07d634a3fc731afdcd309d385f3134ba8c22b37c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -111,7 +111,7 @@
|
||||
inherit version;
|
||||
pname = "dms-shell";
|
||||
src = ./core;
|
||||
vendorHash = "sha256-WFufeGR21sidUJVGQTygubcaBB/aOIkKcBKf5dtwwVk=";
|
||||
vendorHash = "sha256-dScMtUqccp6C8b/hIOFTjdgm+mOy921bEbTuXqcp4ag=";
|
||||
|
||||
subPackages = [ "cmd/dms" ];
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ Singleton {
|
||||
|
||||
readonly property var _pinKeys: ["brightnessDevicePins", "wifiNetworkPins", "bluetoothDevicePins", "audioInputDevicePins", "audioOutputDevicePins"]
|
||||
readonly property var _historyKeys: ["browserUsageHistory", "filePickerUsageHistory"]
|
||||
readonly property var _dataKeys: ["fileBrowserSettings", "processFilterTypes", "pluginViewSort", "pluginViewFilter", "dashFocusCardId", "controlCenterCollapsedCategories", "mediaLyricsOpen", "matugenPreviews", "matugenSeedPreviews", "matugenAppliedKey"].concat(_pinKeys, _historyKeys)
|
||||
readonly property var _dataKeys: ["fileBrowserSettings", "processFilterTypes", "pluginViewSort", "pluginViewFilter", "dashFocusCardId", "controlCenterCollapsedCategories", "mediaLyricsOpen", "matugenPreviews", "matugenSeedPreviews", "matugenAppliedKey", "lockScreenPlacementSamples"].concat(_pinKeys, _historyKeys)
|
||||
|
||||
property string pluginViewFilter: "enabled"
|
||||
property string dashFocusCardId: ""
|
||||
@@ -33,6 +33,7 @@ Singleton {
|
||||
property var matugenPreviews: ({})
|
||||
property var matugenSeedPreviews: ({})
|
||||
property string matugenAppliedKey: ""
|
||||
property var lockScreenPlacementSamples: ({})
|
||||
property var pluginViewSort: ({
|
||||
by: "modified",
|
||||
descending: true
|
||||
@@ -137,6 +138,7 @@ Singleton {
|
||||
matugenPreviews = typeof cache.matugenPreviews?.key === "string" ? cache.matugenPreviews : {};
|
||||
matugenSeedPreviews = cache.matugenSeedPreviews && typeof cache.matugenSeedPreviews === "object" && !Array.isArray(cache.matugenSeedPreviews) ? cache.matugenSeedPreviews : {};
|
||||
matugenAppliedKey = typeof cache.matugenAppliedKey === "string" ? cache.matugenAppliedKey : "";
|
||||
lockScreenPlacementSamples = cache.lockScreenPlacementSamples && typeof cache.lockScreenPlacementSamples === "object" && !Array.isArray(cache.lockScreenPlacementSamples) ? cache.lockScreenPlacementSamples : {};
|
||||
const pluginSort = cache.pluginViewSort;
|
||||
pluginViewSort = {
|
||||
by: ["name", "author", "modified"].includes(pluginSort?.by) ? pluginSort.by : "modified",
|
||||
@@ -218,6 +220,7 @@ Singleton {
|
||||
"matugenPreviews": matugenPreviews,
|
||||
"matugenSeedPreviews": matugenSeedPreviews,
|
||||
"matugenAppliedKey": matugenAppliedKey,
|
||||
"lockScreenPlacementSamples": lockScreenPlacementSamples,
|
||||
"fileBrowserSettings": fileBrowserSettings,
|
||||
"configVersion": cacheConfigVersion
|
||||
};
|
||||
|
||||
@@ -292,6 +292,7 @@ Singleton {
|
||||
property var activeDisplayProfileModes: ({})
|
||||
property var desktopWidgetGridSettings: ({})
|
||||
property var desktopWidgetInstancePositions: ({})
|
||||
property var lockScreenAutoPositions: ({})
|
||||
property var islandFreePositions: ({})
|
||||
property var builtInPluginState: ({})
|
||||
property bool greeterSyncPending: false
|
||||
@@ -651,6 +652,15 @@ Singleton {
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function setLockScreenAutoPositions(screenKey, placement) {
|
||||
if (JSON.stringify(lockScreenAutoPositions[screenKey] ?? null) === JSON.stringify(placement))
|
||||
return;
|
||||
const updated = Object.assign({}, lockScreenAutoPositions);
|
||||
updated[screenKey] = placement;
|
||||
lockScreenAutoPositions = updated;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function removeDesktopWidgetInstancePositions(instanceId) {
|
||||
if (!(instanceId in desktopWidgetInstancePositions))
|
||||
return;
|
||||
|
||||
@@ -21,7 +21,7 @@ Singleton {
|
||||
id: root
|
||||
readonly property var log: Log.scoped("SettingsData")
|
||||
|
||||
readonly property int settingsConfigVersion: 37
|
||||
readonly property int settingsConfigVersion: 38
|
||||
|
||||
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
|
||||
|
||||
@@ -773,13 +773,7 @@ Singleton {
|
||||
property bool modalDarkenBackground: Spec.SPEC.modalDarkenBackground.def
|
||||
|
||||
property bool lockScreenShowPowerActions: Spec.SPEC.lockScreenShowPowerActions.def
|
||||
property bool lockScreenShowSystemIcons: Spec.SPEC.lockScreenShowSystemIcons.def
|
||||
property bool lockScreenShowTime: Spec.SPEC.lockScreenShowTime.def
|
||||
property string lockScreenClockStyle: Spec.SPEC.lockScreenClockStyle.def
|
||||
property bool lockScreenShowDate: Spec.SPEC.lockScreenShowDate.def
|
||||
property bool lockScreenShowProfileImage: Spec.SPEC.lockScreenShowProfileImage.def
|
||||
property bool lockScreenShowPasswordField: Spec.SPEC.lockScreenShowPasswordField.def
|
||||
property bool lockScreenShowMediaPlayer: Spec.SPEC.lockScreenShowMediaPlayer.def
|
||||
property bool lockScreenShowWeather: Spec.SPEC.lockScreenShowWeather.def
|
||||
property bool lockScreenPowerOffMonitorsOnLock: Spec.SPEC.lockScreenPowerOffMonitorsOnLock.def
|
||||
property bool lockAtStartup: Spec.SPEC.lockAtStartup.def
|
||||
@@ -813,7 +807,6 @@ Singleton {
|
||||
property bool lockScreenSecurityKeyShortcutEnabled: Spec.SPEC.lockScreenSecurityKeyShortcutEnabled.def
|
||||
property bool greeterPamExternallyManaged: Spec.SPEC.greeterPamExternallyManaged.def
|
||||
property string lockScreenInactiveColor: Spec.SPEC.lockScreenInactiveColor.def
|
||||
property int lockScreenNotificationMode: Spec.SPEC.lockScreenNotificationMode.def
|
||||
property bool lockScreenVideoEnabled: Spec.SPEC.lockScreenVideoEnabled.def
|
||||
property string lockScreenVideoPath: Spec.SPEC.lockScreenVideoPath.def
|
||||
property bool lockScreenVideoCycling: Spec.SPEC.lockScreenVideoCycling.def
|
||||
@@ -1119,6 +1112,34 @@ Singleton {
|
||||
|
||||
property var desktopWidgetInstances: Spec.SPEC.desktopWidgetInstances.def
|
||||
property var desktopWidgetGroups: Spec.SPEC.desktopWidgetGroups.def
|
||||
property var lockScreenWidgetInstances: Spec.SPEC.lockScreenWidgetInstances.def
|
||||
readonly property var widgetInstanceListKeys: ["desktopWidgetInstances", "lockScreenWidgetInstances"]
|
||||
|
||||
// The greeter still reads these three shared keys, so they follow the lock widgets.
|
||||
onLockScreenWidgetInstancesChanged: {
|
||||
const status = lockWidgetInstance("lockStatus");
|
||||
const auth = lockWidgetInstance("lockAuth");
|
||||
const power = lockWidgetInstance("lockPower");
|
||||
const mirror = (key, value) => {
|
||||
if (root[key] !== value)
|
||||
set(key, value);
|
||||
};
|
||||
mirror("lockScreenShowWeather", !!status && status.enabled !== false && (status.config?.showWeather ?? true));
|
||||
mirror("lockScreenShowProfileImage", !!auth && (auth.config?.showProfileImage ?? true));
|
||||
mirror("lockScreenShowPowerActions", !!power && power.enabled !== false);
|
||||
}
|
||||
|
||||
function lockWidgetInstance(widgetType) {
|
||||
return (lockScreenWidgetInstances || []).find(inst => inst.widgetType === widgetType) ?? null;
|
||||
}
|
||||
|
||||
function resetLockScreenWidgets() {
|
||||
for (const inst of lockScreenWidgetInstances || [])
|
||||
SessionData.removeDesktopWidgetInstancePositions(inst.id);
|
||||
for (const inst of Spec.SPEC.lockScreenWidgetInstances.def)
|
||||
SessionData.removeDesktopWidgetInstancePositions(inst.id);
|
||||
resetToDefault(["lockScreenWidgetInstances"]);
|
||||
}
|
||||
|
||||
function getDefaultSystemMonitorConfig() {
|
||||
return {
|
||||
@@ -1149,45 +1170,53 @@ Singleton {
|
||||
};
|
||||
}
|
||||
|
||||
function createDesktopWidgetInstance(widgetType, name, config) {
|
||||
const id = "dw_" + Date.now() + "_" + Math.random().toString(36).substr(2, 9);
|
||||
function widgetInstanceListKey(instanceId) {
|
||||
return widgetInstanceListKeys.find(key => (root[key] || []).some(inst => inst.id === instanceId)) ?? "desktopWidgetInstances";
|
||||
}
|
||||
|
||||
function createDesktopWidgetInstance(widgetType, name, config, listKey = "desktopWidgetInstances") {
|
||||
const lockScreen = listKey === "lockScreenWidgetInstances";
|
||||
const instance = {
|
||||
id: id,
|
||||
id: (lockScreen ? "lw_" : "dw_") + Date.now() + "_" + Math.random().toString(36).substr(2, 9),
|
||||
widgetType: widgetType,
|
||||
name: name || widgetType,
|
||||
enabled: true,
|
||||
config: config || {}
|
||||
config: Object.assign(lockScreen ? {
|
||||
syncPositionAcrossScreens: true
|
||||
} : {}, config || {})
|
||||
};
|
||||
const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
|
||||
const instances = JSON.parse(JSON.stringify(root[listKey] || []));
|
||||
instances.push(instance);
|
||||
desktopWidgetInstances = instances;
|
||||
root[listKey] = instances;
|
||||
saveSettings();
|
||||
return instance;
|
||||
}
|
||||
|
||||
function updateDesktopWidgetInstance(instanceId, updates) {
|
||||
const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
|
||||
const listKey = widgetInstanceListKey(instanceId);
|
||||
const instances = JSON.parse(JSON.stringify(root[listKey] || []));
|
||||
const idx = instances.findIndex(inst => inst.id === instanceId);
|
||||
if (idx === -1)
|
||||
return;
|
||||
Object.assign(instances[idx], updates);
|
||||
desktopWidgetInstances = instances;
|
||||
root[listKey] = instances;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function updateDesktopWidgetInstanceConfig(instanceId, configUpdates) {
|
||||
const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
|
||||
const listKey = widgetInstanceListKey(instanceId);
|
||||
const instances = JSON.parse(JSON.stringify(root[listKey] || []));
|
||||
const idx = instances.findIndex(inst => inst.id === instanceId);
|
||||
if (idx === -1)
|
||||
return;
|
||||
instances[idx].config = Object.assign({}, instances[idx].config || {}, configUpdates);
|
||||
desktopWidgetInstances = instances;
|
||||
root[listKey] = instances;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function removeDesktopWidgetInstance(instanceId) {
|
||||
const instances = (desktopWidgetInstances || []).filter(inst => inst.id !== instanceId);
|
||||
desktopWidgetInstances = instances;
|
||||
const listKey = widgetInstanceListKey(instanceId);
|
||||
root[listKey] = (root[listKey] || []).filter(inst => inst.id !== instanceId);
|
||||
SessionData.removeDesktopWidgetInstancePositions(instanceId);
|
||||
saveSettings();
|
||||
}
|
||||
@@ -1196,23 +1225,21 @@ Singleton {
|
||||
const source = getDesktopWidgetInstance(instanceId);
|
||||
if (!source)
|
||||
return null;
|
||||
const newId = "dw_" + Date.now() + "_" + Math.random().toString(36).substr(2, 9);
|
||||
const instance = {
|
||||
id: newId,
|
||||
widgetType: source.widgetType,
|
||||
name: source.name + " (Copy)",
|
||||
enabled: source.enabled,
|
||||
config: JSON.parse(JSON.stringify(source.config || {}))
|
||||
};
|
||||
const instances = JSON.parse(JSON.stringify(desktopWidgetInstances || []));
|
||||
instances.push(instance);
|
||||
desktopWidgetInstances = instances;
|
||||
saveSettings();
|
||||
const instance = createDesktopWidgetInstance(source.widgetType, source.name + " (Copy)", JSON.parse(JSON.stringify(source.config || {})), widgetInstanceListKey(instanceId));
|
||||
if (!source.enabled)
|
||||
updateDesktopWidgetInstance(instance.id, {
|
||||
enabled: false
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
|
||||
function getDesktopWidgetInstance(instanceId) {
|
||||
return (desktopWidgetInstances || []).find(inst => inst.id === instanceId) || null;
|
||||
for (const key of widgetInstanceListKeys) {
|
||||
const found = (root[key] || []).find(inst => inst.id === instanceId);
|
||||
if (found)
|
||||
return found;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function moveDesktopWidgetInstanceToGroup(instanceId, groupId, newIndexInGroup) {
|
||||
|
||||
@@ -36,9 +36,6 @@ var LOCAL_SPEC = {
|
||||
terminalOverride: {
|
||||
def: ""
|
||||
},
|
||||
perModeWallpaper: {
|
||||
def: false
|
||||
},
|
||||
wallpaperPathLight: {
|
||||
def: ""
|
||||
},
|
||||
@@ -250,9 +247,6 @@ var LOCAL_SPEC = {
|
||||
desktopWidgetGridSettings: {
|
||||
def: {}
|
||||
},
|
||||
desktopWidgetInstancePositions: {
|
||||
def: {}
|
||||
},
|
||||
islandFreePositions: {
|
||||
def: {}
|
||||
},
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
.import "../../DankCommon/Common/settings/SpecUtil.js" as Util
|
||||
.import "DockConfig.js" as DockConfig
|
||||
|
||||
function lockWidgetDefaults() {
|
||||
return Shared.lockWidgetDefaults();
|
||||
}
|
||||
|
||||
var LOCAL_SPEC = {
|
||||
dockConfigs: {
|
||||
def: [DockConfig.create("dock", "Dock")]
|
||||
@@ -1143,24 +1147,6 @@ var LOCAL_SPEC = {
|
||||
modalDarkenBackground: {
|
||||
def: true
|
||||
},
|
||||
lockScreenShowSystemIcons: {
|
||||
def: true
|
||||
},
|
||||
lockScreenShowTime: {
|
||||
def: true
|
||||
},
|
||||
lockScreenClockStyle: {
|
||||
def: "horizontal"
|
||||
},
|
||||
lockScreenShowDate: {
|
||||
def: true
|
||||
},
|
||||
lockScreenShowPasswordField: {
|
||||
def: true
|
||||
},
|
||||
lockScreenShowMediaPlayer: {
|
||||
def: true
|
||||
},
|
||||
lockScreenPowerOffMonitorsOnLock: {
|
||||
def: false
|
||||
},
|
||||
@@ -1204,9 +1190,6 @@ var LOCAL_SPEC = {
|
||||
lockScreenInactiveColor: {
|
||||
def: "#000000"
|
||||
},
|
||||
lockScreenNotificationMode: {
|
||||
def: 0
|
||||
},
|
||||
lockScreenVideoEnabled: {
|
||||
def: false
|
||||
},
|
||||
@@ -1379,9 +1362,6 @@ var LOCAL_SPEC = {
|
||||
updaterIgnoredPackages: {
|
||||
def: []
|
||||
},
|
||||
displayNameMode: {
|
||||
def: "system"
|
||||
},
|
||||
screenPreferences: {
|
||||
def: {}
|
||||
},
|
||||
|
||||
@@ -713,9 +713,52 @@ function migrateToVersion(obj, targetVersion) {
|
||||
settings.configVersion = 37;
|
||||
}
|
||||
|
||||
if (currentVersion < 38 && targetVersion >= 38) {
|
||||
migrateLockScreenWidgets(settings);
|
||||
settings.configVersion = 38;
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
var LOCK_WIDGET_MOVED_KEYS = ["lockScreenShowSystemIcons", "lockScreenShowTime", "lockScreenClockStyle", "lockScreenShowDate", "lockScreenShowPasswordField", "lockScreenShowMediaPlayer", "lockScreenNotificationMode"];
|
||||
var LOCK_WIDGET_GREETER_KEYS = ["lockScreenShowPowerActions", "lockScreenShowProfileImage", "lockScreenShowWeather"];
|
||||
|
||||
function migrateLockScreenWidgets(settings) {
|
||||
if (Array.isArray(settings.lockScreenWidgetInstances))
|
||||
return;
|
||||
const present = key => key in settings;
|
||||
if (!LOCK_WIDGET_MOVED_KEYS.concat(LOCK_WIDGET_GREETER_KEYS).some(present))
|
||||
return;
|
||||
const instances = SpecModule.lockWidgetDefaults();
|
||||
const byId = id => instances.find(inst => inst.id === id);
|
||||
if (present("lockScreenShowTime"))
|
||||
byId("lock_clock").enabled = !!settings.lockScreenShowTime;
|
||||
if (present("lockScreenClockStyle"))
|
||||
byId("lock_clock").config.style = settings.lockScreenClockStyle;
|
||||
if (present("lockScreenShowDate"))
|
||||
byId("lock_date").enabled = !!settings.lockScreenShowDate;
|
||||
byId("lock_auth").config.profileVisibility = settings.lockScreenShowProfileImage === false ? "never" : "always";
|
||||
byId("lock_auth").config.passwordVisibility = settings.lockScreenShowPasswordField === false ? "typing" : "always";
|
||||
if (present("lockScreenNotificationMode")) {
|
||||
const mode = Number(settings.lockScreenNotificationMode) || 0;
|
||||
byId("lock_notifications").enabled = mode > 0;
|
||||
if (mode > 0)
|
||||
byId("lock_notifications").config.mode = mode;
|
||||
}
|
||||
if (present("lockScreenShowSystemIcons"))
|
||||
byId("lock_status").enabled = !!settings.lockScreenShowSystemIcons;
|
||||
if (present("lockScreenShowMediaPlayer"))
|
||||
byId("lock_status").config.showMediaPlayer = !!settings.lockScreenShowMediaPlayer;
|
||||
if (present("lockScreenShowWeather"))
|
||||
byId("lock_status").config.showWeather = !!settings.lockScreenShowWeather;
|
||||
if (present("lockScreenShowPowerActions"))
|
||||
byId("lock_power").enabled = !!settings.lockScreenShowPowerActions;
|
||||
settings.lockScreenWidgetInstances = instances;
|
||||
for (const key of LOCK_WIDGET_MOVED_KEYS)
|
||||
delete settings[key];
|
||||
}
|
||||
|
||||
function migrateControlCenterHeader(widgets, fixedHeader, columns) {
|
||||
const header = widgets.filter(widget => CC_HEADER_IDS.includes(widget?.id));
|
||||
if (!fixedHeader && header.length === 0)
|
||||
|
||||
@@ -426,7 +426,7 @@ FocusScope {
|
||||
"plugins_manage": "PluginsManageTab.qml"
|
||||
})
|
||||
|
||||
readonly property var pagesWithParentModal: ["dankbar_widgets", "window_rules", "notification_rules", "display_config", "users", "time_weather", "weather", "lock_screen", "greeter", "dank_dash", "wallpaper_cycling", "theme_schedule", "surface_shadows", "keybinds", "dankbar_settings", "dankbar_appearance", "bar_widget", "dock_general", "dock_widgets", "dock_appearance", "dock_advanced", "launcher", "theme", "theme_apps", "media_player", "desktop_widgets", "autostart", "compositor_layout", "updater"]
|
||||
readonly property var pagesWithParentModal: ["dankbar_widgets", "window_rules", "notification_rules", "display_config", "users", "time_weather", "weather", "lock_screen", "greeter", "dank_dash", "wallpaper_cycling", "theme_schedule", "surface_shadows", "keybinds", "dankbar_settings", "dankbar_appearance", "bar_widget", "dock_general", "dock_widgets", "dock_appearance", "dock_advanced", "launcher", "theme", "theme_apps", "media_player", "desktop_widgets", "desktop_widget", "autostart", "compositor_layout", "updater"]
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -11,6 +11,7 @@ Item {
|
||||
property bool vertical: false
|
||||
property bool canAdd: true
|
||||
property bool hasWidgets: true
|
||||
property bool canClear: true
|
||||
property bool hasOptions: false
|
||||
property bool hasCustomActions: false
|
||||
property string pendingAction: ""
|
||||
@@ -172,7 +173,7 @@ Item {
|
||||
iconName: "clear_all"
|
||||
armedColor: Theme.error
|
||||
armedTextColor: Theme.onError
|
||||
visible: root.hasWidgets
|
||||
visible: root.hasWidgets && root.canClear
|
||||
onConfirmed: root.clearRequested()
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Modules.Plugins
|
||||
import qs.Modules.BuiltinDesktopPlugins
|
||||
|
||||
Variants {
|
||||
id: root
|
||||
@@ -12,7 +11,7 @@ Variants {
|
||||
Component.onCompleted: Qt.callLater(autoEnablePluginsForInstances)
|
||||
|
||||
function autoEnablePluginsForInstances() {
|
||||
const instances = SettingsData.desktopWidgetInstances || [];
|
||||
const instances = (SettingsData.desktopWidgetInstances || []).concat(SettingsData.lockScreenWidgetInstances || []);
|
||||
const pluginTypes = new Set();
|
||||
|
||||
for (const inst of instances) {
|
||||
@@ -45,17 +44,6 @@ Variants {
|
||||
required property var modelData
|
||||
|
||||
readonly property var screen: modelData
|
||||
readonly property string screenKey: SettingsData.getScreenDisplayName(screen)
|
||||
|
||||
function shouldShowOnScreen(prefs) {
|
||||
if (!Array.isArray(prefs) || prefs.length === 0 || prefs.includes("all"))
|
||||
return true;
|
||||
return prefs.some(p => {
|
||||
if (typeof p === "string")
|
||||
return p === screenKey || p === modelData.name;
|
||||
return p?.name === modelData.name || p === screenKey;
|
||||
});
|
||||
}
|
||||
|
||||
// Layer surfaces stack by map order, so recreate them in list order on
|
||||
// reorder/enable/display-pref changes or once a plugin component loads (#2715).
|
||||
@@ -104,14 +92,6 @@ Variants {
|
||||
onTriggered: screenDelegate.rebuilding = false
|
||||
}
|
||||
|
||||
property Component clockComponent: Component {
|
||||
DesktopClockWidget {}
|
||||
}
|
||||
|
||||
property Component systemMonitorComponent: Component {
|
||||
SystemMonitorWidget {}
|
||||
}
|
||||
|
||||
property Instantiator widgetInstantiator: Instantiator {
|
||||
model: ScriptModel {
|
||||
objectProp: "id"
|
||||
@@ -133,23 +113,12 @@ Variants {
|
||||
if (!liveInstanceData.enabled)
|
||||
return false;
|
||||
const prefs = liveInstanceData.config?.displayPreferences ?? ["all"];
|
||||
return screenDelegate.shouldShowOnScreen(prefs);
|
||||
return DesktopWidgetRegistry.showsOnScreen(prefs, screenDelegate.screen);
|
||||
}
|
||||
|
||||
pluginId: liveInstanceData.widgetType
|
||||
instanceId: instanceIdRef
|
||||
instanceData: liveInstanceData
|
||||
builtinComponent: {
|
||||
switch (liveInstanceData.widgetType) {
|
||||
case "desktopClock":
|
||||
return screenDelegate.clockComponent;
|
||||
case "systemMonitor":
|
||||
return screenDelegate.systemMonitorComponent;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
pluginService: (liveInstanceData.widgetType !== "desktopClock" && liveInstanceData.widgetType !== "systemMonitor") ? PluginService : null
|
||||
screen: screenDelegate.screen
|
||||
widgetEnabled: shouldBeVisible
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ Scope {
|
||||
readonly property var log: Log.scoped("Lock")
|
||||
|
||||
property string sharedPasswordBuffer: ""
|
||||
property bool inputRevealed: false
|
||||
property bool shouldLock: false
|
||||
|
||||
onSharedPasswordBufferChanged: {
|
||||
@@ -22,6 +23,7 @@ Scope {
|
||||
}
|
||||
|
||||
onShouldLockChanged: {
|
||||
inputRevealed = false;
|
||||
IdleService.isShellLocked = shouldLock;
|
||||
}
|
||||
|
||||
@@ -223,6 +225,12 @@ Scope {
|
||||
resumeRelockTimer.restart();
|
||||
}
|
||||
|
||||
function onLockEditorRequested() {
|
||||
if (shouldLock)
|
||||
return;
|
||||
demoWindow.showDemo();
|
||||
}
|
||||
|
||||
function onLoginctlStateChanged() {
|
||||
if (SessionService.active && pendingLock) {
|
||||
pendingLock = false;
|
||||
@@ -285,6 +293,8 @@ Scope {
|
||||
lock: sessionLock
|
||||
pam: sharedPam
|
||||
sharedPasswordBuffer: root.sharedPasswordBuffer
|
||||
inputRevealed: root.inputRevealed
|
||||
onInputRevealRequested: revealed => root.inputRevealed = revealed
|
||||
screenName: lockSurface.currentScreenName
|
||||
isLocked: shouldLock
|
||||
onUnlockRequested: root.unlock()
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import QtQuick
|
||||
import QtQuick.Window
|
||||
import "LockPlacement.js" as Placement
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property Item sourceItem
|
||||
property string renderKey: ""
|
||||
property bool ready: false
|
||||
property bool busy: false
|
||||
property string capturedKey: ""
|
||||
property var capture: null
|
||||
readonly property size sourceSize: Qt.size(sourceItem.width, sourceItem.height)
|
||||
readonly property real sampleScale: 128 / Math.max(1, sourceItem.width, sourceItem.height)
|
||||
readonly property int sampleWidth: Math.max(1, Math.round(sourceItem.width * sampleScale))
|
||||
readonly property int sampleHeight: Math.max(1, Math.round(sourceItem.height * sampleScale))
|
||||
readonly property bool sourceVisible: sourceItem.visible && (sourceItem.Window.window?.visible ?? false)
|
||||
|
||||
signal sampled(var luminances, int sampleWidth, int sampleHeight)
|
||||
signal failed
|
||||
|
||||
onRenderKeyChanged: schedule.restart()
|
||||
onReadyChanged: schedule.restart()
|
||||
onSourceVisibleChanged: schedule.restart()
|
||||
onSourceSizeChanged: schedule.restart()
|
||||
Component.onCompleted: schedule.restart()
|
||||
|
||||
function finish() {
|
||||
if (capture)
|
||||
canvas.unloadImage(capture.url);
|
||||
capture = null;
|
||||
busy = false;
|
||||
if (capturedKey !== renderKey)
|
||||
schedule.restart();
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: schedule
|
||||
interval: 0
|
||||
onTriggered: {
|
||||
if (!root.ready || !root.sourceVisible || root.busy || !canvas.available || root.sourceItem.width <= 0 || root.sourceItem.height <= 0)
|
||||
return;
|
||||
root.capturedKey = root.renderKey;
|
||||
root.busy = true;
|
||||
const started = root.sourceItem.grabToImage(result => {
|
||||
if (!root.ready || root.capturedKey !== root.renderKey) {
|
||||
root.finish();
|
||||
return;
|
||||
}
|
||||
root.capture = result;
|
||||
canvas.loadImage(result.url);
|
||||
if (canvas.isImageLoaded(result.url))
|
||||
canvas.requestPaint();
|
||||
}, Qt.size(root.sampleWidth, root.sampleHeight));
|
||||
if (!started) {
|
||||
root.finish();
|
||||
root.failed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Canvas {
|
||||
id: canvas
|
||||
width: root.sampleWidth
|
||||
height: root.sampleHeight
|
||||
opacity: 0
|
||||
onAvailableChanged: schedule.restart()
|
||||
onImageLoaded: requestPaint()
|
||||
onPaint: {
|
||||
if (!root.capture)
|
||||
return;
|
||||
if (isImageError(root.capture.url)) {
|
||||
root.finish();
|
||||
root.failed();
|
||||
return;
|
||||
}
|
||||
if (!root.ready || root.capturedKey !== root.renderKey) {
|
||||
root.finish();
|
||||
return;
|
||||
}
|
||||
if (!isImageLoaded(root.capture.url))
|
||||
return;
|
||||
const context = getContext("2d");
|
||||
context.clearRect(0, 0, width, height);
|
||||
context.drawImage(root.capture.url, 0, 0, width, height);
|
||||
const pixels = context.getImageData(0, 0, width, height).data;
|
||||
const luminances = [];
|
||||
for (let i = 0; i < pixels.length; i += 4)
|
||||
luminances.push(Placement.luminance(pixels[i] / 255, pixels[i + 1] / 255, pixels[i + 2] / 255));
|
||||
context.clearRect(0, 0, width, height);
|
||||
root.finish();
|
||||
root.sampled(luminances, width, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
.pragma library
|
||||
|
||||
function luminance(red, green, blue) {
|
||||
const linear = value => value <= 0.04045 ? value / 12.92 : Math.pow((value + 0.055) / 1.055, 2.4);
|
||||
return 0.2126 * linear(red) + 0.7152 * linear(green) + 0.0722 * linear(blue);
|
||||
}
|
||||
|
||||
function intersects(a, b) {
|
||||
return a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;
|
||||
}
|
||||
|
||||
function candidates(width, height, blockWidth, blockHeight, inset, obstacles) {
|
||||
const right = width - inset - blockWidth;
|
||||
const bottom = height - inset - blockHeight;
|
||||
if (right < inset || bottom < inset)
|
||||
return [];
|
||||
const result = [];
|
||||
for (const y of [inset, (height - blockHeight) / 2, bottom]) {
|
||||
for (const x of [(width - blockWidth) / 2, inset, right]) {
|
||||
const rect = {
|
||||
x,
|
||||
y,
|
||||
width: blockWidth,
|
||||
height: blockHeight
|
||||
};
|
||||
if (!obstacles.some(obstacle => intersects(rect, obstacle)))
|
||||
result.push(rect);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function choose(luma, sampleWidth, sampleHeight, width, height, choices, foregrounds) {
|
||||
if (!choices.length || !foregrounds.length || !luma.length || width <= 0 || height <= 0)
|
||||
return null;
|
||||
let best = null;
|
||||
let bestScore = -Infinity;
|
||||
for (const rect of choices) {
|
||||
const left = Math.max(0, Math.floor(rect.x * sampleWidth / width));
|
||||
const top = Math.max(0, Math.floor(rect.y * sampleHeight / height));
|
||||
const right = Math.min(sampleWidth, Math.ceil((rect.x + rect.width) * sampleWidth / width));
|
||||
const bottom = Math.min(sampleHeight, Math.ceil((rect.y + rect.height) * sampleHeight / height));
|
||||
const contrasts = [];
|
||||
let edges = 0;
|
||||
for (let y = top; y < bottom; y++) {
|
||||
for (let x = left; x < right; x++) {
|
||||
const value = luma[y * sampleWidth + x];
|
||||
contrasts.push(Math.min(...foregrounds.map(foreground => (Math.max(value, foreground) + 0.05) / (Math.min(value, foreground) + 0.05))));
|
||||
if (x > left)
|
||||
edges += Math.abs(value - luma[y * sampleWidth + x - 1]);
|
||||
if (y > top)
|
||||
edges += Math.abs(value - luma[(y - 1) * sampleWidth + x]);
|
||||
}
|
||||
}
|
||||
if (!contrasts.length)
|
||||
continue;
|
||||
contrasts.sort((a, b) => a - b);
|
||||
const score = contrasts[Math.floor(contrasts.length / 10)] - edges / contrasts.length * 4;
|
||||
if (score <= bestScore)
|
||||
continue;
|
||||
bestScore = score;
|
||||
best = rect;
|
||||
}
|
||||
return best;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Services
|
||||
|
||||
Variants {
|
||||
model: Quickshell.screens
|
||||
|
||||
Scope {
|
||||
id: scope
|
||||
|
||||
required property var modelData
|
||||
readonly property string backgroundKey: LockPlacementService.backgroundKey(modelData.name, modelData.width, modelData.height)
|
||||
property string failedKey: ""
|
||||
property bool sampling: false
|
||||
onBackgroundKeyChanged: failedKey = ""
|
||||
|
||||
Loader {
|
||||
id: preparation
|
||||
active: scope.sampling || (!IdleService.isShellLocked && LockPlacementService.neededFor(scope.modelData) && scope.failedKey !== scope.backgroundKey && !LockPlacementService.hasSample(scope.modelData.name, scope.backgroundKey))
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: PanelWindow {
|
||||
screen: scope.modelData
|
||||
anchors.top: true
|
||||
anchors.bottom: true
|
||||
anchors.left: true
|
||||
anchors.right: true
|
||||
color: "transparent"
|
||||
WlrLayershell.namespace: "dms:lock-placement"
|
||||
WlrLayershell.layer: WlrLayer.Background
|
||||
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
mask: Region {}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
opacity: 0
|
||||
|
||||
LockScreenBackground {
|
||||
id: background
|
||||
anchors.fill: parent
|
||||
screenName: scope.modelData.name
|
||||
}
|
||||
|
||||
LockBackgroundSampler {
|
||||
id: sampler
|
||||
sourceItem: background
|
||||
ready: background.ready && !IdleService.isShellLocked
|
||||
renderKey: scope.backgroundKey
|
||||
onBusyChanged: scope.sampling = busy
|
||||
onFailed: scope.failedKey = scope.backgroundKey
|
||||
onSampled: (luminances, sampleWidth, sampleHeight) => LockPlacementService.storeSample(scope.modelData.name, scope.backgroundKey, luminances, sampleWidth, sampleHeight)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.DankCommon.Session
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property string screenName: ""
|
||||
readonly property bool hasCustomWallpaper: SettingsData.lockScreenWallpaperPath !== ""
|
||||
|
||||
function encodeFileUrl(path) {
|
||||
if (!path)
|
||||
return "";
|
||||
return "file://" + path.split('/').map(s => encodeURIComponent(s)).join('/');
|
||||
}
|
||||
readonly property bool ready: wallpaperBackground.active ? (wallpaperBackground.item?.ready ?? false) : (backdropLoader.item?.ready || backdropLoader.item?.liveReady || false)
|
||||
readonly property string renderKey: LockPlacementService.backgroundKey(screenName, width, height)
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: SettingsData.effectiveWallpaperBackgroundColor
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: backdropLoader
|
||||
anchors.fill: parent
|
||||
active: {
|
||||
if (root.hasCustomWallpaper)
|
||||
return false;
|
||||
var currentWallpaper = SessionData.getMonitorWallpaper(screenName);
|
||||
return !currentWallpaper || (currentWallpaper && currentWallpaper.startsWith("#"));
|
||||
}
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: DankBackdrop {
|
||||
screenName: root.screenName
|
||||
blur: Theme.lockScreenBlur
|
||||
blurMax: Theme.lockScreenBlurMax
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: wallpaperBackground
|
||||
anchors.fill: parent
|
||||
|
||||
readonly property string wallpaperSource: {
|
||||
if (root.hasCustomWallpaper)
|
||||
return root.encodeFileUrl(SettingsData.lockScreenWallpaperPath);
|
||||
var w = SessionData.getMonitorWallpaper(screenName);
|
||||
return (w && !w.startsWith("#")) ? encodeFileUrl(w) : "";
|
||||
}
|
||||
readonly property string fillModeName: {
|
||||
if (SettingsData.lockScreenWallpaperFillMode !== "")
|
||||
return SettingsData.lockScreenWallpaperFillMode;
|
||||
return root.hasCustomWallpaper ? "Fill" : SessionData.getMonitorWallpaperFillMode(root.screenName);
|
||||
}
|
||||
|
||||
readonly property real screenScale: CompositorService.getScreenScale(Quickshell.screens.find(s => s.name === root.screenName) ?? null)
|
||||
readonly property size decodeSize: Qt.size(Math.round(width * screenScale), Math.round(height * screenScale))
|
||||
|
||||
active: wallpaperSource !== "" && width > 0 && height > 0
|
||||
asynchronous: false
|
||||
|
||||
sourceComponent: fillModeName === "Scrolling" ? scrollWallpaperComp : plainWallpaperComp
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect {
|
||||
autoPaddingEnabled: false
|
||||
blurEnabled: true
|
||||
blur: Theme.lockScreenBlur
|
||||
blurMax: Theme.lockScreenBlurMax
|
||||
blurMultiplier: 1
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: LockMetrics.effectsDuration
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Theme.expressiveCurves.expressiveEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: plainWallpaperComp
|
||||
Image {
|
||||
readonly property bool ready: status === Image.Ready || status === Image.Error
|
||||
source: wallpaperBackground.wallpaperSource
|
||||
sourceSize: wallpaperBackground.decodeSize
|
||||
fillMode: Theme.getFillMode(wallpaperBackground.fillModeName)
|
||||
smooth: true
|
||||
cache: true
|
||||
asynchronous: false
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: scrollWallpaperComp
|
||||
Item {
|
||||
readonly property bool ready: scrollSource.status === Image.Ready || scrollSource.status === Image.Error
|
||||
Image {
|
||||
id: scrollSource
|
||||
anchors.fill: parent
|
||||
visible: false
|
||||
source: wallpaperBackground.wallpaperSource
|
||||
sourceSize: wallpaperBackground.decodeSize
|
||||
asynchronous: false
|
||||
cache: true
|
||||
}
|
||||
|
||||
ShaderEffectSource {
|
||||
id: scrollSrc
|
||||
sourceItem: scrollSource
|
||||
hideSource: true
|
||||
live: false
|
||||
}
|
||||
|
||||
ShaderEffect {
|
||||
anchors.fill: parent
|
||||
|
||||
readonly property var scrollPos: SessionData.getMonitorScrollPosition(screenName)
|
||||
|
||||
property variant source1: scrollSrc
|
||||
property variant source2: scrollSrc
|
||||
property real progress: 0.0
|
||||
property real fillMode: Theme.getShaderFillMode(wallpaperBackground.fillModeName)
|
||||
property real scrollX: scrollPos.scrollX
|
||||
property real scrollY: scrollPos.scrollY
|
||||
property real imageWidth1: scrollSource.implicitWidth > 0 ? scrollSource.implicitWidth : 1
|
||||
property real imageHeight1: scrollSource.implicitHeight > 0 ? scrollSource.implicitHeight : 1
|
||||
property real imageWidth2: imageWidth1
|
||||
property real imageHeight2: imageHeight1
|
||||
property real screenWidth: width > 0 ? width : 1
|
||||
property real screenHeight: height > 0 ? height : 1
|
||||
property vector4d fillColor: Qt.vector4d(0, 0, 0, 1)
|
||||
|
||||
fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/wp_fade.frag.qsb")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Theme.screenOffColor
|
||||
opacity: Theme.lockScreenScrimAlpha
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -5,27 +5,12 @@ import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Services
|
||||
|
||||
PanelWindow {
|
||||
Scope {
|
||||
id: root
|
||||
readonly property var log: Log.scoped("LockScreenDemo")
|
||||
|
||||
property bool demoActive: false
|
||||
|
||||
visible: demoActive
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
bottom: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
|
||||
|
||||
color: "transparent"
|
||||
|
||||
function showDemo(): void {
|
||||
log.debug("Showing lock screen demo");
|
||||
demoActive = true;
|
||||
@@ -36,13 +21,37 @@ PanelWindow {
|
||||
demoActive = false;
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
active: demoActive
|
||||
sourceComponent: LockScreenContent {
|
||||
demoMode: true
|
||||
screenName: root.screen?.name ?? ""
|
||||
onUnlockRequested: root.hideDemo()
|
||||
Variants {
|
||||
model: root.demoActive ? Quickshell.screens : []
|
||||
|
||||
PanelWindow {
|
||||
id: demoWindow
|
||||
|
||||
required property var modelData
|
||||
|
||||
screen: modelData
|
||||
visible: true
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
bottom: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
|
||||
WlrLayershell.namespace: "dms:lock-demo"
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
|
||||
|
||||
color: "transparent"
|
||||
|
||||
LockScreenContent {
|
||||
anchors.fill: parent
|
||||
demoMode: true
|
||||
screenName: demoWindow.screen?.name ?? ""
|
||||
onUnlockRequested: root.hideDemo()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,9 @@ FocusScope {
|
||||
required property var pam
|
||||
required property string sharedPasswordBuffer
|
||||
required property string screenName
|
||||
property bool inputRevealed: false
|
||||
signal inputRevealRequested(bool revealed)
|
||||
|
||||
required property bool isLocked
|
||||
|
||||
signal passwordChanged(string newPassword)
|
||||
@@ -49,6 +52,8 @@ FocusScope {
|
||||
sessionLock: root.lock
|
||||
pam: root.pam
|
||||
passwordBuffer: root.sharedPasswordBuffer
|
||||
inputRevealed: root.inputRevealed
|
||||
onInputRevealRequested: revealed => root.inputRevealRequested(revealed)
|
||||
screenName: root.screenName
|
||||
enabled: !videoScreensaver.active
|
||||
focus: !videoScreensaver.active
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
import qs.Modules.Plugins
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property var instanceData
|
||||
required property var screen
|
||||
required property var hostLayer
|
||||
property var lockHost: null
|
||||
property bool editMode: false
|
||||
|
||||
readonly property string instanceId: instanceData?.id ?? ""
|
||||
readonly property string widgetType: instanceData?.widgetType ?? ""
|
||||
readonly property bool removable: widgetType !== "lockAuth"
|
||||
readonly property bool selected: hostLayer.selectedInstanceId === instanceId
|
||||
readonly property bool interacting: editChrome.item?.interacting ?? false
|
||||
readonly property var contrastColors: content.item?.contrastColors ?? []
|
||||
readonly property bool hasSavedPosition: geometry.hasSavedPosition
|
||||
readonly property bool automaticPlacement: widgetType === "lockClock" && (instanceData?.config?.autoPosition ?? true) && !hasSavedPosition
|
||||
readonly property bool dragging: editChrome.item?.dragging ?? false
|
||||
readonly property var stock: hostLayer.stockRect(widgetType, root)
|
||||
property bool snappedCenterX: false
|
||||
property bool snappedCenterY: false
|
||||
|
||||
signal removeRequested
|
||||
signal optionsRequested
|
||||
|
||||
readonly property bool loaded: content.item !== null
|
||||
onLoadedChanged: {
|
||||
if (loaded)
|
||||
Qt.callLater(() => hostLayer.placeIfPending(root));
|
||||
}
|
||||
|
||||
x: geometry.widgetX
|
||||
y: geometry.widgetY
|
||||
width: geometry.widgetWidth
|
||||
height: geometry.widgetHeight
|
||||
activeFocusOnTab: editMode
|
||||
Accessible.name: instanceData?.name || I18n.tr("Widget")
|
||||
Accessible.role: Accessible.Button
|
||||
onActiveFocusChanged: {
|
||||
if (editMode && activeFocus)
|
||||
hostLayer.selectedInstanceId = instanceId;
|
||||
}
|
||||
Keys.onReturnPressed: {
|
||||
if (editMode)
|
||||
optionsRequested();
|
||||
}
|
||||
Keys.onSpacePressed: {
|
||||
if (editMode)
|
||||
optionsRequested();
|
||||
}
|
||||
|
||||
function guideFor(value, guides) {
|
||||
const threshold = geometry.gridEnabled ? geometry.gridSize : Theme.spacingXL;
|
||||
let best;
|
||||
let bestDistance = threshold;
|
||||
for (const guide of guides) {
|
||||
const distance = Math.abs(value - guide);
|
||||
if (distance > bestDistance)
|
||||
continue;
|
||||
best = guide;
|
||||
bestDistance = distance;
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
function guidedPosition(startX, startY, dx, dy) {
|
||||
const rawX = Math.max(0, Math.min(startX + dx, geometry.screenWidth - width));
|
||||
const rawY = Math.max(0, Math.min(startY + dy, geometry.screenHeight - height));
|
||||
const snapped = geometry.dragMoveTo(startX, startY, dx, dy);
|
||||
const centerX = (hostLayer.width - width) / 2;
|
||||
const centerY = (hostLayer.height - height) / 2;
|
||||
const guidesX = [centerX];
|
||||
const guidesY = [centerY];
|
||||
if (stock) {
|
||||
guidesX.push(stock.x);
|
||||
guidesY.push(stock.y);
|
||||
}
|
||||
const guideX = guideFor(rawX, guidesX);
|
||||
const guideY = guideFor(rawY, guidesY);
|
||||
snappedCenterX = guideX === centerX;
|
||||
snappedCenterY = guideY === centerY;
|
||||
return Qt.point(guideX ?? snapped.x, guideY ?? snapped.y);
|
||||
}
|
||||
|
||||
function commitPosition(finalX, finalY) {
|
||||
if (stock && finalX === stock.x && finalY === stock.y) {
|
||||
SessionData.resetDesktopWidgetInstanceGeometry(instanceId, ["x", "y"]);
|
||||
return;
|
||||
}
|
||||
geometry.savePosition(finalX, finalY);
|
||||
if (widgetType === "lockClock" && (instanceData?.config?.autoPosition ?? true))
|
||||
SettingsData.updateDesktopWidgetInstanceConfig(instanceId, {
|
||||
autoPosition: false
|
||||
});
|
||||
}
|
||||
|
||||
DesktopWidgetGeometry {
|
||||
id: geometry
|
||||
instanceId: root.instanceId
|
||||
instanceData: root.instanceData
|
||||
screen: root.screen
|
||||
minWidth: content.contentMinWidth
|
||||
minHeight: content.contentMinHeight
|
||||
forceSquare: content.contentForceSquare
|
||||
defaultWidth: content.contentDefaultWidth
|
||||
defaultHeight: content.contentDefaultHeight
|
||||
defaultX: root.stock ? root.stock.x : screenWidth / 2 - savedWidth / 2
|
||||
defaultY: root.stock ? root.stock.y : screenHeight / 2 - savedHeight / 2
|
||||
}
|
||||
|
||||
DesktopWidgetContent {
|
||||
id: content
|
||||
anchors.fill: parent
|
||||
active: root.visible
|
||||
focus: root.widgetType === "lockAuth"
|
||||
pluginId: root.widgetType
|
||||
instanceId: root.instanceId
|
||||
instanceData: root.instanceData
|
||||
screen: root.screen
|
||||
geometry: geometry
|
||||
lockScreen: true
|
||||
lockHost: root.lockHost
|
||||
persistDefaults: false
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: editChrome
|
||||
anchors.fill: parent
|
||||
active: root.editMode
|
||||
|
||||
sourceComponent: Item {
|
||||
readonly property bool interacting: dragArea.pressed || chrome.resizing
|
||||
readonly property bool dragging: dragArea.pressed
|
||||
|
||||
MouseArea {
|
||||
id: dragArea
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton
|
||||
cursorShape: pressed ? Qt.ClosedHandCursor : Qt.OpenHandCursor
|
||||
|
||||
property point startPos
|
||||
property real startX
|
||||
property real startY
|
||||
property bool moved: false
|
||||
|
||||
onPressed: mouse => {
|
||||
root.hostLayer.selectedInstanceId = root.instanceId;
|
||||
root.forceActiveFocus();
|
||||
moved = false;
|
||||
startPos = mapToItem(root.parent, mouse.x, mouse.y);
|
||||
startX = geometry.widgetX;
|
||||
startY = geometry.widgetY;
|
||||
geometry.dragOverrideX = startX;
|
||||
geometry.dragOverrideY = startY;
|
||||
}
|
||||
|
||||
onPositionChanged: mouse => {
|
||||
if (!pressed)
|
||||
return;
|
||||
const current = mapToItem(root.parent, mouse.x, mouse.y);
|
||||
if (!moved && Math.hypot(current.x - startPos.x, current.y - startPos.y) < Qt.styleHints.startDragDistance)
|
||||
return;
|
||||
moved = true;
|
||||
const next = root.guidedPosition(startX, startY, current.x - startPos.x, current.y - startPos.y);
|
||||
geometry.dragOverrideX = next.x;
|
||||
geometry.dragOverrideY = next.y;
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
const finalX = geometry.dragOverrideX;
|
||||
const finalY = geometry.dragOverrideY;
|
||||
geometry.clearDragOverrides();
|
||||
root.snappedCenterX = false;
|
||||
root.snappedCenterY = false;
|
||||
if (moved) {
|
||||
root.commitPosition(finalX, finalY);
|
||||
return;
|
||||
}
|
||||
root.optionsRequested();
|
||||
}
|
||||
|
||||
onCanceled: {
|
||||
geometry.clearDragOverrides();
|
||||
root.snappedCenterX = false;
|
||||
root.snappedCenterY = false;
|
||||
}
|
||||
}
|
||||
|
||||
DankGridEditChrome {
|
||||
id: chrome
|
||||
|
||||
property real startWidth
|
||||
property real startHeight
|
||||
property point startPos
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: -contentInset - Theme.spacingL
|
||||
visible: root.selected
|
||||
cornerRadius: Theme.cornerRadius
|
||||
dragging: dragArea.pressed
|
||||
removable: root.removable
|
||||
hasOptions: true
|
||||
cornerResize: content.contentResizable
|
||||
sizeText: Math.round(root.width) + "×" + Math.round(root.height)
|
||||
onRemoveRequested: root.removeRequested()
|
||||
onOptionsRequested: root.optionsRequested()
|
||||
onResizeStarted: (px, py) => {
|
||||
startPos = Qt.point(px, py);
|
||||
startWidth = geometry.widgetWidth;
|
||||
startHeight = geometry.widgetHeight;
|
||||
geometry.dragOverrideW = startWidth;
|
||||
geometry.dragOverrideH = startHeight;
|
||||
resizing = true;
|
||||
}
|
||||
onResizeMoved: (px, py) => {
|
||||
const next = geometry.dragResizeTo(startWidth, startHeight, px - startPos.x, py - startPos.y);
|
||||
geometry.dragOverrideW = next.width;
|
||||
geometry.dragOverrideH = next.height;
|
||||
}
|
||||
onResizeEnded: {
|
||||
resizing = false;
|
||||
geometry.saveSize(geometry.dragOverrideW, geometry.dragOverrideH);
|
||||
geometry.clearDragOverrides();
|
||||
}
|
||||
onResizeCanceled: {
|
||||
resizing = false;
|
||||
geometry.clearDragOverrides();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Modules.Plugins
|
||||
import "LockPlacement.js" as Placement
|
||||
import qs.DankCommon.Session
|
||||
|
||||
FocusScope {
|
||||
id: root
|
||||
|
||||
required property string screenName
|
||||
property var lockHost: null
|
||||
property bool editMode: false
|
||||
property string selectedInstanceId: ""
|
||||
|
||||
readonly property var screen: Quickshell.screens.find(s => s.name === screenName) ?? null
|
||||
readonly property var instances: SettingsData.lockScreenWidgetInstances || []
|
||||
readonly property string screenKey: SettingsData.getScreenDisplayName(screen)
|
||||
property var _gridSettingsTrigger: SessionData.desktopWidgetGridSettings
|
||||
readonly property int gridSize: {
|
||||
void _gridSettingsTrigger;
|
||||
return SessionData.getDesktopWidgetGridSetting(screenKey, "size", 40);
|
||||
}
|
||||
readonly property bool gridEnabled: {
|
||||
void _gridSettingsTrigger;
|
||||
return SessionData.getDesktopWidgetGridSetting(screenKey, "enabled", false);
|
||||
}
|
||||
|
||||
signal focusStolen
|
||||
signal optionsRequested(var instanceData)
|
||||
|
||||
// Widgets added from the editor start in the nearest free area instead of on top of another widget.
|
||||
property var pendingIds: []
|
||||
property real bottomInset: 0
|
||||
|
||||
function rectOf(item) {
|
||||
return {
|
||||
x: item.x,
|
||||
y: item.y,
|
||||
width: item.width,
|
||||
height: item.height
|
||||
};
|
||||
}
|
||||
|
||||
function placeIfPending(item) {
|
||||
if (!pendingIds.includes(item.instanceId))
|
||||
return;
|
||||
pendingIds = pendingIds.filter(id => id !== item.instanceId);
|
||||
if (!editMode || item.width <= 0 || item.height <= 0)
|
||||
return;
|
||||
const obstacles = [];
|
||||
for (let i = 0; i < repeater.count; i++) {
|
||||
const other = repeater.itemAt(i);
|
||||
if (other && other !== item && other.visible)
|
||||
obstacles.push(rectOf(other));
|
||||
}
|
||||
const rect = rectOf(item);
|
||||
if (!obstacles.some(obstacle => Placement.intersects(rect, obstacle)))
|
||||
return;
|
||||
const free = Placement.candidates(width, height - bottomInset, item.width, item.height, Theme.spacingXL, obstacles);
|
||||
if (!free.length)
|
||||
return;
|
||||
const centerX = width / 2;
|
||||
const centerY = height / 2;
|
||||
free.sort((a, b) => Math.hypot(a.x + a.width / 2 - centerX, a.y + a.height / 2 - centerY) - Math.hypot(b.x + b.width / 2 - centerX, b.y + b.height / 2 - centerY));
|
||||
item.commitPosition(free[0].x, free[0].y);
|
||||
}
|
||||
|
||||
function toggleGrid() {
|
||||
SessionData.setDesktopWidgetGridSetting(screenKey, "enabled", !gridEnabled);
|
||||
}
|
||||
|
||||
function stepGrid(delta) {
|
||||
SessionData.setDesktopWidgetGridSetting(screenKey, "size", Math.max(10, Math.min(200, gridSize + delta)));
|
||||
}
|
||||
|
||||
activeFocusOnTab: false
|
||||
onActiveFocusChanged: {
|
||||
if (activeFocus)
|
||||
focusStolen();
|
||||
}
|
||||
|
||||
function itemOfType(widgetType) {
|
||||
for (let i = 0; i < repeater.count; i++) {
|
||||
const item = repeater.itemAt(i);
|
||||
if (item && item.visible && item.widgetType === widgetType)
|
||||
return item;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
readonly property real edgeInset: Theme.spacingXL * 2
|
||||
property bool completed: false
|
||||
property var lockedPositions: ({})
|
||||
readonly property string backgroundKey: LockPlacementService.backgroundKey(screenName, width, height)
|
||||
readonly property var autoPositions: editMode && completed ? currentPlacement() : lockedPositions
|
||||
|
||||
function currentPlacement() {
|
||||
return LockPlacementService.layoutFor(screenName, LockPlacementService.sampleFor(screenName, backgroundKey), placementKey, placeClocks);
|
||||
}
|
||||
|
||||
function refreshPlacement() {
|
||||
if (!completed || editMode || width <= 0 || height <= 0)
|
||||
return;
|
||||
lockedPositions = currentPlacement();
|
||||
SessionData.setLockScreenAutoPositions(screenKey, {
|
||||
width: width,
|
||||
height: height,
|
||||
positions: lockedPositions
|
||||
});
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
completed = true;
|
||||
refreshPlacement();
|
||||
}
|
||||
onWidthChanged: refreshPlacement()
|
||||
onHeightChanged: refreshPlacement()
|
||||
onEditModeChanged: refreshPlacement()
|
||||
|
||||
readonly property string placementKey: JSON.stringify(instances.map(instance => {
|
||||
const item = itemById(instance.id);
|
||||
return [instance.id, instance.enabled, instance.config, item?.width, item?.height, item?.automaticPlacement ? null : item?.x, item?.automaticPlacement ? null : item?.y, item?.hasSavedPosition, item?.contrastColors];
|
||||
}).concat([width, height, Theme.primary.toString(), Theme.secondary.toString(), Theme.lockScreenContentColor.toString(), SessionData.desktopWidgetInstancePositions]))
|
||||
|
||||
function itemById(id) {
|
||||
for (let i = 0; i < repeater.count; i++) {
|
||||
const item = repeater.itemAt(i);
|
||||
if (item && item.visible && item.instanceId === id)
|
||||
return item;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function stockRect(widgetType, item) {
|
||||
const centerX = (width - item.width) / 2;
|
||||
switch (widgetType) {
|
||||
case "lockClock":
|
||||
return item.automaticPlacement && autoPositions[item.instanceId] ? autoPositions[item.instanceId] : {
|
||||
x: Math.max(0, centerX),
|
||||
y: edgeInset
|
||||
};
|
||||
case "lockDate":
|
||||
return {
|
||||
x: edgeInset,
|
||||
y: edgeInset
|
||||
};
|
||||
case "lockAuth":
|
||||
return {
|
||||
x: centerX,
|
||||
y: height / 2 - item.height / 2
|
||||
};
|
||||
case "lockNotifications":
|
||||
{
|
||||
const auth = itemOfType("lockAuth");
|
||||
return {
|
||||
x: centerX,
|
||||
y: auth ? auth.y + auth.height + Theme.spacingL : height / 2 + Theme.spacingL
|
||||
};
|
||||
}
|
||||
case "lockStatus":
|
||||
return {
|
||||
x: width - Theme.spacingXL - item.width,
|
||||
y: Theme.spacingXL
|
||||
};
|
||||
case "lockPower":
|
||||
return {
|
||||
x: Theme.spacingXL,
|
||||
y: height - Theme.spacingXL - item.height
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function placeClocks(luminances, sampleWidth, sampleHeight) {
|
||||
const clocks = [];
|
||||
const obstacles = [];
|
||||
for (let i = 0; i < repeater.count; i++) {
|
||||
const item = repeater.itemAt(i);
|
||||
if (!item || !item.visible)
|
||||
continue;
|
||||
if (item.automaticPlacement) {
|
||||
clocks.push(item);
|
||||
continue;
|
||||
}
|
||||
obstacles.push({
|
||||
x: item.x - Theme.spacingL,
|
||||
y: item.y - Theme.spacingL,
|
||||
width: item.width + Theme.spacingL * 2,
|
||||
height: item.height + Theme.spacingL * 2
|
||||
});
|
||||
}
|
||||
const positions = {};
|
||||
for (const clock of clocks) {
|
||||
const choices = Placement.candidates(width, height, clock.width, clock.height, edgeInset, obstacles);
|
||||
const foregrounds = clock.contrastColors.map(color => Placement.luminance(color.r, color.g, color.b));
|
||||
const chosen = Placement.choose(luminances, sampleWidth, sampleHeight, width, height, choices, foregrounds);
|
||||
if (!chosen)
|
||||
continue;
|
||||
positions[clock.instanceId] = {
|
||||
x: chosen.x,
|
||||
y: chosen.y
|
||||
};
|
||||
obstacles.push(chosen);
|
||||
}
|
||||
return positions;
|
||||
}
|
||||
|
||||
readonly property var draggedItem: {
|
||||
for (let i = 0; i < repeater.count; i++) {
|
||||
const item = repeater.itemAt(i);
|
||||
if (item && item.dragging)
|
||||
return item;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
x: (parent.width - width) / 2
|
||||
width: Theme.dividerWidth
|
||||
height: parent.height
|
||||
visible: root.editMode && root.draggedItem !== null
|
||||
color: root.draggedItem?.snappedCenterX ? Theme.primary : Theme.withAlpha(Theme.primary, Theme.stateLayerDrag)
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
y: (parent.height - height) / 2
|
||||
width: parent.width
|
||||
height: Theme.dividerWidth
|
||||
visible: root.editMode && root.draggedItem !== null
|
||||
color: root.draggedItem?.snappedCenterY ? Theme.primary : Theme.withAlpha(Theme.primary, Theme.stateLayerDrag)
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
active: root.editMode && root.gridEnabled
|
||||
|
||||
sourceComponent: DesktopWidgetGridLines {
|
||||
gridSize: root.gridSize
|
||||
extentWidth: root.width
|
||||
extentHeight: root.height
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: repeater
|
||||
model: ScriptModel {
|
||||
objectProp: "id"
|
||||
values: root.instances
|
||||
}
|
||||
|
||||
LockWidgetItem {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
readonly property var liveInstanceData: root.instances.find(inst => inst.id === modelData.id) ?? modelData
|
||||
|
||||
instanceData: liveInstanceData
|
||||
screen: root.screen
|
||||
hostLayer: root
|
||||
lockHost: root.lockHost
|
||||
editMode: root.editMode
|
||||
visible: liveInstanceData.enabled !== false && DesktopWidgetRegistry.showsOnScreen(liveInstanceData.config?.displayPreferences, root.screen)
|
||||
z: interacting ? repeater.count + 1 : repeater.count - index
|
||||
onRemoveRequested: SettingsData.removeDesktopWidgetInstance(liveInstanceData.id)
|
||||
onOptionsRequested: root.optionsRequested(liveInstanceData)
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,267 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
import qs.DankCommon.Session
|
||||
import "../../../DankCommon/Common/Hct.js" as Hct
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var instanceData: null
|
||||
property var lockHost: null
|
||||
readonly property var cfg: instanceData?.config ?? ({})
|
||||
readonly property string style: cfg.style ?? "expressive"
|
||||
readonly property bool textStyle: style === "horizontal" || style === "vertical"
|
||||
readonly property string colorMode: cfg.colorMode ?? "default"
|
||||
readonly property var primaryHct: Hct.toHct(Theme.primary)
|
||||
readonly property color shadowFaceColor: {
|
||||
if (colorMode !== "default")
|
||||
return contentColor;
|
||||
return Theme.isLightMode ? Hct.fromHct(primaryHct.hue, primaryHct.chroma, 65) : Theme.accentOnPrimaryContainer;
|
||||
}
|
||||
readonly property color shadowColor: {
|
||||
if (colorMode === "default")
|
||||
return Theme.isLightMode ? Hct.fromHct(primaryHct.hue, primaryHct.chroma, 30) : Theme.primaryContainer;
|
||||
const source = Hct.toHct(shadowFaceColor);
|
||||
return Hct.fromHct(source.hue, source.chroma, source.tone > 50 ? 30 : 80);
|
||||
}
|
||||
readonly property int weightOverride: cfg.weight ?? 0
|
||||
readonly property int digitWeight: weightOverride > 0 ? weightOverride : Theme.fontWeight
|
||||
readonly property color contentColor: lockHost?.contentColor(cfg.colorMode ?? "default", cfg.customColor ?? "#ffffff") ?? Theme.lockScreenContentColor
|
||||
readonly property string fontFamily: cfg.fontFamily || SettingsData.lockScreenFontFamily
|
||||
readonly property color minutesColor: (cfg.twoTone ?? true) ? Theme.primary : contentColor
|
||||
readonly property var contrastColors: style === "vertical" ? [shadowFaceColor, shadowColor] : style === "horizontal" || style === "expressive" ? [contentColor, minutesColor] : [contentColor]
|
||||
readonly property real clockSize: LockMetrics.clockSize * (style === "horizontal" ? 1.5 : 1)
|
||||
readonly property real clockDigitWidth: clockSize * 0.625
|
||||
|
||||
readonly property bool resizable: true
|
||||
readonly property bool forceSquare: style === "analog"
|
||||
readonly property real boxWidth: style === "analog" ? LockMetrics.clockSize * 2 : LockMetrics.clockSize * 4
|
||||
readonly property real boxHeight: style === "analog" ? LockMetrics.clockSize * 2 : LockMetrics.clockSize * 1.4
|
||||
readonly property real naturalWidth: face.item?.implicitWidth ?? 0
|
||||
readonly property real naturalHeight: face.item?.implicitHeight ?? 0
|
||||
readonly property real defaultWidth: textStyle ? naturalWidth : boxWidth
|
||||
readonly property real defaultHeight: textStyle ? naturalHeight : boxHeight
|
||||
readonly property real minWidth: textStyle ? naturalWidth / 2 : LockMetrics.fieldHeight * 2
|
||||
readonly property real minHeight: textStyle ? naturalHeight / 2 : LockMetrics.fieldHeight
|
||||
readonly property real fitScale: textStyle && naturalWidth > 0 && naturalHeight > 0 ? Math.min(width / naturalWidth, height / naturalHeight) : 1
|
||||
|
||||
implicitWidth: textStyle ? naturalWidth : boxWidth
|
||||
implicitHeight: textStyle ? naturalHeight : boxHeight
|
||||
|
||||
component ClockDigitText: StyledText {
|
||||
font.pixelSize: root.clockSize
|
||||
font.weight: root.digitWeight
|
||||
font.italic: root.cfg.italic ?? false
|
||||
font.features: ({
|
||||
"tnum": 1
|
||||
})
|
||||
color: root.contentColor
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.family: root.fontFamily !== "" ? root.fontFamily : resolvedFontFamily
|
||||
}
|
||||
|
||||
SystemClock {
|
||||
id: systemClock
|
||||
precision: (root.style === "analog" ? (root.cfg.showAnalogSeconds ?? true) : SettingsData.getEffectiveTimeFormat().includes("s")) ? SystemClock.Seconds : SystemClock.Minutes
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: time
|
||||
|
||||
readonly property string ampm: SettingsData.use24HourClock ? "" : systemClock.date.toLocaleTimeString(I18n.locale(), "AP")
|
||||
readonly property string hours: {
|
||||
const format = SettingsData.use24HourClock || SettingsData.padHours12Hour ? "hh" : "h";
|
||||
const text = systemClock.date.toLocaleTimeString(I18n.locale(), ampm ? format + " AP" : format);
|
||||
return ampm ? text.replace(ampm, "").trim() : text;
|
||||
}
|
||||
readonly property string minutes: systemClock.date.toLocaleTimeString(I18n.locale(), "mm")
|
||||
readonly property string seconds: systemClock.date.toLocaleTimeString(I18n.locale(), "ss")
|
||||
readonly property bool hasSeconds: SettingsData.showSeconds
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: face
|
||||
anchors.fill: root.textStyle ? undefined : parent
|
||||
anchors.centerIn: root.textStyle ? parent : undefined
|
||||
width: root.textStyle ? root.naturalWidth : undefined
|
||||
height: root.textStyle ? root.naturalHeight : undefined
|
||||
scale: root.fitScale
|
||||
sourceComponent: {
|
||||
switch (root.style) {
|
||||
case "vertical":
|
||||
return shadowFace;
|
||||
case "expressive":
|
||||
return expressiveFace;
|
||||
case "analog":
|
||||
return analogFace;
|
||||
}
|
||||
return horizontalFace;
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: horizontalFace
|
||||
|
||||
Item {
|
||||
implicitWidth: row.implicitWidth
|
||||
implicitHeight: row.implicitHeight
|
||||
|
||||
Row {
|
||||
id: row
|
||||
anchors.centerIn: parent
|
||||
layoutDirection: Qt.LeftToRight
|
||||
spacing: 0
|
||||
|
||||
ClockDigitText {
|
||||
id: hourText
|
||||
text: time.hours + ":"
|
||||
}
|
||||
|
||||
ClockDigitText {
|
||||
text: time.minutes
|
||||
color: root.minutesColor
|
||||
}
|
||||
|
||||
ClockDigitText {
|
||||
text: ":" + time.seconds
|
||||
color: root.minutesColor
|
||||
visible: time.hasSeconds
|
||||
}
|
||||
|
||||
ClockDigitText {
|
||||
text: " " + time.ampm
|
||||
font.pixelSize: root.clockSize / 3
|
||||
anchors.baseline: hourText.baseline
|
||||
visible: time.ampm !== ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: shadowFace
|
||||
|
||||
Item {
|
||||
id: shadowItem
|
||||
|
||||
readonly property real digitSize: root.clockSize * 1.25
|
||||
readonly property real tracking: -digitSize * 0.1
|
||||
readonly property real rowAdvance: digitSize * 0.7
|
||||
readonly property int weight: root.weightOverride > 0 ? root.weightOverride : 1000
|
||||
readonly property bool variableFont: root.fontFamily === "" || root.fontFamily === Theme.defaultFontFamily
|
||||
// One cell width for all four glyphs keeps the columns aligned in fonts without tabular figures.
|
||||
readonly property real cellWidth: Math.max(hoursRow.firstAdvance, hoursRow.secondAdvance, minutesRow.firstAdvance, minutesRow.secondAdvance)
|
||||
implicitWidth: Math.max(hoursRow.width, minutesRow.width)
|
||||
implicitHeight: Math.max(hoursRow.height, minutesRow.y + minutesRow.height)
|
||||
|
||||
ShadowRow {
|
||||
id: hoursRow
|
||||
x: (shadowItem.width - shadowItem.implicitWidth) / 2
|
||||
text: time.hours.padStart(2, "0")
|
||||
}
|
||||
|
||||
ShadowRow {
|
||||
id: minutesRow
|
||||
x: hoursRow.x
|
||||
y: hoursRow.baselineOffset + shadowItem.rowAdvance - baselineOffset
|
||||
text: time.minutes
|
||||
lightFirst: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component ClockDigit: StyledText {
|
||||
id: digit
|
||||
|
||||
readonly property rect inkBounds: metrics.tightBoundingRect
|
||||
readonly property real advanceWidth: metrics.advanceWidth
|
||||
|
||||
font.pixelSize: shadowItem.digitSize
|
||||
font.family: root.fontFamily !== "" ? root.fontFamily : Theme.defaultFontFamily
|
||||
font.weight: shadowItem.variableFont ? Font.Normal : Math.min(Font.Black, shadowItem.weight)
|
||||
font.variableAxes: shadowItem.variableFont ? {
|
||||
"wght": shadowItem.weight,
|
||||
"ROND": 0,
|
||||
"opsz": 18
|
||||
} : ({})
|
||||
font.features: ({
|
||||
"tnum": 1
|
||||
})
|
||||
textFormat: Text.PlainText
|
||||
wrapMode: Text.NoWrap
|
||||
elide: Text.ElideNone
|
||||
verticalAlignment: Text.AlignTop
|
||||
|
||||
TextMetrics {
|
||||
id: metrics
|
||||
font: digit.font
|
||||
text: digit.text
|
||||
renderType: digit.renderType
|
||||
}
|
||||
}
|
||||
|
||||
component ShadowRow: Item {
|
||||
id: shadowRow
|
||||
|
||||
property string text: ""
|
||||
property bool lightFirst: false
|
||||
readonly property real firstAdvance: firstDigit.advanceWidth
|
||||
readonly property real secondAdvance: secondDigit.advanceWidth
|
||||
readonly property real cell: shadowItem.cellWidth
|
||||
readonly property real topBearing: Math.min(firstDigit.inkBounds.y, secondDigit.inkBounds.y)
|
||||
readonly property real bottomBearing: Math.max(firstDigit.inkBounds.y + firstDigit.inkBounds.height, secondDigit.inkBounds.y + secondDigit.inkBounds.height)
|
||||
|
||||
implicitWidth: cell * 2 + shadowItem.tracking
|
||||
implicitHeight: bottomBearing - topBearing
|
||||
baselineOffset: -topBearing
|
||||
|
||||
ClockDigit {
|
||||
id: firstDigit
|
||||
x: (shadowRow.cell - advanceWidth) / 2
|
||||
y: shadowRow.baselineOffset - baselineOffset
|
||||
text: shadowRow.text[0] ?? ""
|
||||
color: shadowRow.lightFirst ? root.shadowFaceColor : root.shadowColor
|
||||
}
|
||||
|
||||
ClockDigit {
|
||||
id: secondDigit
|
||||
x: shadowRow.cell + shadowItem.tracking + (shadowRow.cell - advanceWidth) / 2
|
||||
y: shadowRow.baselineOffset - baselineOffset
|
||||
text: shadowRow.text[1] ?? ""
|
||||
color: shadowRow.lightFirst ? root.shadowColor : root.shadowFaceColor
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: expressiveFace
|
||||
|
||||
DankClockFace {
|
||||
readonly property bool tallBox: height > width * 0.6
|
||||
|
||||
hours: time.hours
|
||||
minutes: time.minutes
|
||||
seconds: time.hasSeconds && !tallBox ? time.seconds : ""
|
||||
stacked: tallBox
|
||||
color: root.contentColor
|
||||
minutesColor: root.minutesColor
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: analogFace
|
||||
|
||||
DankAnalogClock {
|
||||
hours: systemClock.date?.getHours() ?? 0
|
||||
minutes: systemClock.date?.getMinutes() ?? 0
|
||||
seconds: systemClock.date?.getSeconds() ?? 0
|
||||
showSeconds: root.cfg.showAnalogSeconds ?? true
|
||||
showNumbers: root.cfg.showAnalogNumbers ?? false
|
||||
color: root.contentColor
|
||||
backgroundColor: "transparent"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var instanceData: null
|
||||
property var lockHost: null
|
||||
readonly property var cfg: instanceData?.config ?? ({})
|
||||
readonly property color contentColor: lockHost?.contentColor(cfg.colorMode ?? "default", cfg.customColor ?? "#ffffff") ?? Theme.lockScreenContentColor
|
||||
readonly property string fontFamily: SettingsData.lockScreenFontFamily
|
||||
readonly property bool resizable: true
|
||||
readonly property real minWidth: implicitWidth / 2
|
||||
readonly property real minHeight: implicitHeight / 2
|
||||
readonly property real fitScale: implicitWidth > 0 && implicitHeight > 0 ? Math.min(width / implicitWidth, height / implicitHeight) : 1
|
||||
|
||||
implicitWidth: dateText.implicitWidth
|
||||
implicitHeight: dateText.implicitHeight
|
||||
|
||||
SystemClock {
|
||||
id: systemClock
|
||||
precision: SystemClock.Minutes
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: dateText
|
||||
anchors.centerIn: parent
|
||||
scale: root.fitScale
|
||||
text: {
|
||||
if (SettingsData.lockDateFormat && SettingsData.lockDateFormat.length > 0)
|
||||
return systemClock.date.toLocaleDateString(I18n.locale(), SettingsData.lockDateFormat);
|
||||
return systemClock.date.toLocaleDateString(I18n.locale(), (root.cfg.format ?? "long") === "short" ? "ddd, d MMM" : Locale.LongFormat);
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeXLarge
|
||||
font.family: root.fontFamily !== "" ? root.fontFamily : resolvedFontFamily
|
||||
color: root.contentColor
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Modules.Notifications as Notifications
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.DankCommon.Session
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var instanceData: null
|
||||
property var lockHost: null
|
||||
readonly property var cfg: instanceData?.config ?? ({})
|
||||
readonly property int notificationMode: cfg.mode ?? 1
|
||||
readonly property bool resizable: true
|
||||
readonly property real minWidth: Theme.listItemTwoLineHeight * 2
|
||||
readonly property real minHeight: Theme.listItemHeight
|
||||
|
||||
readonly property var notifications: NotificationService.groupedNotifications
|
||||
readonly property int totalCount: {
|
||||
let count = 0;
|
||||
for (const group of notifications)
|
||||
count += group.count || 0;
|
||||
return count;
|
||||
}
|
||||
readonly property bool hasNotifications: totalCount > 0
|
||||
readonly property var appNameGroups: {
|
||||
const groups = {};
|
||||
for (const group of notifications) {
|
||||
const appName = (group.appName || "Unknown").toLowerCase();
|
||||
if (!groups[appName]) {
|
||||
groups[appName] = {
|
||||
appName: group.appName || I18n.tr("Unknown"),
|
||||
count: 0,
|
||||
latestNotification: group.latestNotification
|
||||
};
|
||||
}
|
||||
groups[appName].count += group.count || 0;
|
||||
if (group.latestNotification && (!groups[appName].latestNotification || group.latestNotification.time > groups[appName].latestNotification.time))
|
||||
groups[appName].latestNotification = group.latestNotification;
|
||||
}
|
||||
return Object.values(groups).sort((a, b) => b.count - a.count);
|
||||
}
|
||||
|
||||
readonly property bool editPlaceholder: !hasNotifications && (lockHost?.demoMode ?? false)
|
||||
readonly property bool chips: notificationMode === 2 && !editPlaceholder
|
||||
readonly property var visibleGroups: appNameGroups.slice(0, LockMetrics.notificationLimit)
|
||||
readonly property int hiddenGroups: Math.max(0, appNameGroups.length - LockMetrics.notificationLimit)
|
||||
|
||||
implicitWidth: chips ? contentLoader.implicitWidth : LockMetrics.notificationCardWidth
|
||||
implicitHeight: hasNotifications || editPlaceholder ? contentLoader.implicitHeight : 0
|
||||
clip: true
|
||||
|
||||
Loader {
|
||||
id: contentLoader
|
||||
anchors.left: parent.left
|
||||
anchors.right: root.chips ? undefined : parent.right
|
||||
anchors.top: parent.top
|
||||
active: root.hasNotifications || root.editPlaceholder
|
||||
sourceComponent: {
|
||||
if (root.notificationMode === 1 || root.editPlaceholder)
|
||||
return countOnlyComponent;
|
||||
return root.chips ? chipRowComponent : notificationListComponent;
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: chipRowComponent
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Repeater {
|
||||
model: root.visibleGroups
|
||||
|
||||
Rectangle {
|
||||
id: chip
|
||||
|
||||
required property var modelData
|
||||
readonly property string appIcon: NotificationService.notificationAppIcon(modelData.latestNotification?.appIcon || "", modelData.latestNotification?.desktopEntry || "")
|
||||
|
||||
height: Theme.buttonHeightXS
|
||||
width: chipRow.implicitWidth + Theme.spacingM * 2
|
||||
radius: Theme.cornerRadiusM
|
||||
color: Theme.notificationFloatingSurface
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineVariant
|
||||
Accessible.name: modelData.appName
|
||||
|
||||
Row {
|
||||
id: chipRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Item {
|
||||
width: Theme.iconSizeSmall
|
||||
height: width
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Image {
|
||||
id: appImage
|
||||
anchors.fill: parent
|
||||
asynchronous: true
|
||||
smooth: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
sourceSize: Qt.size(width * 2, height * 2)
|
||||
source: NotificationService.notificationImageSource("", chip.appIcon)
|
||||
visible: status === Image.Ready
|
||||
}
|
||||
|
||||
AppIconRenderer {
|
||||
anchors.fill: parent
|
||||
visible: !appImage.visible
|
||||
iconValue: NotificationService.notificationFallbackIcon("", chip.appIcon)
|
||||
iconSize: Theme.iconSizeSmall
|
||||
iconColor: Theme.onSurfaceVariant
|
||||
colorOverride: "transparent"
|
||||
fallbackText: (chip.modelData.appName || "?").charAt(0).toUpperCase()
|
||||
fallbackRadius: Theme.fullRadius(width, height)
|
||||
fallbackBackgroundColor: Theme.secondaryContainer
|
||||
fallbackTextColor: Theme.onSecondaryContainer
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: chip.modelData.appName
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Theme.fontWeightMedium
|
||||
color: Theme.onSurface
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: chip.modelData.count > 1
|
||||
width: Math.max(height, countText.implicitWidth + Theme.spacingXS * 2)
|
||||
height: Theme.iconSizeSmall
|
||||
radius: Theme.fullRadius(width, height)
|
||||
color: Theme.primary
|
||||
|
||||
StyledText {
|
||||
id: countText
|
||||
anchors.centerIn: parent
|
||||
text: chip.modelData.count
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Theme.fontWeightMedium
|
||||
color: Theme.onPrimary
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: root.hiddenGroups > 0
|
||||
height: Theme.buttonHeightXS
|
||||
width: moreText.implicitWidth + Theme.spacingM * 2
|
||||
radius: Theme.cornerRadiusM
|
||||
color: Theme.notificationFloatingSurface
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineVariant
|
||||
|
||||
StyledText {
|
||||
id: moreText
|
||||
anchors.centerIn: parent
|
||||
text: I18n.tr("+ %1 more", "lock screen notification list overflow, %1 is a count of hidden apps").arg(root.hiddenGroups)
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Theme.fontWeightMedium
|
||||
color: Theme.onSurfaceVariant
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: countOnlyComponent
|
||||
|
||||
LockNotificationCard {
|
||||
implicitHeight: Theme.listItemHeight
|
||||
color: Theme.notificationFloatingSurface
|
||||
Accessible.name: countLabel.text
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: "notifications"
|
||||
size: Theme.iconSize
|
||||
color: Theme.onSurfaceVariant
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: countLabel
|
||||
text: root.editPlaceholder ? I18n.tr("Notifications") : root.totalCount === 1 ? I18n.tr("1 notification") : I18n.tr("%1 notifications").arg(root.totalCount)
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.onSurface
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: notificationListComponent
|
||||
|
||||
DankFlickable {
|
||||
implicitHeight: Math.min(notificationColumn.implicitHeight, LockMetrics.notificationMaxHeight, Math.max(0, (root.lockHost?.height ?? 0) - (root.parent?.y ?? 0) - Theme.spacingXL))
|
||||
height: Math.min(notificationColumn.implicitHeight, root.height)
|
||||
contentHeight: notificationColumn.implicitHeight
|
||||
clip: true
|
||||
|
||||
Column {
|
||||
id: notificationColumn
|
||||
width: parent.width
|
||||
spacing: Theme.groupedListGap
|
||||
|
||||
Repeater {
|
||||
id: notificationRepeater
|
||||
model: root.appNameGroups.slice(0, LockMetrics.notificationLimit)
|
||||
|
||||
Notifications.NotificationCard {
|
||||
required property var modelData
|
||||
required property int index
|
||||
width: notificationColumn.width
|
||||
notificationData: modelData.latestNotification
|
||||
groupCount: modelData.count
|
||||
interactive: false
|
||||
headerOnly: false
|
||||
showActions: false
|
||||
showDismiss: false
|
||||
showClose: false
|
||||
showTime: !headerOnly
|
||||
animateHeight: false
|
||||
firstInGroup: index === 0
|
||||
lastInGroup: index === notificationRepeater.count - 1
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
visible: root.appNameGroups.length > LockMetrics.notificationLimit
|
||||
text: I18n.tr("+ %1 more", "lock screen notification list overflow, %1 is a count of hidden apps").arg(root.appNameGroups.length - LockMetrics.notificationLimit)
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.lockScreenContentColor
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
import qs.DankCommon.Session
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var instanceData: null
|
||||
property var lockHost: null
|
||||
readonly property var cfg: instanceData?.config ?? ({})
|
||||
readonly property string shape: cfg.shape ?? "round"
|
||||
readonly property bool round: shape === "round"
|
||||
readonly property bool resizable: false
|
||||
readonly property real minWidth: implicitWidth
|
||||
readonly property real minHeight: implicitHeight
|
||||
|
||||
implicitWidth: powerButton.width
|
||||
implicitHeight: powerButton.height
|
||||
|
||||
DankMaterialShape {
|
||||
anchors.fill: powerButton
|
||||
visible: !root.round
|
||||
shape: root.shape
|
||||
color: Theme.secondaryContainer
|
||||
scale: powerButton.pressed ? 0.9 : 1
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: LockMetrics.shakeDuration
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Theme.expressiveCurves.expressiveFastSpatial
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LockActionButton {
|
||||
id: powerButton
|
||||
Accessible.name: I18n.tr("Power Options")
|
||||
iconName: "power_settings_new"
|
||||
iconColor: Theme.onSecondaryContainer
|
||||
backgroundColor: root.round ? Theme.secondaryContainer : "transparent"
|
||||
radius: pressed ? Theme.cornerRadiusS : Theme.fullRadius(width, height)
|
||||
buttonSize: Theme.buttonHeightM
|
||||
onClicked: root.lockHost?.showPowerMenu()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Modules.Lock
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var instanceData: null
|
||||
property var lockHost: null
|
||||
readonly property var cfg: instanceData?.config ?? ({})
|
||||
readonly property bool background: cfg.background ?? false
|
||||
readonly property real pad: background ? Theme.spacingM : 0
|
||||
readonly property bool resizable: false
|
||||
readonly property real minWidth: implicitWidth
|
||||
readonly property real minHeight: implicitHeight
|
||||
|
||||
implicitWidth: statusRow.implicitWidth + pad * 2
|
||||
implicitHeight: statusRow.implicitHeight + pad * 2
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
visible: root.background
|
||||
radius: Theme.fullRadius(width, height)
|
||||
color: Theme.readableSurface
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
}
|
||||
|
||||
LockStatusRow {
|
||||
id: statusRow
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.margins: root.pad
|
||||
interactive: !(root.lockHost?.demoMode ?? true)
|
||||
showMediaPlayer: root.cfg.showMediaPlayer ?? true
|
||||
showWeather: root.cfg.showWeather ?? true
|
||||
useFahrenheit: SettingsData.useFahrenheit
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ Item {
|
||||
property string pluginId: ""
|
||||
property string instanceId: ""
|
||||
property var instanceData: null
|
||||
property bool lockScreen: false
|
||||
|
||||
property real widgetWidth: 200
|
||||
property real widgetHeight: 200
|
||||
|
||||
@@ -13,27 +13,20 @@ Item {
|
||||
required property string pluginId
|
||||
required property var screen
|
||||
|
||||
property var builtinComponent: null
|
||||
property var pluginService: null
|
||||
property string instanceId: ""
|
||||
property var instanceData: null
|
||||
onInstanceDataChanged: contentLoader.updateInstanceData()
|
||||
property bool widgetEnabled: true
|
||||
|
||||
readonly property bool isBuiltin: pluginId === "desktopClock" || pluginId === "systemMonitor"
|
||||
readonly property var activeComponent: isBuiltin ? builtinComponent : PluginService.pluginDesktopComponents[pluginId] ?? null
|
||||
|
||||
readonly property bool showOnOverlay: instanceData?.config?.showOnOverlay ?? false
|
||||
readonly property bool showOnOverview: instanceData?.config?.showOnOverview ?? false
|
||||
readonly property bool showOnOverviewOnly: instanceData?.config?.showOnOverviewOnly ?? false
|
||||
readonly property bool overviewActive: CompositorService.isNiri && NiriService.inOverview
|
||||
readonly property bool clickThrough: instanceData?.config?.clickThrough ?? false
|
||||
readonly property bool syncPositionAcrossScreens: instanceData?.config?.syncPositionAcrossScreens ?? false
|
||||
|
||||
// Unmapping with the widget still in the last buffer leaves a stale image in
|
||||
// Hyprland's blur cache behind transparent tiled windows (#2955), so hide the
|
||||
// content, present a transparent frame, then unmap.
|
||||
readonly property bool contentShowing: widgetEnabled && activeComponent !== null && (!showOnOverviewOnly || overviewActive)
|
||||
readonly property bool contentShowing: widgetEnabled && content.activeComponent !== null && (!showOnOverviewOnly || overviewActive)
|
||||
property bool surfaceLingering: false
|
||||
|
||||
onContentShowingChanged: {
|
||||
@@ -52,157 +45,25 @@ Item {
|
||||
onTriggered: root.surfaceLingering = false
|
||||
}
|
||||
|
||||
readonly property string settingsKey: instanceId ? instanceId : pluginId
|
||||
readonly property bool isInstance: instanceId !== "" && instanceData !== null
|
||||
|
||||
QtObject {
|
||||
id: instanceScopedPluginService
|
||||
|
||||
readonly property var availablePlugins: PluginService.availablePlugins
|
||||
readonly property var loadedPlugins: PluginService.loadedPlugins
|
||||
readonly property var pluginDesktopComponents: PluginService.pluginDesktopComponents
|
||||
|
||||
signal pluginDataChanged(string pluginId)
|
||||
signal pluginLoaded(string pluginId)
|
||||
signal pluginUnloaded(string pluginId)
|
||||
|
||||
function loadPluginData(pluginId, key, defaultValue) {
|
||||
const cfg = root.instanceData?.config;
|
||||
if (cfg && key in cfg)
|
||||
return cfg[key];
|
||||
return SettingsData.getPluginSetting(pluginId, key, defaultValue);
|
||||
}
|
||||
|
||||
function savePluginData(pluginId, key, value) {
|
||||
if (!root.instanceId)
|
||||
return false;
|
||||
var updates = {};
|
||||
updates[key] = value;
|
||||
SettingsData.updateDesktopWidgetInstanceConfig(root.instanceId, updates);
|
||||
Qt.callLater(() => pluginDataChanged(pluginId));
|
||||
return true;
|
||||
}
|
||||
|
||||
function getPluginVariants(pluginId) {
|
||||
return PluginService.getPluginVariants(pluginId);
|
||||
}
|
||||
|
||||
function isPluginLoaded(pluginId) {
|
||||
return PluginService.isPluginLoaded(pluginId);
|
||||
}
|
||||
DesktopWidgetGeometry {
|
||||
id: geometry
|
||||
instanceId: root.instanceId
|
||||
instanceData: root.instanceData
|
||||
screen: root.screen
|
||||
minWidth: content.contentMinWidth
|
||||
minHeight: content.contentMinHeight
|
||||
forceSquare: content.contentForceSquare
|
||||
}
|
||||
readonly property string screenKey: SettingsData.getScreenDisplayName(screen)
|
||||
readonly property string positionKey: syncPositionAcrossScreens ? "_synced" : screenKey
|
||||
readonly property var storedPositions: SessionData.desktopWidgetInstancePositions[instanceId] ?? null
|
||||
|
||||
readonly property int screenWidth: screen?.width ?? 1920
|
||||
readonly property int screenHeight: screen?.height ?? 1080
|
||||
|
||||
readonly property bool useGhostPreview: !CompositorService.isNiri
|
||||
|
||||
property real previewX: widgetX
|
||||
property real previewY: widgetY
|
||||
property real previewWidth: widgetWidth
|
||||
property real previewHeight: widgetHeight
|
||||
property real previewX: geometry.widgetX
|
||||
property real previewY: geometry.widgetY
|
||||
property real previewWidth: geometry.widgetWidth
|
||||
property real previewHeight: geometry.widgetHeight
|
||||
|
||||
function storedGeometry(key) {
|
||||
if (!isInstance)
|
||||
return undefined;
|
||||
return storedPositions?.[positionKey]?.[key];
|
||||
}
|
||||
|
||||
function storedCoordinate(key, extent, fallback) {
|
||||
const val = storedGeometry(key);
|
||||
if (val === undefined)
|
||||
return fallback;
|
||||
return syncPositionAcrossScreens ? val * extent : val;
|
||||
}
|
||||
|
||||
readonly property bool hasSavedPosition: storedGeometry("x") !== undefined
|
||||
readonly property bool hasSavedSize: storedGeometry("width") !== undefined
|
||||
|
||||
property real savedX: storedCoordinate("x", screenWidth, screenWidth / 2 - savedWidth / 2)
|
||||
property real savedY: storedCoordinate("y", screenHeight, screenHeight / 2 - savedHeight / 2)
|
||||
property real savedWidth: storedGeometry("width") ?? 280
|
||||
property real savedHeight: forceSquare ? savedWidth : (storedGeometry("height") ?? 180)
|
||||
|
||||
property real dragOverrideX: -1
|
||||
property real dragOverrideY: -1
|
||||
property real dragOverrideW: -1
|
||||
property real dragOverrideH: -1
|
||||
|
||||
readonly property real effectiveX: dragOverrideX >= 0 ? dragOverrideX : savedX
|
||||
readonly property real effectiveY: dragOverrideY >= 0 ? dragOverrideY : savedY
|
||||
readonly property real effectiveW: dragOverrideW >= 0 ? dragOverrideW : savedWidth
|
||||
readonly property real effectiveH: dragOverrideH >= 0 ? dragOverrideH : savedHeight
|
||||
|
||||
readonly property real widgetX: Math.max(0, Math.min(effectiveX, screenWidth - widgetWidth))
|
||||
readonly property real widgetY: Math.max(0, Math.min(effectiveY, screenHeight - widgetHeight))
|
||||
readonly property real widgetWidth: Math.max(minWidth, Math.min(effectiveW, screenWidth))
|
||||
readonly property real widgetHeight: Math.max(minHeight, Math.min(effectiveH, screenHeight))
|
||||
|
||||
function clearDragOverrides() {
|
||||
dragOverrideX = -1;
|
||||
dragOverrideY = -1;
|
||||
dragOverrideW = -1;
|
||||
dragOverrideH = -1;
|
||||
}
|
||||
|
||||
function requestResize(width, height) {
|
||||
if (width < minWidth || height < minHeight)
|
||||
return;
|
||||
if (width > screenWidth || height > screenHeight)
|
||||
return;
|
||||
dragOverrideW = width;
|
||||
dragOverrideH = height;
|
||||
}
|
||||
|
||||
function clearResize() {
|
||||
dragOverrideW = -1;
|
||||
dragOverrideH = -1;
|
||||
}
|
||||
|
||||
property real minWidth: contentLoader.item?.minWidth ?? 100
|
||||
property real minHeight: contentLoader.item?.minHeight ?? 100
|
||||
property bool forceSquare: contentLoader.item?.forceSquare ?? false
|
||||
property bool acceptsKeyboardFocus: contentLoader.item?.acceptsKeyboardFocus ?? false
|
||||
property bool isInteracting: dragArea.pressed || resizeArea.pressed
|
||||
|
||||
property var _gridSettingsTrigger: SessionData.desktopWidgetGridSettings
|
||||
readonly property int gridSize: {
|
||||
void _gridSettingsTrigger;
|
||||
return SessionData.getDesktopWidgetGridSetting(screenKey, "size", 40);
|
||||
}
|
||||
readonly property bool gridEnabled: {
|
||||
void _gridSettingsTrigger;
|
||||
return SessionData.getDesktopWidgetGridSetting(screenKey, "enabled", false);
|
||||
}
|
||||
|
||||
function snapToGrid(value) {
|
||||
return Math.round(value / gridSize) * gridSize;
|
||||
}
|
||||
|
||||
function saveGeometry(updates) {
|
||||
if (!isInstance)
|
||||
return;
|
||||
SessionData.updateDesktopWidgetInstancePosition(instanceId, positionKey, updates);
|
||||
}
|
||||
|
||||
function savePosition(finalX, finalY) {
|
||||
saveGeometry({
|
||||
x: syncPositionAcrossScreens ? finalX / screenWidth : finalX,
|
||||
y: syncPositionAcrossScreens ? finalY / screenHeight : finalY
|
||||
});
|
||||
}
|
||||
|
||||
function saveSize(finalW, finalH) {
|
||||
const sizeVal = forceSquare ? Math.max(finalW, finalH) : finalW;
|
||||
saveGeometry({
|
||||
width: sizeVal,
|
||||
height: forceSquare ? sizeVal : finalH
|
||||
});
|
||||
}
|
||||
|
||||
PanelWindow {
|
||||
id: widgetWindow
|
||||
screen: root.screen
|
||||
@@ -234,7 +95,7 @@ Item {
|
||||
return WlrKeyboardFocus.OnDemand;
|
||||
return WlrKeyboardFocus.Exclusive;
|
||||
}
|
||||
if (root.acceptsKeyboardFocus)
|
||||
if (content.acceptsKeyboardFocus)
|
||||
return WlrKeyboardFocus.OnDemand;
|
||||
return WlrKeyboardFocus.None;
|
||||
}
|
||||
@@ -253,15 +114,15 @@ Item {
|
||||
return;
|
||||
switch (event.key) {
|
||||
case Qt.Key_G:
|
||||
SessionData.setDesktopWidgetGridSetting(root.screenKey, "enabled", !root.gridEnabled);
|
||||
SessionData.setDesktopWidgetGridSetting(geometry.screenKey, "enabled", !geometry.gridEnabled);
|
||||
event.accepted = true;
|
||||
break;
|
||||
case Qt.Key_Z:
|
||||
SessionData.setDesktopWidgetGridSetting(root.screenKey, "size", Math.max(10, root.gridSize - 10));
|
||||
SessionData.setDesktopWidgetGridSetting(geometry.screenKey, "size", Math.max(10, geometry.gridSize - 10));
|
||||
event.accepted = true;
|
||||
break;
|
||||
case Qt.Key_X:
|
||||
SessionData.setDesktopWidgetGridSetting(root.screenKey, "size", Math.min(200, root.gridSize + 10));
|
||||
SessionData.setDesktopWidgetGridSetting(geometry.screenKey, "size", Math.min(200, geometry.gridSize + 10));
|
||||
event.accepted = true;
|
||||
break;
|
||||
}
|
||||
@@ -274,75 +135,23 @@ Item {
|
||||
}
|
||||
|
||||
WlrLayershell.margins {
|
||||
left: root.widgetX
|
||||
top: root.widgetY
|
||||
left: geometry.widgetX
|
||||
top: geometry.widgetY
|
||||
}
|
||||
|
||||
implicitWidth: root.widgetWidth
|
||||
implicitHeight: root.widgetHeight
|
||||
implicitWidth: geometry.widgetWidth
|
||||
implicitHeight: geometry.widgetHeight
|
||||
|
||||
Loader {
|
||||
id: contentLoader
|
||||
DesktopWidgetContent {
|
||||
id: content
|
||||
anchors.fill: parent
|
||||
active: root.widgetEnabled && root.activeComponent !== null
|
||||
active: root.widgetEnabled
|
||||
visible: root.contentShowing
|
||||
sourceComponent: root.activeComponent
|
||||
opacity: 0
|
||||
|
||||
NumberAnimation {
|
||||
id: revealFade
|
||||
target: contentLoader
|
||||
property: "opacity"
|
||||
from: 0
|
||||
to: 1
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
function updateInstanceData() {
|
||||
if (!item || item.instanceData === undefined)
|
||||
return;
|
||||
item.instanceData = root.instanceData;
|
||||
}
|
||||
|
||||
onLoaded: {
|
||||
if (!item)
|
||||
return;
|
||||
|
||||
revealFade.restart();
|
||||
|
||||
if (item.pluginService !== undefined) {
|
||||
item.pluginService = root.isInstance ? instanceScopedPluginService : root.pluginService;
|
||||
}
|
||||
if (item.pluginId !== undefined)
|
||||
item.pluginId = root.pluginId;
|
||||
if (item.instanceId !== undefined)
|
||||
item.instanceId = root.instanceId;
|
||||
if (item.instanceData !== undefined)
|
||||
item.instanceData = root.instanceData;
|
||||
if (!root.hasSavedSize) {
|
||||
const defW = item.defaultWidth ?? item.widgetWidth ?? 280;
|
||||
const defH = item.defaultHeight ?? item.widgetHeight ?? 180;
|
||||
const finalW = Math.max(root.minWidth, Math.min(defW, root.screenWidth));
|
||||
const finalH = Math.max(root.minHeight, Math.min(defH, root.screenHeight));
|
||||
root.saveSize(finalW, finalH);
|
||||
}
|
||||
if (!root.hasSavedPosition) {
|
||||
const finalX = Math.max(0, Math.min(root.screenWidth / 2 - root.widgetWidth / 2, root.screenWidth - root.widgetWidth));
|
||||
const finalY = Math.max(0, Math.min(root.screenHeight / 2 - root.widgetHeight / 2, root.screenHeight - root.widgetHeight));
|
||||
root.savePosition(finalX, finalY);
|
||||
}
|
||||
if (item.widgetWidth !== undefined)
|
||||
item.widgetWidth = Qt.binding(() => contentLoader.width);
|
||||
if (item.widgetHeight !== undefined)
|
||||
item.widgetHeight = Qt.binding(() => contentLoader.height);
|
||||
if (item.screen !== undefined)
|
||||
item.screen = Qt.binding(() => root.screen);
|
||||
if (item.requestResize !== undefined)
|
||||
item.requestResize = root.requestResize;
|
||||
if (item.clearResize !== undefined)
|
||||
item.clearResize = root.clearResize;
|
||||
}
|
||||
pluginId: root.pluginId
|
||||
instanceId: root.instanceId
|
||||
instanceData: root.instanceData
|
||||
screen: root.screen
|
||||
geometry: geometry
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -380,38 +189,33 @@ Item {
|
||||
|
||||
onPressed: mouse => {
|
||||
startPos = root.useGhostPreview ? Qt.point(mouse.x, mouse.y) : mapToGlobal(mouse.x, mouse.y);
|
||||
startX = root.widgetX;
|
||||
startY = root.widgetY;
|
||||
root.previewX = root.widgetX;
|
||||
root.previewY = root.widgetY;
|
||||
root.dragOverrideX = root.widgetX;
|
||||
root.dragOverrideY = root.widgetY;
|
||||
startX = geometry.widgetX;
|
||||
startY = geometry.widgetY;
|
||||
root.previewX = geometry.widgetX;
|
||||
root.previewY = geometry.widgetY;
|
||||
geometry.dragOverrideX = geometry.widgetX;
|
||||
geometry.dragOverrideY = geometry.widgetY;
|
||||
}
|
||||
|
||||
onPositionChanged: mouse => {
|
||||
if (!pressed)
|
||||
return;
|
||||
const currentPos = root.useGhostPreview ? Qt.point(mouse.x, mouse.y) : mapToGlobal(mouse.x, mouse.y);
|
||||
let newX = Math.max(0, Math.min(startX + currentPos.x - startPos.x, root.screenWidth - root.widgetWidth));
|
||||
let newY = Math.max(0, Math.min(startY + currentPos.y - startPos.y, root.screenHeight - root.widgetHeight));
|
||||
if (root.gridEnabled) {
|
||||
newX = Math.max(0, Math.min(root.snapToGrid(newX), root.screenWidth - root.widgetWidth));
|
||||
newY = Math.max(0, Math.min(root.snapToGrid(newY), root.screenHeight - root.widgetHeight));
|
||||
}
|
||||
const next = geometry.dragMoveTo(startX, startY, currentPos.x - startPos.x, currentPos.y - startPos.y);
|
||||
if (root.useGhostPreview) {
|
||||
root.previewX = newX;
|
||||
root.previewY = newY;
|
||||
root.previewX = next.x;
|
||||
root.previewY = next.y;
|
||||
return;
|
||||
}
|
||||
root.dragOverrideX = newX;
|
||||
root.dragOverrideY = newY;
|
||||
geometry.dragOverrideX = next.x;
|
||||
geometry.dragOverrideY = next.y;
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
const finalX = root.useGhostPreview ? root.previewX : root.dragOverrideX;
|
||||
const finalY = root.useGhostPreview ? root.previewY : root.dragOverrideY;
|
||||
root.savePosition(finalX, finalY);
|
||||
root.clearDragOverrides();
|
||||
const finalX = root.useGhostPreview ? root.previewX : geometry.dragOverrideX;
|
||||
const finalY = root.useGhostPreview ? root.previewY : geometry.dragOverrideY;
|
||||
geometry.savePosition(finalX, finalY);
|
||||
geometry.clearDragOverrides();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -431,43 +235,33 @@ Item {
|
||||
|
||||
onPressed: mouse => {
|
||||
startPos = root.useGhostPreview ? Qt.point(mouse.x, mouse.y) : mapToGlobal(mouse.x, mouse.y);
|
||||
startWidth = root.widgetWidth;
|
||||
startHeight = root.widgetHeight;
|
||||
root.previewWidth = root.widgetWidth;
|
||||
root.previewHeight = root.widgetHeight;
|
||||
root.dragOverrideW = root.widgetWidth;
|
||||
root.dragOverrideH = root.widgetHeight;
|
||||
startWidth = geometry.widgetWidth;
|
||||
startHeight = geometry.widgetHeight;
|
||||
root.previewWidth = geometry.widgetWidth;
|
||||
root.previewHeight = geometry.widgetHeight;
|
||||
geometry.dragOverrideW = geometry.widgetWidth;
|
||||
geometry.dragOverrideH = geometry.widgetHeight;
|
||||
}
|
||||
|
||||
onPositionChanged: mouse => {
|
||||
if (!pressed)
|
||||
return;
|
||||
const currentPos = root.useGhostPreview ? Qt.point(mouse.x, mouse.y) : mapToGlobal(mouse.x, mouse.y);
|
||||
let newW = Math.max(root.minWidth, Math.min(startWidth + currentPos.x - startPos.x, root.screenWidth - root.widgetX));
|
||||
let newH = Math.max(root.minHeight, Math.min(startHeight + currentPos.y - startPos.y, root.screenHeight - root.widgetY));
|
||||
if (root.gridEnabled) {
|
||||
newW = Math.max(root.minWidth, root.snapToGrid(newW));
|
||||
newH = Math.max(root.minHeight, root.snapToGrid(newH));
|
||||
}
|
||||
if (root.forceSquare) {
|
||||
const size = Math.max(newW, newH);
|
||||
newW = Math.min(size, root.screenWidth - root.widgetX);
|
||||
newH = Math.min(size, root.screenHeight - root.widgetY);
|
||||
}
|
||||
const next = geometry.dragResizeTo(startWidth, startHeight, currentPos.x - startPos.x, currentPos.y - startPos.y);
|
||||
if (root.useGhostPreview) {
|
||||
root.previewWidth = newW;
|
||||
root.previewHeight = newH;
|
||||
root.previewWidth = next.width;
|
||||
root.previewHeight = next.height;
|
||||
return;
|
||||
}
|
||||
root.dragOverrideW = newW;
|
||||
root.dragOverrideH = newH;
|
||||
geometry.dragOverrideW = next.width;
|
||||
geometry.dragOverrideH = next.height;
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
const finalW = root.useGhostPreview ? root.previewWidth : root.dragOverrideW;
|
||||
const finalH = root.useGhostPreview ? root.previewHeight : root.dragOverrideH;
|
||||
root.saveSize(finalW, finalH);
|
||||
root.clearDragOverrides();
|
||||
const finalW = root.useGhostPreview ? root.previewWidth : geometry.dragOverrideW;
|
||||
const finalH = root.useGhostPreview ? root.previewHeight : geometry.dragOverrideH;
|
||||
geometry.saveSize(finalW, finalH);
|
||||
geometry.clearDragOverrides();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -494,37 +288,13 @@ Item {
|
||||
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
|
||||
Item {
|
||||
DesktopWidgetGridLines {
|
||||
id: gridOverlay
|
||||
anchors.fill: parent
|
||||
visible: root.gridEnabled
|
||||
opacity: 0.3
|
||||
|
||||
Repeater {
|
||||
model: Math.ceil(root.screenWidth / root.gridSize)
|
||||
|
||||
Rectangle {
|
||||
required property int index
|
||||
x: index * root.gridSize
|
||||
y: 0
|
||||
width: 1
|
||||
height: root.screenHeight
|
||||
color: Theme.primary
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Math.ceil(root.screenHeight / root.gridSize)
|
||||
|
||||
Rectangle {
|
||||
required property int index
|
||||
x: 0
|
||||
y: index * root.gridSize
|
||||
width: root.screenWidth
|
||||
height: 1
|
||||
color: Theme.primary
|
||||
}
|
||||
}
|
||||
visible: geometry.gridEnabled
|
||||
gridSize: geometry.gridSize
|
||||
extentWidth: geometry.screenWidth
|
||||
extentHeight: geometry.screenHeight
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -554,7 +324,7 @@ Item {
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: root.isInteracting && root.gridEnabled && !root.useGhostPreview
|
||||
active: root.isInteracting && geometry.gridEnabled && !root.useGhostPreview
|
||||
|
||||
sourceComponent: PanelWindow {
|
||||
screen: root.screen
|
||||
@@ -574,35 +344,11 @@ Item {
|
||||
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
|
||||
Item {
|
||||
DesktopWidgetGridLines {
|
||||
anchors.fill: parent
|
||||
opacity: 0.3
|
||||
|
||||
Repeater {
|
||||
model: Math.ceil(root.screenWidth / root.gridSize)
|
||||
|
||||
Rectangle {
|
||||
required property int index
|
||||
x: index * root.gridSize
|
||||
y: 0
|
||||
width: 1
|
||||
height: root.screenHeight
|
||||
color: Theme.primary
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Math.ceil(root.screenHeight / root.gridSize)
|
||||
|
||||
Rectangle {
|
||||
required property int index
|
||||
x: 0
|
||||
y: index * root.gridSize
|
||||
width: root.screenWidth
|
||||
height: 1
|
||||
color: Theme.primary
|
||||
}
|
||||
}
|
||||
gridSize: geometry.gridSize
|
||||
extentWidth: geometry.screenWidth
|
||||
extentHeight: geometry.screenHeight
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -651,15 +397,15 @@ Item {
|
||||
DankIcon {
|
||||
name: "grid_on"
|
||||
size: 16
|
||||
color: root.gridEnabled ? Theme.primary : Theme.surfaceText
|
||||
color: geometry.gridEnabled ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: root.gridEnabled ? I18n.tr("Grid: ON", "Widget grid snap status") : I18n.tr("Grid: OFF", "Widget grid snap status")
|
||||
text: geometry.gridEnabled ? I18n.tr("Grid: ON", "Widget grid snap status") : I18n.tr("Grid: OFF", "Widget grid snap status")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.family: Theme.fontFamily
|
||||
color: root.gridEnabled ? Theme.primary : Theme.surfaceText
|
||||
color: geometry.gridEnabled ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
@@ -686,7 +432,7 @@ Item {
|
||||
}
|
||||
|
||||
NumericText {
|
||||
text: root.gridSize + "px"
|
||||
text: geometry.gridSize + "px"
|
||||
reserveText: "200px"
|
||||
width: Math.ceil(reservedWidth)
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Modules.BuiltinDesktopPlugins
|
||||
import qs.Modules.Lock.Widgets
|
||||
|
||||
Loader {
|
||||
id: root
|
||||
|
||||
required property string pluginId
|
||||
required property DesktopWidgetGeometry geometry
|
||||
property string instanceId: ""
|
||||
property var instanceData: null
|
||||
property var screen: null
|
||||
property bool lockScreen: false
|
||||
property var lockHost: null
|
||||
property bool persistDefaults: true
|
||||
|
||||
readonly property bool isInstance: instanceId !== "" && instanceData !== null
|
||||
readonly property var activeComponent: {
|
||||
switch (pluginId) {
|
||||
case "desktopClock":
|
||||
return clockComponent;
|
||||
case "systemMonitor":
|
||||
return systemMonitorComponent;
|
||||
case "lockClock":
|
||||
return lockClockComponent;
|
||||
case "lockDate":
|
||||
return lockDateComponent;
|
||||
case "lockAuth":
|
||||
return lockAuthComponent;
|
||||
case "lockNotifications":
|
||||
return lockNotificationsComponent;
|
||||
case "lockStatus":
|
||||
return lockStatusComponent;
|
||||
case "lockPower":
|
||||
return lockPowerComponent;
|
||||
}
|
||||
return PluginService.pluginDesktopComponents[pluginId] ?? null;
|
||||
}
|
||||
readonly property real contentMinWidth: item?.minWidth ?? 100
|
||||
readonly property real contentMinHeight: item?.minHeight ?? 100
|
||||
readonly property bool contentForceSquare: item?.forceSquare ?? false
|
||||
readonly property bool contentResizable: item?.resizable ?? true
|
||||
readonly property real contentDefaultWidth: item?.defaultWidth ?? ((item?.implicitWidth ?? 0) > 0 ? item.implicitWidth : 280)
|
||||
readonly property real contentDefaultHeight: item?.defaultHeight ?? ((item?.implicitHeight ?? 0) > 0 ? item.implicitHeight : 180)
|
||||
readonly property bool acceptsKeyboardFocus: item?.acceptsKeyboardFocus ?? false
|
||||
|
||||
property Component clockComponent: Component {
|
||||
DesktopClockWidget {}
|
||||
}
|
||||
|
||||
property Component systemMonitorComponent: Component {
|
||||
SystemMonitorWidget {}
|
||||
}
|
||||
|
||||
property Component lockClockComponent: Component {
|
||||
LockClockWidget {}
|
||||
}
|
||||
|
||||
property Component lockDateComponent: Component {
|
||||
LockDateWidget {}
|
||||
}
|
||||
|
||||
property Component lockAuthComponent: Component {
|
||||
LockAuthWidget {}
|
||||
}
|
||||
|
||||
property Component lockNotificationsComponent: Component {
|
||||
LockNotificationsWidget {}
|
||||
}
|
||||
|
||||
property Component lockStatusComponent: Component {
|
||||
LockStatusWidget {}
|
||||
}
|
||||
|
||||
property Component lockPowerComponent: Component {
|
||||
LockPowerWidget {}
|
||||
}
|
||||
|
||||
onInstanceDataChanged: {
|
||||
if (!item || item.instanceData === undefined)
|
||||
return;
|
||||
item.instanceData = instanceData;
|
||||
}
|
||||
|
||||
sourceComponent: activeComponent
|
||||
opacity: 0
|
||||
|
||||
NumberAnimation {
|
||||
id: revealFade
|
||||
target: root
|
||||
property: "opacity"
|
||||
from: 0
|
||||
to: 1
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: instanceScopedPluginService
|
||||
|
||||
readonly property var availablePlugins: PluginService.availablePlugins
|
||||
readonly property var loadedPlugins: PluginService.loadedPlugins
|
||||
readonly property var pluginDesktopComponents: PluginService.pluginDesktopComponents
|
||||
|
||||
signal pluginDataChanged(string pluginId)
|
||||
signal pluginLoaded(string pluginId)
|
||||
signal pluginUnloaded(string pluginId)
|
||||
|
||||
function loadPluginData(pluginId, key, defaultValue) {
|
||||
const cfg = root.instanceData?.config;
|
||||
if (cfg && key in cfg)
|
||||
return cfg[key];
|
||||
return SettingsData.getPluginSetting(pluginId, key, defaultValue);
|
||||
}
|
||||
|
||||
function savePluginData(pluginId, key, value) {
|
||||
if (!root.instanceId)
|
||||
return false;
|
||||
var updates = {};
|
||||
updates[key] = value;
|
||||
SettingsData.updateDesktopWidgetInstanceConfig(root.instanceId, updates);
|
||||
Qt.callLater(() => pluginDataChanged(pluginId));
|
||||
return true;
|
||||
}
|
||||
|
||||
function getPluginVariants(pluginId) {
|
||||
return PluginService.getPluginVariants(pluginId);
|
||||
}
|
||||
|
||||
function isPluginLoaded(pluginId) {
|
||||
return PluginService.isPluginLoaded(pluginId);
|
||||
}
|
||||
}
|
||||
|
||||
onLoaded: {
|
||||
if (!item)
|
||||
return;
|
||||
revealFade.restart();
|
||||
if (item.pluginService !== undefined)
|
||||
item.pluginService = instanceScopedPluginService;
|
||||
if (item.pluginId !== undefined)
|
||||
item.pluginId = root.pluginId;
|
||||
if (item.instanceId !== undefined)
|
||||
item.instanceId = root.instanceId;
|
||||
if (item.instanceData !== undefined)
|
||||
item.instanceData = root.instanceData;
|
||||
if (item.lockScreen !== undefined)
|
||||
item.lockScreen = root.lockScreen;
|
||||
if (item.lockHost !== undefined)
|
||||
item.lockHost = root.lockHost;
|
||||
if (root.persistDefaults)
|
||||
geometry.saveDefaultGeometry(item.defaultWidth ?? item.widgetWidth ?? 280, item.defaultHeight ?? item.widgetHeight ?? 180);
|
||||
if (item.widgetWidth !== undefined)
|
||||
item.widgetWidth = Qt.binding(() => root.width);
|
||||
if (item.widgetHeight !== undefined)
|
||||
item.widgetHeight = Qt.binding(() => root.height);
|
||||
if (item.screen !== undefined)
|
||||
item.screen = Qt.binding(() => root.screen);
|
||||
if (item.requestResize !== undefined)
|
||||
item.requestResize = geometry.requestResize;
|
||||
if (item.clearResize !== undefined)
|
||||
item.clearResize = geometry.clearResize;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
property string instanceId: ""
|
||||
property var instanceData: null
|
||||
property var screen: null
|
||||
property real minWidth: 100
|
||||
property real minHeight: 100
|
||||
property bool forceSquare: false
|
||||
|
||||
readonly property bool isInstance: instanceId !== "" && instanceData !== null
|
||||
readonly property bool syncPositionAcrossScreens: instanceData?.config?.syncPositionAcrossScreens ?? false
|
||||
readonly property string screenKey: SettingsData.getScreenDisplayName(screen)
|
||||
readonly property string positionKey: syncPositionAcrossScreens ? "_synced" : screenKey
|
||||
readonly property var storedPositions: SessionData.desktopWidgetInstancePositions[instanceId] ?? null
|
||||
|
||||
readonly property int screenWidth: screen?.width ?? 1920
|
||||
readonly property int screenHeight: screen?.height ?? 1080
|
||||
|
||||
function storedGeometry(key) {
|
||||
if (!isInstance)
|
||||
return undefined;
|
||||
return storedPositions?.[positionKey]?.[key];
|
||||
}
|
||||
|
||||
function storedCoordinate(key, extent, fallback) {
|
||||
const val = storedGeometry(key);
|
||||
if (val === undefined)
|
||||
return fallback;
|
||||
return syncPositionAcrossScreens ? val * extent : val;
|
||||
}
|
||||
|
||||
readonly property bool hasSavedPosition: storedGeometry("x") !== undefined
|
||||
readonly property bool hasSavedSize: storedGeometry("width") !== undefined
|
||||
|
||||
property real defaultWidth: 280
|
||||
property real defaultHeight: 180
|
||||
property real defaultX: screenWidth / 2 - savedWidth / 2
|
||||
property real defaultY: screenHeight / 2 - savedHeight / 2
|
||||
|
||||
readonly property real savedX: storedCoordinate("x", screenWidth, defaultX)
|
||||
readonly property real savedY: storedCoordinate("y", screenHeight, defaultY)
|
||||
readonly property real savedWidth: storedGeometry("width") ?? defaultWidth
|
||||
readonly property real savedHeight: forceSquare ? savedWidth : (storedGeometry("height") ?? defaultHeight)
|
||||
|
||||
property real dragOverrideX: -1
|
||||
property real dragOverrideY: -1
|
||||
property real dragOverrideW: -1
|
||||
property real dragOverrideH: -1
|
||||
|
||||
readonly property real effectiveX: dragOverrideX >= 0 ? dragOverrideX : savedX
|
||||
readonly property real effectiveY: dragOverrideY >= 0 ? dragOverrideY : savedY
|
||||
readonly property real effectiveW: dragOverrideW >= 0 ? dragOverrideW : savedWidth
|
||||
readonly property real effectiveH: dragOverrideH >= 0 ? dragOverrideH : savedHeight
|
||||
|
||||
readonly property real widgetX: Math.max(0, Math.min(effectiveX, screenWidth - widgetWidth))
|
||||
readonly property real widgetY: Math.max(0, Math.min(effectiveY, screenHeight - widgetHeight))
|
||||
readonly property real widgetWidth: Math.max(minWidth, Math.min(effectiveW, screenWidth))
|
||||
readonly property real widgetHeight: Math.max(minHeight, Math.min(effectiveH, screenHeight))
|
||||
|
||||
property var _gridSettingsTrigger: SessionData.desktopWidgetGridSettings
|
||||
readonly property int gridSize: {
|
||||
void _gridSettingsTrigger;
|
||||
return SessionData.getDesktopWidgetGridSetting(screenKey, "size", 40);
|
||||
}
|
||||
readonly property bool gridEnabled: {
|
||||
void _gridSettingsTrigger;
|
||||
return SessionData.getDesktopWidgetGridSetting(screenKey, "enabled", false);
|
||||
}
|
||||
|
||||
function snapToGrid(value) {
|
||||
return Math.round(value / gridSize) * gridSize;
|
||||
}
|
||||
|
||||
function clearDragOverrides() {
|
||||
dragOverrideX = -1;
|
||||
dragOverrideY = -1;
|
||||
dragOverrideW = -1;
|
||||
dragOverrideH = -1;
|
||||
}
|
||||
|
||||
function requestResize(width, height) {
|
||||
if (width < minWidth || height < minHeight)
|
||||
return;
|
||||
if (width > screenWidth || height > screenHeight)
|
||||
return;
|
||||
dragOverrideW = width;
|
||||
dragOverrideH = height;
|
||||
}
|
||||
|
||||
function clearResize() {
|
||||
dragOverrideW = -1;
|
||||
dragOverrideH = -1;
|
||||
}
|
||||
|
||||
function saveGeometry(updates) {
|
||||
if (!isInstance)
|
||||
return;
|
||||
SessionData.updateDesktopWidgetInstancePosition(instanceId, positionKey, updates);
|
||||
}
|
||||
|
||||
function savePosition(finalX, finalY) {
|
||||
saveGeometry({
|
||||
x: syncPositionAcrossScreens ? finalX / screenWidth : finalX,
|
||||
y: syncPositionAcrossScreens ? finalY / screenHeight : finalY
|
||||
});
|
||||
}
|
||||
|
||||
function saveSize(finalW, finalH) {
|
||||
const sizeVal = forceSquare ? Math.max(finalW, finalH) : finalW;
|
||||
saveGeometry({
|
||||
width: sizeVal,
|
||||
height: forceSquare ? sizeVal : finalH
|
||||
});
|
||||
}
|
||||
|
||||
function saveDefaultGeometry(defaultWidth, defaultHeight) {
|
||||
if (!hasSavedSize) {
|
||||
const finalW = Math.max(minWidth, Math.min(defaultWidth, screenWidth));
|
||||
const finalH = Math.max(minHeight, Math.min(defaultHeight, screenHeight));
|
||||
saveSize(finalW, finalH);
|
||||
}
|
||||
if (hasSavedPosition)
|
||||
return;
|
||||
const finalX = Math.max(0, Math.min(screenWidth / 2 - widgetWidth / 2, screenWidth - widgetWidth));
|
||||
const finalY = Math.max(0, Math.min(screenHeight / 2 - widgetHeight / 2, screenHeight - widgetHeight));
|
||||
savePosition(finalX, finalY);
|
||||
}
|
||||
|
||||
function dragMoveTo(startX, startY, dx, dy) {
|
||||
let newX = Math.max(0, Math.min(startX + dx, screenWidth - widgetWidth));
|
||||
let newY = Math.max(0, Math.min(startY + dy, screenHeight - widgetHeight));
|
||||
if (!gridEnabled)
|
||||
return Qt.point(newX, newY);
|
||||
newX = Math.max(0, Math.min(snapToGrid(newX), screenWidth - widgetWidth));
|
||||
newY = Math.max(0, Math.min(snapToGrid(newY), screenHeight - widgetHeight));
|
||||
return Qt.point(newX, newY);
|
||||
}
|
||||
|
||||
function dragResizeTo(startWidth, startHeight, dx, dy) {
|
||||
let newW = Math.max(minWidth, Math.min(startWidth + dx, screenWidth - widgetX));
|
||||
let newH = Math.max(minHeight, Math.min(startHeight + dy, screenHeight - widgetY));
|
||||
if (gridEnabled) {
|
||||
newW = Math.max(minWidth, snapToGrid(newW));
|
||||
newH = Math.max(minHeight, snapToGrid(newH));
|
||||
}
|
||||
if (!forceSquare)
|
||||
return Qt.size(newW, newH);
|
||||
const size = Math.max(newW, newH);
|
||||
return Qt.size(Math.min(size, screenWidth - widgetX), Math.min(size, screenHeight - widgetY));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property int gridSize
|
||||
required property real extentWidth
|
||||
required property real extentHeight
|
||||
|
||||
opacity: 0.3
|
||||
|
||||
Repeater {
|
||||
model: Math.ceil(root.extentWidth / root.gridSize)
|
||||
|
||||
Rectangle {
|
||||
required property int index
|
||||
x: index * root.gridSize
|
||||
y: 0
|
||||
width: 1
|
||||
height: root.extentHeight
|
||||
color: Theme.primary
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Math.ceil(root.extentHeight / root.gridSize)
|
||||
|
||||
Rectangle {
|
||||
required property int index
|
||||
x: 0
|
||||
y: index * root.gridSize
|
||||
width: root.extentWidth
|
||||
height: 1
|
||||
color: Theme.primary
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,20 +8,22 @@ import qs.Widgets
|
||||
WidgetPickerWindow {
|
||||
id: root
|
||||
|
||||
property string listKey: "desktopWidgetInstances"
|
||||
|
||||
signal widgetAdded(string widgetType)
|
||||
|
||||
function addWidget(widget) {
|
||||
const widgetType = widget.id;
|
||||
const defaultConfig = DesktopWidgetRegistry.getDefaultConfig(widgetType);
|
||||
const name = widget.name || widgetType;
|
||||
SettingsData.createDesktopWidgetInstance(widgetType, name, defaultConfig);
|
||||
SettingsData.createDesktopWidgetInstance(widgetType, name, defaultConfig, listKey);
|
||||
root.widgetAdded(widgetType);
|
||||
root.hide();
|
||||
}
|
||||
|
||||
objectName: "desktopWidgetBrowser"
|
||||
title: I18n.tr("Add Desktop Widget")
|
||||
widgets: DesktopWidgetRegistry.registeredWidgetsList || []
|
||||
widgets: (DesktopWidgetRegistry.registeredWidgetsList || []).filter(widget => !widget.lockOnly)
|
||||
featuredFirst: true
|
||||
showEmptyState: true
|
||||
widgetDelegate: tileDelegate
|
||||
|
||||
@@ -14,7 +14,8 @@ SettingsReorderRow {
|
||||
readonly property string instanceId: instanceData?.id ?? ""
|
||||
readonly property string widgetType: instanceData?.widgetType ?? ""
|
||||
readonly property var widgetDef: DesktopWidgetRegistry.getWidget(widgetType)
|
||||
readonly property string widgetName: instanceData?.name ?? widgetDef?.name ?? widgetType
|
||||
readonly property string widgetName: instanceData?.name || widgetDef?.name || widgetType
|
||||
property bool fixed: false
|
||||
|
||||
signal configureRequested
|
||||
signal deleteRequested
|
||||
@@ -40,6 +41,7 @@ SettingsReorderRow {
|
||||
},
|
||||
DankToggle {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: !root.fixed
|
||||
hideText: true
|
||||
text: root.title
|
||||
checked: root.instanceData?.enabled ?? true
|
||||
@@ -51,6 +53,7 @@ SettingsReorderRow {
|
||||
},
|
||||
DankActionButton {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: !root.fixed
|
||||
iconName: "content_copy"
|
||||
tooltipText: I18n.tr("Duplicate", "verb, desktop widget menu action")
|
||||
onClicked: root.duplicateRequested()
|
||||
@@ -58,6 +61,7 @@ SettingsReorderRow {
|
||||
SettingsDeleteButton {
|
||||
id: deleteButton
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: !root.fixed
|
||||
onDeleteRequested: root.deleteRequested()
|
||||
}
|
||||
]
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
import QtQuick
|
||||
import qs.Modules.Settings.LockWidgetSettings as LWS
|
||||
|
||||
Loader {
|
||||
id: root
|
||||
|
||||
property string instanceId: ""
|
||||
property var instanceData: null
|
||||
property var widgetDef: null
|
||||
property var parentModal: null
|
||||
readonly property string widgetType: instanceData?.widgetType ?? ""
|
||||
|
||||
active: instanceData !== null
|
||||
sourceComponent: {
|
||||
switch (widgetType) {
|
||||
case "desktopClock":
|
||||
return clockSettings;
|
||||
case "systemMonitor":
|
||||
return systemMonitorSettings;
|
||||
case "lockClock":
|
||||
return lockClockSettings;
|
||||
case "lockAuth":
|
||||
return lockAuthSettings;
|
||||
case "lockNotifications":
|
||||
return lockNotificationsSettings;
|
||||
case "lockStatus":
|
||||
return lockStatusSettings;
|
||||
case "lockDate":
|
||||
return lockDateSettings;
|
||||
case "lockPower":
|
||||
return lockPowerSettings;
|
||||
default:
|
||||
return pluginSettings;
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: clockSettings
|
||||
|
||||
ClockSettings {
|
||||
instanceId: root.instanceId
|
||||
instanceData: root.instanceData
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: systemMonitorSettings
|
||||
|
||||
SystemMonitorSettings {
|
||||
instanceId: root.instanceId
|
||||
instanceData: root.instanceData
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: lockClockSettings
|
||||
|
||||
LWS.LockClockSettings {
|
||||
instanceId: root.instanceId
|
||||
instanceData: root.instanceData
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: lockAuthSettings
|
||||
|
||||
LWS.LockAuthSettings {
|
||||
instanceId: root.instanceId
|
||||
instanceData: root.instanceData
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: lockNotificationsSettings
|
||||
|
||||
LWS.LockNotificationsSettings {
|
||||
instanceId: root.instanceId
|
||||
instanceData: root.instanceData
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: lockStatusSettings
|
||||
|
||||
LWS.LockStatusSettings {
|
||||
instanceId: root.instanceId
|
||||
instanceData: root.instanceData
|
||||
parentModal: root.parentModal
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: lockDateSettings
|
||||
|
||||
LWS.LockDateSettings {
|
||||
instanceId: root.instanceId
|
||||
instanceData: root.instanceData
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: lockPowerSettings
|
||||
|
||||
LWS.LockPowerSettings {
|
||||
instanceId: root.instanceId
|
||||
instanceData: root.instanceData
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: pluginSettings
|
||||
|
||||
PluginDesktopWidgetSettings {
|
||||
instanceId: root.instanceId
|
||||
instanceData: root.instanceData
|
||||
widgetType: root.widgetType
|
||||
widgetDef: root.widgetDef
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,11 +11,15 @@ import qs.Modules.Settings.DesktopWidgetSettings as DWS
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var parentModal: null
|
||||
|
||||
readonly property string instanceId: SettingsUiState.selectedDesktopWidgetId
|
||||
readonly property var instanceData: (SettingsData.desktopWidgetInstances || []).find(instance => instance.id === instanceId) ?? null
|
||||
readonly property bool lockScreenInstance: SettingsData.widgetInstanceListKey(instanceId) === "lockScreenWidgetInstances"
|
||||
readonly property var instanceData: SettingsData.getDesktopWidgetInstance(instanceId)
|
||||
readonly property string widgetType: instanceData?.widgetType ?? ""
|
||||
readonly property var widgetDef: DesktopWidgetRegistry.getWidget(widgetType)
|
||||
readonly property string widgetName: instanceData?.name ?? widgetDef?.name ?? widgetType
|
||||
readonly property string widgetName: instanceData?.name || widgetDef?.name || widgetType
|
||||
readonly property bool fixed: widgetType === "lockAuth"
|
||||
readonly property var cfg: instanceData?.config ?? {}
|
||||
readonly property string overlayCommand: "dms ipc call desktopWidget toggleOverlay " + instanceId
|
||||
readonly property var groupOptions: [
|
||||
@@ -36,40 +40,18 @@ Item {
|
||||
|
||||
onWidgetNameChanged: SettingsUiState.selectedWidgetTitle = widgetName
|
||||
|
||||
Component {
|
||||
id: clockSettings
|
||||
|
||||
DWS.ClockSettings {
|
||||
instanceId: root.instanceId
|
||||
instanceData: root.instanceData
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: systemMonitorSettings
|
||||
|
||||
DWS.SystemMonitorSettings {
|
||||
instanceId: root.instanceId
|
||||
instanceData: root.instanceData
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: pluginSettings
|
||||
|
||||
DWS.PluginDesktopWidgetSettings {
|
||||
instanceId: root.instanceId
|
||||
instanceData: root.instanceData
|
||||
widgetType: root.widgetType
|
||||
widgetDef: root.widgetDef
|
||||
}
|
||||
}
|
||||
|
||||
SettingsPage {
|
||||
visible: root.instanceData !== null
|
||||
|
||||
SettingsCard {
|
||||
SettingsRow {
|
||||
visible: root.fixed
|
||||
iconName: root.widgetDef?.icon ?? "widgets"
|
||||
title: root.widgetDef?.name ?? root.widgetType
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
visible: !root.fixed
|
||||
iconName: root.widgetDef?.icon ?? "widgets"
|
||||
text: root.widgetDef?.name ?? root.widgetType
|
||||
description: root.widgetDef?.description ?? ""
|
||||
@@ -99,25 +81,19 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
DWS.DesktopWidgetTypeSettings {
|
||||
width: parent.width
|
||||
active: root.instanceData !== null
|
||||
sourceComponent: {
|
||||
switch (root.widgetType) {
|
||||
case "desktopClock":
|
||||
return clockSettings;
|
||||
case "systemMonitor":
|
||||
return systemMonitorSettings;
|
||||
default:
|
||||
return pluginSettings;
|
||||
}
|
||||
}
|
||||
instanceId: root.instanceId
|
||||
instanceData: root.instanceData
|
||||
widgetDef: root.widgetDef
|
||||
parentModal: root.parentModal
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
title: I18n.tr("Behavior")
|
||||
|
||||
SettingsToggleRow {
|
||||
visible: !root.lockScreenInstance
|
||||
text: I18n.tr("Show on overlay")
|
||||
description: I18n.tr("Keeps the widget above windows instead of below them", "desktop widget show on overlay toggle description")
|
||||
checked: root.cfg.showOnOverlay ?? false
|
||||
@@ -125,20 +101,21 @@ Item {
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
visible: CompositorService.isNiri
|
||||
visible: CompositorService.isNiri && !root.lockScreenInstance
|
||||
text: I18n.tr("Show on overview")
|
||||
checked: root.cfg.showOnOverview ?? false
|
||||
onToggled: checked => root.updateConfig("showOnOverview", checked)
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
visible: CompositorService.isNiri
|
||||
visible: CompositorService.isNiri && !root.lockScreenInstance
|
||||
text: I18n.tr("Show on overview only")
|
||||
checked: root.cfg.showOnOverviewOnly ?? false
|
||||
onToggled: checked => root.updateConfig("showOnOverviewOnly", checked)
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
visible: !root.lockScreenInstance
|
||||
text: I18n.tr("Click through")
|
||||
checked: root.cfg.clickThrough ?? false
|
||||
onToggled: checked => root.updateConfig("clickThrough", checked)
|
||||
@@ -156,6 +133,7 @@ Item {
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
visible: !root.lockScreenInstance
|
||||
title: I18n.tr("Command")
|
||||
|
||||
SettingsRow {
|
||||
|
||||
@@ -268,109 +268,77 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
id: lockWidgetBrowserLoader
|
||||
active: false
|
||||
|
||||
DesktopWidgetBrowser {
|
||||
parentModal: root.parentModal
|
||||
listKey: "lockScreenWidgetInstances"
|
||||
title: I18n.tr("Add widget")
|
||||
onWidgetAdded: ToastService.showInfo(I18n.tr("Widget added"))
|
||||
}
|
||||
}
|
||||
|
||||
SettingsPage {
|
||||
id: mainColumn
|
||||
|
||||
SettingsCard {
|
||||
width: parent.width
|
||||
iconName: "lock"
|
||||
title: I18n.tr("Layout")
|
||||
settingKey: "lockLayout"
|
||||
iconName: "widgets"
|
||||
title: I18n.tr("Widgets")
|
||||
settingKey: "lockScreenWidgets"
|
||||
tags: ["lock", "screen", "widgets", "clock", "plugins"]
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "lockScreenShowPowerActions"
|
||||
tags: ["lock", "screen", "power", "actions", "shutdown", "reboot"]
|
||||
text: I18n.tr("Show power actions")
|
||||
checked: SettingsData.lockScreenShowPowerActions
|
||||
onToggled: checked => SettingsData.set("lockScreenShowPowerActions", checked)
|
||||
}
|
||||
SettingsReorderList {
|
||||
id: lockWidgetList
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "lockScreenShowSystemIcons"
|
||||
tags: ["lock", "screen", "system", "icons", "status"]
|
||||
text: I18n.tr("Show system icons")
|
||||
checked: SettingsData.lockScreenShowSystemIcons
|
||||
onToggled: checked => SettingsData.set("lockScreenShowSystemIcons", checked)
|
||||
}
|
||||
model: SettingsData.lockScreenWidgetInstances || []
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "lockScreenShowTime"
|
||||
tags: ["lock", "screen", "time", "clock", "display"]
|
||||
text: I18n.tr("Show time")
|
||||
checked: SettingsData.lockScreenShowTime
|
||||
onToggled: checked => SettingsData.set("lockScreenShowTime", checked)
|
||||
}
|
||||
delegate: DesktopWidgetInstanceCard {
|
||||
required property var modelData
|
||||
|
||||
SettingsButtonGroupRow {
|
||||
settingKey: "lockScreenClockStyle"
|
||||
tags: ["lock", "screen", "time", "clock", "style", "vertical"]
|
||||
text: I18n.tr("Clock style")
|
||||
visible: SettingsData.lockScreenShowTime
|
||||
model: [I18n.tr("Horizontal", "lock screen clock style option"), I18n.tr("Vertical", "lock screen clock style option")]
|
||||
currentIndex: SettingsData.lockScreenClockStyle === "vertical" ? 1 : 0
|
||||
onSelectionChanged: (index, selected) => {
|
||||
if (!selected)
|
||||
return;
|
||||
SettingsData.set("lockScreenClockStyle", index === 1 ? "vertical" : "horizontal");
|
||||
}
|
||||
}
|
||||
reorderList: lockWidgetList
|
||||
reorderEnabled: false
|
||||
instanceData: modelData
|
||||
fixed: modelData.widgetType === "lockAuth"
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "lockScreenShowDate"
|
||||
tags: ["lock", "screen", "date", "calendar", "display"]
|
||||
text: I18n.tr("Show date")
|
||||
checked: SettingsData.lockScreenShowDate
|
||||
onToggled: checked => SettingsData.set("lockScreenShowDate", checked)
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "lockScreenShowProfileImage"
|
||||
tags: ["lock", "screen", "profile", "image", "avatar", "picture"]
|
||||
text: I18n.tr("Show profile image")
|
||||
checked: SettingsData.lockScreenShowProfileImage
|
||||
onToggled: checked => SettingsData.set("lockScreenShowProfileImage", checked)
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "lockScreenShowPasswordField"
|
||||
tags: ["lock", "screen", "password", "field", "input", "visible"]
|
||||
text: I18n.tr("Show password field")
|
||||
description: I18n.tr("A hidden field appears as soon as a key is pressed")
|
||||
checked: SettingsData.lockScreenShowPasswordField
|
||||
onToggled: checked => SettingsData.set("lockScreenShowPasswordField", checked)
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "lockScreenShowMediaPlayer"
|
||||
tags: ["lock", "screen", "media", "player", "music", "mpris"]
|
||||
text: I18n.tr("Show media player")
|
||||
checked: SettingsData.lockScreenShowMediaPlayer
|
||||
onToggled: checked => SettingsData.set("lockScreenShowMediaPlayer", checked)
|
||||
}
|
||||
|
||||
SettingsSplitRow {
|
||||
settingKey: "lockScreenShowWeather"
|
||||
tab: "lock_screen"
|
||||
tags: ["weather", "temperature"]
|
||||
title: I18n.tr("Weather")
|
||||
checked: SettingsData.lockScreenShowWeather
|
||||
onToggled: checked => SettingsData.set("lockScreenShowWeather", checked)
|
||||
onNavigated: keyboard => root.parentModal?.navigateTo("weather", keyboard)
|
||||
}
|
||||
|
||||
SettingsDropdownRow {
|
||||
settingKey: "lockScreenNotificationMode"
|
||||
tags: ["lock", "screen", "notification", "notifications", "privacy"]
|
||||
text: I18n.tr("Notifications")
|
||||
options: [I18n.tr("Disabled", "lock screen notification mode option"), I18n.tr("Count only", "lock screen notification mode option"), I18n.tr("App names", "lock screen notification mode option"), I18n.tr("Full content", "lock screen notification mode option")]
|
||||
currentValue: options[SettingsData.lockScreenNotificationMode] || options[0]
|
||||
onValueChanged: value => {
|
||||
const idx = options.indexOf(value);
|
||||
if (idx >= 0) {
|
||||
SettingsData.set("lockScreenNotificationMode", idx);
|
||||
onConfigureRequested: {
|
||||
SettingsUiState.selectedDesktopWidgetId = instanceId;
|
||||
SettingsUiState.selectedWidgetTitle = widgetName;
|
||||
root.parentModal?.navigateTo("desktop_widget");
|
||||
}
|
||||
onDuplicateRequested: SettingsData.duplicateDesktopWidgetInstance(instanceId)
|
||||
onDeleteRequested: {
|
||||
SettingsData.removeDesktopWidgetInstance(instanceId);
|
||||
ToastService.showInfo(I18n.tr("Widget removed"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsRow {
|
||||
iconName: "add"
|
||||
title: I18n.tr("Add widget")
|
||||
clickable: true
|
||||
onClicked: {
|
||||
lockWidgetBrowserLoader.active = true;
|
||||
lockWidgetBrowserLoader.item?.show();
|
||||
}
|
||||
}
|
||||
|
||||
SettingsRow {
|
||||
iconName: "edit"
|
||||
title: I18n.tr("Edit widgets")
|
||||
clickable: true
|
||||
onClicked: SessionService.lockEditorRequested()
|
||||
}
|
||||
|
||||
SettingsRow {
|
||||
iconName: "restart_alt"
|
||||
title: I18n.tr("Reset to default")
|
||||
clickable: true
|
||||
onClicked: SettingsData.resetLockScreenWidgets()
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Modules.Settings.Widgets
|
||||
import qs.Modules.Settings.DesktopWidgetSettings
|
||||
|
||||
DesktopWidgetInstanceSettings {
|
||||
id: root
|
||||
|
||||
readonly property var styles: ["pill", "expressive", "ring", "minimal", "dots"]
|
||||
readonly property var styleLabels: [I18n.tr("Pill", "lock screen password field style"), I18n.tr("Expressive"), I18n.tr("Ring"), I18n.tr("Minimal", "lock screen password field style"), I18n.tr("Dots", "lock screen password field style")]
|
||||
readonly property var visibilityModes: ["always", "typing", "never"]
|
||||
readonly property var visibilityLabels: [I18n.tr("Always show", "lock screen widget visibility"), I18n.tr("Hide until typing", "lock screen widget visibility"), I18n.tr("Never")]
|
||||
readonly property string profileVisibility: cfg.profileVisibility ?? (cfg.showProfileImage === false ? "never" : "always")
|
||||
readonly property string passwordVisibility: cfg.passwordVisibility ?? (cfg.showPasswordField === false ? "typing" : "always")
|
||||
|
||||
showAppearance: false
|
||||
|
||||
SettingsDropdownRow {
|
||||
text: I18n.tr("Style")
|
||||
options: root.styleLabels
|
||||
currentValue: root.styleLabels[Math.max(0, root.styles.indexOf(root.cfg.style ?? "expressive"))]
|
||||
onValueChanged: value => {
|
||||
const idx = root.styleLabels.indexOf(value);
|
||||
if (idx >= 0)
|
||||
root.updateConfig("style", root.styles[idx]);
|
||||
}
|
||||
}
|
||||
|
||||
SettingsDropdownRow {
|
||||
text: I18n.tr("Profile image")
|
||||
options: root.visibilityLabels
|
||||
currentValue: root.visibilityLabels[Math.max(0, root.visibilityModes.indexOf(root.profileVisibility))]
|
||||
onValueChanged: value => {
|
||||
const index = root.visibilityLabels.indexOf(value);
|
||||
if (index >= 0)
|
||||
root.updateConfig("profileVisibility", root.visibilityModes[index]);
|
||||
}
|
||||
}
|
||||
|
||||
SettingsDropdownRow {
|
||||
text: I18n.tr("Password field", "lock screen password field visibility setting")
|
||||
description: root.passwordVisibility === "typing" ? I18n.tr("Typing reveals the field. Escape hides it again. The editor always shows it.", "lock screen hide until typing behavior") : ""
|
||||
options: root.visibilityLabels.slice(0, 2)
|
||||
currentValue: root.visibilityLabels[Math.max(0, root.visibilityModes.indexOf(root.passwordVisibility))]
|
||||
onValueChanged: value => {
|
||||
const index = root.visibilityLabels.indexOf(value);
|
||||
if (index >= 0 && index < 2)
|
||||
root.updateConfig("passwordVisibility", root.visibilityModes[index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Modules.Settings.Widgets
|
||||
import qs.Modules.Settings.DesktopWidgetSettings
|
||||
|
||||
DesktopWidgetInstanceSettings {
|
||||
id: root
|
||||
|
||||
readonly property var styles: ["horizontal", "vertical", "expressive", "analog"]
|
||||
readonly property var styleLabels: [I18n.tr("Horizontal", "lock screen clock style option"), I18n.tr("Vertical", "lock screen clock style option"), I18n.tr("Expressive"), I18n.tr("Analog")]
|
||||
readonly property string style: cfg.style ?? "expressive"
|
||||
readonly property bool textStyle: style === "horizontal" || style === "vertical"
|
||||
|
||||
showAppearance: false
|
||||
|
||||
SettingsDropdownRow {
|
||||
text: I18n.tr("Clock style")
|
||||
options: root.styleLabels
|
||||
currentValue: root.styleLabels[Math.max(0, root.styles.indexOf(root.style))]
|
||||
onValueChanged: value => {
|
||||
const idx = root.styleLabels.indexOf(value);
|
||||
if (idx >= 0)
|
||||
root.updateConfig("style", root.styles[idx]);
|
||||
}
|
||||
}
|
||||
|
||||
SettingsSliderRow {
|
||||
visible: root.textStyle
|
||||
text: I18n.tr("Weight")
|
||||
minimum: 0
|
||||
maximum: 900
|
||||
step: 100
|
||||
minimumLabel: I18n.tr("Default")
|
||||
value: root.cfg.weight ?? 0
|
||||
onSliderValueChanged: newValue => root.updateConfig("weight", newValue)
|
||||
}
|
||||
|
||||
SettingsFontDropdownRow {
|
||||
visible: root.textStyle
|
||||
text: I18n.tr("Font")
|
||||
currentFont: root.cfg.fontFamily ?? ""
|
||||
defaultFamily: SettingsData.lockScreenFontFamily || Theme.defaultFontFamily
|
||||
onFontSelected: family => root.updateConfig("fontFamily", family)
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
visible: root.style === "horizontal"
|
||||
text: I18n.tr("Italic")
|
||||
checked: root.cfg.italic ?? false
|
||||
onToggled: checked => root.updateConfig("italic", checked)
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
visible: root.style === "horizontal" || root.style === "expressive"
|
||||
text: I18n.tr("Accent minutes", "lock screen clock colors")
|
||||
checked: root.cfg.twoTone ?? true
|
||||
onToggled: checked => root.updateConfig("twoTone", checked)
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
text: I18n.tr("Automatic placement", "lock screen clock placement")
|
||||
description: I18n.tr("Choose a clear area of the background. Drag the clock to place it yourself.", "lock screen automatic clock placement")
|
||||
checked: root.cfg.autoPosition ?? true
|
||||
onToggled: checked => {
|
||||
if (checked)
|
||||
SessionData.resetDesktopWidgetInstanceGeometry(root.instanceId, ["x", "y"]);
|
||||
root.updateConfig("autoPosition", checked);
|
||||
}
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
visible: root.style === "analog"
|
||||
text: I18n.tr("Show hour numbers")
|
||||
checked: root.cfg.showAnalogNumbers ?? false
|
||||
onToggled: checked => root.updateConfig("showAnalogNumbers", checked)
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
visible: root.style === "analog"
|
||||
text: I18n.tr("Show seconds")
|
||||
checked: root.cfg.showAnalogSeconds ?? true
|
||||
onToggled: checked => root.updateConfig("showAnalogSeconds", checked)
|
||||
}
|
||||
|
||||
SettingsColorPicker {
|
||||
showDefault: true
|
||||
colorMode: root.cfg.colorMode ?? "default"
|
||||
customColor: root.cfg.customColor ?? "#ffffff"
|
||||
onColorModeSelected: mode => root.updateConfig("colorMode", mode)
|
||||
onCustomColorSelected: selectedColor => root.updateConfig("customColor", selectedColor.toString())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Modules.Settings.Widgets
|
||||
import qs.Modules.Settings.DesktopWidgetSettings
|
||||
|
||||
DesktopWidgetInstanceSettings {
|
||||
id: root
|
||||
|
||||
showAppearance: false
|
||||
|
||||
SettingsToggleRow {
|
||||
text: I18n.tr("Show date")
|
||||
checked: root.instanceData?.enabled !== false
|
||||
onToggled: checked => SettingsData.updateDesktopWidgetInstance(root.instanceId, {
|
||||
enabled: checked
|
||||
})
|
||||
}
|
||||
|
||||
SettingsDropdownRow {
|
||||
text: I18n.tr("Date format")
|
||||
options: [I18n.tr("Short"), I18n.tr("Long")]
|
||||
currentValue: (root.cfg.format ?? "long") === "short" ? I18n.tr("Short") : I18n.tr("Long")
|
||||
enabled: SettingsData.lockDateFormat === ""
|
||||
onValueChanged: value => root.updateConfig("format", value === I18n.tr("Short") ? "short" : "long")
|
||||
}
|
||||
|
||||
SettingsColorPicker {
|
||||
showDefault: true
|
||||
colorMode: root.cfg.colorMode ?? "default"
|
||||
customColor: root.cfg.customColor ?? "#ffffff"
|
||||
onColorModeSelected: mode => root.updateConfig("colorMode", mode)
|
||||
onCustomColorSelected: selectedColor => root.updateConfig("customColor", selectedColor.toString())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Modules.Settings.Widgets
|
||||
import qs.Modules.Settings.DesktopWidgetSettings
|
||||
|
||||
DesktopWidgetInstanceSettings {
|
||||
id: root
|
||||
|
||||
showAppearance: false
|
||||
|
||||
SettingsDropdownRow {
|
||||
text: I18n.tr("Lock screen content")
|
||||
options: [I18n.tr("Count only", "lock screen notification mode option"), I18n.tr("App names", "lock screen notification mode option"), I18n.tr("Full content", "lock screen notification mode option")]
|
||||
currentValue: options[(root.cfg.mode ?? 1) - 1] ?? options[0]
|
||||
onValueChanged: value => {
|
||||
const idx = options.indexOf(value);
|
||||
if (idx < 0)
|
||||
return;
|
||||
root.updateConfig("mode", idx + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
import qs.Modules.Settings.Widgets
|
||||
import qs.Modules.Settings.DesktopWidgetSettings
|
||||
|
||||
DesktopWidgetInstanceSettings {
|
||||
id: root
|
||||
|
||||
readonly property var shapes: ["round", "cookie9", "clover4", "sunny", "softBurst"]
|
||||
readonly property string shape: cfg.shape ?? "round"
|
||||
|
||||
showAppearance: false
|
||||
|
||||
SettingsRow {
|
||||
title: I18n.tr("Style")
|
||||
|
||||
body: Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Repeater {
|
||||
model: root.shapes
|
||||
|
||||
Rectangle {
|
||||
id: swatch
|
||||
|
||||
required property string modelData
|
||||
readonly property bool selected: root.shape === modelData
|
||||
|
||||
width: (parent.width - Theme.spacingS * (root.shapes.length - 1)) / root.shapes.length
|
||||
height: Theme.listItemHeight + Theme.spacingXS
|
||||
radius: Theme.cornerRadius
|
||||
color: selected ? Theme.primarySelected : SettingsMetrics.controlColor
|
||||
border.color: selected ? Theme.primary : Theme.withAlpha(Theme.primary, 0)
|
||||
border.width: Theme.outlineWidthFocused
|
||||
Accessible.role: Accessible.RadioButton
|
||||
Accessible.name: modelData
|
||||
|
||||
DankMaterialShape {
|
||||
anchors.centerIn: parent
|
||||
width: Theme.iconSizeLarge
|
||||
height: Theme.iconSizeLarge
|
||||
shape: swatch.modelData === "round" ? "circle" : swatch.modelData
|
||||
color: Theme.secondaryContainer
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "power_settings_new"
|
||||
size: Theme.iconSizeSmall
|
||||
color: Theme.onSecondaryContainer
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.updateConfig("shape", swatch.modelData)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Modules.Settings.Widgets
|
||||
import qs.Modules.Settings.DesktopWidgetSettings
|
||||
|
||||
DesktopWidgetInstanceSettings {
|
||||
id: root
|
||||
|
||||
property var parentModal: null
|
||||
|
||||
showAppearance: false
|
||||
|
||||
SettingsToggleRow {
|
||||
text: I18n.tr("Background")
|
||||
checked: root.cfg.background ?? false
|
||||
onToggled: checked => root.updateConfig("background", checked)
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
text: I18n.tr("Show media player")
|
||||
checked: root.cfg.showMediaPlayer ?? true
|
||||
onToggled: checked => root.updateConfig("showMediaPlayer", checked)
|
||||
}
|
||||
|
||||
SettingsSplitRow {
|
||||
title: I18n.tr("Weather")
|
||||
checked: root.cfg.showWeather ?? true
|
||||
onToggled: checked => root.updateConfig("showWeather", checked)
|
||||
onNavigated: keyboard => root.parentModal?.navigateTo("weather", keyboard)
|
||||
}
|
||||
}
|
||||
@@ -319,16 +319,26 @@ Column {
|
||||
}
|
||||
|
||||
SettingsDropdownRow {
|
||||
settingKey: "lockScreenNotificationMode"
|
||||
readonly property var lockNotifications: SettingsData.lockWidgetInstance("lockNotifications")
|
||||
readonly property int currentMode: !lockNotifications || lockNotifications.enabled === false ? 0 : (lockNotifications.config?.mode ?? 1)
|
||||
|
||||
settingKey: "lockNotificationsMode"
|
||||
tags: ["lock", "screen", "notification", "notifications", "privacy"]
|
||||
visible: lockNotifications !== null
|
||||
text: I18n.tr("Lock screen content")
|
||||
options: [I18n.tr("Disabled", "lock screen notification mode option"), I18n.tr("Count only", "lock screen notification mode option"), I18n.tr("App names", "lock screen notification mode option"), I18n.tr("Full content", "lock screen notification mode option")]
|
||||
currentValue: options[SettingsData.lockScreenNotificationMode] || options[0]
|
||||
currentValue: options[currentMode] || options[0]
|
||||
onValueChanged: value => {
|
||||
const idx = options.indexOf(value);
|
||||
if (idx < 0)
|
||||
return;
|
||||
SettingsData.set("lockScreenNotificationMode", idx);
|
||||
SettingsData.updateDesktopWidgetInstance(lockNotifications.id, {
|
||||
enabled: idx > 0
|
||||
});
|
||||
if (idx > 0)
|
||||
SettingsData.updateDesktopWidgetInstanceConfig(lockNotifications.id, {
|
||||
mode: idx
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,12 +127,17 @@ Item {
|
||||
settingKey: "weatherDisplay"
|
||||
|
||||
SettingsToggleRow {
|
||||
readonly property var lockStatus: SettingsData.lockWidgetInstance("lockStatus")
|
||||
|
||||
tab: "weather"
|
||||
settingKey: "lockScreenShowWeather"
|
||||
settingKey: "lockStatusWeather"
|
||||
tags: ["weather", "lock", "screen"]
|
||||
visible: lockStatus !== null
|
||||
text: I18n.tr("Lock screen")
|
||||
checked: SettingsData.lockScreenShowWeather
|
||||
onToggled: checked => SettingsData.set("lockScreenShowWeather", checked)
|
||||
checked: lockStatus?.config?.showWeather ?? true
|
||||
onToggled: checked => SettingsData.updateDesktopWidgetInstanceConfig(lockStatus.id, {
|
||||
showWeather: checked
|
||||
})
|
||||
}
|
||||
|
||||
SettingsNavRow {
|
||||
|
||||
@@ -11,6 +11,8 @@ SettingsRow {
|
||||
property string colorMode: "primary"
|
||||
property color customColor: "#ffffff"
|
||||
property string pickerTitle: I18n.tr("Choose color", "color picker title")
|
||||
property bool showDefault: false
|
||||
property color defaultColor: Theme.lockScreenContentColor
|
||||
|
||||
signal colorModeSelected(string mode)
|
||||
signal customColorSelected(color selectedColor)
|
||||
@@ -22,7 +24,14 @@ SettingsRow {
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Repeater {
|
||||
model: [
|
||||
id: swatches
|
||||
model: (root.showDefault ? [
|
||||
{
|
||||
id: "default",
|
||||
label: I18n.tr("Default"),
|
||||
color: root.defaultColor
|
||||
}
|
||||
] : []).concat([
|
||||
{
|
||||
id: "primary",
|
||||
label: I18n.tr("Primary"),
|
||||
@@ -38,13 +47,13 @@ SettingsRow {
|
||||
label: I18n.tr("Custom"),
|
||||
color: root.customColor
|
||||
}
|
||||
]
|
||||
])
|
||||
|
||||
Rectangle {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: (parent.width - Theme.spacingS * 2) / 3
|
||||
width: (parent.width - Theme.spacingS * (swatches.count - 1)) / swatches.count
|
||||
height: Theme.listItemHeight + Theme.spacingXS
|
||||
radius: Theme.cornerRadius
|
||||
color: root.colorMode === modelData.id ? Theme.primarySelected : SettingsMetrics.controlColor
|
||||
|
||||
@@ -41,6 +41,7 @@ SettingsRow {
|
||||
DankDragHandle {
|
||||
id: handle
|
||||
|
||||
visible: root.reorderEnabled
|
||||
coordinateItem: root.reorderList
|
||||
upDownKeysMove: false
|
||||
label: root.title
|
||||
|
||||
@@ -4,6 +4,7 @@ pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import "../Common/settings/SettingsSpec.js" as Spec
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
@@ -15,6 +16,7 @@ Singleton {
|
||||
|
||||
Component.onCompleted: {
|
||||
registerBuiltins();
|
||||
registerLockBuiltins();
|
||||
Qt.callLater(syncPluginWidgets);
|
||||
}
|
||||
|
||||
@@ -64,6 +66,28 @@ Singleton {
|
||||
});
|
||||
}
|
||||
|
||||
function registerLockBuiltins() {
|
||||
const lockWidget = (id, name, icon) => registerWidget({
|
||||
id: id,
|
||||
name: name,
|
||||
icon: icon,
|
||||
description: "",
|
||||
type: "builtin",
|
||||
lockOnly: true,
|
||||
defaultConfig: Spec.SPEC.lockScreenWidgetInstances.def.find(inst => inst.widgetType === id)?.config ?? {},
|
||||
defaultSize: {
|
||||
width: 200,
|
||||
height: 200
|
||||
}
|
||||
});
|
||||
lockWidget("lockClock", I18n.tr("Clock"), "schedule");
|
||||
lockWidget("lockDate", I18n.tr("Date"), "event");
|
||||
lockWidget("lockAuth", I18n.tr("Password"), "lock");
|
||||
lockWidget("lockNotifications", I18n.tr("Notifications"), "notifications");
|
||||
lockWidget("lockStatus", I18n.tr("Status"), "wifi");
|
||||
lockWidget("lockPower", I18n.tr("Power"), "power_settings_new");
|
||||
}
|
||||
|
||||
function getDefaultClockConfig() {
|
||||
return {
|
||||
style: "analog",
|
||||
@@ -133,18 +157,24 @@ Singleton {
|
||||
if (!widget)
|
||||
return {};
|
||||
|
||||
if (widget.type === "builtin") {
|
||||
switch (widgetType) {
|
||||
case "desktopClock":
|
||||
return getDefaultClockConfig();
|
||||
case "systemMonitor":
|
||||
return getDefaultSystemMonitorConfig();
|
||||
default:
|
||||
return widget.defaultConfig ?? {};
|
||||
}
|
||||
switch (widgetType) {
|
||||
case "desktopClock":
|
||||
return getDefaultClockConfig();
|
||||
case "systemMonitor":
|
||||
return getDefaultSystemMonitorConfig();
|
||||
}
|
||||
return JSON.parse(JSON.stringify(widget.defaultConfig ?? {}));
|
||||
}
|
||||
|
||||
return widget.defaultConfig ?? {};
|
||||
function showsOnScreen(prefs, screen) {
|
||||
if (!Array.isArray(prefs) || prefs.length === 0 || prefs.includes("all"))
|
||||
return true;
|
||||
const screenKey = SettingsData.getScreenDisplayName(screen);
|
||||
return prefs.some(p => {
|
||||
if (typeof p === "string")
|
||||
return p === screenKey || p === screen?.name;
|
||||
return p?.name === screen?.name || p === screenKey;
|
||||
});
|
||||
}
|
||||
|
||||
function getDefaultSize(widgetType) {
|
||||
@@ -218,7 +248,11 @@ Singleton {
|
||||
}
|
||||
|
||||
function getBuiltinWidgets() {
|
||||
return registeredWidgetsList.filter(w => w.type === "builtin");
|
||||
return registeredWidgetsList.filter(w => w.type === "builtin" && !w.lockOnly);
|
||||
}
|
||||
|
||||
function getDesktopWidgets() {
|
||||
return registeredWidgetsList.filter(w => !w.lockOnly);
|
||||
}
|
||||
|
||||
function getPluginWidgets() {
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property var decodedSamples: ({})
|
||||
property var layouts: ({})
|
||||
|
||||
function neededFor(screen) {
|
||||
return SettingsData.lockScreenWidgetInstances.some(instance => {
|
||||
if (instance.widgetType !== "lockClock" || instance.enabled === false || instance.config?.autoPosition === false || !DesktopWidgetRegistry.showsOnScreen(instance.config?.displayPreferences, screen))
|
||||
return false;
|
||||
const positionKey = instance.config?.syncPositionAcrossScreens ? "_synced" : SettingsData.getScreenDisplayName(screen);
|
||||
return SessionData.desktopWidgetInstancePositions[instance.id]?.[positionKey]?.x === undefined;
|
||||
});
|
||||
}
|
||||
|
||||
function backgroundKey(screenName, width, height) {
|
||||
const custom = SettingsData.lockScreenWallpaperPath !== "";
|
||||
const source = custom ? SettingsData.lockScreenWallpaperPath : SessionData.getMonitorWallpaper(screenName);
|
||||
const backdrop = !source || source.startsWith("#");
|
||||
const fill = SettingsData.lockScreenWallpaperFillMode || (custom ? "Fill" : SessionData.getMonitorWallpaperFillMode(screenName));
|
||||
return JSON.stringify([1, source, width, height, fill, fill === "Scrolling" ? SessionData.getMonitorScrollPosition(screenName) : null, backdrop ? SessionData.getMonitorMaterialWallpaper(screenName) : null, backdrop ? Theme.wallpaperPalette() : null, Theme.lockScreenBlur, Theme.lockScreenBlurMax, Theme.lockScreenScrimAlpha, Theme.screenOffColor.toString(), SettingsData.effectiveWallpaperBackgroundColor.toString()]);
|
||||
}
|
||||
|
||||
function sampleFor(screenName, key) {
|
||||
const sample = CacheData.lockScreenPlacementSamples[screenName];
|
||||
if (!sample || sample.key !== key || typeof sample.key !== "string" || !Number.isInteger(sample.width) || !Number.isInteger(sample.height) || sample.width < 1 || sample.height < 1 || sample.width > 128 || sample.height > 128 || typeof sample.pixels !== "string" || sample.pixels.length !== sample.width * sample.height * 2)
|
||||
return null;
|
||||
return sample;
|
||||
}
|
||||
|
||||
function hasSample(screenName, key) {
|
||||
return sampleFor(screenName, key) !== null;
|
||||
}
|
||||
|
||||
function storeSample(screenName, key, luminances, width, height) {
|
||||
const pixels = luminances.map(value => Math.round(Math.max(0, Math.min(1, value)) * 255).toString(16).padStart(2, "0")).join("");
|
||||
delete decodedSamples[screenName];
|
||||
delete layouts[screenName];
|
||||
CacheData.set("lockScreenPlacementSamples", Object.assign({}, CacheData.lockScreenPlacementSamples, {
|
||||
[screenName]: {
|
||||
key,
|
||||
pixels,
|
||||
width,
|
||||
height
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
function layoutFor(screenName, sample, key, calculate) {
|
||||
if (!sample)
|
||||
return {};
|
||||
const cached = layouts[screenName];
|
||||
if (cached?.sampleKey === sample.key && cached.key === key)
|
||||
return cached.positions;
|
||||
let decoded = decodedSamples[screenName];
|
||||
if (decoded?.key !== sample.key) {
|
||||
const luminances = [];
|
||||
for (let i = 0; i < sample.pixels.length; i += 2) {
|
||||
const value = parseInt(sample.pixels.slice(i, i + 2), 16);
|
||||
if (!Number.isFinite(value))
|
||||
return {};
|
||||
luminances.push(value / 255);
|
||||
}
|
||||
decoded = {
|
||||
key: sample.key,
|
||||
luminances
|
||||
};
|
||||
decodedSamples[screenName] = decoded;
|
||||
}
|
||||
const positions = calculate(decoded.luminances, sample.width, sample.height);
|
||||
layouts[screenName] = {
|
||||
sampleKey: sample.key,
|
||||
key,
|
||||
positions
|
||||
};
|
||||
return positions;
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,7 @@ Singleton {
|
||||
signal sessionLocked
|
||||
signal sessionUnlocked
|
||||
signal sessionResumed
|
||||
signal lockEditorRequested
|
||||
signal lidOpened
|
||||
signal loginctlStateChanged
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import Quickshell
|
||||
import qs.Common
|
||||
import qs.DankCommon.Common as DC
|
||||
import qs.Modules
|
||||
import qs.Modules.Lock
|
||||
import qs.Services
|
||||
|
||||
ShellRoot {
|
||||
@@ -49,6 +50,7 @@ ShellRoot {
|
||||
|
||||
sourceComponent: Scope {
|
||||
WallpaperBackground {}
|
||||
LockPlacementPreparer {}
|
||||
|
||||
Loader {
|
||||
active: SettingsData.blurredWallpaperLayer && CompositorService.isNiri
|
||||
|
||||
@@ -1516,6 +1516,30 @@
|
||||
"page": "personalization",
|
||||
"description": "Requires restart"
|
||||
},
|
||||
{
|
||||
"section": "lockNotificationsMode",
|
||||
"label": "Lock screen content",
|
||||
"tabIndex": -1,
|
||||
"category": "Notifications",
|
||||
"keywords": [
|
||||
"alert",
|
||||
"alerts",
|
||||
"content",
|
||||
"lock",
|
||||
"lockscreen",
|
||||
"login",
|
||||
"messages",
|
||||
"notif",
|
||||
"notification",
|
||||
"notifications",
|
||||
"notifs",
|
||||
"privacy",
|
||||
"screen",
|
||||
"security",
|
||||
"toast"
|
||||
],
|
||||
"page": "notifications"
|
||||
},
|
||||
{
|
||||
"section": "notificationHistorySaveLow",
|
||||
"label": "Low",
|
||||
@@ -7088,26 +7112,6 @@
|
||||
"parentLabel": "Security",
|
||||
"icon": "lock"
|
||||
},
|
||||
{
|
||||
"section": "lockScreenClockStyle",
|
||||
"label": "Clock style",
|
||||
"tabIndex": 11,
|
||||
"category": "Lock screen",
|
||||
"keywords": [
|
||||
"clock",
|
||||
"lock",
|
||||
"lockscreen",
|
||||
"login",
|
||||
"password",
|
||||
"screen",
|
||||
"security",
|
||||
"style",
|
||||
"time",
|
||||
"vertical",
|
||||
"watch"
|
||||
],
|
||||
"parentLabel": "Security"
|
||||
},
|
||||
{
|
||||
"section": "lockDisplay",
|
||||
"label": "Displays",
|
||||
@@ -7174,33 +7178,6 @@
|
||||
],
|
||||
"parentLabel": "Security"
|
||||
},
|
||||
{
|
||||
"section": "lockLayout",
|
||||
"label": "Layout",
|
||||
"tabIndex": 11,
|
||||
"category": "Lock screen",
|
||||
"keywords": [
|
||||
"actions",
|
||||
"appears",
|
||||
"field",
|
||||
"hidden",
|
||||
"layout",
|
||||
"lock",
|
||||
"lockscreen",
|
||||
"login",
|
||||
"password",
|
||||
"power",
|
||||
"pressed",
|
||||
"reboot",
|
||||
"screen",
|
||||
"security",
|
||||
"shutdown",
|
||||
"soon"
|
||||
],
|
||||
"parentLabel": "Security",
|
||||
"icon": "lock",
|
||||
"description": "A hidden field appears as soon as a key is pressed"
|
||||
},
|
||||
{
|
||||
"section": "lockAtStartup",
|
||||
"label": "Lock at startup",
|
||||
@@ -7260,28 +7237,6 @@
|
||||
"parentLabel": "Security",
|
||||
"description": "Layout, authentication, screensaver"
|
||||
},
|
||||
{
|
||||
"section": "lockScreenNotificationMode",
|
||||
"label": "Notifications",
|
||||
"tabIndex": 11,
|
||||
"category": "Lock screen",
|
||||
"keywords": [
|
||||
"alert",
|
||||
"alerts",
|
||||
"lock",
|
||||
"lockscreen",
|
||||
"login",
|
||||
"notif",
|
||||
"notification",
|
||||
"notifications",
|
||||
"notifs",
|
||||
"password",
|
||||
"privacy",
|
||||
"screen",
|
||||
"security"
|
||||
],
|
||||
"parentLabel": "Security"
|
||||
},
|
||||
{
|
||||
"section": "lockScreenVideoEnabled",
|
||||
"label": "Play video on lock",
|
||||
@@ -7431,153 +7386,6 @@
|
||||
],
|
||||
"parentLabel": "Security"
|
||||
},
|
||||
{
|
||||
"section": "lockScreenShowDate",
|
||||
"label": "Show date",
|
||||
"tabIndex": 11,
|
||||
"category": "Lock screen",
|
||||
"keywords": [
|
||||
"calendar",
|
||||
"date",
|
||||
"display",
|
||||
"lock",
|
||||
"lockscreen",
|
||||
"login",
|
||||
"password",
|
||||
"screen",
|
||||
"security",
|
||||
"show"
|
||||
],
|
||||
"parentLabel": "Security"
|
||||
},
|
||||
{
|
||||
"section": "lockScreenShowMediaPlayer",
|
||||
"label": "Show media player",
|
||||
"tabIndex": 11,
|
||||
"category": "Lock screen",
|
||||
"keywords": [
|
||||
"audio",
|
||||
"lock",
|
||||
"lockscreen",
|
||||
"login",
|
||||
"media",
|
||||
"mpris",
|
||||
"music",
|
||||
"password",
|
||||
"playback",
|
||||
"player",
|
||||
"screen",
|
||||
"security",
|
||||
"show"
|
||||
],
|
||||
"parentLabel": "Security"
|
||||
},
|
||||
{
|
||||
"section": "lockScreenShowPasswordField",
|
||||
"label": "Show password field",
|
||||
"tabIndex": 11,
|
||||
"category": "Lock screen",
|
||||
"keywords": [
|
||||
"appears",
|
||||
"field",
|
||||
"hidden",
|
||||
"input",
|
||||
"lock",
|
||||
"lockscreen",
|
||||
"login",
|
||||
"password",
|
||||
"pressed",
|
||||
"screen",
|
||||
"security",
|
||||
"show",
|
||||
"soon",
|
||||
"visible"
|
||||
],
|
||||
"parentLabel": "Security",
|
||||
"description": "A hidden field appears as soon as a key is pressed"
|
||||
},
|
||||
{
|
||||
"section": "lockScreenShowPowerActions",
|
||||
"label": "Show power actions",
|
||||
"tabIndex": 11,
|
||||
"category": "Lock screen",
|
||||
"keywords": [
|
||||
"actions",
|
||||
"hibernate",
|
||||
"lock",
|
||||
"lockscreen",
|
||||
"login",
|
||||
"password",
|
||||
"power",
|
||||
"reboot",
|
||||
"restart",
|
||||
"screen",
|
||||
"security",
|
||||
"show",
|
||||
"shutdown",
|
||||
"sleep",
|
||||
"suspend"
|
||||
],
|
||||
"parentLabel": "Security"
|
||||
},
|
||||
{
|
||||
"section": "lockScreenShowProfileImage",
|
||||
"label": "Show profile image",
|
||||
"tabIndex": 11,
|
||||
"category": "Lock screen",
|
||||
"keywords": [
|
||||
"avatar",
|
||||
"image",
|
||||
"lock",
|
||||
"lockscreen",
|
||||
"login",
|
||||
"password",
|
||||
"picture",
|
||||
"profile",
|
||||
"screen",
|
||||
"security",
|
||||
"show"
|
||||
],
|
||||
"parentLabel": "Security"
|
||||
},
|
||||
{
|
||||
"section": "lockScreenShowSystemIcons",
|
||||
"label": "Show system icons",
|
||||
"tabIndex": 11,
|
||||
"category": "Lock screen",
|
||||
"keywords": [
|
||||
"icons",
|
||||
"lock",
|
||||
"lockscreen",
|
||||
"login",
|
||||
"password",
|
||||
"screen",
|
||||
"security",
|
||||
"show",
|
||||
"status",
|
||||
"system"
|
||||
],
|
||||
"parentLabel": "Security"
|
||||
},
|
||||
{
|
||||
"section": "lockScreenShowTime",
|
||||
"label": "Show time",
|
||||
"tabIndex": 11,
|
||||
"category": "Lock screen",
|
||||
"keywords": [
|
||||
"clock",
|
||||
"display",
|
||||
"lock",
|
||||
"lockscreen",
|
||||
"login",
|
||||
"password",
|
||||
"screen",
|
||||
"security",
|
||||
"show",
|
||||
"time"
|
||||
],
|
||||
"parentLabel": "Security"
|
||||
},
|
||||
{
|
||||
"section": "lockPamExternallyManaged",
|
||||
"label": "Use system PAM authentication",
|
||||
@@ -7643,23 +7451,25 @@
|
||||
"parentLabel": "Security"
|
||||
},
|
||||
{
|
||||
"section": "lockScreenShowWeather",
|
||||
"label": "Weather",
|
||||
"section": "lockScreenWidgets",
|
||||
"label": "Widgets",
|
||||
"tabIndex": 11,
|
||||
"category": "Lock screen",
|
||||
"keywords": [
|
||||
"climate",
|
||||
"forecast",
|
||||
"clock",
|
||||
"components",
|
||||
"lock",
|
||||
"lockscreen",
|
||||
"login",
|
||||
"modules",
|
||||
"password",
|
||||
"plugins",
|
||||
"screen",
|
||||
"security",
|
||||
"temperature",
|
||||
"weather"
|
||||
"widgets"
|
||||
],
|
||||
"parentLabel": "Security"
|
||||
"parentLabel": "Security",
|
||||
"icon": "widgets"
|
||||
},
|
||||
{
|
||||
"section": "loginctlLockIntegration",
|
||||
@@ -16145,6 +15955,27 @@
|
||||
],
|
||||
"parentLabel": "Date, time & region"
|
||||
},
|
||||
{
|
||||
"section": "lockStatusWeather",
|
||||
"label": "Lock screen",
|
||||
"tabIndex": 56,
|
||||
"category": "Weather",
|
||||
"keywords": [
|
||||
"climate",
|
||||
"date,",
|
||||
"forecast",
|
||||
"lock",
|
||||
"lockscreen",
|
||||
"login",
|
||||
"region",
|
||||
"screen",
|
||||
"security",
|
||||
"temperature",
|
||||
"time",
|
||||
"weather"
|
||||
],
|
||||
"parentLabel": "Date, time & region"
|
||||
},
|
||||
{
|
||||
"section": "weatherEnabled",
|
||||
"label": "Weather",
|
||||
|
||||
Reference in new issue
Block a user