1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-06-23 19:45:21 -04:00

Compare commits

...

5 Commits

Author SHA1 Message Date
Feng Yu 0b55fbcb15 fix(DankBar): Resolve tray freeze and wallpaper loss after DPMS resume (#2457)
Fixes #2354

Root cause (tray freeze): In clickThrough mode, the PanelWindow mask uses
sectionRect() with mapToItem() to compute input regions. After DPMS resume,
the PanelWindow is recreated with width=0, and mapToItem() returns wrong
positions. The right section's implicitWidth doesn't change after creation
(fixed-size tray icons), so the mask binding is never re-evaluated when the
compositor sets the actual screen width. Adding barWindow.width as a binding
dependency ensures the mask recalculates on resize.

Root cause (wallpaper loss): Wallpaper PanelWindows are recreated by Variants
during screen reconnection before the compositor finishes output initialization.
The wallpaper Image renders at 0x0 dimensions, resulting in a black screen.

Changes:
- DankBarWindow: add barWindow.width dependency to clickThrough mask bindings
- DMSShell: add surface recovery mechanism (screen reconnect + session resume)
  with progressive 2-pass timer (800ms + 2800ms) that recreates bar, Frame,
  wallpaper, and dock surfaces after the compositor is ready
- WlrOutputService: re-request output state on session resume
2026-05-22 09:05:41 -04:00
Domen Kožar 7476a220b5 feat: Blink WiFi/Bluetooth icons while connecting (#2448)
Pulses the WiFi and Bluetooth status icons while a connection is in
progress (lock screen, DankBar control center button, control center
compound pill). The pulse is implemented as a reusable Widgets/DankBlink
component, and the wifi-connecting condition is centralized as
NetworkService.isWifiConnecting.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 09:03:25 -04:00
Huỳnh Thiện Lộc aaff1ab61e feat: implement interactive microphone volume OSD and IPC controls (#2406)
* feat: implement interactive microphone volume OSD and persistence

Addresses #2388

* refactor: reduce scope to interactive microphone OSD and IPC controls only
2026-05-22 09:00:12 -04:00
Cloud 39622eb62a fix(lock): avoid U2F PAM polling in OR mode (#2459) 2026-05-22 08:59:11 -04:00
Lucas eea039f575 feat(Launcher/Spotlight): improve context keyboard navigation and mode persistence (#2467)
* feat(Spotlight): fix submenu keyboard navigation

* feat(Launcher/Spotlight): disable persisting last mode when changed by triggers

* feat(Launcher/Spotlight): add option to disable last mode being persisted

* fix(Launcher/Spotlight): fix context menu keys navigation

* fix(NiriOverviewOverlay): fix context menu keys navigation and position
2026-05-22 08:53:45 -04:00
30 changed files with 735 additions and 102 deletions
+1 -1
View File
@@ -66,7 +66,7 @@ const DMS_ACTIONS = [
{ id: "spawn dms ipc call mpris increment 5", label: "Player Volume Up (5%)" },
{ id: "spawn dms ipc call mpris decrement 5", label: "Player Volume Down (5%)" },
{ id: "spawn dms ipc call audio mute", label: "Volume Mute Toggle" },
{ id: "spawn dms ipc call audio micmute", label: "Microphone Mute Toggle" },
{ id: "spawn dms ipc call mic mute", label: "Microphone Mute Toggle" },
{ id: "spawn dms ipc call audio cycleoutput", label: "Audio Output: Cycle" },
{ id: "spawn dms ipc call brightness increment 5 \"\"", label: "Brightness Up" },
{ id: "spawn dms ipc call brightness increment 1 \"\"", label: "Brightness Up (1%)" },
+6
View File
@@ -1179,6 +1179,12 @@ Singleton {
saveSettings();
}
function getLauncherRestoreMode() {
if (!SettingsData.rememberLastMode)
return "all";
return launcherLastMode || "all";
}
function setLauncherLastFileSearchType(type) {
launcherLastFileSearchType = type;
saveSettings();
+2
View File
@@ -435,6 +435,7 @@ Singleton {
property int appLauncherGridColumns: 4
property bool spotlightCloseNiriOverview: true
property bool rememberLastQuery: false
property bool rememberLastMode: true
property var spotlightSectionViewModes: ({})
onSpotlightSectionViewModesChanged: saveSettings()
property var appDrawerSectionViewModes: ({})
@@ -707,6 +708,7 @@ Singleton {
property bool osdBrightnessEnabled: true
property bool osdIdleInhibitorEnabled: true
property bool osdMicMuteEnabled: true
property bool osdMicVolumeEnabled: true
property bool osdCapsLockEnabled: true
property bool osdPowerProfileEnabled: true
property bool osdAudioOutputEnabled: true
@@ -203,6 +203,7 @@ var SPEC = {
appLauncherGridColumns: { def: 4 },
spotlightCloseNiriOverview: { def: true },
rememberLastQuery: { def: false },
rememberLastMode: { def: true },
spotlightSectionViewModes: { def: {} },
appDrawerSectionViewModes: { def: {} },
niriOverviewOverlayEnabled: { def: true },
+112 -4
View File
@@ -63,15 +63,27 @@ Item {
}
}
property bool wallpaperSurfacesLoaded: true
Loader {
id: blurredWallpaperBackgroundLoader
active: SettingsData.blurredWallpaperLayer && CompositorService.isNiri
active: root.wallpaperSurfacesLoaded && SettingsData.blurredWallpaperLayer && CompositorService.isNiri
asynchronous: false
sourceComponent: BlurredWallpaperBackground {}
}
WallpaperBackground {}
DeferredAction {
id: wallpaperSurfaceReloadAction
onTriggered: root.wallpaperSurfacesLoaded = true
}
Loader {
id: wallpaperBackgroundLoader
active: root.wallpaperSurfacesLoaded
asynchronous: false
sourceComponent: WallpaperBackground {}
}
DesktopWidgetLayer {}
@@ -168,6 +180,8 @@ Item {
property bool barSurfacesLoaded: true
function recreateBarSurfaces() {
log.info("Recreating bar surfaces, screens:", Quickshell.screens.length,
Quickshell.screens.map(s => s.name).join(","));
if (barSurfacesLoaded)
barSurfacesLoaded = false;
barSurfaceReloadAction.schedule();
@@ -217,7 +231,18 @@ Item {
}
}
Frame {}
property bool frameSurfacesLoaded: true
Loader {
active: root.frameSurfacesLoaded
asynchronous: false
sourceComponent: Frame {}
}
DeferredAction {
id: frameSurfaceReloadAction
onTriggered: root.frameSurfacesLoaded = true
}
Repeater {
id: dankBarRepeater
@@ -301,6 +326,81 @@ Item {
onTriggered: root.osdSurfacesLoaded = true
}
property bool hadRealScreen: true
function _hasRealScreen() {
for (let i = 0; i < Quickshell.screens.length; i++) {
if (Quickshell.screens[i].name.length > 0)
return true;
}
return false;
}
function triggerSurfaceRecovery(source) {
log.info("Surface recovery triggered by:", source,
"screens:", Quickshell.screens.length,
Quickshell.screens.map(s => s.name).join(","),
"barLoaded:", root.barSurfacesLoaded,
"frameLoaded:", root.frameSurfacesLoaded,
"dockEnabled:", root.dockEnabled);
surfaceResumeRecoveryTimer.pass = 0;
surfaceResumeRecoveryTimer.interval = 800;
surfaceResumeRecoveryTimer.restart();
}
Connections {
target: Quickshell
function onScreensChanged() {
const hasReal = root._hasRealScreen();
log.info("Screens changed:", Quickshell.screens.length,
Quickshell.screens.map(s => "'" + s.name + "'").join(","),
"hasReal:", hasReal, "hadReal:", root.hadRealScreen);
if (!root.hadRealScreen && hasReal) {
log.info("Real screen reappeared after placeholder state, triggering surface recovery");
root.triggerSurfaceRecovery("screen-reconnect");
}
root.hadRealScreen = hasReal;
}
}
Timer {
id: surfaceResumeRecoveryTimer
interval: 800
repeat: false
property int pass: 0
onTriggered: {
pass++;
log.info("Surface recovery pass", pass,
"screens:", Quickshell.screens.length,
Quickshell.screens.map(s => s.name).join(","));
root.recreateBarSurfaces();
if (root.frameSurfacesLoaded) {
root.frameSurfacesLoaded = false;
frameSurfaceReloadAction.schedule();
}
if (root.wallpaperSurfacesLoaded) {
root.wallpaperSurfacesLoaded = false;
wallpaperSurfaceReloadAction.schedule();
}
root.dockEnabled = false;
Qt.callLater(() => {
root.dockEnabled = true;
});
if (pass < 2) {
interval = 2000;
restart();
} else {
pass = 0;
interval = 800;
}
}
}
Component.onCompleted: {
dockRecreateDebounce.start();
// Force PolkitService singleton to initialize
@@ -887,9 +987,17 @@ Item {
target: SessionService
function onSessionResumed() {
log.info("Session resumed: screens:", Quickshell.screens.length,
Quickshell.screens.map(s => s.name).join(","),
"barLoaded:", root.barSurfacesLoaded,
"frameLoaded:", root.frameSurfacesLoaded,
"dockEnabled:", root.dockEnabled);
root.pendingOsdResumeReloads = 2;
osdResumeRecreateTimer.interval = 400;
osdResumeRecreateTimer.restart();
root.triggerSurfaceRecovery("sessionResumed");
}
}
@@ -1114,7 +1222,7 @@ Item {
Variants {
model: SettingsData.getFilteredScreens("osd")
delegate: MicMuteOSD {
delegate: MicVolumeOSD {
modelData: item
}
}
+30
View File
@@ -1794,6 +1794,36 @@ Item {
target: "outputs"
}
IpcHandler {
target: "mic"
function setvolume(percentage: string): string {
return AudioService.setMicVolume(parseInt(percentage));
}
function increment(step: string): string {
return AudioService.incrementMicVolume(step);
}
function decrement(step: string): string {
return AudioService.decrementMicVolume(step);
}
function mute(): string {
return AudioService.toggleMicMute();
}
function status(): string {
if (!AudioService.source || !AudioService.source.audio) {
return "No audio source available";
}
const volume = Math.round(AudioService.source.audio.volume * 100);
const muteStatus = AudioService.source.audio.muted ? " (muted)" : "";
return `Microphone: ${volume}%${muteStatus}`;
}
}
IpcHandler {
function findTrayItem(itemId: string): var {
if (!itemId)
@@ -39,7 +39,7 @@ Item {
signal itemExecuted
signal searchCompleted
signal modeChanged(string mode)
signal modeChanged(string mode, bool userInitiated)
signal queryChanged(string query)
signal viewModeChanged(string sectionId, string mode)
signal searchQueryRequested(string query)
@@ -440,7 +440,7 @@ Item {
}
}
function setMode(mode, isAutoSwitch, fileTypeOverride) {
function setMode(mode, isAutoSwitch, fileTypeOverride, notPersist) {
if (searchMode === mode) {
if (mode === "files" && fileTypeOverride !== undefined && fileSearchType !== fileTypeOverride) {
fileSearchType = fileTypeOverride;
@@ -458,7 +458,7 @@ Item {
if (mode === "files") {
fileSearchType = fileTypeOverride !== undefined ? fileTypeOverride : (SessionData.launcherLastFileSearchType || "all");
}
modeChanged(mode);
modeChanged(mode, !isAutoSwitch && notPersist !== true);
performSearch();
var filesInAll = mode === "all" && (SettingsData.dankLauncherV2IncludeFilesInAll || SettingsData.dankLauncherV2IncludeFoldersInAll) && searchQuery.length > 0;
if (mode === "files" || filesInAll) {
@@ -471,7 +471,7 @@ Item {
return;
autoSwitchedToFiles = false;
searchMode = previousSearchMode;
modeChanged(previousSearchMode);
modeChanged(previousSearchMode, false);
performSearch();
}
@@ -1897,7 +1897,7 @@ Item {
if (browseTrigger && browseTrigger.length > 0) {
searchQueryRequested(browseTrigger);
} else {
setMode("plugins");
setMode("plugins", false, undefined, true);
pluginFilter = browsePluginId;
performSearch();
}
@@ -396,7 +396,7 @@ Item {
spotlightContent.searchField.text = query;
}
if (spotlightContent.controller) {
var targetMode = mode || SessionData.launcherLastMode || "all";
var targetMode = mode || SessionData.getLauncherRestoreMode();
spotlightContent.controller.searchMode = targetMode;
spotlightContent.controller.activePluginId = "";
spotlightContent.controller.activePluginName = "";
@@ -539,8 +539,8 @@ Item {
Connections {
target: spotlightContent?.controller ?? null
function onModeChanged(mode) {
if (spotlightContent.controller.autoSwitchedToFiles)
function onModeChanged(mode, userInitiated) {
if (!userInitiated || !SettingsData.rememberLastMode)
return;
SessionData.setLauncherLastMode(mode);
}
@@ -928,8 +928,12 @@ Item {
}
}
Keys.onPressed: event => root.spotlightContent?.activeContextMenu?.handleKey(event)
Keys.onEscapePressed: event => {
root.hide();
root.spotlightContent?.activeContextMenu?.handleKey(event);
if (!event.accepted)
root.hide();
event.accepted = true;
}
}
@@ -145,7 +145,7 @@ Item {
spotlightContent.closeTransientUi?.();
const targetQuery = query || (SettingsData.rememberLastQuery ? (SessionData.launcherLastQuery || "") : "");
const targetMode = mode || SessionData.launcherLastMode || "all";
const targetMode = mode || SessionData.getLauncherRestoreMode();
if (spotlightContent.searchField) {
spotlightContent.searchField.text = targetQuery;
@@ -489,8 +489,12 @@ Item {
}
}
Keys.onPressed: event => root.spotlightContent?.activeContextMenu?.handleKey(event)
Keys.onEscapePressed: event => {
root.hide();
root.spotlightContent?.activeContextMenu?.handleKey(event);
if (!event.accepted)
root.hide();
event.accepted = true;
}
}
@@ -148,7 +148,7 @@ Item {
spotlightContent.searchField.text = targetQuery;
}
if (spotlightContent.controller) {
var targetMode = mode || SessionData.launcherLastMode || "all";
var targetMode = mode || SessionData.getLauncherRestoreMode();
spotlightContent.controller.searchMode = targetMode;
spotlightContent.controller.activePluginId = "";
spotlightContent.controller.activePluginName = "";
@@ -260,8 +260,8 @@ Item {
Connections {
target: spotlightContent?.controller ?? null
function onModeChanged(mode) {
if (spotlightContent.controller.autoSwitchedToFiles)
function onModeChanged(mode, userInitiated) {
if (!userInitiated || !SettingsData.rememberLastMode || (mode !== "all" && mode !== "apps"))
return;
SessionData.setLauncherLastMode(mode);
}
@@ -536,8 +536,12 @@ Item {
}
}
Keys.onPressed: event => root.spotlightContent?.activeContextMenu?.handleKey(event)
Keys.onEscapePressed: event => {
root.hide();
root.spotlightContent?.activeContextMenu?.handleKey(event);
if (!event.accepted)
root.hide();
event.accepted = true;
}
}
@@ -17,6 +17,7 @@ FocusScope {
property alias controller: controller
property alias resultsList: resultsList
property alias actionPanel: actionPanel
readonly property alias activeContextMenu: contextMenu
property bool editMode: false
property var editingApp: null
@@ -340,6 +340,31 @@ Item {
return count;
}
function handleKey(event) {
if (!openState)
return;
switch (event.key) {
case Qt.Key_Down:
selectNext();
event.accepted = true;
return;
case Qt.Key_Up:
selectPrevious();
event.accepted = true;
return;
case Qt.Key_Return:
case Qt.Key_Enter:
activateSelected();
event.accepted = true;
return;
case Qt.Key_Left:
case Qt.Key_Escape:
hide();
event.accepted = true;
return;
}
}
function selectNext() {
if (visibleItemCount > 0) {
keyboardNavigation = true;
@@ -12,6 +12,7 @@ FocusScope {
property var parentModal: null
property alias searchField: searchInput
property alias controller: searchController
readonly property alias activeContextMenu: contextMenu
readonly property bool _hasQuery: searchInput.text.length > 0
readonly property real _searchBarH: 56
@@ -239,8 +240,8 @@ FocusScope {
Connections {
target: searchController
function onModeChanged(mode) {
if (searchController.autoSwitchedToFiles)
function onModeChanged(mode, userInitiated) {
if (!userInitiated || !SettingsData.rememberLastMode)
return;
SessionData.setLauncherLastMode(mode);
}
@@ -301,12 +301,22 @@ Column {
property var widgetDef: root.model?.getWidgetForId(widgetData.id || "")
width: parent.width
height: 60
iconBlinking: {
const id = widgetData.id || "";
if (id === "wifi")
return NetworkService.isWifiConnecting;
if (id === "bluetooth")
return BluetoothService.connecting;
return false;
}
iconName: {
switch (widgetData.id || "") {
case "wifi":
{
if (NetworkService.wifiToggling)
return "sync";
if (NetworkService.isConnecting && !NetworkService.ethernetConnected)
return NetworkService.wifiSignalIcon;
const status = NetworkService.networkStatus;
if (status === "ethernet")
@@ -360,6 +370,8 @@ Column {
{
if (NetworkService.wifiToggling)
return NetworkService.wifiEnabled ? I18n.tr("Disabling WiFi...", "network status") : I18n.tr("Enabling WiFi...", "network status");
if (NetworkService.isConnecting && !NetworkService.ethernetConnected)
return NetworkService.connectingSSID || I18n.tr("Connecting...", "network status");
const status = NetworkService.networkStatus;
if (status === "ethernet")
@@ -400,6 +412,8 @@ Column {
{
if (NetworkService.wifiToggling)
return I18n.tr("Please wait...", "network status");
if (NetworkService.isConnecting && !NetworkService.ethernetConnected)
return I18n.tr("Connecting...", "network status");
const status = NetworkService.networkStatus;
if (status === "ethernet")
@@ -422,6 +436,8 @@ Column {
return I18n.tr("No adapters", "bluetooth status");
if (!BluetoothService.adapter || !BluetoothService.adapter.enabled)
return I18n.tr("Off", "bluetooth status");
if (BluetoothService.connecting)
return I18n.tr("Connecting...", "bluetooth status");
const primaryDevice = (() => {
if (!BluetoothService.adapter || !BluetoothService.adapter.devices)
return null;
@@ -10,6 +10,7 @@ Rectangle {
property string iconName: ""
property color iconColor: Theme.surfaceText
property bool iconBlinking: false
property string primaryText: ""
property string secondaryText: ""
property bool expanded: false
@@ -109,10 +110,16 @@ Rectangle {
}
DankIcon {
id: pillIcon
anchors.centerIn: parent
name: iconName
size: Theme.iconSize
color: isActive ? _tileIconActive : _tileIconInactive
DankBlink {
target: pillIcon
running: root.iconBlinking
}
}
DankRipple {
+3 -3
View File
@@ -726,7 +726,7 @@ PanelWindow {
item: clickThroughEnabled ? null : inputMask
Region {
readonly property var r: barWindow.clickThroughEnabled ? barWindow.sectionRect(barWindow._leftSection, false, barWindow._revealProgress) : {
readonly property var r: barWindow.clickThroughEnabled ? barWindow.sectionRect(barWindow._leftSection, false, barWindow._revealProgress + barWindow.width * 0) : {
"x": 0,
"y": 0,
"w": 0,
@@ -739,7 +739,7 @@ PanelWindow {
}
Region {
readonly property var r: barWindow.clickThroughEnabled ? barWindow.sectionRect(barWindow._centerSection, true, barWindow._revealProgress) : {
readonly property var r: barWindow.clickThroughEnabled ? barWindow.sectionRect(barWindow._centerSection, true, barWindow._revealProgress + barWindow.width * 0) : {
"x": 0,
"y": 0,
"w": 0,
@@ -752,7 +752,7 @@ PanelWindow {
}
Region {
readonly property var r: barWindow.clickThroughEnabled ? barWindow.sectionRect(barWindow._rightSection, false, barWindow._revealProgress) : {
readonly property var r: barWindow.clickThroughEnabled ? barWindow.sectionRect(barWindow._rightSection, false, barWindow._revealProgress + barWindow.width * 0) : {
"x": 0,
"y": 0,
"w": 0,
@@ -131,9 +131,19 @@ BasePill {
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 getVolumeIconName() {
if (!AudioService.sink?.audio)
return "volume_up";
@@ -485,6 +495,7 @@ BasePill {
}
DankIcon {
id: vIconOnlyItem
anchors.centerIn: parent
visible: !verticalGroupItem.modelData.composite
name: {
@@ -515,7 +526,7 @@ BasePill {
case "vpn":
return NetworkService.vpnConnected ? Theme.primary : Theme.surfaceText;
case "bluetooth":
return BluetoothService.connected ? Theme.primary : Theme.surfaceText;
return (BluetoothService.connected || BluetoothService.connecting) ? Theme.primary : Theme.surfaceText;
case "battery":
return root.getBatteryIconColor();
case "printer":
@@ -524,6 +535,11 @@ BasePill {
return Theme.widgetIconColor;
}
}
DankBlink {
target: vIconOnlyItem
running: root.getIconBlinking(verticalGroupItem.modelData.id)
}
}
DankIcon {
@@ -687,7 +703,7 @@ BasePill {
case "vpn":
return NetworkService.vpnConnected ? Theme.primary : Theme.surfaceText;
case "bluetooth":
return BluetoothService.connected ? Theme.primary : Theme.surfaceText;
return (BluetoothService.connected || BluetoothService.connecting) ? Theme.primary : Theme.surfaceText;
case "battery":
return root.getBatteryIconColor();
case "printer":
@@ -696,6 +712,11 @@ BasePill {
return Theme.widgetIconColor;
}
}
DankBlink {
target: iconOnlyItem
running: root.getIconBlinking(horizontalGroupItem.modelData.id)
}
}
Rectangle {
+41 -6
View File
@@ -73,6 +73,10 @@ Item {
return pam && (pam.u2fState === "waiting" || pam.u2fState === "insert") && !pam.u2fPending;
}
function canStartSecurityKeyUnlock() {
return !demoMode && pam && pam.u2f && pam.u2f.available && SettingsData.enableU2f && SettingsData.u2fMode === "or" && !pam.passwd.active && !pam.u2f.active && !pam.u2fPending && !root.unlocking;
}
Component.onCompleted: {
WeatherService.addRef();
UserInfoService.getUserInfo();
@@ -761,6 +765,9 @@ Item {
if (enterButton.visible) {
margin += enterButton.width + 2;
}
if (securityKeyButton.visible) {
margin += securityKeyButton.width;
}
if (virtualKeyboardButton.visible) {
margin += virtualKeyboardButton.width;
}
@@ -854,7 +861,7 @@ Item {
anchors.left: lockIconContainer.right
anchors.leftMargin: Theme.spacingM
anchors.right: (revealButton.visible ? revealButton.left : (virtualKeyboardButton.visible ? virtualKeyboardButton.left : (enterButton.visible ? enterButton.left : (loadingSpinner.visible ? loadingSpinner.left : parent.right))))
anchors.right: (revealButton.visible ? revealButton.left : (virtualKeyboardButton.visible ? virtualKeyboardButton.left : (securityKeyButton.visible ? securityKeyButton.left : (enterButton.visible ? enterButton.left : (loadingSpinner.visible ? loadingSpinner.left : parent.right)))))
anchors.rightMargin: 2
anchors.verticalCenter: parent.verticalCenter
text: {
@@ -896,7 +903,7 @@ Item {
StyledText {
anchors.left: lockIconContainer.right
anchors.leftMargin: Theme.spacingM
anchors.right: (revealButton.visible ? revealButton.left : (virtualKeyboardButton.visible ? virtualKeyboardButton.left : (enterButton.visible ? enterButton.left : (loadingSpinner.visible ? loadingSpinner.left : parent.right))))
anchors.right: (revealButton.visible ? revealButton.left : (virtualKeyboardButton.visible ? virtualKeyboardButton.left : (securityKeyButton.visible ? securityKeyButton.left : (enterButton.visible ? enterButton.left : (loadingSpinner.visible ? loadingSpinner.left : parent.right)))))
anchors.rightMargin: 2
anchors.verticalCenter: parent.verticalCenter
text: {
@@ -926,7 +933,7 @@ Item {
DankActionButton {
id: revealButton
anchors.right: virtualKeyboardButton.visible ? virtualKeyboardButton.left : (enterButton.visible ? enterButton.left : (loadingSpinner.visible ? loadingSpinner.left : parent.right))
anchors.right: virtualKeyboardButton.visible ? virtualKeyboardButton.left : (securityKeyButton.visible ? securityKeyButton.left : (enterButton.visible ? enterButton.left : (loadingSpinner.visible ? loadingSpinner.left : parent.right)))
anchors.rightMargin: 0
anchors.verticalCenter: parent.verticalCenter
iconName: parent.showPassword ? "visibility_off" : "visibility"
@@ -936,10 +943,26 @@ Item {
onClicked: parent.showPassword = !parent.showPassword
}
DankActionButton {
id: virtualKeyboardButton
id: securityKeyButton
anchors.right: enterButton.visible ? enterButton.left : (loadingSpinner.visible ? loadingSpinner.left : parent.right)
anchors.rightMargin: enterButton.visible ? 0 : Theme.spacingS
anchors.rightMargin: 0
anchors.verticalCenter: parent.verticalCenter
iconName: "passkey"
buttonSize: 32
visible: root.canStartSecurityKeyUnlock()
enabled: visible
onClicked: {
passwordField.text = "";
root.passwordBuffer = "";
pam.u2f.startForAlternativeAuth();
}
}
DankActionButton {
id: virtualKeyboardButton
anchors.right: securityKeyButton.visible ? securityKeyButton.left : (enterButton.visible ? enterButton.left : (loadingSpinner.visible ? loadingSpinner.left : parent.right))
anchors.rightMargin: securityKeyButton.visible || enterButton.visible ? 0 : Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
iconName: "keyboard"
buttonSize: 32
@@ -1438,6 +1461,7 @@ Item {
}
DankIcon {
id: lockNetworkIcon
name: {
if (NetworkService.wifiToggling)
return "sync";
@@ -1451,9 +1475,14 @@ Item {
}
}
size: Theme.iconSize - 2
color: NetworkService.networkStatus !== "disconnected" ? "white" : Qt.rgba(255, 255, 255, 0.5)
color: (NetworkService.networkStatus !== "disconnected" || NetworkService.isConnecting) ? "white" : Qt.rgba(255, 255, 255, 0.5)
anchors.verticalCenter: parent.verticalCenter
visible: NetworkService.networkAvailable
DankBlink {
target: lockNetworkIcon
running: NetworkService.isWifiConnecting
}
}
DankIcon {
@@ -1465,11 +1494,17 @@ Item {
}
DankIcon {
id: lockBluetoothIcon
name: "bluetooth"
size: Theme.iconSize - 2
color: "white"
anchors.verticalCenter: parent.verticalCenter
visible: BluetoothService.available && BluetoothService.enabled
DankBlink {
target: lockBluetoothIcon
running: BluetoothService.connecting
}
}
DankIcon {
+31 -5
View File
@@ -20,6 +20,7 @@ Scope {
property string fprintState
property string u2fState
property bool u2fPending: false
property string u2fPendingMode
property string buffer
signal flashMsg
@@ -35,6 +36,7 @@ Scope {
passwdActiveTimeout.running = false;
unlockRequestTimeout.running = false;
root.u2fPending = false;
root.u2fPendingMode = "";
root.u2fState = "";
root.unlockInProgress = false;
}
@@ -58,6 +60,7 @@ Scope {
u2fErrorRetry.running = false;
u2fPendingTimeout.running = false;
root.u2fPending = false;
root.u2fPendingMode = "";
root.u2fState = "";
unlockRequestTimeout.restart();
unlockRequested();
@@ -79,6 +82,7 @@ Scope {
u2fErrorRetry.running = false;
u2fPendingTimeout.running = false;
root.u2fPending = false;
root.u2fPendingMode = "";
root.u2fState = "";
fprint.checkAvail();
}
@@ -142,6 +146,7 @@ Scope {
unlockRequestTimeout.running = false;
root.unlockInProgress = false;
root.u2fPending = false;
root.u2fPendingMode = "";
root.u2fState = "";
u2fPendingTimeout.running = false;
u2f.abort();
@@ -243,9 +248,8 @@ Scope {
return;
}
if (SettingsData.u2fMode === "or") {
start();
}
if (SettingsData.u2fMode === "or")
abort();
}
function startForSecondFactor(): void {
@@ -255,6 +259,18 @@ Scope {
}
abort();
root.u2fPending = true;
root.u2fPendingMode = "and";
root.u2fState = "";
u2fPendingTimeout.restart();
start();
}
function startForAlternativeAuth(): void {
if (!available || !SettingsData.enableU2f || SettingsData.u2fMode !== "or" || root.unlockInProgress || passwd.active || active)
return;
abort();
root.u2fPending = true;
root.u2fPendingMode = "or";
root.u2fState = "";
u2fPendingTimeout.restart();
start();
@@ -281,9 +297,19 @@ Scope {
abort();
if (root.u2fPending) {
if (root.u2fPendingMode === "or") {
root.u2fPending = false;
root.u2fPendingMode = "";
root.u2fState = root.u2fState === "waiting" ? "" : "insert";
u2fPendingTimeout.running = false;
fprint.checkAvail();
return;
}
if (root.u2fState === "waiting") {
// AND mode: device was found but auth failed → back to password
root.u2fPending = false;
root.u2fPendingMode = "";
root.u2fState = "";
fprint.checkAvail();
} else {
@@ -292,9 +318,7 @@ Scope {
u2fErrorRetry.restart();
}
} else {
// OR mode: prompt to insert key, silently retry
root.u2fState = "insert";
u2fErrorRetry.restart();
}
}
}
@@ -367,6 +391,7 @@ Scope {
root.fprintState = "";
root.u2fState = "";
root.u2fPending = false;
root.u2fPendingMode = "";
root.lockMessage = "";
root.resetAuthFlows();
fprint.checkAvail();
@@ -399,6 +424,7 @@ Scope {
u2fPendingTimeout.running = false;
unlockRequestTimeout.running = false;
root.u2fPending = false;
root.u2fPendingMode = "";
root.u2fState = "";
u2f.checkAvail();
}
-29
View File
@@ -1,29 +0,0 @@
import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
DankOSD {
id: root
osdWidth: Theme.iconSize + Theme.spacingS * 2
osdHeight: Theme.iconSize + Theme.spacingS * 2
autoHideInterval: 2000
enableMouseInteraction: false
Connections {
target: AudioService
function onMicMuteChanged() {
if (SettingsData.osdMicMuteEnabled) {
root.show()
}
}
}
content: DankIcon {
anchors.centerIn: parent
name: AudioService.source && AudioService.source.audio && AudioService.source.audio.muted ? "mic_off" : "mic"
size: Theme.iconSize
color: AudioService.source && AudioService.source.audio && AudioService.source.audio.muted ? Theme.error : Theme.primary
}
}
+253
View File
@@ -0,0 +1,253 @@
import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
DankOSD {
id: root
readonly property bool useVertical: isVerticalLayout
property int _displayVolume: 0
function _syncVolume() {
if (!AudioService.source?.audio)
return;
_displayVolume = Math.round(AudioService.source.audio.volume * 100);
}
osdWidth: useVertical ? (40 + Theme.spacingS * 2) : Math.min(260, Screen.width - Theme.spacingM * 2)
osdHeight: useVertical ? Math.min(260, Screen.height - Theme.spacingM * 2) : (40 + Theme.spacingS * 2)
autoHideInterval: 3000
enableMouseInteraction: true
Connections {
target: AudioService.source?.audio ?? null
function onVolumeChanged() {
root._syncVolume();
if (SettingsData.osdMicVolumeEnabled)
root.show();
}
function onMutedChanged() {
if (SettingsData.osdMicMuteEnabled)
root.show();
}
}
Connections {
target: AudioService
function onSourceChanged() {
root._syncVolume();
if (root.shouldBeVisible && SettingsData.osdMicVolumeEnabled)
root.show();
}
}
content: Loader {
anchors.fill: parent
sourceComponent: useVertical ? verticalContent : horizontalContent
}
Component {
id: horizontalContent
Item {
property int gap: Theme.spacingS
anchors.centerIn: parent
width: parent.width - Theme.spacingS * 2
height: 40
Rectangle {
width: Theme.iconSize
height: Theme.iconSize
radius: Theme.iconSize / 2
color: "transparent"
x: parent.gap
anchors.verticalCenter: parent.verticalCenter
DankIcon {
anchors.centerIn: parent
name: AudioService.source?.audio?.muted ? "mic_off" : "mic"
size: Theme.iconSize
color: muteButton.containsMouse ? Theme.primary : (AudioService.source?.audio?.muted ? Theme.error : Theme.surfaceText)
}
MouseArea {
id: muteButton
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: AudioService.toggleMicMute()
onContainsMouseChanged: setChildHovered(containsMouse || volumeSlider.containsMouse)
}
}
DankSlider {
id: volumeSlider
width: parent.width - Theme.iconSize - parent.gap * 3
height: 40
x: parent.gap * 2 + Theme.iconSize
anchors.verticalCenter: parent.verticalCenter
minimum: 0
maximum: 100
enabled: AudioService.source?.audio ?? false
showValue: true
unit: "%"
thumbOutlineColor: Theme.surfaceContainer
valueOverride: root._displayVolume
alwaysShowValue: SettingsData.osdAlwaysShowValue
Component.onCompleted: {
root._syncVolume();
value = root._displayVolume;
}
onSliderValueChanged: newValue => {
if (!AudioService.source?.audio)
return;
SessionData.suppressOSDTemporarily();
AudioService.source.audio.volume = newValue / 100;
resetHideTimer();
}
onContainsMouseChanged: setChildHovered(containsMouse || muteButton.containsMouse)
Binding on value {
value: root._displayVolume
when: !volumeSlider.pressed
}
}
}
}
Component {
id: verticalContent
Item {
anchors.fill: parent
property int gap: Theme.spacingS
Rectangle {
width: Theme.iconSize
height: Theme.iconSize
radius: Theme.iconSize / 2
color: "transparent"
anchors.horizontalCenter: parent.horizontalCenter
y: gap
DankIcon {
anchors.centerIn: parent
name: AudioService.source?.audio?.muted ? "mic_off" : "mic"
size: Theme.iconSize
color: muteButtonVert.containsMouse ? Theme.primary : (AudioService.source?.audio?.muted ? Theme.error : Theme.surfaceText)
}
MouseArea {
id: muteButtonVert
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: AudioService.toggleMicMute()
onContainsMouseChanged: setChildHovered(containsMouse || vertSliderArea.containsMouse)
}
}
Item {
id: vertSlider
width: 12
height: parent.height - Theme.iconSize - gap * 3 - 24
anchors.horizontalCenter: parent.horizontalCenter
y: gap * 2 + Theme.iconSize
property bool dragging: false
property int value: root._displayVolume
Rectangle {
id: vertTrack
width: parent.width
height: parent.height
anchors.centerIn: parent
color: Theme.outline
radius: Theme.cornerRadius
}
Rectangle {
id: vertFill
width: parent.width
height: (vertSlider.value / 100) * parent.height
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
color: AudioService.source?.audio?.muted ? Theme.error : Theme.primary
radius: Theme.cornerRadius
}
Rectangle {
id: vertHandle
width: 24
height: 8
radius: Theme.cornerRadius
y: {
const ratio = vertSlider.value / 100;
const travel = parent.height - height;
return Math.max(0, Math.min(travel, travel * (1 - ratio)));
}
anchors.horizontalCenter: parent.horizontalCenter
color: AudioService.source?.audio?.muted ? Theme.error : Theme.primary
border.width: 3
border.color: Theme.surfaceContainer
}
MouseArea {
id: vertSliderArea
anchors.fill: parent
anchors.margins: -12
enabled: AudioService.source?.audio ?? false
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onContainsMouseChanged: setChildHovered(containsMouse || muteButtonVert.containsMouse)
onPressed: mouse => {
vertSlider.dragging = true;
updateVolume(mouse);
}
onReleased: vertSlider.dragging = false
onPositionChanged: mouse => {
if (pressed)
updateVolume(mouse);
}
onClicked: mouse => updateVolume(mouse)
function updateVolume(mouse) {
if (!AudioService.source?.audio)
return;
const ratio = 1.0 - (mouse.y / height);
const volume = Math.max(0, Math.min(100, Math.round(ratio * 100)));
SessionData.suppressOSDTemporarily();
AudioService.source.audio.volume = volume / 100;
resetHideTimer();
}
}
}
StyledText {
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottomMargin: gap
text: vertSlider.value + "%"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
visible: SettingsData.osdAlwaysShowValue
}
}
}
}
@@ -1121,6 +1121,15 @@ Item {
onToggled: checked => SessionData.setSearchAppActions(checked)
}
SettingsToggleRow {
settingKey: "rememberLastMode"
tags: ["launcher", "remember", "last", "mode", "tab"]
text: I18n.tr("Remember Last Mode")
description: I18n.tr("Restore the last selected mode (tab) when the launcher is opened")
checked: SettingsData.rememberLastMode
onToggled: checked => SettingsData.set("rememberLastMode", checked)
}
SettingsToggleRow {
settingKey: "rememberLastQuery"
tags: ["launcher", "remember", "last", "search", "query"]
@@ -338,45 +338,61 @@ Scope {
border.width: 1
}
LauncherContent {
id: launcherContent
FocusScope {
anchors.fill: parent
anchors.margins: 0
focus: true
property var fakeParentModal: QtObject {
property bool spotlightOpen: spotlightContainer.visible
property bool isClosing: niriOverviewScope.isClosing
function hide() {
if (niriOverviewScope.searchActive) {
niriOverviewScope.hideSpotlight();
return;
Keys.onPressed: event => launcherContent.activeContextMenu?.handleKey(event)
Keys.onEscapePressed: event => {
launcherContent.activeContextMenu?.handleKey(event);
if (!event.accepted)
launcherContent.parentModal?.hide();
event.accepted = true;
}
LauncherContent {
id: launcherContent
anchors.fill: parent
anchors.margins: 0
property var fakeParentModal: QtObject {
property bool spotlightOpen: spotlightContainer.visible
property bool isClosing: niriOverviewScope.isClosing
property real alignedX: spotlightContainer.x
property real alignedY: spotlightContainer.y
function hide() {
if (niriOverviewScope.searchActive) {
niriOverviewScope.hideSpotlight();
return;
}
NiriService.toggleOverview();
}
NiriService.toggleOverview();
}
}
Connections {
target: launcherContent.searchField
function onTextChanged() {
if (launcherContent.searchField.text.length > 0 || !niriOverviewScope.searchActive)
return;
niriOverviewScope.hideSpotlight();
Connections {
target: launcherContent.searchField
function onTextChanged() {
if (launcherContent.searchField.text.length > 0 || !niriOverviewScope.searchActive)
return;
niriOverviewScope.hideSpotlight();
}
}
}
Component.onCompleted: {
parentModal = fakeParentModal;
}
Connections {
target: launcherContent.controller
function onItemExecuted() {
niriOverviewScope.releaseKeyboard = true;
Component.onCompleted: {
parentModal = fakeParentModal;
}
function onModeChanged(mode) {
if (launcherContent.controller.autoSwitchedToFiles)
return;
SessionData.setNiriOverviewLastMode(mode);
Connections {
target: launcherContent.controller
function onItemExecuted() {
niriOverviewScope.releaseKeyboard = true;
}
function onModeChanged(mode) {
if (launcherContent.controller.autoSwitchedToFiles)
return;
SessionData.setNiriOverviewLastMode(mode);
}
}
}
}
+39 -4
View File
@@ -397,6 +397,14 @@ EOFCONFIG
}
}
Connections {
target: root.source?.audio ?? null
function onMutedChanged() {
root.micMuteChanged();
}
}
function checkGsettings() {
Proc.runCommand("checkGsettings", ["sh", "-c", "gsettings get org.gnome.desktop.sound theme-name 2>/dev/null"], (output, exitCode) => {
gsettingsAvailable = (exitCode === 0);
@@ -844,6 +852,36 @@ EOFCONFIG
return root.source.audio.muted ? "Microphone muted" : "Microphone unmuted";
}
function incrementMicVolume(step) {
if (!root.source?.audio)
return "No audio source available";
if (root.source.audio.muted)
root.source.audio.muted = false;
const currentVolume = Math.round(root.source.audio.volume * 100);
const stepValue = parseInt(step || "5");
const newVolume = Math.max(0, Math.min(100, currentVolume + stepValue));
root.source.audio.volume = newVolume / 100;
return `Microphone volume increased to ${newVolume}%`;
}
function decrementMicVolume(step) {
if (!root.source?.audio)
return "No audio source available";
if (root.source.audio.muted)
root.source.audio.muted = false;
const currentVolume = Math.round(root.source.audio.volume * 100);
const stepValue = parseInt(step || "5");
const newVolume = Math.max(0, Math.min(100, currentVolume - stepValue));
root.source.audio.volume = newVolume / 100;
return `Microphone volume decreased to ${newVolume}%`;
}
IpcHandler {
target: "audio"
@@ -892,9 +930,7 @@ EOFCONFIG
}
function micmute(): string {
const result = root.toggleMicMute();
root.micMuteChanged();
return result;
return root.toggleMicMute();
}
function status(): string {
@@ -957,7 +993,6 @@ EOFCONFIG
return `Switched to: ${result}`;
}
}
Connections {
target: SettingsData
function onUseSystemSoundThemeChanged() {
+14
View File
@@ -28,6 +28,20 @@ Singleton {
});
return isConnected;
}
readonly property bool connecting: {
if (!adapter || !adapter.devices) {
return false;
}
let busy = false;
adapter.devices.values.forEach(dev => {
if (!dev)
return;
if (dev.pairing || dev.state === BluetoothDeviceState.Connecting)
busy = true;
});
return busy;
}
readonly property var pairedDevices: {
if (!adapter || !adapter.devices) {
return [];
@@ -41,6 +41,9 @@ Singleton {
property var savedConnections: []
property var ssidToConnectionName: ({})
property var wifiSignalIcon: {
if (isConnecting) {
return "wifi";
}
if (!wifiConnected) {
return "wifi_off";
}
@@ -99,6 +99,9 @@ Singleton {
}
readonly property string wifiSignalIcon: {
if (isConnecting) {
return "wifi";
}
if (!wifiConnected || networkStatus !== "wifi") {
return "wifi_off";
}
+1
View File
@@ -42,6 +42,7 @@ Singleton {
property string userPreference: activeService?.userPreference ?? "auto"
property bool isConnecting: activeService?.isConnecting ?? false
readonly property bool isWifiConnecting: isConnecting && !ethernetConnected && !wifiToggling
property string connectingSSID: activeService?.connectingSSID ?? ""
property string connectionError: activeService?.connectionError ?? ""
+9
View File
@@ -345,4 +345,13 @@ Singleton {
return 0;
}
}
Connections {
target: SessionService
function onSessionResumed() {
log.info("Session resumed, re-requesting output state, current outputs:", outputs.length);
requestState();
}
}
}
+28
View File
@@ -0,0 +1,28 @@
import QtQuick
SequentialAnimation {
id: root
property Item target
property real minOpacity: 0.3
property int pulseDuration: 600
loops: Animation.Infinite
NumberAnimation {
target: root.target
property: "opacity"
to: root.minOpacity
duration: root.pulseDuration
easing.type: Easing.InOutQuad
}
NumberAnimation {
target: root.target
property: "opacity"
to: 1.0
duration: root.pulseDuration
easing.type: Easing.InOutQuad
}
onStopped: if (root.target) root.target.opacity = 1.0
}