1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-06-08 04:09:15 -04:00

popout: fix opening popouts across monitors

cc/brightness: fix delegate bindings and pinning
This commit is contained in:
bbedward
2026-05-19 11:23:03 -04:00
parent 4845299cc2
commit cdc1102092
6 changed files with 249 additions and 134 deletions
@@ -59,21 +59,19 @@ Item {
ignoreUnknownSignals: true
function onDeviceNameChanged(newDeviceName) {
if (root.expandedWidgetData && root.expandedWidgetData.id === "brightnessSlider") {
const widgets = SettingsData.controlCenterWidgets || [];
const newWidgets = widgets.map(w => {
if (w.id === "brightnessSlider" && w.instanceId === root.expandedWidgetData.instanceId) {
const updatedWidget = Object.assign({}, w);
updatedWidget.deviceName = newDeviceName;
return updatedWidget;
}
return w;
});
SettingsData.set("controlCenterWidgets", newWidgets);
if (root.collapseCallback) {
root.collapseCallback();
}
if (!root.expandedWidgetData || root.expandedWidgetData.id !== "brightnessSlider") {
return;
}
const widgets = SettingsData.controlCenterWidgets || [];
const newWidgets = widgets.map(w => {
if (w.id === "brightnessSlider" && w.instanceId === root.expandedWidgetData.instanceId) {
const updatedWidget = Object.assign({}, w);
updatedWidget.deviceName = newDeviceName;
return updatedWidget;
}
return w;
});
SettingsData.set("controlCenterWidgets", newWidgets);
}
}
@@ -23,79 +23,103 @@ Rectangle {
if (!screenName)
return "";
const screen = Quickshell.screens.find(s => s.name === screenName);
if (screen) {
if (screen)
return SettingsData.getScreenDisplayName(screen);
}
if (SettingsData.displayNameMode === "model" && screenModel && screenModel.length > 0) {
if (SettingsData.displayNameMode === "model" && screenModel && screenModel.length > 0)
return screenModel;
}
return screenName;
}
function resolveDeviceName() {
if (!DisplayService.brightnessAvailable || !DisplayService.devices || DisplayService.devices.length === 0) {
function resolveCurrentDevice() {
const devices = DisplayService.devices || [];
if (!DisplayService.brightnessAvailable || devices.length === 0)
return "";
}
const pinKey = getScreenPinKey();
if (pinKey.length > 0) {
const pins = SettingsData.brightnessDevicePins || {};
const pinnedDevice = pins[pinKey];
if (pinnedDevice && pinnedDevice.length > 0) {
const found = DisplayService.devices.find(dev => dev.name === pinnedDevice);
const found = devices.find(d => d.name === pinnedDevice);
if (found)
return found.name;
}
}
if (instanceId) {
const widgets = SettingsData.controlCenterWidgets || [];
const widget = widgets.find(w => w.id === "brightnessSlider" && w.instanceId === instanceId);
if (widget && typeof widget.deviceName === "string" && widget.deviceName.length > 0) {
const found = devices.find(d => d.name === widget.deviceName);
if (found)
return found.name;
}
}
if (DisplayService.currentDevice) {
const found = devices.find(d => d.name === DisplayService.currentDevice);
if (found)
return found.name;
}
if (initialDeviceName && initialDeviceName.length > 0) {
const found = DisplayService.devices.find(dev => dev.name === initialDeviceName);
const found = devices.find(d => d.name === initialDeviceName);
if (found)
return found.name;
}
const currentDeviceNameFromService = DisplayService.currentDevice;
if (currentDeviceNameFromService) {
const found = DisplayService.devices.find(dev => dev.name === currentDeviceNameFromService);
if (found)
return found.name;
}
const backlight = DisplayService.devices.find(d => d.class === "backlight");
const backlight = devices.find(d => d.class === "backlight");
if (backlight)
return backlight.name;
const ddc = DisplayService.devices.find(d => d.class === "ddc");
const ddc = devices.find(d => d.class === "ddc");
if (ddc)
return ddc.name;
return DisplayService.devices.length > 0 ? DisplayService.devices[0].name : "";
return devices[0].name;
}
function selectDevice(deviceName) {
if (!deviceName || deviceName === root.currentDeviceName) {
return;
}
const pinKey = getScreenPinKey();
if (pinKey.length > 0) {
const pins = SettingsData.brightnessDevicePins || {};
const existing = pins[pinKey];
if (existing && existing !== deviceName) {
const next = JSON.parse(JSON.stringify(pins));
delete next[pinKey];
SettingsData.set("brightnessDevicePins", next);
}
}
root.currentDeviceName = deviceName;
DisplayService.setCurrentDevice(deviceName, true);
Qt.callLater(() => root.deviceNameChanged(deviceName));
}
Component.onCompleted: {
currentDeviceName = resolveDeviceName();
root.currentDeviceName = resolveCurrentDevice();
}
property bool isPinnedToScreen: {
function isDevicePinnedToScreen(deviceName) {
const pinKey = getScreenPinKey();
if (!pinKey || pinKey.length === 0)
if (!pinKey || !deviceName)
return false;
const pins = SettingsData.brightnessDevicePins || {};
return pins[pinKey] === currentDeviceName;
return pins[pinKey] === deviceName;
}
function togglePinToScreen() {
function togglePinForDevice(deviceName) {
const pinKey = getScreenPinKey();
if (!pinKey || pinKey.length === 0 || !currentDeviceName || currentDeviceName.length === 0)
if (!pinKey || !deviceName)
return;
const pins = JSON.parse(JSON.stringify(SettingsData.brightnessDevicePins || {}));
if (isPinnedToScreen) {
if (pins[pinKey] === deviceName) {
delete pins[pinKey];
} else {
pins[pinKey] = currentDeviceName;
pins[pinKey] = deviceName;
}
SettingsData.set("brightnessDevicePins", pins);
}
@@ -153,18 +177,23 @@ Rectangle {
}
Rectangle {
id: monitorHeader
width: parent.width
height: 40
visible: screenName && screenName.length > 0 && DisplayService.devices && DisplayService.devices.length > 1
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
property bool currentDevicePinned: root.isDevicePinnedToScreen(currentDeviceName)
Item {
anchors.fill: parent
anchors.margins: Theme.spacingM
Row {
anchors.left: parent.left
anchors.right: globalPinButton.left
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
@@ -180,47 +209,51 @@ Rectangle {
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight
width: parent.width - Theme.iconSize - Theme.spacingM
}
}
Rectangle {
id: globalPinButton
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
width: pinRow.width + Theme.spacingS * 2
width: globalPinRow.width + Theme.spacingS * 2
height: 28
radius: height / 2
color: isPinnedToScreen ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceText, 0.05)
color: monitorHeader.currentDevicePinned ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) : Theme.withAlpha(Theme.surfaceText, 0.05)
Row {
id: pinRow
id: globalPinRow
anchors.centerIn: parent
spacing: 4
DankIcon {
name: isPinnedToScreen ? "push_pin" : "push_pin"
name: "push_pin"
size: 16
color: isPinnedToScreen ? Theme.primary : Theme.surfaceText
color: monitorHeader.currentDevicePinned ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: isPinnedToScreen ? I18n.tr("Pinned") : I18n.tr("Pin")
text: monitorHeader.currentDevicePinned ? I18n.tr("Pinned") : I18n.tr("Pin")
font.pixelSize: Theme.fontSizeSmall
color: isPinnedToScreen ? Theme.primary : Theme.surfaceText
color: monitorHeader.currentDevicePinned ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
DankRipple {
id: pinRipple
id: globalPinRipple
cornerRadius: parent.radius
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onPressed: mouse => pinRipple.trigger(mouse.x, mouse.y)
onClicked: root.togglePinToScreen()
enabled: currentDeviceName && currentDeviceName.length > 0
onPressed: mouse => globalPinRipple.trigger(mouse.x, mouse.y)
onClicked: root.togglePinForDevice(currentDeviceName)
}
}
}
@@ -229,9 +262,17 @@ Rectangle {
Repeater {
model: DisplayService.devices || []
delegate: Rectangle {
id: deviceCard
required property var modelData
required property int index
readonly property bool selected: !!(modelData && modelData.name === root.currentDeviceName)
readonly property bool devicePinnedHere: {
SettingsData.brightnessDevicePins;
return root.isDevicePinnedToScreen(modelData ? modelData.name : "");
}
property real deviceBrightness: {
DisplayService.brightnessVersion;
return DisplayService.getDeviceBrightness(modelData.name);
@@ -241,8 +282,8 @@ Rectangle {
height: 100
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
border.color: modelData.name === currentDeviceName ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: modelData.name === currentDeviceName ? 2 : 0
border.color: selected ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: selected ? 2 : 0
Column {
anchors.fill: parent
@@ -251,10 +292,12 @@ Rectangle {
Item {
width: parent.width
height: Math.max(deviceIconColumn.height, deviceInfoColumn.height, exponentControls.height)
height: Math.max(deviceIconColumn.height, deviceInfoColumn.height, rightControls.height)
Row {
anchors.left: parent.left
anchors.right: rightControls.left
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
@@ -281,7 +324,7 @@ Rectangle {
}
}
size: Theme.iconSize
color: modelData.name === currentDeviceName ? Theme.primary : Theme.surfaceText
color: deviceCard.selected ? Theme.primary : Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
@@ -296,7 +339,7 @@ Rectangle {
Column {
id: deviceInfoColumn
anchors.verticalCenter: parent.verticalCenter
width: parent.parent.width - deviceIconColumn.width - exponentControls.width - Theme.spacingM * 3
width: parent.width - deviceIconColumn.width - Theme.spacingM
StyledText {
text: {
@@ -309,7 +352,7 @@ Rectangle {
}
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: modelData.name === currentDeviceName ? Font.Medium : Font.Normal
font.weight: deviceCard.selected ? Font.Medium : Font.Normal
elide: Text.ElideRight
width: parent.width
horizontalAlignment: Text.AlignLeft
@@ -345,80 +388,107 @@ Rectangle {
}
Row {
id: exponentControls
width: 140
id: rightControls
height: 28
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingXS
visible: SessionData.getBrightnessExponential(modelData.name)
spacing: Theme.spacingS
z: 1
StyledRect {
width: 28
Row {
id: exponentControls
height: 28
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
opacity: SessionData.getBrightnessExponent(modelData.name) > 1.0 ? 1.0 : 0.4
spacing: Theme.spacingXS
visible: SessionData.getBrightnessExponential(modelData.name)
DankIcon {
anchors.centerIn: parent
name: "remove"
size: 14
color: Theme.surfaceText
StyledRect {
width: 28
height: 28
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
opacity: SessionData.getBrightnessExponent(modelData.name) > 1.0 ? 1.0 : 0.4
DankIcon {
anchors.centerIn: parent
name: "remove"
size: 14
color: Theme.surfaceText
}
StateLayer {
stateColor: Theme.primary
cornerRadius: parent.radius
enabled: SessionData.getBrightnessExponent(modelData.name) > 1.0
onClicked: {
const current = SessionData.getBrightnessExponent(modelData.name);
const newValue = Math.max(1.0, Math.round((current - 0.1) * 10) / 10);
SessionData.setBrightnessExponent(modelData.name, newValue);
}
}
}
StateLayer {
stateColor: Theme.primary
cornerRadius: parent.radius
enabled: SessionData.getBrightnessExponent(modelData.name) > 1.0
onClicked: {
const current = SessionData.getBrightnessExponent(modelData.name);
const newValue = Math.max(1.0, Math.round((current - 0.1) * 10) / 10);
SessionData.setBrightnessExponent(modelData.name, newValue);
StyledRect {
width: 50
height: 28
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
border.width: 0
StyledText {
anchors.centerIn: parent
text: SessionData.getBrightnessExponent(modelData.name).toFixed(1)
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.primary
}
}
StyledRect {
width: 28
height: 28
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
opacity: SessionData.getBrightnessExponent(modelData.name) < 2.5 ? 1.0 : 0.4
DankIcon {
anchors.centerIn: parent
name: "add"
size: 14
color: Theme.surfaceText
}
StateLayer {
stateColor: Theme.primary
cornerRadius: parent.radius
enabled: SessionData.getBrightnessExponent(modelData.name) < 2.5
onClicked: {
const current = SessionData.getBrightnessExponent(modelData.name);
const newValue = Math.min(2.5, Math.round((current + 0.1) * 10) / 10);
SessionData.setBrightnessExponent(modelData.name, newValue);
}
}
}
}
StyledRect {
width: 50
height: 28
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
border.width: 0
StyledText {
anchors.centerIn: parent
text: SessionData.getBrightnessExponent(modelData.name).toFixed(1)
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.primary
}
}
StyledRect {
id: pinButton
width: 28
height: 28
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
opacity: SessionData.getBrightnessExponent(modelData.name) < 2.5 ? 1.0 : 0.4
visible: root.screenName && root.screenName.length > 0 && DisplayService.devices && DisplayService.devices.length > 1
color: devicePinnedHere ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) : Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency)
DankIcon {
anchors.centerIn: parent
name: "add"
name: "push_pin"
size: 14
color: Theme.surfaceText
color: devicePinnedHere ? Theme.primary : Theme.surfaceText
}
StateLayer {
stateColor: Theme.primary
cornerRadius: parent.radius
enabled: SessionData.getBrightnessExponent(modelData.name) < 2.5
onClicked: {
const current = SessionData.getBrightnessExponent(modelData.name);
const newValue = Math.min(2.5, Math.round((current + 0.1) * 10) / 10);
SessionData.setBrightnessExponent(modelData.name, newValue);
}
onClicked: root.togglePinForDevice(modelData.name)
}
}
}
@@ -474,22 +544,11 @@ Rectangle {
MouseArea {
anchors.fill: parent
anchors.bottomMargin: 28
anchors.rightMargin: SessionData.getBrightnessExponential(modelData.name) ? 145 : 0
anchors.rightMargin: rightControls.width + Theme.spacingS
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: mouse => deviceRipple.trigger(mouse.x, mouse.y)
onClicked: {
const pinKey = root.getScreenPinKey();
if (pinKey.length > 0 && modelData.name !== currentDeviceName) {
const pins = JSON.parse(JSON.stringify(SettingsData.brightnessDevicePins || {}));
if (pins[pinKey]) {
delete pins[pinKey];
SettingsData.set("brightnessDevicePins", pins);
}
}
currentDeviceName = modelData.name;
deviceNameChanged(modelData.name);
}
onClicked: root.selectDevice(modelData.name)
}
}
}
@@ -1,4 +1,5 @@
import QtQuick
import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
@@ -31,8 +32,10 @@ Row {
}
if (screenName && screenName.length > 0) {
const screen = Quickshell.screens.find(s => s.name === screenName);
const pinKey = screen ? SettingsData.getScreenDisplayName(screen) : screenName;
const pins = SettingsData.brightnessDevicePins || {};
const pinnedDevice = pins[screenName];
const pinnedDevice = pins[pinKey];
if (pinnedDevice && pinnedDevice.length > 0) {
const found = DisplayService.devices.find(dev => dev.name === pinnedDevice);
if (found) {
+26 -6
View File
@@ -357,14 +357,19 @@ Item {
animationsEnabled = false;
_primeContent = true;
if (_lastOpenedScreen !== null && _lastOpenedScreen !== screen) {
const screenChanged = _lastOpenedScreen !== null && _lastOpenedScreen !== screen;
if (screenChanged) {
// Hide on this tick so Qt actually tears down the wl_surface; the show
// gets deferred below so the unmap is processed before the remap.
contentWindow.visible = false;
}
_lastOpenedScreen = screen;
if (contentContainer) {
// animationsEnabled is false here, so this snaps to closed without animating.
morph.openProgress = 0;
// Snap morph closed only on a fresh open; on screen-change re-open we stay at 1
// because shouldBeVisible doesn't change and won't drive morph back to 1.
if (!shouldBeVisible)
morph.openProgress = 0;
_captureChromeAnimTravel();
}
@@ -375,12 +380,25 @@ Item {
_chromeClaimId = "";
}
contentWindow.visible = true;
if (screenChanged) {
// Defer the show one event-loop tick. Qt coalesces a synchronous
// false→true visibility flip into a no-op, leaving WindowBlur committed
// to the previous screen's wl_surface. Splitting the flip across ticks
// forces a real surface destroy+create so BackgroundEffect.surfaceCreated
// fires and the blur region republishes on the new surface.
Qt.callLater(() => {
if (!root.shouldBeVisible)
return;
contentWindow.visible = true;
popoutBlur.kick();
});
} else {
contentWindow.visible = true;
}
animationsEnabled = true;
shouldBeVisible = true;
if (shouldBeVisible && screen) {
contentWindow.visible = true;
PopoutManager.showPopout(popoutHandle);
opened();
}
@@ -1081,7 +1099,9 @@ Item {
Connections {
target: contentWindow
function onVisibleChanged() {
if (!contentWindow.visible)
// open() flips contentWindow.visible to rebind the layer surface to
// a new screen; don't deactivate the wrapper while still open.
if (!contentWindow.visible && !root.shouldBeVisible)
contentWrapper._renderActive = false;
}
}
+31 -7
View File
@@ -287,21 +287,43 @@ Item {
_frozenMaskWidth = maskWidth;
_frozenMaskHeight = maskHeight;
if (_lastOpenedScreen !== null && _lastOpenedScreen !== screen) {
const screenChanged = _lastOpenedScreen !== null && _lastOpenedScreen !== screen;
if (screenChanged) {
// Hide on this tick so Qt actually tears down the wl_surface; the show
// gets deferred below so the unmap is processed before the remap.
contentWindow.visible = false;
backgroundWindow.visible = false;
}
_lastOpenedScreen = screen;
if (contentContainer) {
// animationsEnabled is false here, so this snaps to closed without animating.
if (contentContainer && !shouldBeVisible) {
// Snap morph closed only on a fresh open; on screen-change re-open we stay at 1
// because shouldBeVisible doesn't change and won't drive morph back to 1.
morph.openProgress = 0;
}
_setSurfaceGeometry(alignedX, alignedY, alignedWidth, alignedHeight);
if (backgroundWindowRequired)
backgroundWindow.visible = true;
contentWindow.visible = true;
if (screenChanged) {
// Defer the show one event-loop tick. Qt coalesces a synchronous
// false→true visibility flip into a no-op, leaving WindowBlur committed
// to the previous screen's wl_surface. Splitting the flip across ticks
// forces a real surface destroy+create so BackgroundEffect.surfaceCreated
// fires and the blur region republishes on the new surface.
Qt.callLater(() => {
if (!root.shouldBeVisible)
return;
if (root.backgroundWindowRequired)
backgroundWindow.visible = true;
contentWindow.visible = true;
popoutBlur.kick();
_bgCommitWindow = true;
bgCommitSettleTimer.restart();
});
} else {
if (backgroundWindowRequired)
backgroundWindow.visible = true;
contentWindow.visible = true;
}
animationsEnabled = true;
shouldBeVisible = true;
@@ -840,7 +862,9 @@ Item {
Connections {
target: contentWindow
function onVisibleChanged() {
if (!contentWindow.visible)
// open() flips contentWindow.visible to rebind the layer surface to
// a new screen; don't deactivate the wrapper while still open.
if (!contentWindow.visible && !root.shouldBeVisible)
contentWrapper._renderActive = false;
}
}
+11
View File
@@ -34,6 +34,17 @@ Item {
targetWindow.BackgroundEffect.blurRegion = _active ? blurRegion : null;
}
// Force BackgroundEffect to re-publish the blur region on the current wl_surface.
// Clearing first bypasses Quickshell's same-Region dedup in BackgroundEffect::setBlurRegion,
// setting pendingBlurRegion=true so the next polish actually ships the region — needed
// when the underlying surface has been remapped (e.g. PanelWindow.screen change).
function kick() {
if (!targetWindow)
return;
targetWindow.BackgroundEffect.blurRegion = null;
targetWindow.BackgroundEffect.blurRegion = _active ? blurRegion : null;
}
on_ActiveChanged: _apply()
onTargetWindowChanged: _apply()