feat(island): state pulses, mic, privacy & duo status glyph activities

- caps lock, power profile, idle inhibitor and charging pulse in the island
- microphone mute and volume as a level transient
- privacy slot while mic, camera or screen share is in use
- duo battery style: arc around the network glyph with signal dots
This commit is contained in:
purian23 committed 2026-10-03 15:28:06 -04:00
1 parent 51f115f11e
commit 19abc7dc96
28 files changed
+776 -78

No files matched your search

+5 -1
View File
@@ -973,7 +973,7 @@ Singleton {
function islandStripThickness(bc) {
return LayoutResolver.islandThickness(islandSettings(bc), islandDefaultsFor(bc));
}
readonly property var _islandHomeGroupIds: ["media", "clock", "weather", "status", "volume", "brightness", "notifications"]
readonly property var _islandHomeGroupIds: ["media", "clock", "weather", "status", "volume", "brightness", "notifications", "privacy"]
readonly property var _islandHomeLayoutDefault: [
{
"id": "media",
@@ -1002,6 +1002,10 @@ Singleton {
{
"id": "notifications",
"enabled": true
},
{
"id": "privacy",
"enabled": true
}
]
function getIslandHomeLayout(bc) {
@@ -36,6 +36,7 @@ var ISLAND_DEFAULTS = {
islandNotificationExpand: false,
islandNotificationPopups: false,
islandSystemOsd: true,
islandChargingPulse: true,
islandHomeCompactTight: false,
islandHomeClockDisplay: "both",
islandHomeVolumeDisplay: "both",
+4 -4
View File
@@ -1280,7 +1280,7 @@ Item {
}
Variants {
model: SettingsData.getFilteredScreens("osd")
model: root.legacySystemLevelOsdScreens
delegate: MicVolumeOSD {}
}
@@ -1292,19 +1292,19 @@ Item {
}
Variants {
model: SettingsData.getFilteredScreens("osd")
model: root.legacySystemLevelOsdScreens
delegate: IdleInhibitorOSD {}
}
Variants {
model: SettingsData.osdPowerProfileEnabled ? SettingsData.getFilteredScreens("osd") : []
model: SettingsData.osdPowerProfileEnabled ? root.legacySystemLevelOsdScreens : []
delegate: PowerProfileOSD {}
}
Variants {
model: SettingsData.getFilteredScreens("osd")
model: root.legacySystemLevelOsdScreens
delegate: CapsLockOSD {}
}
@@ -31,7 +31,7 @@ CcTile {
}
active: !!audio && !audio.muted
showExpand: true
enabled: widgetDef?.enabled ?? true
available: widgetDef?.enabled ?? true
tallContent: Component {
Item {
DankRingGauge {
@@ -49,6 +49,8 @@ Item {
property bool opensPage: false
property color restIconColor: CcMetrics.tileInactiveIcon
property Component tallContent: null
// The tile's own state. The island disables a face while it fades in, and that must not restyle the tile.
property bool available: true
property bool interactive: true
property bool iconBlinking: false
property real iconRotation: 0
@@ -68,29 +70,29 @@ Item {
property bool acceptsInput: interactive && enabled
readonly property bool bodyActive: showsActive && !hasIconBox
readonly property color bodyColor: {
if (!enabled)
if (!available)
return Theme.onSurface_12;
return bodyActive ? CcMetrics.tileActiveColor : CcMetrics.tileInactiveColor;
}
readonly property color contentColor: {
if (!enabled)
if (!available)
return Theme.onSurface_38;
return bodyActive ? CcMetrics.tileActiveContent : CcMetrics.tileInactiveContent;
}
readonly property color subtitleColor: {
if (!enabled)
if (!available)
return Theme.onSurface_38;
return bodyActive ? CcMetrics.tileActiveContent : CcMetrics.tileInactiveSubtitle;
}
readonly property color iconColor: {
if (!enabled)
if (!available)
return Theme.onSurface_38;
if (hasIconBox)
return showsActive ? CcMetrics.tileActiveContent : CcMetrics.tileInactiveContent;
return bodyActive ? CcMetrics.tileActiveContent : root.restIconColor;
}
readonly property color iconBoxColor: {
if (!enabled)
if (!available)
return Theme.onSurface_12;
return showsActive ? CcMetrics.tileActiveColor : CcMetrics.iconBoxInactiveColor;
}
@@ -106,6 +108,7 @@ Item {
width: parent?.width ?? 0
height: CcMetrics.tileHeight
enabled: available
activeFocusOnTab: acceptsInput
Accessible.role: toggle && (!showExpand || compact) && !opensPage ? Accessible.CheckBox : Accessible.Button
Accessible.checkable: toggle && (!showExpand || compact) && !opensPage
@@ -39,7 +39,7 @@ CcTile {
}
active: false
opensPage: true
enabled: DgopService.dgopAvailable
available: DgopService.dgopAvailable
tallContent: Component {
Item {
DankRingGauge {
@@ -95,7 +95,7 @@ CcTile {
}
}
showExpand: true
enabled: widgetDef?.enabled ?? true
available: widgetDef?.enabled ?? true
onClicked: {
if (status === "ethernet" || status === "cellular" || NetworkService.wifiToggling)
@@ -9,7 +9,7 @@ CcTile {
iconName: NightModeService.nightModeEnabled ? "nightlight" : "dark_mode"
title: I18n.tr("Night mode")
active: NightModeService.nightModeEnabled || false
enabled: NightModeService.automationAvailable
available: NightModeService.automationAvailable
onClicked: NightModeService.toggleNightMode()
expandedContent: Component {
@@ -21,7 +21,7 @@ CcTile {
expandedContent: instance?.ccExpandedContent ?? null
bodyContent: docked ? (instance?.ccFooterContent ?? null) : null
expandedMinimumHeight: instance?.ccExpandedMinimumHeight ?? Theme.listItemHeight
enabled: instance !== null
available: instance !== null
onClicked: {
if ((compact || !toggle) && hasDetail) {
@@ -109,12 +109,15 @@ BasePill {
return parts.join(" ");
}
// Percent always stays inside the pill; time and wattage show beside it.
// Percent stays inside the pill unless the duo glyph occupies it; time and wattage show beside it.
readonly property string horizontalSideText: {
if (!pillStyle) {
return horizontalDisplayText;
}
const parts = [];
if (showPercent && batteryStyle === "duo") {
parts.push(`${BatteryService.batteryLevel}%`);
}
if (showTime && batteryTimeText) {
parts.push(batteryTimeText);
}
@@ -10,6 +10,7 @@ Item {
required property var controller
required property string activityId
required property string label
property string value: ""
property string iconName: ""
property color iconColor: Theme.primary
property Component leading: null
@@ -54,6 +55,14 @@ Item {
font.pixelSize: Theme.fontSizeMedium
font.weight: Theme.fontWeightMedium
}
StyledText {
anchors.verticalCenter: parent.verticalCenter
visible: root.value !== ""
text: root.value
color: Theme.surfaceTextSecondary
font.pixelSize: Theme.fontSizeMedium
}
}
// The pill is only as wide as the strip on a side edge, so the label is dropped.
@@ -80,5 +89,13 @@ Item {
size: Theme.iconSizeSmall
color: root.iconColor
}
StyledText {
anchors.horizontalCenter: parent.horizontalCenter
visible: root.value !== ""
text: root.value
color: Theme.surfaceTextSecondary
font.pixelSize: Theme.fontSizeSmall
}
}
}
@@ -46,6 +46,8 @@ Item {
return !!AudioService.sink?.audio;
case "brightness":
return BrightnessService.brightnessAvailable && !!root.brightnessDevice;
case "privacy":
return PrivacyService.anyPrivacyActive;
}
return true;
}
@@ -81,6 +83,9 @@ Item {
case "brightness":
root.systemModel.open(groupId);
return;
case "privacy":
root.controller.requestControlCenter("audioInput", false);
return;
}
root.controller.requestControlCenter("", false);
}
@@ -226,6 +231,15 @@ Item {
}
}
component PrivacyIcon: DankIcon {
required property bool active
visible: active
size: root.statusIconSize
color: Theme.error
filled: true
}
component GroupHoverArea: IslandSlotHoverArea {
required property var group
@@ -243,6 +257,10 @@ Item {
if (event.button === Qt.MiddleButton) {
if (group.usesConnectivity)
root.toggleConnectivity(connectivityTypeAt(event));
if (group.isPrivacy && PrivacyService.microphoneActive && AudioService.source?.audio) {
SessionData.suppressOSDTemporarily();
AudioService.toggleMicMute();
}
if (group.isMedia && root.controller.mediaAvailable && MprisController.activePlayer?.canTogglePlaying)
MprisController.activePlayer.togglePlaying();
if (group.isVolume && AudioService.sink?.audio) {
@@ -277,6 +295,7 @@ Item {
readonly property bool isNotifications: group.groupId === "notifications"
readonly property bool isVolume: group.groupId === "volume"
readonly property bool isBrightness: group.groupId === "brightness"
readonly property bool isPrivacy: group.groupId === "privacy"
readonly property bool isSystemLevel: group.isVolume || group.isBrightness
readonly property bool usesConnectivity: group.isStatus && root.controller.homeStatusContent === "connectivity"
readonly property bool usesBattery: group.isStatus && !group.usesConnectivity && BatteryService.batteryAvailable
@@ -394,6 +413,27 @@ Item {
}
}
Grid {
visible: group.isPrivacy
columns: group.vertical ? 1 : 3
spacing: Theme.spacingXXS
PrivacyIcon {
name: "mic"
active: PrivacyService.microphoneActive
}
PrivacyIcon {
name: "camera_video"
active: PrivacyService.cameraActive
}
PrivacyIcon {
name: "screen_share"
active: PrivacyService.screensharingActive
}
}
DankIcon {
visible: group.isStatus && !group.usesBattery && !group.usesConnectivity
name: "tune"
@@ -37,7 +37,7 @@ Item {
visible: !root.isVertical
iconName: root.systemModel.iconName
tonalIcon: false
iconInteractive: root.systemModel.volumeActivity
iconInteractive: root.systemModel.mutable
iconLabel: root.systemModel.muted ? I18n.tr("Unmute") : I18n.tr("Mute")
value: Math.round(root.systemModel.value)
minimum: root.systemModel.minimum
@@ -65,11 +65,11 @@ Item {
anchors.top: parent.top
name: root.systemModel.iconName
size: Math.min(Theme.iconSize, root.iconSize)
color: root.systemModel.volumeActivity ? Theme.surfaceText : Theme.primary
color: root.systemModel.mutable ? Theme.surfaceText : Theme.primary
MouseArea {
anchors.fill: parent
enabled: root.systemModel.volumeActivity
enabled: root.systemModel.mutable
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: root.systemModel.toggleMute()
}
@@ -33,7 +33,7 @@ Item {
}
StyledText {
text: root.systemModel.volumeActivity ? I18n.tr("Click the icon to mute", "island volume face: hint under the title") : I18n.tr("Display brightness control", "island brightness face: hint under the title")
text: root.systemModel.mutable ? I18n.tr("Click the icon to mute", "island volume face: hint under the title") : I18n.tr("Display brightness control", "island brightness face: hint under the title")
color: Theme.surfaceTextSecondary
font.pixelSize: Theme.fontSizeSmall
}
@@ -56,12 +56,13 @@ Item {
width: parent.width
size: "m"
insetIcon: root.systemModel.iconName
insetIconClickable: root.systemModel.volumeActivity
insetIconClickable: root.systemModel.mutable
insetIconLabel: root.systemModel.muted ? I18n.tr("Unmute") : I18n.tr("Mute")
Accessible.name: root.systemModel.title
onInsetIconClicked: root.systemModel.toggleMute()
minimum: root.systemModel.minimum
maximum: Math.max(root.systemModel.minimum + 1, Math.round(root.systemModel.maximum))
visible: root.systemModel.levelActivity
enabled: root.systemModel.available
showValue: false
unit: root.systemModel.unit
@@ -0,0 +1,30 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs.Common
DestinationCompact {
id: root
required property var systemModel
readonly property bool linksControlCenter: root.systemModel.controlCenterSection !== ""
activityId: root.systemModel.kind
iconName: root.systemModel.iconName
label: root.systemModel.title
value: root.systemModel.displayValue
IslandSlotHoverArea {
anchors.fill: parent
controller: root.controller
cursorShape: root.linksControlCenter ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: {
if (root.linksControlCenter) {
root.controller.requestControlCenter(root.systemModel.controlCenterSection, false);
return;
}
root.controller.requestCollapse();
}
}
}
@@ -680,6 +680,14 @@ Item {
Component {
id: compactSystemComponent
Loader {
sourceComponent: root.systemModel.levelActivity ? compactSystemLevelComponent : compactSystemStateComponent
}
}
Component {
id: compactSystemLevelComponent
SystemLevelCompact {
systemModel: root.systemModel
isVertical: root.isVertical
@@ -687,6 +695,15 @@ Item {
}
}
Component {
id: compactSystemStateComponent
SystemStateCompact {
systemModel: root.systemModel
controller: root.controller
}
}
Component {
id: expandedSystemComponent
@@ -424,6 +424,7 @@ Item {
controller: controller
enabled: root.setting("islandSystemOsd") && root.barConfig?.visible !== false
chargingPulseEnabled: root.setting("islandChargingPulse")
}
IslandNotificationSource {
@@ -54,7 +54,15 @@ Item {
readonly property real outgoingCompactFade: root.fadeCompact(root.outgoingMorph)
readonly property real outgoingExpandedFade: root.fadeExpanded(root.outgoingMorph)
readonly property bool mediaSurfaceActive: root.surfaceActive("media")
readonly property bool systemSurfaceActive: root.surfaceActive("volume") || root.surfaceActive("brightness")
readonly property bool systemSurfaceActive: root.controller.systemActivities.some(id => root.surfaceActive(id))
function systemCompactOpacity() {
return Math.max(0, ...root.controller.systemActivities.map(id => root.compactOpacity(id)));
}
function systemExpandedOpacity() {
return Math.max(0, ...root.controller.systemActivities.map(id => root.expandedOpacity(id)));
}
property string renderedActivity: "home"
property string outgoingActivity: ""
@@ -342,7 +350,7 @@ Item {
active: root.systemSurfaceActive
activity: "volume"
face: root.systemCompactComponent
opacity: Math.max(root.compactOpacity("volume"), root.compactOpacity("brightness"))
opacity: root.systemCompactOpacity()
}
ExpandedFace {
@@ -350,7 +358,7 @@ Item {
active: root.systemSurfaceActive && (root.expanded || root.expandedFade > 0)
asynchronous: false
sourceComponent: root.systemExpandedComponent
opacity: Math.max(root.expandedOpacity("volume"), root.expandedOpacity("brightness"))
opacity: root.systemExpandedOpacity()
}
CompactFace {
@@ -236,6 +236,10 @@ QtObject {
}
function setDestinationContentLength(activityId, length) {
if (isSystemActivity(activityId)) {
setSystemStateContentLength(length);
return;
}
const entry = destinationState[activityId];
const next = Math.ceil(length);
if (!entry || !isFinite(next) || next <= 0 || Math.abs(next - entry.contentLength) < 2)
@@ -429,7 +433,13 @@ QtObject {
"bottomLeftRadius": root.dotSize / 2,
"bottomRightRadius": root.dotSize / 2
})
readonly property var dotTransientActivities: ["notification", "volume", "brightness"]
readonly property var systemLevelActivities: ["volume", "brightness", "mic"]
readonly property var systemStateActivities: ["capslock", "powerprofile", "idleinhibitor", "charging"]
readonly property var systemActivities: systemLevelActivities.concat(systemStateActivities)
readonly property var dotTransientActivities: ["notification"].concat(systemActivities)
function isSystemActivity(activityId) {
return root.systemActivities.indexOf(activityId) !== -1;
}
// Transients keep their island pill so the dot can still show a notification or a level.
function usesDotFace(activityId) {
return root.dotMode && root.dotTransientActivities.indexOf(activityId) === -1;
@@ -441,13 +451,15 @@ QtObject {
readonly property var launcherExpandedTarget: sheetTarget(Math.min(dashboardAvailableWidth, LauncherMetrics.sizeWidth(SettingsData.dankLauncherV2Size)), Math.min(dashboardAvailableHeight, LauncherMetrics.sizeHeight(SettingsData.dankLauncherV2Size)))
readonly property var controlCenterExpandedTarget: sheetTarget(controlCenterSheetWidth + controlCenterSheetInset, controlCenterHeight)
readonly property var systemCompactTarget: pillTarget(root.isVertical ? 240 : (SettingsData.osdAlwaysShowValue ? 330 : 282), compactFaceThickness)
property real systemStateContentLength: 0
readonly property var systemStateCompactTarget: pillTarget(Math.ceil(Math.max(compactFaceThickness, systemStateContentLength + destinationCompactEndPad * 2)), compactFaceThickness)
readonly property var systemExpandedTarget: sheetTarget(460, 176)
readonly property var notificationCompactTarget: pillTarget(Math.ceil(Math.max(notificationCompactMinLength, Math.min(notificationCompactMaxLength, notificationContentLength))), compactFaceThickness)
readonly property var notificationExpandedTarget: sheetTarget(520, 220)
readonly property var notificationCenterExpandedTarget: dashboardTargetFor("notificationcenter")
readonly property var clipboardExpandedTarget: sheetTarget(Math.min(dashboardAvailableWidth, ClipboardConstants.sizeWidth(SettingsData.clipboardSize)), Math.min(dashboardAvailableHeight, ClipboardConstants.sizeHeight(SettingsData.clipboardSize)))
readonly property bool systemActivityActive: activeActivity === "volume" || activeActivity === "brightness"
readonly property bool systemActivityActive: isSystemActivity(activeActivity)
readonly property bool notificationActive: activeActivity === "notification"
readonly property bool notificationHeldForSystem: root.systemActivityActive && root.transientReturnActivity === "notification"
readonly property bool transientActive: systemActivityActive || notificationActive
@@ -460,14 +472,15 @@ QtObject {
switch (activityId) {
case "notification":
return notificationCompactTarget;
case "volume":
case "brightness":
return systemCompactTarget;
case "media":
return mediaCompactTarget;
case "home":
return homeCompactTarget;
}
if (systemLevelActivities.indexOf(activityId) !== -1)
return systemCompactTarget;
if (systemStateActivities.indexOf(activityId) !== -1)
return systemStateCompactTarget;
return isDestination(activityId) ? pillTarget(destinationCompactLength(activityId), compactFaceThickness) : homeCompactTarget;
}
@@ -478,9 +491,6 @@ QtObject {
return homeExpandedTarget;
case "notification":
return notificationExpandedTarget;
case "volume":
case "brightness":
return systemExpandedTarget;
case "launcher":
return launcherExpandedTarget;
case "controlcenter":
@@ -492,7 +502,7 @@ QtObject {
case "media":
return mediaExpandedTarget;
}
return dashboardTargetFor(activityId);
return isSystemActivity(activityId) ? systemExpandedTarget : dashboardTargetFor(activityId);
}
readonly property var expandedTarget: expandedTargetFor(activeActivity)
@@ -758,8 +768,15 @@ QtObject {
activeActivity = returnActivity;
}
function setSystemStateContentLength(length) {
const next = Math.ceil(length);
if (!isFinite(next) || next <= 0 || Math.abs(next - systemStateContentLength) < 2)
return;
systemStateContentLength = next;
}
function requestSystemActivity(activityId) {
if (activityId !== "volume" && activityId !== "brightness")
if (!isSystemActivity(activityId))
return false;
launcherSessionActive = false;
@@ -10,21 +10,114 @@ QtObject {
required property IslandController controller
property string kind: "volume"
// Off: volume and brightness keep their OSDs and only the face's own controls open this activity.
// Off: system levels and state changes keep their OSDs and only the face's own controls open this activity.
property bool enabled: true
property bool chargingPulseEnabled: true
readonly property bool volumeActivity: kind === "volume"
readonly property bool available: volumeActivity ? !!AudioService.sink?.audio : BrightnessService.brightnessAvailable
readonly property bool muted: volumeActivity && (AudioService.sink?.audio?.muted ?? false)
readonly property real value: volumeActivity ? AudioService.sinkVolumePercent : BrightnessService.brightnessLevel
readonly property bool micActivity: kind === "mic"
readonly property bool brightnessActivity: kind === "brightness"
readonly property bool levelActivity: volumeActivity || micActivity || brightnessActivity
readonly property var audio: volumeActivity ? AudioService.sink?.audio : (micActivity ? AudioService.source?.audio : null)
readonly property bool available: levelActivity ? (brightnessActivity ? BrightnessService.brightnessAvailable : !!audio) : true
readonly property bool muted: !!audio && (audio.muted ?? false)
readonly property bool mutable: volumeActivity || micActivity
readonly property real value: {
switch (kind) {
case "volume":
return AudioService.sinkVolumePercent;
case "mic":
return AudioService.sourceVolumePercent;
case "brightness":
return BrightnessService.brightnessLevel;
}
return 0;
}
readonly property var brightnessDevice: BrightnessService.getCurrentDeviceInfo()
readonly property real maximum: volumeActivity ? AudioService.sinkMaxVolume : BrightnessService.brightnessMaximum(brightnessDevice)
readonly property int minimum: volumeActivity ? 0 : BrightnessService.brightnessMinimum(brightnessDevice)
readonly property real maximum: {
switch (kind) {
case "volume":
return AudioService.sinkMaxVolume;
case "brightness":
return BrightnessService.brightnessMaximum(brightnessDevice);
}
return 100;
}
readonly property int minimum: brightnessActivity ? BrightnessService.brightnessMinimum(brightnessDevice) : 0
readonly property real ratio: maximum > 0 ? Math.max(0, Math.min(1, value / maximum)) : 0
readonly property string title: volumeActivity ? I18n.tr("Volume", "island system face: volume title") : I18n.tr("Brightness", "island system face: brightness title")
readonly property string unit: volumeActivity ? "%" : BrightnessService.brightnessUnit(brightnessDevice)
readonly property string displayValue: muted ? I18n.tr("Muted", "island system face: muted value label") : Math.round(value) + unit
readonly property string iconName: volumeActivity ? AudioService.sinkVolumeIconName : BrightnessService.brightnessIconName(brightnessDevice, value)
readonly property string title: {
switch (kind) {
case "volume":
return I18n.tr("Volume", "island system face: volume title");
case "mic":
return I18n.tr("Microphone", "island system face: microphone title");
case "brightness":
return I18n.tr("Brightness", "island system face: brightness title");
case "capslock":
return I18n.tr("Caps lock", "island system face: caps lock title");
case "powerprofile":
return I18n.tr("Power profile", "island system face: power profile title");
case "idleinhibitor":
return I18n.tr("Idle inhibitor", "island system face: idle inhibitor title");
case "charging":
return BatteryService.isPluggedIn ? I18n.tr("Plugged in", "island system face: power connected title") : I18n.tr("Discharging", "island system face: power disconnected title");
}
return "";
}
readonly property string unit: brightnessActivity ? BrightnessService.brightnessUnit(brightnessDevice) : "%"
readonly property string displayValue: {
switch (kind) {
case "capslock":
return DMSService.capsLockState ? I18n.tr("On") : I18n.tr("Off");
case "powerprofile":
return Theme.getPowerProfileLabel(PowerProfileWatcher.currentProfile);
case "idleinhibitor":
return SessionService.idleInhibited ? I18n.tr("On") : I18n.tr("Off");
case "charging":
return root.chargingValue();
}
return muted ? I18n.tr("Muted", "island system face: muted value label") : Math.round(value) + unit;
}
readonly property string iconName: {
switch (kind) {
case "volume":
return AudioService.sinkVolumeIconName;
case "mic":
return muted ? "mic_off" : "mic";
case "brightness":
return BrightnessService.brightnessIconName(brightnessDevice, value);
case "capslock":
return DMSService.capsLockState ? "shift_lock" : "shift_lock_off";
case "powerprofile":
return Theme.getPowerProfileIcon(PowerProfileWatcher.currentProfile);
case "idleinhibitor":
return SessionService.idleInhibited ? "motion_sensor_active" : "motion_sensor_idle";
case "charging":
return BatteryService.getBatteryIcon();
}
return "";
}
// Level pulses carry their own controls, so only state pulses link out.
readonly property string controlCenterSection: {
switch (kind) {
case "mic":
return "audioInput";
case "powerprofile":
case "charging":
return "battery";
case "idleinhibitor":
return "idleInhibitor";
}
return "";
}
function chargingValue() {
const level = Math.round(BatteryService.batteryLevel) + "%";
const seconds = BatteryService.estimatedSeconds();
if (!seconds)
return level;
return level + " · " + BatteryService.formatTimeRemaining();
}
function show(activityKind) {
if (!enabled || SessionData.suppressOSD)
@@ -40,17 +133,27 @@ QtObject {
function setRatio(nextRatio) {
const clampedRatio = Math.max(0, Math.min(1, nextRatio));
SessionData.suppressOSDTemporarily();
if (volumeActivity) {
switch (kind) {
case "volume":
AudioService.setVolume(Math.round(clampedRatio * maximum));
return;
case "mic":
AudioService.setMicVolume(Math.round(clampedRatio * maximum));
return;
case "brightness":
BrightnessService.setBrightness(Math.round(clampedRatio * maximum), BrightnessService.lastIpcDevice, true);
return;
}
BrightnessService.setBrightness(Math.round(clampedRatio * maximum), BrightnessService.lastIpcDevice, true);
}
function toggleMute() {
if (!volumeActivity || !available)
if (!mutable || !available)
return;
SessionData.suppressOSDTemporarily();
if (micActivity) {
AudioService.toggleMicMute();
return;
}
AudioService.toggleMute();
}
@@ -68,6 +171,24 @@ QtObject {
}
}
property Connections micConnection: Connections {
target: AudioService.source?.audio ?? null
function onMutedChanged() {
if (SettingsData.osdMicMuteEnabled)
root.show("mic");
}
}
property Connections micServiceConnection: Connections {
target: AudioService
function onMicVolumeChanged() {
if (SettingsData.osdMicVolumeEnabled)
root.show("mic");
}
}
property Connections brightnessConnection: Connections {
target: BrightnessService
@@ -76,4 +197,40 @@ QtObject {
root.show("brightness");
}
}
property Connections capsLockConnection: Connections {
target: DMSService
function onCapsLockStateChanged() {
if (SettingsData.osdCapsLockEnabled)
root.show("capslock");
}
}
property Connections powerProfileConnection: Connections {
target: PowerProfileWatcher
function onProfileChanged() {
if (SettingsData.osdPowerProfileEnabled)
root.show("powerprofile");
}
}
property Connections idleInhibitorConnection: Connections {
target: SessionService
function onInhibitorChanged() {
if (SettingsData.osdIdleInhibitorEnabled)
root.show("idleinhibitor");
}
}
property Connections batteryConnection: Connections {
target: BatteryService
function onIsPluggedInChanged() {
if (root.chargingPulseEnabled && BatteryService.batteryAvailable)
root.show("charging");
}
}
}
@@ -8,7 +8,7 @@ Column {
property var page: null
readonly property var styleValues: ["icon", "solid", "outline", "ring"]
readonly property var styleValues: ["icon", "solid", "outline", "ring", "duo"]
readonly property var barConfig: {
SettingsData.barConfigs;
return SettingsData.getBarConfig(page?.barId ?? "");
@@ -84,7 +84,7 @@ Column {
resetStore: root.page
resetKeys: ["batteryStyle"]
text: I18n.tr("Battery style")
model: [I18n.tr("Icon", "battery widget: system battery glyph"), I18n.tr("Solid", "island settings: filled battery meter style"), I18n.tr("Outline", "island settings: outlined battery meter style"), I18n.tr("Circle", "island settings: circular battery meter style")]
model: [I18n.tr("Icon", "battery widget: system battery glyph"), I18n.tr("Solid", "island settings: filled battery meter style"), I18n.tr("Outline", "island settings: outlined battery meter style"), I18n.tr("Circle", "island settings: circular battery meter style"), I18n.tr("Duo", "battery meter style: open battery arc around the network glyph")]
currentIndex: Math.max(0, root.styleValues.indexOf(root.page.value("batteryStyle")))
onSelectionChanged: (index, selected) => {
if (selected)
@@ -14,7 +14,7 @@ SettingsCard {
readonly property var clockDisplayValues: ["time", "date", "both"]
readonly property var systemLevelDisplayValues: ["icon", "percentage", "both"]
readonly property var statusContentValues: ["battery", "connectivity"]
readonly property var batteryStyleValues: ["solid", "outline", "ring"]
readonly property var batteryStyleValues: ["solid", "outline", "ring", "duo"]
readonly property bool batteryShown: SettingsData.islandHomeGroupEnabled(root.store.config, "status") && BatteryService.batteryAvailable && SettingsData.islandHomeStatusContent(root.store.config) === "battery"
@@ -110,12 +110,12 @@ SettingsCard {
SettingsButtonGroupRow {
settingKey: root.keyPrefix + "BatteryStyle"
tags: ["island", "battery", "gauge", "solid", "outline", "ring", "circle", "appearance"]
tags: ["island", "battery", "gauge", "solid", "outline", "ring", "circle", "duo", "wifi", "appearance"]
resetStore: root.store
resetKeys: ["islandBatteryStyle"]
text: I18n.tr("Battery style", "island settings: battery meter style row")
visible: root.batteryShown
model: [I18n.tr("Solid", "island settings: filled battery meter style"), I18n.tr("Outline", "island settings: outlined battery meter style"), I18n.tr("Circle", "island settings: circular battery meter style")]
model: [I18n.tr("Solid", "island settings: filled battery meter style"), I18n.tr("Outline", "island settings: outlined battery meter style"), I18n.tr("Circle", "island settings: circular battery meter style"), I18n.tr("Duo", "battery meter style: open battery arc around the network glyph")]
values: root.batteryStyleValues
value: root.store.setting("islandBatteryStyle")
fallbackValue: "solid"
@@ -55,6 +55,11 @@ Column {
"icon": "notifications",
"text": I18n.tr("Notifications", "island settings: notification badge slot row"),
"description": ""
},
"privacy": {
"icon": "privacy_tip",
"text": I18n.tr("Privacy", "island settings: microphone, camera and screen share indicator slot row"),
"description": I18n.tr("Shown only while the microphone, camera or screen sharing is in use", "island settings: privacy slot hint")
}
})
@@ -1,5 +1,6 @@
import QtQuick
import qs.Common
import qs.Services
SettingsCard {
id: root
@@ -27,7 +28,7 @@ SettingsCard {
SettingsToggleRow {
settingKey: root.keyPrefix + "SystemOsd"
tags: ["island", "volume", "brightness", "osd", "level", "standard"]
tags: ["island", "volume", "brightness", "microphone", "caps lock", "power profile", "idle inhibitor", "osd", "level", "standard"]
resetStore: root.store
resetKeys: ["islandSystemOsd"]
text: I18n.tr("Use standard OSDs", "island settings: show volume and brightness changes as the regular on-screen displays instead of in the island")
@@ -35,6 +36,18 @@ SettingsCard {
onToggled: checked => root.store.apply("islandSystemOsd", !checked)
}
SettingsToggleRow {
settingKey: root.keyPrefix + "ChargingPulse"
tags: ["island", "battery", "charging", "plugged", "power", "pulse"]
resetStore: root.store
resetKeys: ["islandChargingPulse"]
text: I18n.tr("Charging", "island settings: pulse the island when power is connected or removed")
description: I18n.tr("Show the battery briefly when power is connected or removed", "island settings: charging pulse toggle description")
visible: root.store.setting("islandSystemOsd") && BatteryService.batteryAvailable
checked: root.store.setting("islandChargingPulse")
onToggled: checked => root.store.apply("islandChargingPulse", checked)
}
SettingsToggleRow {
settingKey: root.keyPrefix + "NotificationExpand"
tags: ["island", "notifications", "expand", "arrival", "size"]
+109 -17
View File
@@ -22,6 +22,12 @@ Item {
readonly property bool outlined: root.meterStyle === "outline"
readonly property bool ring: root.meterStyle === "ring"
// Duo: an open arc around the network glyph, signal dots in the gap.
readonly property bool duo: root.meterStyle === "duo"
readonly property bool circular: root.ring || root.duo
readonly property real arcStart: root.duo ? 150 : -90
readonly property real arcSpan: root.duo ? 240 : 360
readonly property real chargeTarget: Math.max(root.level, Math.min(100, SettingsData.batteryChargeLimit))
readonly property real unit: root.thickness / 14
readonly property real level: Math.max(0, Math.min(100, BatteryService.batteryLevel))
readonly property bool charging: BatteryService.batteryAvailable && BatteryService.isCharging
@@ -39,6 +45,8 @@ Item {
}
readonly property color dimColor: Theme.withAlpha(root.fillColor, root.hovered ? 0.6 : 0.48)
readonly property color trackColor: {
if (root.duo)
return Theme.withAlpha(root.fillColor, root.hovered ? 0.3 : 0.16);
if (root.ring)
return Theme.withAlpha(root.fillColor, root.hovered ? 0.4 : 0.26);
if (root.outlined)
@@ -52,7 +60,7 @@ Item {
}
readonly property int glyphWeight: Theme.fontWeight
readonly property string numberText: Math.round(root.level).toString()
readonly property bool boltVisible: root.charging && root.showBolt
readonly property bool boltVisible: root.charging && root.showBolt && !root.duo
readonly property bool numberInside: !root.vertical && root.showNumber && BatteryService.batteryAvailable
readonly property bool ringNumberVisible: root.ring && root.showNumber && !root.boltVisible && BatteryService.batteryAvailable
readonly property real strokeWidth: root.outlined ? 1.5 * root.unit : 0
@@ -72,9 +80,9 @@ Item {
readonly property real ringInnerRadius: Math.max(1, (root.ringDiameter - 2 * root.ringStroke) / 2)
readonly property real ringChordWidth: 2 * Math.sqrt(Math.max(1, Math.pow(root.ringInnerRadius, 2) - Math.pow(fitMetrics.tightBoundingRect.height / 2, 2)))
readonly property real ringTextSize: root.textNeed > 0 ? Math.min(root.baseTextSize, root.baseTextSize * root.ringChordWidth / root.textNeed) : root.baseTextSize
readonly property real textSize: root.ring ? root.ringTextSize : root.baseTextSize
readonly property real textSize: root.circular ? root.ringTextSize : root.baseTextSize
readonly property real textBaseline: root.height / 2 - digitInk.tightBoundingRect.y - digitInk.tightBoundingRect.height / 2
readonly property real boltBadgeSize: Math.round((root.ring ? 12 : 9) * root.unit)
readonly property real boltBadgeSize: Math.round((root.circular ? 12 : 9) * root.unit)
readonly property real boltBadgeWidth: Math.round(root.boltBadgeSize * (8 / 13))
readonly property real bodyLength: Math.max(Math.round(25 * root.unit), Math.ceil(root.numberInside ? root.textNeed + root.textCanvasLeft + 1.5 * root.unit : 0))
readonly property real capGap: Math.max(1, Math.round(root.unit))
@@ -82,8 +90,8 @@ Item {
readonly property real capBreadth: Math.max(1, Math.round(1.25 * root.unit))
readonly property real capSpan: Math.round(6 * root.unit)
implicitWidth: root.ring ? root.ringDiameter : root.vertical ? Math.round(14 * root.unit) : root.capOffset + (root.boltVisible ? root.boltBadgeWidth : root.capBreadth)
implicitHeight: root.ring ? root.ringDiameter : root.vertical ? root.capOffset + root.capBreadth : Math.round(14 * root.unit)
implicitWidth: root.circular ? root.ringDiameter : root.vertical ? Math.round(14 * root.unit) : root.capOffset + (root.boltVisible ? root.boltBadgeWidth : root.capBreadth)
implicitHeight: root.circular ? root.ringDiameter : root.vertical ? root.capOffset + root.capBreadth : Math.round(14 * root.unit)
StyledTextMetrics {
id: fitMetrics
@@ -151,7 +159,7 @@ Item {
Shape {
id: gauge
property real sweep: root.level * 3.6
property real sweep: root.level / 100 * root.arcSpan
anchors.fill: parent
preferredRendererType: Shape.CurveRenderer
@@ -174,8 +182,24 @@ Item {
centerY: root.ringDiameter / 2
radiusX: root.ringRadius
radiusY: root.ringRadius
startAngle: -90
sweepAngle: 360
startAngle: root.arcStart
sweepAngle: root.arcSpan
}
}
ShapePath {
fillColor: "transparent"
strokeColor: root.duo && root.charging ? Theme.withAlpha(root.fillColor, 0.45) : "transparent"
strokeWidth: root.ringStroke
capStyle: ShapePath.RoundCap
PathAngleArc {
centerX: root.ringDiameter / 2
centerY: root.ringDiameter / 2
radiusX: root.ringRadius
radiusY: root.ringRadius
startAngle: root.arcStart + gauge.sweep
sweepAngle: Math.max(0, (root.chargeTarget - root.level) / 100 * root.arcSpan)
}
}
@@ -190,7 +214,7 @@ Item {
centerY: root.ringDiameter / 2
radiusX: root.ringRadius
radiusY: root.ringRadius
startAngle: -90
startAngle: root.arcStart
sweepAngle: gauge.sweep
}
}
@@ -198,13 +222,81 @@ Item {
}
Loader {
active: root.ring
active: root.circular
anchors.centerIn: parent
width: root.ringDiameter
height: root.ringDiameter
sourceComponent: ringGauge
}
Loader {
active: root.duo
anchors.centerIn: parent
width: root.ringDiameter
height: root.ringDiameter
sourceComponent: duoFace
}
Component {
id: duoFace
Item {
id: face
readonly property string networkIcon: {
if (NetworkService.wifiToggling)
return "sync";
switch (NetworkService.networkStatus) {
case "ethernet":
return "lan";
case "cellular":
return "network_cell";
case "vpn":
return NetworkService.ethernetConnected ? "lan" : (NetworkService.cellularConnected ? "network_cell" : NetworkService.wifiSignalIcon);
}
return NetworkService.wifiEnabled && NetworkService.wifiAvailable ? NetworkService.wifiSignalIcon : "wifi_off";
}
// Inner pair first so a partial signal stays symmetric.
readonly property int litDots: NetworkService.wifiConnected ? (NetworkService.wifiSignalStrength > 50 ? 4 : 2) : 0
readonly property real dotSize: Math.max(2, Math.round(root.ringStroke * 1.25))
readonly property int glyphInset: Math.round(root.ringDiameter * 0.2)
DankIcon {
id: glyph
x: face.glyphInset
y: face.glyphInset
name: face.networkIcon
size: root.ringDiameter - 2 * face.glyphInset
color: NetworkService.networkStatus !== "disconnected" ? Theme.surfaceText : Theme.surfaceTextMedium
DankBlink {
target: glyph
running: face.visible && (NetworkService.wifiToggling || NetworkService.isWifiConnecting)
}
}
Repeater {
model: 4
Rectangle {
id: dot
required property int index
readonly property real angle: (54 + dot.index * 24) * Math.PI / 180
readonly property bool lit: dot.index === 1 || dot.index === 2 ? face.litDots >= 2 : face.litDots >= 4
x: root.ringDiameter / 2 + Math.cos(dot.angle) * root.ringRadius - width / 2
y: root.ringDiameter / 2 + Math.sin(dot.angle) * root.ringRadius - height / 2
width: face.dotSize
height: face.dotSize
radius: width / 2
color: dot.lit ? Theme.surfaceText : Theme.withAlpha(Theme.surfaceText, 0.16)
}
}
}
}
NumericText {
isMonospace: false
visible: root.ringNumberVisible
@@ -224,7 +316,7 @@ Item {
width: root.vertical ? root.capSpan : root.capBreadth
height: root.vertical ? root.capBreadth : root.capSpan
radius: root.capBreadth / 2
visible: !root.ring && (root.vertical || !root.boltVisible)
visible: !root.circular && (root.vertical || !root.boltVisible)
color: root.outlined ? root.fillColor : root.dimColor
}
@@ -236,7 +328,7 @@ Item {
width: root.vertical ? root.width : root.bodyLength
height: root.vertical ? root.bodyLength : root.height
radius: 4 * root.unit
visible: !root.ring
visible: !root.circular
color: root.trackColor
border.width: root.strokeWidth
border.color: root.fillColor
@@ -250,7 +342,7 @@ Item {
width: frame.width - root.strokeWidth * 2
height: frame.height - root.strokeWidth * 2
radius: Math.max(0, frame.radius - root.strokeWidth)
visible: !root.ring
visible: !root.circular
color: "transparent"
Rectangle {
@@ -301,7 +393,7 @@ Item {
}
Glyphs {
visible: root.numberInside && !root.ring
visible: root.numberInside && !root.circular
ink: Theme.surfaceText
}
@@ -311,7 +403,7 @@ Item {
width: fill.width
height: fill.height
clip: true
visible: root.numberInside && !root.outlined && !root.ring
visible: root.numberInside && !root.outlined && !root.circular
Glyphs {
x: -parent.x
@@ -322,8 +414,8 @@ Item {
Bolt {
visible: root.boltVisible
x: root.ring || root.vertical ? (root.width - width) / 2 : root.capOffset
y: !root.ring && root.vertical ? frame.y + (frame.height - height) / 2 : (root.height - height) / 2
x: root.circular || root.vertical ? (root.width - width) / 2 : root.capOffset
y: !root.circular && root.vertical ? frame.y + (frame.height - height) / 2 : (root.height - height) / 2
fillColor: Theme.surfaceText
}
}
+54
View File
@@ -593,3 +593,57 @@ test("an expanding hosted island closes its own bar's popout only for deliberate
host.closeSameEdgeSurfaces();
assert.deepEqual(closed.splice(0), ["modal:launcher"], "connected chrome closes only the same-edge slot, and only this screen's modal");
});
const controllerSource = readFileSync(new URL("../Modules/DankIsland/IslandController.qml", import.meta.url), "utf8");
function controllerContext() {
const context = vm.createContext({
systemLevelActivities: ["volume", "brightness", "mic"],
systemStateActivities: ["capslock", "powerprofile", "idleinhibitor", "charging"],
activeActivity: "home",
expanded: false,
notificationActive: false,
launcherSessionActive: false,
transientReturnActivity: "",
hoverExpanded: false,
keyboardDismissRequested: false,
transientTimer: { stop() {}, restart() {} },
notificationTimer: { stop() {} },
hoverOpenTimer: { stop() {} },
hoverCloseTimer: { stop() {} },
destinations: [],
destinationState: {},
destinationRevision: 0,
timeoutSuspended: false
});
context.root = context;
context.systemActivities = context.systemLevelActivities.concat(context.systemStateActivities);
Object.defineProperty(context, "systemActivityActive", { get: () => context.isSystemActivity(context.activeActivity) });
return functions(controllerSource, " ", context);
}
test("state pulses are system transients that return to the interrupted activity", () => {
const controller = controllerContext();
controller.activeActivity = "media";
assert.equal(controller.requestSystemActivity("capslock"), true);
assert.equal(controller.activeActivity, "capslock");
assert.equal(controller.transientReturnActivity, "media");
assert.equal(controller.requestSystemActivity("charging"), true, "a second pulse replaces the first");
assert.equal(controller.transientReturnActivity, "media", "the return point is kept across chained pulses");
assert.equal(controller.requestSystemActivity("bogus"), false);
assert.equal(controller.activeActivity, "charging");
controller.expanded = true;
assert.equal(controller.requestSystemActivity("mic"), false, "an open sheet is not interrupted");
});
test("the privacy slot is a known home layout entry appended after stored ones", () => {
const groupIds = JSON.parse(settingsSource.match(/_islandHomeGroupIds: (\[.*\])/)[1]);
const layoutDefault = JSON.parse(settingsSource.match(/_islandHomeLayoutDefault: (\[[\s\S]*?\n \])/)[1]);
const settings = functions(settingsSource, " ", vm.createContext({ _islandHomeGroupIds: groupIds, _islandHomeLayoutDefault: layoutDefault }));
settings.islandSetting = (config, key) => config[key];
settings.islandSettings = config => config;
const legacy = { islandHomeLayout: [{ id: "clock" }, { id: "media", enabled: false }] };
const ids = settings.getIslandHomeLayout(legacy).map(entry => entry.id);
assert.ok(ids.includes("privacy"), "a stored layout gains the privacy slot");
assert.equal(ids.indexOf("privacy"), ids.length - 1, "new slots append after the stored order");
});
+149
View File
@@ -0,0 +1,149 @@
import QtQuick
import Quickshell
import qs.Common
import qs.Services
import qs.Modules.DankBar
import qs.Modules.DankIsland
import qs.Modules.Frame
import qs.DankCommon.Common as DC
// State pulses (caps lock, charging) and the mic level share the system transient with volume, and the duo status glyph loads in the home face.
ShellRoot {
id: root
readonly property var screen: Quickshell.screens[0]
readonly property string barId: "main"
Item {
Repeater {
model: ScriptModel {
values: SettingsData.barConfigs
objectProp: "id"
}
delegate: DankBar {
required property var modelData
barConfig: modelData
}
}
}
Frame {}
DankIsland {}
Component.onCompleted: {
Quickshell.watchFiles = false;
DC.Style.theme = Theme;
DC.Style.settings = SettingsData;
DC.I18n.backend = I18n;
}
Timer {
interval: 0
running: SettingsData._hasLoaded && SessionData._hasLoaded
onTriggered: {
SettingsData.reduceMotion = true;
SettingsData.frameMode = "connected";
SettingsData.frameEnabled = true;
FrameTransitionState.acknowledge(FrameTransitionState.revision);
SettingsData.barConfigs = [
{
id: root.barId,
enabled: true,
visible: true,
position: 0,
screenPreferences: ["all"],
leftWidgets: ["clock"],
centerWidgets: [
{
id: "island",
enabled: true,
islandBatteryStyle: "duo",
islandHomeLayout: [
{
id: "status",
enabled: true
},
{
id: "clock",
enabled: true
}
]
}
],
rightWidgets: ["clock"]
}
];
}
}
Timer {
id: steps
property int step: 0
property int waited: 0
readonly property var body: IslandHostRegistry.hostFor(root.screen.name, root.barId)
interval: 25
repeat: true
running: !!body && !!body.islandController && SettingsData.frameEnabled
function finish(message) {
if (message)
console.error("FIXTURE_FAIL", message);
else
console.log("FIXTURE_PASS");
stop();
Qt.quit();
}
function expectActivity(activityId) {
if (body.islandController.activeActivity === activityId)
return true;
finish("expected " + activityId + " but the island shows " + body.islandController.activeActivity);
return false;
}
onTriggered: {
if (++waited > 800) {
finish("timed out at step " + step);
return;
}
if (body.motionRunning || body.bandThickness <= 0)
return;
const controller = body.islandController;
waited = 0;
switch (step++) {
case 0:
if (!controller.requestSystemActivity("capslock")) {
finish("caps lock is not a system transient");
return;
}
expectActivity("capslock");
return;
case 1:
if (!controller.requestSystemActivity("charging"))
finish("charging is not a system transient");
return;
case 2:
if (!expectActivity("charging"))
return;
if (controller.compactTargetFor("charging").width === controller.compactTargetFor("volume").width)
finish("a state pulse borrows the level pill width");
return;
case 3:
if (!controller.requestSystemActivity("mic"))
finish("mic is not a system level");
return;
case 4:
if (!expectActivity("mic"))
return;
controller.requestCollapse();
return;
default:
if (expectActivity("home"))
finish("");
}
}
}
}
@@ -3181,6 +3181,40 @@
"description": "Routes launcher, dash, control center and notification shortcuts",
"conditionKey": "islandEnabled"
},
{
"section": "islandChargingPulse",
"label": "Charging",
"tabIndex": 3,
"category": "General",
"keywords": [
"bar",
"battery",
"briefly",
"charge",
"charging",
"connected",
"general",
"hibernate",
"island",
"panel",
"plugged",
"power",
"pulse",
"reboot",
"removed",
"restart",
"show",
"shutdown",
"sleep",
"statusbar",
"suspend",
"taskbar",
"topbar"
],
"parentLabel": "Bar",
"description": "Show the battery briefly when power is connected or removed",
"conditionKey": "islandEnabled"
},
{
"section": "islandNotificationBadgeClearOnOpen",
"label": "Clear badge on open",
@@ -4257,12 +4291,16 @@
"keywords": [
"bar",
"brightness",
"caps lock",
"general",
"idle inhibitor",
"island",
"level",
"microphone",
"osd",
"osds",
"panel",
"power profile",
"standard",
"statusbar",
"taskbar",
@@ -4802,6 +4840,7 @@
"charge",
"charging",
"circle",
"duo",
"gauge",
"island",
"outline",
@@ -4812,7 +4851,8 @@
"statusbar",
"style",
"taskbar",
"topbar"
"topbar",
"wifi"
],
"parentLabel": "Bar",
"conditionKey": "islandEnabled"
@@ -9814,7 +9854,15 @@
"optionFile": "PrivacyOptions.qml"
},
{
"section": "barWidgetOption:IslandOptions.qml:26",
"section": "barWidgetOption:IslandOptions.qml:25",
"label": "Charging",
"tabIndex": 22,
"keywords": [],
"runtimeType": "barWidgetOption",
"optionFile": "IslandOptions.qml"
},
{
"section": "barWidgetOption:IslandOptions.qml:27",
"label": "Clear badge on open",
"tabIndex": 22,
"keywords": [],
@@ -10030,7 +10078,7 @@
"optionFile": "AppsDockOptions.qml"
},
{
"section": "barWidgetOption:IslandOptions.qml:25",
"section": "barWidgetOption:IslandOptions.qml:26",
"label": "Expand by default",
"tabIndex": 22,
"keywords": [],
@@ -10326,7 +10374,7 @@
"optionFile": "WorkspaceSwitcherOptions.qml"
},
{
"section": "barWidgetOption:IslandOptions.qml:27",
"section": "barWidgetOption:IslandOptions.qml:28",
"label": "Motion",
"tabIndex": 22,
"keywords": [],
@@ -10422,7 +10470,7 @@
"optionFile": "AppsDockOptions.qml"
},
{
"section": "barWidgetOption:IslandOptions.qml:28",
"section": "barWidgetOption:IslandOptions.qml:29",
"label": "Reduce motion",
"tabIndex": 22,
"keywords": [],
@@ -10638,7 +10686,7 @@
"optionFile": "AppsDockOptions.qml"
},
{
"section": "barWidgetOption:IslandOptions.qml:30",
"section": "barWidgetOption:IslandOptions.qml:31",
"label": "Spring damping",
"tabIndex": 22,
"keywords": [],
@@ -10646,7 +10694,7 @@
"optionFile": "IslandOptions.qml"
},
{
"section": "barWidgetOption:IslandOptions.qml:31",
"section": "barWidgetOption:IslandOptions.qml:32",
"label": "Spring mass",
"tabIndex": 22,
"keywords": [],
@@ -10654,7 +10702,7 @@
"optionFile": "IslandOptions.qml"
},
{
"section": "barWidgetOption:IslandOptions.qml:29",
"section": "barWidgetOption:IslandOptions.qml:30",
"label": "Spring stiffness",
"tabIndex": 22,
"keywords": [],
@@ -16854,6 +16902,40 @@
"description": "Routes launcher, dash, control center and notification shortcuts",
"conditionKey": "dotEnabled"
},
{
"section": "dotChargingPulse",
"label": "Charging",
"tabIndex": 65,
"category": "Dot",
"keywords": [
"bar",
"battery",
"briefly",
"charge",
"charging",
"connected",
"dot",
"hibernate",
"island",
"panel",
"plugged",
"power",
"pulse",
"reboot",
"removed",
"restart",
"show",
"shutdown",
"sleep",
"statusbar",
"suspend",
"taskbar",
"topbar"
],
"parentLabel": "Bar",
"description": "Show the battery briefly when power is connected or removed",
"conditionKey": "dotEnabled"
},
{
"section": "dotNotificationBadgeClearOnOpen",
"label": "Clear badge on open",
@@ -17491,12 +17573,16 @@
"keywords": [
"bar",
"brightness",
"caps lock",
"dot",
"idle inhibitor",
"island",
"level",
"microphone",
"osd",
"osds",
"panel",
"power profile",
"standard",
"statusbar",
"taskbar",