mirror of
https://github.com/arthur-pbty/arthur-os.git
synced 2026-08-01 20:28:16 +02:00
Add new display widget for the bar
This commit is contained in:
@@ -21,21 +21,13 @@ runtime_dir="${XDG_RUNTIME_DIR:-/tmp}"
|
||||
exec 9>"$runtime_dir/omarchy-brightness-display.lock"
|
||||
flock -n 9 || exit 0
|
||||
|
||||
# Start with the first possible output, then refine to the most likely given an order heuristic.
|
||||
device="$(ls -1 /sys/class/backlight 2>/dev/null | head -n1)"
|
||||
for candidate in amdgpu_bl* intel_backlight acpi_video*; do
|
||||
if [[ -e /sys/class/backlight/$candidate ]]; then
|
||||
device="$candidate"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
if omarchy-hyprland-monitor-focused-apple; then
|
||||
omarchy-brightness-display-apple "$step"
|
||||
exit
|
||||
fi
|
||||
|
||||
device="$(omarchy-hw-display)" || exit 1
|
||||
|
||||
# Current brightness percentage
|
||||
current=$(brightnessctl -d "$device" -m | cut -d',' -f4 | tr -d '%')
|
||||
|
||||
|
||||
@@ -29,6 +29,5 @@ else
|
||||
fi
|
||||
|
||||
sudo asdcontrol "$device" -- "$step" >/dev/null
|
||||
value="$(sudo asdcontrol "$device" | awk -F= '/BRIGHTNESS=/{print $2+0}')"
|
||||
omarchy-osd -i brightness -p "$(( value * 100 / 60000 ))"
|
||||
omarchy-osd -i brightness -p "$(omarchy-brightness-display-apple-get)"
|
||||
fi
|
||||
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Get Apple Studio Display or XDR Display brightness using asdcontrol.
|
||||
# omarchy:examples=omarchy brightness display apple get | omarchy-brightness-display-apple-get
|
||||
|
||||
device="$(sudo asdcontrol --detect /dev/usb/hiddev* /dev/hiddev* 2>/dev/null | awk -F: '/^\/dev\/(usb\/)?hiddev/{ print $1; exit }')"
|
||||
|
||||
if [[ -z $device ]]; then
|
||||
echo "No Apple Display HID device found" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sudo asdcontrol "$device" 2>/dev/null | awk -F= '
|
||||
/BRIGHTNESS=/ {
|
||||
print int($2 * 100 / 60000)
|
||||
found = 1
|
||||
}
|
||||
|
||||
END { exit !found }
|
||||
'
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Get brightness for the most likely display device.
|
||||
# omarchy:examples=omarchy-brightness-display-get
|
||||
|
||||
if omarchy-hyprland-monitor-focused-apple; then
|
||||
omarchy-brightness-display-apple-get
|
||||
else
|
||||
brightnessctl -d "$(omarchy-hw-display)" -m 2>/dev/null | awk -F, '{ gsub("%", "", $4); print $4; found=1 } END{ exit !found }'
|
||||
fi
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Print the most likely display backlight device.
|
||||
# omarchy:examples=omarchy-hw-display
|
||||
|
||||
# Start with the first possible output, then refine to the most likely given an order heuristic.
|
||||
device="$(ls -1 /sys/class/backlight 2>/dev/null | head -n1)"
|
||||
for candidate in amdgpu_bl* intel_backlight acpi_video*; do
|
||||
if [[ -e /sys/class/backlight/$candidate ]]; then
|
||||
device="$candidate"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -n $device ]]; then
|
||||
printf '%s\n' "$device"
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Get focused Hyprland monitor scaling
|
||||
# omarchy:examples=omarchy-hyprland-monitor-scaling-get
|
||||
|
||||
hyprctl monitors -j | jq -r '.[] | select(.focused == true) | .scale'
|
||||
@@ -313,6 +313,7 @@ Item {
|
||||
readonly property var firstPartyWidgetMetadata: ({
|
||||
"media": { displayName: "Media", description: "MPRIS now-playing with playback controls", category: "Media", allowMultiple: false },
|
||||
"audioPanel": { displayName: "Audio", description: "Volume slider, output picker, per-app mixer", category: "Audio", allowMultiple: false },
|
||||
"monitorPanel": { displayName: "Display", description: "Brightness slider and laptop display controls", category: "System", allowMultiple: false },
|
||||
"networkPanel": { displayName: "Network", description: "Wi-Fi list and connection state", category: "Network", allowMultiple: false },
|
||||
"bluetoothPanel": { displayName: "Bluetooth", description: "Bluetooth device list with connect/disconnect", category: "Network", allowMultiple: false },
|
||||
"calendar": { displayName: "Calendar", description: "Clock with month-grid popup", category: "Time", allowMultiple: false, settingsForm: "calendarSettings" },
|
||||
|
||||
@@ -0,0 +1,408 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import "../common" as Common
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property QtObject bar: null
|
||||
property string moduleName: "monitorPanel"
|
||||
property var settings: ({})
|
||||
|
||||
property bool popupOpen: false
|
||||
property int brightnessPercent: 0
|
||||
property int pendingBrightnessPercent: 0
|
||||
property bool brightnessSetQueued: false
|
||||
property bool brightnessAvailable: false
|
||||
property string internalMonitor: ""
|
||||
property string externalMonitor: ""
|
||||
property string focusedMonitor: ""
|
||||
property bool internalEnabled: false
|
||||
property bool mirrorEnabled: false
|
||||
property string monitorScale: ""
|
||||
property var displays: []
|
||||
property int enabledDisplayCount: 0
|
||||
|
||||
function closePopout() { popupOpen = false }
|
||||
|
||||
IpcHandler {
|
||||
target: "monitorPanel"
|
||||
|
||||
function brightness(percent: string): string {
|
||||
var value = Number(percent)
|
||||
root.setBrightness(value)
|
||||
return "got " + root.pendingBrightnessPercent
|
||||
}
|
||||
|
||||
function state(): string {
|
||||
return JSON.stringify({
|
||||
brightness: root.brightnessPercent,
|
||||
brightnessAvailable: root.brightnessAvailable,
|
||||
focusedMonitor: root.focusedMonitor,
|
||||
scale: root.monitorScale,
|
||||
displays: root.displays
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
if (!stateProc.running) stateProc.running = true
|
||||
}
|
||||
|
||||
function setBrightness(value) {
|
||||
var percent = Math.max(1, Math.min(100, Math.round(value)))
|
||||
root.brightnessPercent = percent
|
||||
root.pendingBrightnessPercent = percent
|
||||
|
||||
if (setBrightnessProc.running) {
|
||||
root.brightnessSetQueued = true
|
||||
return
|
||||
}
|
||||
|
||||
root.brightnessSetQueued = false
|
||||
setBrightnessProc.command = ["bash", "-lc", "omarchy-brightness-display " + percent + "%"]
|
||||
setBrightnessProc.running = true
|
||||
}
|
||||
|
||||
function previewBrightness(value) {
|
||||
root.brightnessPercent = Math.max(1, Math.min(100, Math.round(value)))
|
||||
brightnessDebounce.restart()
|
||||
}
|
||||
|
||||
function toggleMirror() {
|
||||
if (!internalMonitor || !externalMonitor) return
|
||||
actionProc.command = ["bash", "-lc", "if hyprctl monitors -j | jq -e --arg i '" + internalMonitor + "' --arg e '" + externalMonitor + "' '.[] | select(.name == $i and .mirrorOf == $e)' >/dev/null; then hyprctl keyword monitor '" + internalMonitor + ",preferred,auto,auto'; else hyprctl keyword monitor '" + internalMonitor + ",preferred,auto,auto,mirror," + externalMonitor + "'; fi"]
|
||||
if (!actionProc.running) actionProc.running = true
|
||||
}
|
||||
|
||||
function toggleInternal() {
|
||||
if (!internalMonitor || !externalMonitor) return
|
||||
actionProc.command = ["bash", "-lc", "if hyprctl monitors -j | jq -e --arg i '" + internalMonitor + "' '.[] | select(.name == $i)' >/dev/null; then hyprctl keyword monitor '" + internalMonitor + ",disable'; else hyprctl keyword monitor '" + internalMonitor + ",preferred,auto,auto'; fi"]
|
||||
if (!actionProc.running) actionProc.running = true
|
||||
}
|
||||
|
||||
function normalizeScale(scale) {
|
||||
var n = parseFloat(String(scale || ""))
|
||||
if (!isFinite(n)) return ""
|
||||
return String(Math.round(n * 100) / 100)
|
||||
}
|
||||
|
||||
function updateDisplays(displaysJson) {
|
||||
try {
|
||||
root.displays = displaysJson ? JSON.parse(displaysJson) : []
|
||||
} catch(e) {
|
||||
root.displays = []
|
||||
}
|
||||
|
||||
var count = 0
|
||||
for (var i = 0; i < root.displays.length; i++)
|
||||
if (root.displays[i] && root.displays[i].enabled) count++
|
||||
root.enabledDisplayCount = count
|
||||
}
|
||||
|
||||
function toggleDisplay(name, enabled) {
|
||||
if (!name) return
|
||||
if (enabled && root.enabledDisplayCount <= 1) return
|
||||
|
||||
actionProc.command = ["hyprctl", "keyword", "monitor", name + (enabled ? ",disable" : ",preferred,auto,auto")]
|
||||
if (!actionProc.running) actionProc.running = true
|
||||
}
|
||||
|
||||
function setScale(scale) {
|
||||
actionProc.command = ["bash", "-lc", "omarchy-hyprland-monitor-scaling-set " + scale]
|
||||
if (!actionProc.running) actionProc.running = true
|
||||
}
|
||||
|
||||
implicitWidth: button.implicitWidth
|
||||
implicitHeight: button.implicitHeight
|
||||
|
||||
Component.onCompleted: refresh()
|
||||
|
||||
Timer {
|
||||
interval: 5000
|
||||
running: true
|
||||
repeat: true
|
||||
onTriggered: root.refresh()
|
||||
}
|
||||
|
||||
Process {
|
||||
id: stateProc
|
||||
command: ["bash", "-lc", "omarchy-brightness-display-get 2>/dev/null || true; monitors_json=$(hyprctl monitors all -j); printf '%s\\n' \"$monitors_json\" | jq -r 'def internal: test(\"^(eDP|LVDS|DSI)-\"); ([.[] | select(.name | internal)][0].name // \"\"), ([.[] | select((.name | internal) | not)][0].name // \"\"), ([.[] | select((.name | internal) and .disabled != true)][0].name // \"\"), ([.[] | select((.name | internal) and .mirrorOf != \"none\")][0].mirrorOf // \"\")'; omarchy-hyprland-monitor-focused 2>/dev/null || echo; omarchy-hyprland-monitor-scaling-get 2>/dev/null || echo; printf '%s\\n' \"$monitors_json\" | jq -c '[.[] | {name, enabled:(.disabled != true), focused:(.focused == true)}]'"]
|
||||
stdout: StdioCollector {
|
||||
waitForEnd: true
|
||||
onStreamFinished: {
|
||||
var lines = String(text || "").split("\n")
|
||||
var brightness = String(lines[0] || "").trim()
|
||||
root.brightnessAvailable = brightness !== "unavailable" && brightness !== ""
|
||||
root.brightnessPercent = root.brightnessAvailable ? Math.max(0, Math.min(100, parseInt(brightness, 10))) : 0
|
||||
root.internalMonitor = String(lines[1] || "").trim()
|
||||
root.externalMonitor = String(lines[2] || "").trim()
|
||||
root.internalEnabled = String(lines[3] || "").trim() !== ""
|
||||
root.mirrorEnabled = String(lines[4] || "").trim() === root.externalMonitor && root.externalMonitor !== ""
|
||||
root.focusedMonitor = String(lines[5] || "").trim()
|
||||
root.monitorScale = root.normalizeScale(String(lines[6] || "").trim())
|
||||
root.updateDisplays(String(lines[7] || "[]").trim())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: brightnessDebounce
|
||||
interval: 180
|
||||
repeat: false
|
||||
onTriggered: root.setBrightness(root.brightnessPercent)
|
||||
}
|
||||
|
||||
Process {
|
||||
id: setBrightnessProc
|
||||
stdout: StdioCollector { waitForEnd: true }
|
||||
onRunningChanged: {
|
||||
if (running) return
|
||||
if (root.brightnessSetQueued) {
|
||||
root.setBrightness(root.pendingBrightnessPercent)
|
||||
} else {
|
||||
root.refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: actionProc
|
||||
stdout: StdioCollector { waitForEnd: true }
|
||||
onRunningChanged: if (!running) root.refresh()
|
||||
}
|
||||
|
||||
Common.WidgetButton {
|
||||
id: button
|
||||
anchors.fill: parent
|
||||
bar: root.bar
|
||||
text: ""
|
||||
fontSize: 13
|
||||
onPressed: function(b) { root.popupOpen = !root.popupOpen }
|
||||
onWheelMoved: function(delta) {
|
||||
if (root.brightnessAvailable) root.setBrightness(root.brightnessPercent + (delta > 0 ? 5 : -5))
|
||||
}
|
||||
}
|
||||
|
||||
Common.PopupCard {
|
||||
anchorItem: button
|
||||
owner: root
|
||||
bar: root.bar
|
||||
open: root.popupOpen
|
||||
contentWidth: 320
|
||||
contentHeight: panelColumn.implicitHeight + 28
|
||||
|
||||
Column {
|
||||
id: panelColumn
|
||||
anchors.fill: parent
|
||||
spacing: 14
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: 8
|
||||
|
||||
Text {
|
||||
text: "Display"
|
||||
color: Qt.darker(root.bar.foreground, 1.5)
|
||||
font.family: root.bar.fontFamily
|
||||
font.pixelSize: 11
|
||||
font.bold: true
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.focusedMonitor ? "· " + root.focusedMonitor : ""
|
||||
color: Qt.darker(root.bar.foreground, 1.8)
|
||||
font.family: root.bar.fontFamily
|
||||
font.pixelSize: 11
|
||||
elide: Text.ElideRight
|
||||
width: parent.width - 70
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: 8
|
||||
visible: root.brightnessAvailable
|
||||
|
||||
Text {
|
||||
text: ""
|
||||
color: root.bar.foreground
|
||||
font.family: root.bar.fontFamily
|
||||
font.pixelSize: 16
|
||||
width: 22
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Common.Slider {
|
||||
id: brightnessSlider
|
||||
bar: root.bar
|
||||
width: parent.width - 22 - brightnessPercent.width - 16
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
minimum: 1
|
||||
maximum: 100
|
||||
step: 1
|
||||
value: root.brightnessPercent
|
||||
integer: true
|
||||
onMoved: function(v) { root.previewBrightness(v) }
|
||||
onReleased: function(v) {
|
||||
brightnessDebounce.stop()
|
||||
root.setBrightness(v)
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: brightnessPercent
|
||||
text: Math.round(brightnessSlider.dragging ? brightnessSlider.liveValue : root.brightnessPercent) + "%"
|
||||
color: root.bar.foreground
|
||||
font.family: root.bar.fontFamily
|
||||
font.pixelSize: 11
|
||||
width: 36
|
||||
horizontalAlignment: Text.AlignRight
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
visible: !root.brightnessAvailable
|
||||
text: "No controllable backlight found"
|
||||
color: Qt.darker(root.bar.foreground, 1.5)
|
||||
font.family: root.bar.fontFamily
|
||||
font.pixelSize: 11
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: 8
|
||||
|
||||
Text {
|
||||
text: "Scale" + (root.monitorScale ? " · " + root.monitorScale + "x" : "")
|
||||
color: Qt.darker(root.bar.foreground, 1.5)
|
||||
font.family: root.bar.fontFamily
|
||||
font.pixelSize: 11
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: 6
|
||||
|
||||
ScaleButton { scaleValue: "1" }
|
||||
ScaleButton { scaleValue: "1.25" }
|
||||
ScaleButton { scaleValue: "1.6" }
|
||||
ScaleButton { scaleValue: "2" }
|
||||
ScaleButton { scaleValue: "3" }
|
||||
ScaleButton { scaleValue: "4" }
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: 8
|
||||
visible: root.displays.length > 0
|
||||
|
||||
Text {
|
||||
text: "Monitors"
|
||||
color: Qt.darker(root.bar.foreground, 1.5)
|
||||
font.family: root.bar.fontFamily
|
||||
font.pixelSize: 11
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.displays
|
||||
|
||||
ToggleRow {
|
||||
required property var modelData
|
||||
|
||||
label: modelData.name + (modelData.focused ? " · focused" : "")
|
||||
checked: modelData.enabled
|
||||
enabled: !modelData.enabled || root.enabledDisplayCount > 1
|
||||
opacity: enabled ? 1.0 : 0.45
|
||||
onClicked: root.toggleDisplay(modelData.name, modelData.enabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component ScaleButton: Rectangle {
|
||||
property string scaleValue: ""
|
||||
readonly property bool active: root.normalizeScale(root.monitorScale) === root.normalizeScale(scaleValue)
|
||||
|
||||
width: (panelColumn.width - 30) / 6
|
||||
height: 28
|
||||
radius: 0
|
||||
color: scaleArea.pressed ? Qt.rgba(root.bar.foreground.r, root.bar.foreground.g, root.bar.foreground.b, 0.24)
|
||||
: scaleArea.containsMouse ? Qt.rgba(root.bar.foreground.r, root.bar.foreground.g, root.bar.foreground.b, 0.14)
|
||||
: active ? Qt.rgba(root.bar.foreground.r, root.bar.foreground.g, root.bar.foreground.b, 0.20)
|
||||
: "transparent"
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: scaleValue + "x"
|
||||
color: root.bar.foreground
|
||||
font.family: root.bar.fontFamily
|
||||
font.pixelSize: 11
|
||||
font.bold: active
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: scaleArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.setScale(scaleValue)
|
||||
}
|
||||
}
|
||||
|
||||
component ToggleRow: Rectangle {
|
||||
property string label: ""
|
||||
property bool checked: false
|
||||
signal clicked()
|
||||
|
||||
width: panelColumn.width
|
||||
height: 30
|
||||
color: area.pressed ? Qt.rgba(root.bar.foreground.r, root.bar.foreground.g, root.bar.foreground.b, 0.22)
|
||||
: area.containsMouse ? Qt.rgba(root.bar.foreground.r, root.bar.foreground.g, root.bar.foreground.b, 0.12)
|
||||
: "transparent"
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 8
|
||||
|
||||
Text {
|
||||
text: checked ? "" : ""
|
||||
color: root.bar.foreground
|
||||
font.family: root.bar.fontFamily
|
||||
font.pixelSize: 14
|
||||
width: 18
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: label
|
||||
color: root.bar.foreground
|
||||
font.family: root.bar.fontFamily
|
||||
font.pixelSize: 12
|
||||
elide: Text.ElideRight
|
||||
width: parent.width - 26
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: area
|
||||
anchors.fill: parent
|
||||
enabled: parent.enabled
|
||||
hoverEnabled: true
|
||||
cursorShape: parent.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: parent.clicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -156,7 +156,7 @@ Item {
|
||||
right: [
|
||||
{ id: "tray" }, { id: "systemStats" }, { id: "microphone" },
|
||||
{ id: "bluetoothPanel" }, { id: "networkPanel" }, { id: "audioPanel" },
|
||||
{ id: "battery" }
|
||||
{ id: "monitorPanel" }, { id: "battery" }
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -52,6 +52,9 @@
|
||||
{
|
||||
"id": "audioPanel"
|
||||
},
|
||||
{
|
||||
"id": "monitorPanel"
|
||||
},
|
||||
{
|
||||
"id": "battery"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user