diff --git a/assets/icon-missing.png b/assets/icon-missing.png deleted file mode 100644 index 434f071..0000000 Binary files a/assets/icon-missing.png and /dev/null differ diff --git a/assets/icon-missing.svg b/assets/icon-missing.svg deleted file mode 100644 index 71f1e2e..0000000 --- a/assets/icon-missing.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/config/Config.qml b/config/Config.qml index 16481c8..d8b9e22 100644 --- a/config/Config.qml +++ b/config/Config.qml @@ -161,8 +161,6 @@ Singleton { } readonly property QtObject services: QtObject { - readonly property real batteryWarning: 0.15 - readonly property string weatherLocation: "" readonly property bool useFahrenheit: [Locale.ImperialUSSystem, Locale.ImperialSystem].includes(Qt.locale().measurementSystem) @@ -171,8 +169,6 @@ Singleton { readonly property string sunsetFrom: "21:00" readonly property string sunsetTo: "9:00" readonly property int sunsetTemperature: 4500 - - readonly property real brightnessExp: 4.0 } readonly property QtObject session: QtObject { diff --git a/modules/bar/modules/StatusIcons.qml b/modules/bar/modules/StatusIcons.qml index f5bff04..59a9c17 100644 --- a/modules/bar/modules/StatusIcons.qml +++ b/modules/bar/modules/StatusIcons.qml @@ -95,7 +95,7 @@ Container { readonly property bool hasBattery: UPower.displayDevice.isLaptopBattery readonly property real percentage: UPower.displayDevice.percentage readonly property bool charging: !UPower.onBattery && batteryText.text !== "100" - readonly property bool warning: UPower.onBattery && percentage < Config.services.batteryWarning + 0.01 + readonly property bool warning: UPower.onBattery && percentage < 0.15 text: { if (!hasBattery) { @@ -142,7 +142,7 @@ Container { anchors.verticalCenter: parent.verticalCenter anchors.verticalCenterOffset: 0.5 - text: Math.floor(battery.percentage * 100) + text: Math.round(battery.percentage * 100) color: battery.warning ? Config.colors.batteryWarning : Config.colors.bg font.family: Config.font.family.mono font.pointSize: 6 diff --git a/modules/bar/popouts/ActiveWindow.qml b/modules/bar/popouts/ActiveWindow.qml index 673e52f..bb1e625 100644 --- a/modules/bar/popouts/ActiveWindow.qml +++ b/modules/bar/popouts/ActiveWindow.qml @@ -73,13 +73,6 @@ Item { property: "scale" to: 1 } - // Fixes really weird transition bug - NumberAnimation { - targets: header - property: "height" - to: Config.font.size.normal - duration: 20 - } } // Reveal on window title change @@ -118,7 +111,6 @@ Item { anchors.right: infobox.right anchors.top: infobox.top anchors.margins: 12 - anchors.topMargin: 11 } } diff --git a/modules/bar/popouts/Battery.qml b/modules/bar/popouts/Battery.qml index ff1fafa..5553f32 100644 --- a/modules/bar/popouts/Battery.qml +++ b/modules/bar/popouts/Battery.qml @@ -13,15 +13,14 @@ ColumnLayout { spacing: 4 - readonly property bool hasBattery: UPower.displayDevice.isLaptopBattery - readonly property real percentage: UPower.displayDevice.percentage - readonly property bool warning: UPower.onBattery && percentage < Config.services.batteryWarning + 0.01 - readonly property color color: warning ? Config.colors.batteryWarning : Config.colors.battery + readonly property color color: UPower.onBattery && UPower.displayDevice.percentage < 0.15 ? + Config.colors.batteryWarning : + Config.colors.battery Loader { Layout.alignment: Qt.AlignHCenter - active: root.hasBattery + active: UPower.displayDevice.isLaptopBattery asynchronous: true height: active ? (item?.implicitHeight ?? 0) : 0 @@ -77,7 +76,7 @@ ColumnLayout { radiusX: (meter.size + meter.thickness) / 2 + meter.padding radiusY: radiusX startAngle: -90 - meter.angle / 2 - sweepAngle: meter.angle * root.percentage + sweepAngle: meter.angle * UPower.displayDevice.percentage } Behavior on strokeColor { @@ -99,7 +98,7 @@ ColumnLayout { CustomText { anchors.horizontalCenter: parent.horizontalCenter - text: Math.floor(root.percentage * 100) + "%" + text: Math.round(UPower.displayDevice.percentage * 100) + "%" font.pointSize: Config.font.size.largest } diff --git a/modules/bar/popouts/Content.qml b/modules/bar/popouts/Content.qml index 18e50ae..1ab7152 100644 --- a/modules/bar/popouts/Content.qml +++ b/modules/bar/popouts/Content.qml @@ -67,7 +67,7 @@ Item { name: "battery" source: "Battery.qml" color: UPower.displayDevice.isLaptopBattery && - UPower.onBattery && UPower.displayDevice.percentage < Config.services.batteryWarning + 0.01 ? + UPower.onBattery && UPower.displayDevice.percentage < 0.15 ? Config.colors.batteryWarning : Config.colors.battery } diff --git a/modules/bar/popouts/Wrapper.qml b/modules/bar/popouts/Wrapper.qml index df5184f..305f17f 100644 --- a/modules/bar/popouts/Wrapper.qml +++ b/modules/bar/popouts/Wrapper.qml @@ -13,6 +13,7 @@ Item { id: root required property PersistentProperties uiState + required property ShellScreen screen readonly property real nonAnimWidth: content.implicitWidth readonly property real nonAnimHeight: y > 0 || hasCurrent ? content.implicitHeight : 0 diff --git a/modules/dashboard/Mixer.qml b/modules/dashboard/Mixer.qml index a83ae58..7ca119e 100644 --- a/modules/dashboard/Mixer.qml +++ b/modules/dashboard/Mixer.qml @@ -110,8 +110,8 @@ Item { source: { const icon = entry.modelData.properties["application.icon-name"]; if (icon) - return Icons.getAppIcon(icon, "icon-missing"); - Icons.getAppIcon(entry.modelData.name, "icon-missing") + return Icons.getAppIcon(icon, "image-missing"); + Icons.getAppIcon(entry.modelData.name, "image-missing") } } diff --git a/modules/dashboard/Workspaces.qml b/modules/dashboard/Workspaces.qml index 6f5db42..4feeef9 100644 --- a/modules/dashboard/Workspaces.qml +++ b/modules/dashboard/Workspaces.qml @@ -290,7 +290,6 @@ Item { property var ipc: modelData.lastIpcObject opacity: ipc && ipc.at && !ipc.hidden ? 1 : 0 - visible: opacity > 0 property real nonAnimX: ipc?.at ? (ipc.at[0] - preview.monX) * preview.sizeRatio : 0 property real nonAnimY: ipc?.at ? (ipc.at[1] - preview.monY) * preview.sizeRatio : 0 diff --git a/modules/dashboard/dash/Weather.qml b/modules/dashboard/dash/Weather.qml index ee61b91..af520fc 100644 --- a/modules/dashboard/dash/Weather.qml +++ b/modules/dashboard/dash/Weather.qml @@ -83,6 +83,7 @@ Item { anchors.left: parent.right anchors.bottom: parent.bottom anchors.leftMargin: 12 + anchors.bottomMargin: 1 animate: true text: Weather.feelsLike diff --git a/modules/launcher/items/AppItem.qml b/modules/launcher/items/AppItem.qml index 3d8b4e6..dea01c2 100644 --- a/modules/launcher/items/AppItem.qml +++ b/modules/launcher/items/AppItem.qml @@ -37,7 +37,7 @@ Item { IconImage { id: icon - source: Quickshell.iconPath(root.modelData?.icon, "icon-missing") + source: Quickshell.iconPath(root.modelData?.icon, "image-missing") implicitSize: parent.height * 0.9 anchors.verticalCenter: parent.verticalCenter diff --git a/modules/osd/Content.qml b/modules/osd/Content.qml index 1449135..3495b96 100644 --- a/modules/osd/Content.qml +++ b/modules/osd/Content.qml @@ -11,6 +11,7 @@ Item { id: root required property var uiState + required property Brightness.Monitor monitor anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left @@ -94,10 +95,13 @@ Item { implicitHeight: Config.osd.sliderLength function onWheel(event: WheelEvent) { + const monitor = root.monitor; + if (!monitor) + return; if (event.angleDelta.y > 0) - Brightness.setBrightness(Brightness.brightness + 0.1); + monitor.setBrightness(monitor.brightness + 0.1); else if (event.angleDelta.y < 0) - Brightness.setBrightness(Brightness.brightness - 0.1); + monitor.setBrightness(monitor.brightness - 0.1); } CustomFilledSlider { @@ -105,8 +109,8 @@ Item { color: Config.colors.brightness icon: Icons.getBrightnessIcon(value) - value: Brightness.brightness - onMoved: Brightness.setBrightness(value) + value: root.monitor?.brightness ?? 0 + onMoved: root.monitor?.setBrightness(value) } } } diff --git a/modules/osd/Interactions.qml b/modules/osd/Interactions.qml index 4c2f05f..3036a35 100644 --- a/modules/osd/Interactions.qml +++ b/modules/osd/Interactions.qml @@ -7,8 +7,10 @@ Scope { id: root required property PersistentProperties uiState + required property ShellScreen screen required property bool hovered required property bool suppressed + readonly property Brightness.Monitor monitor: Brightness.getMonitorForScreen(screen) function show(): void { if (!root.suppressed) { @@ -32,7 +34,7 @@ Scope { } Connections { - target: Brightness + target: root.monitor function onBrightnessChanged(): void { if (root.uiState.osdBrightnessReact) diff --git a/modules/osd/Wrapper.qml b/modules/osd/Wrapper.qml index a764f19..109909c 100644 --- a/modules/osd/Wrapper.qml +++ b/modules/osd/Wrapper.qml @@ -8,6 +8,7 @@ Item { id: root required property var uiState + required property ShellScreen screen visible: width > 0 implicitWidth: 0 @@ -60,5 +61,6 @@ Item { id: content uiState: root.uiState + monitor: Brightness.getMonitorForScreen(root.screen) } } diff --git a/modules/ui/Interactions.qml b/modules/ui/Interactions.qml index 3e08345..f3d601e 100644 --- a/modules/ui/Interactions.qml +++ b/modules/ui/Interactions.qml @@ -62,10 +62,6 @@ CustomMouseArea { if (y < Config.bar.height && !popoutsSuppressed && !popouts.persistent) { bar.checkPopout(x); } - // Hide bar popouts (if user moves mouse along edge) - if (y > popouts.nonAnimHeight + Config.bar.height) { - popouts.hasCurrent = false; - } // Show osd on hover const showOsd = inRightPanel(panels.osd, x, y); @@ -120,6 +116,7 @@ CustomMouseArea { Osd.Interactions { uiState: root.uiState + screen: root.screen hovered: root.osdHovered suppressed: root.osdSuppressed } diff --git a/modules/ui/Panels.qml b/modules/ui/Panels.qml index b1b39c1..f808fa2 100644 --- a/modules/ui/Panels.qml +++ b/modules/ui/Panels.qml @@ -13,6 +13,7 @@ Item { id: root required property PersistentProperties uiState + required property ShellScreen screen required property Item bar readonly property alias popouts: popouts @@ -30,12 +31,14 @@ Item { id: popouts uiState: root.uiState + screen: root.screen } Osd.Wrapper { id: osd uiState: root.uiState + screen: root.screen anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right diff --git a/modules/ui/UI.qml b/modules/ui/UI.qml index e988ab0..896cdc4 100644 --- a/modules/ui/UI.qml +++ b/modules/ui/UI.qml @@ -110,6 +110,7 @@ Variants { visible: !uiState.uiState.hidden uiState: uiState.uiState + screen: scope.modelData bar: bar } } diff --git a/package.nix b/package.nix index 79feb46..e1e3c4d 100644 --- a/package.nix +++ b/package.nix @@ -55,9 +55,6 @@ in stdenv.mkDerivation { cp -R . $out/share/${pname} - mkdir -p $out/share/icons/hicolor/512x512/apps/ - cp assets/icon-missing.png $out/share/icons/hicolor/512x512/apps/ - makeWrapper ${quickshell}/bin/qs $out/bin/${pname} \ --prefix PATH : "${lib.makeBinPath runtimeDeps}" \ --set FONTCONFIG_FILE "${fontconfig}" \ diff --git a/services/Brightness.qml b/services/Brightness.qml index b7f8bc9..856b0c7 100644 --- a/services/Brightness.qml +++ b/services/Brightness.qml @@ -12,38 +12,58 @@ Singleton { reloadableId: "brightness" - property real brightness: 1.0 - property real maxBrightness: 0.0 + property list ddcMonitors: [] + readonly property list monitors: variants.instances + property bool appleDisplayPresent: false + + function getMonitorForScreen(screen: ShellScreen): var { + return monitors.find(m => m.modelData === screen); + } function increaseBrightness(): void { - setBrightness(brightness + Config.osd.brightnessIncrement); + const focusedName = Hypr.focusedMonitor.name; + const monitor = monitors.find(m => focusedName === m.modelData.name); + if (monitor) + monitor.setBrightness(monitor.brightness + Config.osd.brightnessIncrement); } function decreaseBrightness(): void { - setBrightness(brightness - Config.osd.brightnessIncrement); + const focusedName = Hypr.focusedMonitor.name; + const monitor = monitors.find(m => focusedName === m.modelData.name); + if (monitor) + monitor.setBrightness(monitor.brightness - Config.osd.brightnessIncrement); } - function setBrightness(value: real): void { - value = Math.max(0, Math.min(1, value)); - if (Math.abs(brightness - value) < 0.01) return; - brightness = value; - - const exp = Config.services.brightnessExp; - const raw = Math.round((value ** exp) * maxBrightness); - Quickshell.execDetached(["brightnessctl", "s", `${raw}`]); + onMonitorsChanged: { + ddcMonitors = []; + ddcProc.running = true; + } + + Variants { + id: variants + + model: Quickshell.screens + + Monitor {} } - Component.onCompleted: initProc.running = true Process { - id: initProc - command: ["sh", "-c", "echo $(brightnessctl g) $(brightnessctl m)"] + running: true + command: ["sh", "-c", "asdbctl get"] // To avoid warnings if asdbctl is not installed stdout: StdioCollector { - onStreamFinished: { - const exp = Config.services.brightnessExp; - const [cur, max] = text.split(" "); - root.maxBrightness = parseInt(max); - root.brightness = (parseInt(cur) / root.maxBrightness) ** (1 / exp); - } + onStreamFinished: root.appleDisplayPresent = text.trim().length > 0 + } + } + + Process { + id: ddcProc + + command: ["ddcutil", "detect", "--brief"] + stdout: StdioCollector { + onStreamFinished: root.ddcMonitors = text.trim().split("\n\n").filter(d => d.startsWith("Display ")).map(d => ({ + model: d.match(/Monitor:.*:(.*):.*/)[1], + busNum: d.match(/I2C bus:[ ]*\/dev\/i2c-([0-9]+)/)[1] + })) } } @@ -90,4 +110,77 @@ Singleton { root.decreaseBrightness(); } } + + component Monitor: QtObject { + id: monitor + + required property ShellScreen modelData + readonly property bool isDdc: root.ddcMonitors.some(m => m.model === modelData.model) + readonly property string busNum: root.ddcMonitors.find(m => m.model === modelData.model)?.busNum ?? "" + readonly property bool isAppleDisplay: root.appleDisplayPresent && modelData.model.startsWith("StudioDisplay") + property real brightness + property real queuedBrightness: NaN + + readonly property Process initProc: Process { + stdout: StdioCollector { + onStreamFinished: { + if (monitor.isAppleDisplay) { + const val = parseInt(text.trim()); + monitor.brightness = val / 101; + } else { + const [, , , cur, max] = text.split(" "); + monitor.brightness = parseInt(cur) / parseInt(max); + } + } + } + } + + readonly property Timer timer: Timer { + interval: 500 + onTriggered: { + if (!isNaN(monitor.queuedBrightness)) { + monitor.setBrightness(monitor.queuedBrightness); + monitor.queuedBrightness = NaN; + } + } + } + + function setBrightness(value: real): void { + value = Math.max(0, Math.min(1, value)); + const rounded = Math.round(value * 100); + if (Math.round(brightness * 100) === rounded) + return; + + if (isDdc && timer.running) { + queuedBrightness = value; + return; + } + + brightness = value; + + if (isAppleDisplay) + Quickshell.execDetached(["asdbctl", "set", rounded]); + else if (isDdc) + Quickshell.execDetached(["ddcutil", "-b", busNum, "setvcp", "10", rounded]); + else + Quickshell.execDetached(["brightnessctl", "s", `${rounded}%`]); + + if (isDdc) + timer.restart(); + } + + function initBrightness(): void { + if (isAppleDisplay) + initProc.command = ["asdbctl", "get"]; + else if (isDdc) + initProc.command = ["ddcutil", "-b", busNum, "getvcp", "10", "--brief"]; + else + initProc.command = ["sh", "-c", "echo a b c $(brightnessctl g) $(brightnessctl m)"]; + + initProc.running = true; + } + + onBusNumChanged: initBrightness() + Component.onCompleted: initBrightness() + } }