mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-28 14:07:47 -04:00
* feat: add cellular support * fix(settings): resolve cellular tab index collision * fix: address review feedback for cellular support Bump the network API version, drop the deleted term freeze file, regenerate the settings search index, and reuse existing translation terms where they already cover the new cellular UI. * fix: do not assign readonly userPreference when switching network type Assigning SettingsData.networkPreference through the readonly alias threw on click, so the Cellular segment in Control Center did nothing. * fix: make Cellular selectable and the modem row clickable The Control Center type switcher was bound only to preference, so Cellular snapped back to Ethernet. The settings modem row highlighted on hover but had no click handler. Show the modem as a connect target when no APN profiles exist. * fix: create a GSM profile when connecting cellular with none saved Clicking Mobile broadband only activated an existing profile, so a modem with no APN did nothing useful. Fall back to AddAndActivate like ethernet connect. * fix: address second round of cellular review feedback - Guard cellularDevices map with devMutex via snapshot/by-iface/set/remove helpers, mirroring the existing ethernet and wifi accessors - Regenerate mocks with mockery v3 instead of hand-editing mock_Backend.go - Reuse refreshCellularState() in SetCellularEnabled, DisconnectCellular, DisconnectCellularDevice, ActivateCellularConnection and device removal - Wrap retryErr rather than the first error when the auto-config retry fails - Use Theme.surfaceTextSecondary/primaryHover/primaryHoverLight in the cellular-off state instead of hand-rolled Qt.rgba values - Drop the duplicate "No devices found" label so it can no longer render twice with no modem and no saved config Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> * fix: address remaining cellular review comments - Toast "Failed to toggle cellular" instead of always "enable" - Remove unused enableCellularRadio() wrappers - Store Control Center network type as a value, not a list index * fix: pass nmcli connection selector as separate argument `nmcli con mod uuid:<UUID>` is not valid syntax -- nmcli expects the selector keyword as its own argument, so every priority update failed with "no such connection profile". Because practically every NM profile has a UUID, this silently disabled autoconnect-priority and route-metric updates for ethernet and WiFi as well as cellular. Also fix the Cellular settings status text, which said "Connecting..." for a flag that only tracks radio enable/disable, and add the seven cellular terms that were missing from the translation catalogs. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> * apply review comments * fix lints * update debug doc * fix style inconsistencies --------- Co-authored-by: Yechiel Worenklein <[email protected]> Co-authored-by: Claude Opus 5 (1M context) <[email protected]> Co-authored-by: bbedward <[email protected]>
858 lines
38 KiB
QML
858 lines
38 KiB
QML
pragma ComponentBehavior: Bound
|
|
import QtQuick
|
|
import Quickshell
|
|
import qs.Common
|
|
import qs.Modules.Plugins
|
|
import qs.Services
|
|
import qs.Widgets
|
|
|
|
BasePill {
|
|
id: root
|
|
|
|
property bool isActive: false
|
|
property var popoutTarget: null
|
|
property var widgetData: null
|
|
property string screenName: ""
|
|
property string screenModel: ""
|
|
property bool showNetworkIcon: widgetData?.showNetworkIcon !== undefined ? widgetData.showNetworkIcon : SettingsData.controlCenterShowNetworkIcon
|
|
property bool showBluetoothIcon: widgetData?.showBluetoothIcon !== undefined ? widgetData.showBluetoothIcon : SettingsData.controlCenterShowBluetoothIcon
|
|
property bool showAudioIcon: widgetData?.showAudioIcon !== undefined ? widgetData.showAudioIcon : SettingsData.controlCenterShowAudioIcon
|
|
property bool showAudioPercent: widgetData?.showAudioPercent !== undefined ? widgetData.showAudioPercent : SettingsData.controlCenterShowAudioPercent
|
|
property bool showVpnIcon: widgetData?.showVpnIcon !== undefined ? widgetData.showVpnIcon : SettingsData.controlCenterShowVpnIcon
|
|
property bool showBrightnessIcon: widgetData?.showBrightnessIcon !== undefined ? widgetData.showBrightnessIcon : SettingsData.controlCenterShowBrightnessIcon
|
|
property bool showBrightnessPercent: widgetData?.showBrightnessPercent !== undefined ? widgetData.showBrightnessPercent : SettingsData.controlCenterShowBrightnessPercent
|
|
property bool showMicIcon: widgetData?.showMicIcon !== undefined ? widgetData.showMicIcon : SettingsData.controlCenterShowMicIcon
|
|
property bool showMicPercent: widgetData?.showMicPercent !== undefined ? widgetData.showMicPercent : SettingsData.controlCenterShowMicPercent
|
|
property bool showBatteryIcon: widgetData?.showBatteryIcon !== undefined ? widgetData.showBatteryIcon : SettingsData.controlCenterShowBatteryIcon
|
|
property bool showPrinterIcon: widgetData?.showPrinterIcon !== undefined ? widgetData.showPrinterIcon : SettingsData.controlCenterShowPrinterIcon
|
|
property bool showScreenSharingIcon: widgetData?.showScreenSharingIcon !== undefined ? widgetData.showScreenSharingIcon : SettingsData.controlCenterShowScreenSharingIcon
|
|
property bool showIdleInhibitorIcon: widgetData?.showIdleInhibitorIcon !== undefined ? widgetData.showIdleInhibitorIcon : SettingsData.controlCenterShowIdleInhibitorIcon
|
|
property bool showDoNotDisturbIcon: widgetData?.showDoNotDisturbIcon !== undefined ? widgetData.showDoNotDisturbIcon : SettingsData.controlCenterShowDoNotDisturbIcon
|
|
property real touchpadThreshold: 100
|
|
property real micAccumulator: 0
|
|
property real volumeAccumulator: 0
|
|
property real brightnessAccumulator: 0
|
|
readonly property real vIconSize: Theme.barIconSize(root.barThickness, -4, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)
|
|
property var _hRow: null
|
|
property var _vCol: null
|
|
property var _hAudio: null
|
|
property var _hBrightness: null
|
|
property var _hMic: null
|
|
property var _vAudio: null
|
|
property var _vBrightness: null
|
|
property var _vMic: null
|
|
property var _interactionDelegates: []
|
|
readonly property var defaultControlCenterGroupOrder: ["network", "vpn", "bluetooth", "audio", "microphone", "brightness", "battery", "printer", "screenSharing", "idleInhibitor", "doNotDisturb"]
|
|
readonly property var effectiveControlCenterGroupOrder: getEffectiveControlCenterGroupOrder()
|
|
readonly property var controlCenterRenderModel: getControlCenterRenderModel()
|
|
|
|
onIsVerticalOrientationChanged: root.clearInteractionRefs()
|
|
|
|
onWheel: function (wheelEvent) {
|
|
const delta = wheelEvent.angleDelta.y;
|
|
if (delta === 0)
|
|
return;
|
|
|
|
root.refreshInteractionRefs();
|
|
|
|
const rootX = wheelEvent.x - root.leftMargin;
|
|
const rootY = wheelEvent.y - root.topMargin;
|
|
|
|
if (root.isVerticalOrientation && _vCol) {
|
|
const pos = root.mapToItem(_vCol, rootX, rootY);
|
|
if (_vBrightness?.visible && pos.y >= _vBrightness.y && pos.y < _vBrightness.y + _vBrightness.height) {
|
|
root.handleBrightnessWheel(delta);
|
|
} else if (_vMic?.visible && pos.y >= _vMic.y && pos.y < _vMic.y + _vMic.height) {
|
|
root.handleMicWheel(delta);
|
|
} else {
|
|
root.handleVolumeWheel(delta);
|
|
}
|
|
} else if (_hRow) {
|
|
const pos = root.mapToItem(_hRow, rootX, rootY);
|
|
if (_hBrightness?.visible && pos.x >= _hBrightness.x && pos.x < _hBrightness.x + _hBrightness.width) {
|
|
root.handleBrightnessWheel(delta);
|
|
} else if (_hMic?.visible && pos.x >= _hMic.x && pos.x < _hMic.x + _hMic.width) {
|
|
root.handleMicWheel(delta);
|
|
} else {
|
|
root.handleVolumeWheel(delta);
|
|
}
|
|
} else {
|
|
root.handleVolumeWheel(delta);
|
|
}
|
|
wheelEvent.accepted = true;
|
|
}
|
|
|
|
onRightClicked: function (rootX, rootY) {
|
|
root.refreshInteractionRefs();
|
|
|
|
if (root.isVerticalOrientation && _vCol) {
|
|
const pos = root.mapToItem(_vCol, rootX, rootY);
|
|
if (_vAudio?.visible && pos.y >= _vAudio.y && pos.y < _vAudio.y + _vAudio.height) {
|
|
AudioService.toggleMute();
|
|
return;
|
|
}
|
|
if (_vMic?.visible && pos.y >= _vMic.y && pos.y < _vMic.y + _vMic.height) {
|
|
AudioService.toggleMicMute();
|
|
return;
|
|
}
|
|
} else if (_hRow) {
|
|
const pos = root.mapToItem(_hRow, rootX, rootY);
|
|
if (_hAudio?.visible && pos.x >= _hAudio.x && pos.x < _hAudio.x + _hAudio.width) {
|
|
AudioService.toggleMute();
|
|
return;
|
|
}
|
|
if (_hMic?.visible && pos.x >= _hMic.x && pos.x < _hMic.x + _hMic.width) {
|
|
AudioService.toggleMicMute();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
Loader {
|
|
active: root.showPrinterIcon
|
|
sourceComponent: Component {
|
|
Ref {
|
|
service: CupsService
|
|
}
|
|
}
|
|
}
|
|
|
|
function getNetworkIconName() {
|
|
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);
|
|
default:
|
|
return NetworkService.wifiSignalIcon;
|
|
}
|
|
}
|
|
|
|
function getNetworkIconColor() {
|
|
if (NetworkService.wifiToggling)
|
|
return Theme.primary;
|
|
if (NetworkService.isConnecting && !NetworkService.ethernetConnected)
|
|
return Theme.primary;
|
|
return NetworkService.networkStatus !== "disconnected" ? Theme.primary : Theme.surfaceText;
|
|
}
|
|
|
|
function getIconBlinking(id) {
|
|
if (id === "network")
|
|
return NetworkService.isWifiConnecting;
|
|
if (id === "bluetooth")
|
|
return BluetoothService.connecting;
|
|
return false;
|
|
}
|
|
|
|
function getMicIconName() {
|
|
if (!AudioService.source?.audio)
|
|
return "mic";
|
|
if (AudioService.source.audio.muted || AudioService.source.audio.volume === 0)
|
|
return "mic_off";
|
|
return "mic";
|
|
}
|
|
|
|
function getMicIconColor() {
|
|
if (!AudioService.source?.audio)
|
|
return Theme.surfaceText;
|
|
if (AudioService.source.audio.muted || AudioService.source.audio.volume === 0)
|
|
return Theme.surfaceText;
|
|
return Theme.widgetIconColor;
|
|
}
|
|
|
|
function getBrightnessIconName() {
|
|
const deviceName = getEffectiveBrightnessDevice();
|
|
return DisplayService.brightnessIconName(DisplayService.getCurrentDeviceInfoByName(deviceName), DisplayService.getDeviceBrightness(deviceName));
|
|
}
|
|
|
|
function getScreenPinKey() {
|
|
if (!root.screenName)
|
|
return "";
|
|
const screen = Quickshell.screens.find(s => s.name === root.screenName);
|
|
if (screen) {
|
|
return SettingsData.getScreenDisplayName(screen);
|
|
}
|
|
if (SettingsData.displayNameMode === "model" && root.screenModel && root.screenModel.length > 0) {
|
|
return root.screenModel;
|
|
}
|
|
return root.screenName;
|
|
}
|
|
|
|
function getPinnedBrightnessDevice() {
|
|
const pinKey = getScreenPinKey();
|
|
if (!pinKey)
|
|
return "";
|
|
const pins = CacheData.brightnessDevicePins || {};
|
|
return pins[pinKey] || "";
|
|
}
|
|
|
|
function getEffectiveBrightnessDevice() {
|
|
return getPinnedBrightnessDevice() || DisplayService.getDefaultDevice();
|
|
}
|
|
|
|
function handleVolumeWheel(delta) {
|
|
if (!AudioService.sink?.audio)
|
|
return;
|
|
|
|
var step = 5;
|
|
const isMouseWheel = Math.abs(delta) >= 120 && (Math.abs(delta) % 120) === 0;
|
|
if (!isMouseWheel) {
|
|
step = 1;
|
|
volumeAccumulator += delta;
|
|
if (Math.abs(volumeAccumulator) < touchpadThreshold)
|
|
return;
|
|
|
|
delta = volumeAccumulator;
|
|
volumeAccumulator = 0;
|
|
}
|
|
|
|
const maxVol = AudioService.sinkMaxVolume;
|
|
const currentVolume = AudioService.sink.audio.volume * 100;
|
|
const newVolume = delta > 0 ? Math.min(maxVol, currentVolume + step) : Math.max(0, currentVolume - step);
|
|
AudioService.sink.audio.muted = false;
|
|
AudioService.sink.audio.volume = newVolume / 100;
|
|
AudioService.playVolumeChangeSoundIfEnabled();
|
|
}
|
|
|
|
function handleMicWheel(delta) {
|
|
if (!AudioService.source?.audio)
|
|
return;
|
|
|
|
var step = 5;
|
|
const isMouseWheel = Math.abs(delta) >= 120 && (Math.abs(delta) % 120) === 0;
|
|
if (!isMouseWheel) {
|
|
step = 1;
|
|
micAccumulator += delta;
|
|
if (Math.abs(micAccumulator) < touchpadThreshold)
|
|
return;
|
|
|
|
delta = micAccumulator;
|
|
micAccumulator = 0;
|
|
}
|
|
|
|
const currentVolume = AudioService.source.audio.volume * 100;
|
|
const newVolume = delta > 0 ? Math.min(100, currentVolume + step) : Math.max(0, currentVolume - step);
|
|
AudioService.source.audio.muted = false;
|
|
AudioService.source.audio.volume = newVolume / 100;
|
|
AudioService.micVolumeChanged();
|
|
}
|
|
|
|
function handleBrightnessWheel(delta) {
|
|
const deviceName = getEffectiveBrightnessDevice();
|
|
if (!deviceName) {
|
|
return;
|
|
}
|
|
|
|
var step = 5;
|
|
const isMouseWheel = Math.abs(delta) >= 120 && (Math.abs(delta) % 120) === 0;
|
|
if (!isMouseWheel) {
|
|
step = 1;
|
|
brightnessAccumulator += delta;
|
|
if (Math.abs(brightnessAccumulator) < touchpadThreshold)
|
|
return;
|
|
|
|
delta = brightnessAccumulator;
|
|
brightnessAccumulator = 0;
|
|
}
|
|
|
|
const currentBrightness = DisplayService.getDeviceBrightness(deviceName);
|
|
const newBrightness = delta > 0 ? Math.min(100, currentBrightness + step) : Math.max(1, currentBrightness - step);
|
|
DisplayService.setBrightness(newBrightness, deviceName);
|
|
}
|
|
|
|
function getBrightness() {
|
|
const deviceName = getEffectiveBrightnessDevice();
|
|
if (!deviceName) {
|
|
return;
|
|
}
|
|
return DisplayService.getDeviceBrightness(deviceName) / 100;
|
|
}
|
|
|
|
function getBatteryIconColor() {
|
|
if (!BatteryService.batteryAvailable)
|
|
return Theme.widgetIconColor;
|
|
if (BatteryService.isLowBattery && !BatteryService.isCharging)
|
|
return Theme.error;
|
|
if (BatteryService.isCharging || BatteryService.isPluggedIn)
|
|
return Theme.primary;
|
|
return Theme.widgetIconColor;
|
|
}
|
|
|
|
function hasPrintJobs() {
|
|
return CupsService.getTotalJobsNum() > 0;
|
|
}
|
|
|
|
function getControlCenterIconSize() {
|
|
return Theme.barIconSize(root.barThickness, -4, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale);
|
|
}
|
|
|
|
function getEffectiveControlCenterGroupOrder() {
|
|
const knownIds = root.defaultControlCenterGroupOrder;
|
|
const savedOrder = root.widgetData?.controlCenterGroupOrder;
|
|
const result = [];
|
|
const seen = {};
|
|
|
|
if (savedOrder && typeof savedOrder.length === "number") {
|
|
for (let i = 0; i < savedOrder.length; ++i) {
|
|
const groupId = savedOrder[i];
|
|
if (knownIds.indexOf(groupId) === -1 || seen[groupId])
|
|
continue;
|
|
|
|
seen[groupId] = true;
|
|
result.push(groupId);
|
|
}
|
|
}
|
|
|
|
for (let i = 0; i < knownIds.length; ++i) {
|
|
const groupId = knownIds[i];
|
|
if (seen[groupId])
|
|
continue;
|
|
|
|
seen[groupId] = true;
|
|
result.push(groupId);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function isGroupVisible(groupId) {
|
|
switch (groupId) {
|
|
case "screenSharing":
|
|
return root.showScreenSharingIcon && NiriService.hasCasts;
|
|
case "network":
|
|
return root.showNetworkIcon && NetworkService.networkAvailable;
|
|
case "vpn":
|
|
return root.showVpnIcon && NetworkService.vpnAvailable && NetworkService.vpnConnected;
|
|
case "bluetooth":
|
|
return root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled;
|
|
case "audio":
|
|
return root.showAudioIcon;
|
|
case "microphone":
|
|
return root.showMicIcon;
|
|
case "brightness":
|
|
return root.showBrightnessIcon && DisplayService.brightnessAvailable && root.getEffectiveBrightnessDevice().length > 0;
|
|
case "battery":
|
|
return root.showBatteryIcon && BatteryService.batteryAvailable;
|
|
case "printer":
|
|
return root.showPrinterIcon && CupsService.cupsAvailable && root.hasPrintJobs();
|
|
case "idleInhibitor":
|
|
return root.showIdleInhibitorIcon && SessionService.idleInhibited;
|
|
case "doNotDisturb":
|
|
return root.showDoNotDisturbIcon && SessionData.doNotDisturb;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function isCompositeGroup(groupId) {
|
|
return groupId === "audio" || groupId === "microphone" || groupId === "brightness";
|
|
}
|
|
|
|
function getControlCenterRenderModel() {
|
|
return root.effectiveControlCenterGroupOrder.map(groupId => ({
|
|
"id": groupId,
|
|
"visible": root.isGroupVisible(groupId),
|
|
"composite": root.isCompositeGroup(groupId)
|
|
}));
|
|
}
|
|
|
|
function clearInteractionRefs() {
|
|
root._hAudio = null;
|
|
root._hBrightness = null;
|
|
root._hMic = null;
|
|
root._vAudio = null;
|
|
root._vBrightness = null;
|
|
root._vMic = null;
|
|
}
|
|
|
|
function registerInteractionDelegate(isVertical, item) {
|
|
if (!item)
|
|
return;
|
|
|
|
for (let i = 0; i < root._interactionDelegates.length; ++i) {
|
|
const entry = root._interactionDelegates[i];
|
|
if (entry && entry.item === item) {
|
|
entry.isVertical = isVertical;
|
|
return;
|
|
}
|
|
}
|
|
|
|
root._interactionDelegates = root._interactionDelegates.concat([
|
|
{
|
|
"isVertical": isVertical,
|
|
"item": item
|
|
}
|
|
]);
|
|
}
|
|
|
|
function unregisterInteractionDelegate(item) {
|
|
if (!item)
|
|
return;
|
|
|
|
root._interactionDelegates = root._interactionDelegates.filter(entry => entry && entry.item !== item);
|
|
}
|
|
|
|
function refreshInteractionRefs() {
|
|
root.clearInteractionRefs();
|
|
|
|
for (let i = 0; i < root._interactionDelegates.length; ++i) {
|
|
const entry = root._interactionDelegates[i];
|
|
const item = entry?.item;
|
|
if (!item || !item.visible)
|
|
continue;
|
|
|
|
const groupId = item.interactionGroupId;
|
|
if (entry.isVertical) {
|
|
if (groupId === "audio")
|
|
root._vAudio = item;
|
|
else if (groupId === "microphone")
|
|
root._vMic = item;
|
|
else if (groupId === "brightness")
|
|
root._vBrightness = item;
|
|
} else {
|
|
if (groupId === "audio")
|
|
root._hAudio = item;
|
|
else if (groupId === "microphone")
|
|
root._hMic = item;
|
|
else if (groupId === "brightness")
|
|
root._hBrightness = item;
|
|
}
|
|
}
|
|
}
|
|
|
|
function hasNoVisibleIcons() {
|
|
return !root.controlCenterRenderModel.some(entry => entry.visible);
|
|
}
|
|
|
|
content: Component {
|
|
Item {
|
|
implicitWidth: root.isVerticalOrientation ? (root.widgetThickness - root.horizontalPadding * 2) : controlIndicators.implicitWidth
|
|
implicitHeight: root.isVerticalOrientation ? controlColumn.implicitHeight : (root.widgetThickness - root.horizontalPadding * 2)
|
|
|
|
Component.onCompleted: {
|
|
root._hRow = controlIndicators;
|
|
root._vCol = controlColumn;
|
|
root.clearInteractionRefs();
|
|
}
|
|
|
|
Column {
|
|
id: controlColumn
|
|
visible: root.isVerticalOrientation
|
|
width: parent.width
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
spacing: Theme.spacingXS
|
|
|
|
Repeater {
|
|
model: root.controlCenterRenderModel
|
|
Item {
|
|
id: verticalGroupItem
|
|
required property var modelData
|
|
required property int index
|
|
property string interactionGroupId: modelData.id
|
|
|
|
width: parent.width
|
|
height: {
|
|
switch (modelData.id) {
|
|
case "audio":
|
|
return root.vIconSize + (audioPercentV.visible ? audioPercentV.implicitHeight + 2 : 0);
|
|
case "microphone":
|
|
return root.vIconSize + (micPercentV.visible ? micPercentV.implicitHeight + 2 : 0);
|
|
case "brightness":
|
|
return root.vIconSize + (brightnessPercentV.visible ? brightnessPercentV.implicitHeight + 2 : 0);
|
|
default:
|
|
return root.vIconSize;
|
|
}
|
|
}
|
|
visible: modelData.visible
|
|
|
|
Component.onCompleted: {
|
|
root.registerInteractionDelegate(true, verticalGroupItem);
|
|
root.refreshInteractionRefs();
|
|
}
|
|
Component.onDestruction: {
|
|
if (root) {
|
|
root.unregisterInteractionDelegate(verticalGroupItem);
|
|
root.refreshInteractionRefs();
|
|
}
|
|
}
|
|
onVisibleChanged: root.refreshInteractionRefs()
|
|
onInteractionGroupIdChanged: {
|
|
root.refreshInteractionRefs();
|
|
}
|
|
|
|
DankIcon {
|
|
id: vIconOnlyItem
|
|
anchors.centerIn: parent
|
|
visible: !verticalGroupItem.modelData.composite
|
|
name: {
|
|
switch (verticalGroupItem.modelData.id) {
|
|
case "screenSharing":
|
|
return "screen_record";
|
|
case "network":
|
|
return root.getNetworkIconName();
|
|
case "vpn":
|
|
return "vpn_lock";
|
|
case "bluetooth":
|
|
return BluetoothService.connected ? "bluetooth_connected" : "bluetooth";
|
|
case "battery":
|
|
return Theme.getBatteryIcon(BatteryService.batteryLevel, BatteryService.isCharging, BatteryService.batteryAvailable);
|
|
case "printer":
|
|
return "print";
|
|
case "idleInhibitor":
|
|
return "motion_sensor_active";
|
|
case "doNotDisturb":
|
|
return "do_not_disturb_on";
|
|
default:
|
|
return "settings";
|
|
}
|
|
}
|
|
size: root.vIconSize
|
|
color: {
|
|
switch (verticalGroupItem.modelData.id) {
|
|
case "screenSharing":
|
|
return NiriService.hasActiveCast ? Theme.primary : Theme.surfaceText;
|
|
case "network":
|
|
return root.getNetworkIconColor();
|
|
case "vpn":
|
|
return NetworkService.vpnConnected ? Theme.primary : Theme.surfaceText;
|
|
case "bluetooth":
|
|
return (BluetoothService.connected || BluetoothService.connecting) ? Theme.primary : Theme.surfaceText;
|
|
case "battery":
|
|
return root.getBatteryIconColor();
|
|
case "printer":
|
|
return Theme.primary;
|
|
case "idleInhibitor":
|
|
return Theme.primary;
|
|
case "doNotDisturb":
|
|
return Theme.primary;
|
|
default:
|
|
return Theme.widgetIconColor;
|
|
}
|
|
}
|
|
|
|
DankBlink {
|
|
target: vIconOnlyItem
|
|
running: root.getIconBlinking(verticalGroupItem.modelData.id)
|
|
}
|
|
}
|
|
|
|
DankIcon {
|
|
id: audioIconV
|
|
visible: verticalGroupItem.modelData.id === "audio"
|
|
name: AudioService.sinkVolumeIconName
|
|
size: root.vIconSize
|
|
color: Theme.widgetIconColor
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
anchors.top: parent.top
|
|
}
|
|
|
|
NumericText {
|
|
id: audioPercentV
|
|
visible: verticalGroupItem.modelData.id === "audio" && root.showAudioPercent && isFinite(AudioService.sink?.audio?.volume)
|
|
text: Math.round((AudioService.sink?.audio?.volume ?? 0) * 100) + "%"
|
|
reserveText: "100%"
|
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
|
color: Theme.widgetTextColor
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
anchors.top: audioIconV.bottom
|
|
anchors.topMargin: 2
|
|
}
|
|
|
|
DankIcon {
|
|
id: micIconV
|
|
visible: verticalGroupItem.modelData.id === "microphone"
|
|
name: root.getMicIconName()
|
|
size: root.vIconSize
|
|
color: root.getMicIconColor()
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
anchors.top: parent.top
|
|
}
|
|
|
|
NumericText {
|
|
id: micPercentV
|
|
visible: verticalGroupItem.modelData.id === "microphone" && root.showMicPercent && isFinite(AudioService.source?.audio?.volume)
|
|
text: Math.round((AudioService.source?.audio?.volume ?? 0) * 100) + "%"
|
|
reserveText: "100%"
|
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
|
color: Theme.widgetTextColor
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
anchors.top: micIconV.bottom
|
|
anchors.topMargin: 2
|
|
}
|
|
|
|
DankIcon {
|
|
id: brightnessIconV
|
|
visible: verticalGroupItem.modelData.id === "brightness"
|
|
name: root.getBrightnessIconName()
|
|
size: root.vIconSize
|
|
color: Theme.widgetIconColor
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
anchors.top: parent.top
|
|
}
|
|
|
|
NumericText {
|
|
id: brightnessPercentV
|
|
visible: verticalGroupItem.modelData.id === "brightness" && root.showBrightnessPercent && isFinite(getBrightness())
|
|
text: Math.round(getBrightness() * 100) + "%"
|
|
reserveText: "100%"
|
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
|
color: Theme.widgetTextColor
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
anchors.top: brightnessIconV.bottom
|
|
anchors.topMargin: 2
|
|
}
|
|
}
|
|
}
|
|
|
|
Item {
|
|
width: parent.width
|
|
height: root.vIconSize
|
|
visible: root.hasNoVisibleIcons()
|
|
|
|
DankIcon {
|
|
name: "settings"
|
|
size: root.vIconSize
|
|
color: root.isActive ? Theme.primary : Theme.widgetIconColor
|
|
anchors.centerIn: parent
|
|
}
|
|
}
|
|
}
|
|
|
|
Row {
|
|
id: controlIndicators
|
|
visible: !root.isVerticalOrientation
|
|
anchors.centerIn: parent
|
|
spacing: Theme.spacingXS
|
|
|
|
Repeater {
|
|
model: root.controlCenterRenderModel
|
|
|
|
Item {
|
|
id: horizontalGroupItem
|
|
required property var modelData
|
|
required property int index
|
|
property string interactionGroupId: modelData.id
|
|
|
|
width: {
|
|
switch (modelData.id) {
|
|
case "audio":
|
|
return audioGroup.width;
|
|
case "microphone":
|
|
return micGroup.width;
|
|
case "brightness":
|
|
return brightnessGroup.width;
|
|
default:
|
|
return root.getControlCenterIconSize();
|
|
}
|
|
}
|
|
implicitWidth: width
|
|
height: root.widgetThickness - root.horizontalPadding * 2
|
|
visible: modelData.visible
|
|
|
|
Component.onCompleted: {
|
|
root.registerInteractionDelegate(false, horizontalGroupItem);
|
|
root.refreshInteractionRefs();
|
|
}
|
|
Component.onDestruction: {
|
|
if (root) {
|
|
root.unregisterInteractionDelegate(horizontalGroupItem);
|
|
root.refreshInteractionRefs();
|
|
}
|
|
}
|
|
onVisibleChanged: root.refreshInteractionRefs()
|
|
onInteractionGroupIdChanged: {
|
|
root.refreshInteractionRefs();
|
|
}
|
|
|
|
DankIcon {
|
|
id: iconOnlyItem
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
anchors.left: parent.left
|
|
visible: !horizontalGroupItem.modelData.composite
|
|
name: {
|
|
switch (horizontalGroupItem.modelData.id) {
|
|
case "screenSharing":
|
|
return "screen_record";
|
|
case "network":
|
|
return root.getNetworkIconName();
|
|
case "vpn":
|
|
return "vpn_lock";
|
|
case "bluetooth":
|
|
return BluetoothService.connected ? "bluetooth_connected" : "bluetooth";
|
|
case "battery":
|
|
return Theme.getBatteryIcon(BatteryService.batteryLevel, BatteryService.isCharging, BatteryService.batteryAvailable);
|
|
case "printer":
|
|
return "print";
|
|
case "idleInhibitor":
|
|
return "motion_sensor_active";
|
|
case "doNotDisturb":
|
|
return "do_not_disturb_on";
|
|
default:
|
|
return "settings";
|
|
}
|
|
}
|
|
size: root.getControlCenterIconSize()
|
|
color: {
|
|
switch (horizontalGroupItem.modelData.id) {
|
|
case "screenSharing":
|
|
return NiriService.hasActiveCast ? Theme.primary : Theme.surfaceText;
|
|
case "network":
|
|
return root.getNetworkIconColor();
|
|
case "vpn":
|
|
return NetworkService.vpnConnected ? Theme.primary : Theme.surfaceText;
|
|
case "bluetooth":
|
|
return (BluetoothService.connected || BluetoothService.connecting) ? Theme.primary : Theme.surfaceText;
|
|
case "battery":
|
|
return root.getBatteryIconColor();
|
|
case "printer":
|
|
return Theme.primary;
|
|
case "idleInhibitor":
|
|
return Theme.primary;
|
|
case "doNotDisturb":
|
|
return Theme.primary;
|
|
default:
|
|
return Theme.widgetIconColor;
|
|
}
|
|
}
|
|
|
|
DankBlink {
|
|
target: iconOnlyItem
|
|
running: root.getIconBlinking(horizontalGroupItem.modelData.id)
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
id: audioGroup
|
|
width: audioContent.implicitWidth + 2
|
|
implicitWidth: width
|
|
height: parent.height
|
|
color: "transparent"
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
visible: horizontalGroupItem.modelData.id === "audio"
|
|
|
|
Row {
|
|
id: audioContent
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: 1
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
spacing: Theme.spacingXXS
|
|
|
|
DankIcon {
|
|
id: audioIcon
|
|
name: AudioService.sinkVolumeIconName
|
|
size: root.getControlCenterIconSize()
|
|
color: Theme.widgetIconColor
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
|
|
NumericText {
|
|
id: audioPercent
|
|
visible: root.showAudioPercent && isFinite(AudioService.sink?.audio?.volume)
|
|
text: Math.round((AudioService.sink?.audio?.volume ?? 0) * 100) + "%"
|
|
reserveText: "100%"
|
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
|
color: Theme.widgetTextColor
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
width: visible ? implicitWidth : 0
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
id: micGroup
|
|
width: micContent.implicitWidth + 2
|
|
implicitWidth: width
|
|
height: parent.height
|
|
color: "transparent"
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
visible: horizontalGroupItem.modelData.id === "microphone"
|
|
|
|
Row {
|
|
id: micContent
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: 1
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
spacing: Theme.spacingXXS
|
|
|
|
DankIcon {
|
|
id: micIcon
|
|
name: root.getMicIconName()
|
|
size: root.getControlCenterIconSize()
|
|
color: root.getMicIconColor()
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
|
|
NumericText {
|
|
id: micPercent
|
|
visible: root.showMicPercent && isFinite(AudioService.source?.audio?.volume)
|
|
text: Math.round((AudioService.source?.audio?.volume ?? 0) * 100) + "%"
|
|
reserveText: "100%"
|
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
|
color: Theme.widgetTextColor
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
width: visible ? implicitWidth : 0
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
id: brightnessGroup
|
|
width: brightnessContent.implicitWidth + 2
|
|
implicitWidth: width
|
|
height: parent.height
|
|
color: "transparent"
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
visible: horizontalGroupItem.modelData.id === "brightness"
|
|
|
|
Row {
|
|
id: brightnessContent
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: 1
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
spacing: Theme.spacingXXS
|
|
|
|
DankIcon {
|
|
id: brightnessIcon
|
|
name: root.getBrightnessIconName()
|
|
size: root.getControlCenterIconSize()
|
|
color: Theme.widgetIconColor
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
|
|
NumericText {
|
|
id: brightnessPercent
|
|
visible: root.showBrightnessPercent && isFinite(getBrightness())
|
|
text: Math.round(getBrightness() * 100) + "%"
|
|
reserveText: "100%"
|
|
font.pixelSize: Theme.barTextSize(root.barThickness, root.barConfig?.fontScale, root.barConfig?.maximizeWidgetText)
|
|
color: Theme.widgetTextColor
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
width: visible ? implicitWidth : 0
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DankIcon {
|
|
name: "settings"
|
|
size: root.getControlCenterIconSize()
|
|
color: root.isActive ? Theme.primary : Theme.widgetIconColor
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
visible: root.hasNoVisibleIcons()
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
cursorShape: Qt.PointingHandCursor
|
|
acceptedButtons: Qt.NoButton
|
|
}
|
|
}
|
|
}
|
|
}
|