mute source audio

This commit is contained in:
Zacharias-Brohn
2026-02-09 22:23:53 +01:00
parent 5c64cc0bb9
commit 144d763c67
11 changed files with 414 additions and 110 deletions
+2 -8
View File
@@ -150,7 +150,7 @@ Variants {
Panels { Panels {
id: panels id: panels
screen: bar.modelData screen: scope.modelData
bar: backgroundRect bar: backgroundRect
visibilities: visibilities visibilities: visibilities
} }
@@ -175,13 +175,7 @@ Variants {
popouts: panels.popouts popouts: panels.popouts
bar: bar bar: bar
visibilities: visibilities visibilities: visibilities
} screen: scope.modelData
WindowTitle {
anchors.centerIn: parent
width: Math.min( 300, parent.width * 0.4 )
height: parent.height
z: 1
} }
} }
} }
+5
View File
@@ -0,0 +1,5 @@
import Quickshell.Hyprland
GlobalShortcut {
appid: "zshell"
}
+1
View File
@@ -3,4 +3,5 @@ import QtQuick
JsonObject { JsonObject {
property string weatherLocation: "" property string weatherLocation: ""
property real brightnessIncrement: 0.1
} }
+226
View File
@@ -0,0 +1,226 @@
pragma Singleton
pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Io
import QtQuick
import qs.Config
import qs.Components
Singleton {
id: root
property list<var> ddcMonitors: []
readonly property list<Monitor> monitors: variants.instances
property bool appleDisplayPresent: false
function getMonitorForScreen(screen: ShellScreen): var {
return monitors.find(m => m.modelData === screen);
}
function getMonitor(query: string): var {
if (query === "active") {
return monitors.find(m => Hypr.monitorFor(m.modelData)?.focused);
}
if (query.startsWith("model:")) {
const model = query.slice(6);
return monitors.find(m => m.modelData.model === model);
}
if (query.startsWith("serial:")) {
const serial = query.slice(7);
return monitors.find(m => m.modelData.serialNumber === serial);
}
if (query.startsWith("id:")) {
const id = parseInt(query.slice(3), 10);
return monitors.find(m => Hypr.monitorFor(m.modelData)?.id === id);
}
return monitors.find(m => m.modelData.name === query);
}
function increaseBrightness(): void {
const monitor = getMonitor("active");
if (monitor)
monitor.setBrightness(monitor.brightness + Config.services.brightnessIncrement);
}
function decreaseBrightness(): void {
const monitor = getMonitor("active");
if (monitor)
monitor.setBrightness(monitor.brightness - Config.services.brightnessIncrement);
}
onMonitorsChanged: {
ddcMonitors = [];
ddcProc.running = true;
}
Variants {
id: variants
model: Quickshell.screens
Monitor {}
}
Process {
running: true
command: ["sh", "-c", "asdbctl get"] // To avoid warnings if asdbctl is not installed
stdout: StdioCollector {
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 => ({
busNum: d.match(/I2C bus:[ ]*\/dev\/i2c-([0-9]+)/)[1],
connector: d.match(/DRM connector:\s+(.*)/)[1].replace(/^card\d+-/, "") // strip "card1-"
}))
}
}
CustomShortcut {
name: "brightnessUp"
description: "Increase brightness"
onPressed: root.increaseBrightness()
}
CustomShortcut {
name: "brightnessDown"
description: "Decrease brightness"
onPressed: root.decreaseBrightness()
}
IpcHandler {
target: "brightness"
function get(): real {
return getFor("active");
}
// Allows searching by active/model/serial/id/name
function getFor(query: string): real {
return root.getMonitor(query)?.brightness ?? -1;
}
function set(value: string): string {
return setFor("active", value);
}
// Handles brightness value like brightnessctl: 0.1, +0.1, 0.1-, 10%, +10%, 10%-
function setFor(query: string, value: string): string {
const monitor = root.getMonitor(query);
if (!monitor)
return "Invalid monitor: " + query;
let targetBrightness;
if (value.endsWith("%-")) {
const percent = parseFloat(value.slice(0, -2));
targetBrightness = monitor.brightness - (percent / 100);
} else if (value.startsWith("+") && value.endsWith("%")) {
const percent = parseFloat(value.slice(1, -1));
targetBrightness = monitor.brightness + (percent / 100);
} else if (value.endsWith("%")) {
const percent = parseFloat(value.slice(0, -1));
targetBrightness = percent / 100;
} else if (value.startsWith("+")) {
const increment = parseFloat(value.slice(1));
targetBrightness = monitor.brightness + increment;
} else if (value.endsWith("-")) {
const decrement = parseFloat(value.slice(0, -1));
targetBrightness = monitor.brightness - decrement;
} else if (value.includes("%") || value.includes("-") || value.includes("+")) {
return `Invalid brightness format: ${value}\nExpected: 0.1, +0.1, 0.1-, 10%, +10%, 10%-`;
} else {
targetBrightness = parseFloat(value);
}
if (isNaN(targetBrightness))
return `Failed to parse value: ${value}\nExpected: 0.1, +0.1, 0.1-, 10%, +10%, 10%-`;
monitor.setBrightness(targetBrightness);
return `Set monitor ${monitor.modelData.name} brightness to ${+monitor.brightness.toFixed(2)}`;
}
}
component Monitor: QtObject {
id: monitor
required property ShellScreen modelData
readonly property bool isDdc: root.ddcMonitors.some(m => m.connector === modelData.name)
readonly property string busNum: root.ddcMonitors.find(m => m.connector === modelData.name)?.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()
}
}
+7
View File
@@ -376,6 +376,13 @@ Item {
source: iconPath1 !== "" ? iconPath1 : iconPath2 source: iconPath1 !== "" ? iconPath1 : iconPath2
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
implicitSize: 42 implicitSize: 42
StateLayer {
radius: 1000
onClicked: {
appBox.modelData.audio.muted = !appBox.modelData.audio.muted;
}
}
} }
ColumnLayout { ColumnLayout {
+6 -2
View File
@@ -18,6 +18,7 @@ RowLayout {
required property Wrapper popouts required property Wrapper popouts
required property PersistentProperties visibilities required property PersistentProperties visibilities
required property PanelWindow bar required property PanelWindow bar
required property ShellScreen screen
function checkPopout(x: real): void { function checkPopout(x: real): void {
const ch = childAt(x, height / 2) as WrappedLoader; const ch = childAt(x, height / 2) as WrappedLoader;
@@ -60,7 +61,7 @@ RowLayout {
// popouts.currentName = "calendar"; // popouts.currentName = "calendar";
// popouts.currentCenter = Qt.binding( () => item.mapToItem( root, itemWidth / 2, 0 ).x ); // popouts.currentCenter = Qt.binding( () => item.mapToItem( root, itemWidth / 2, 0 ).x );
// popouts.hasCurrent = true; // popouts.hasCurrent = true;
} else if ( x > (root.width / 2 + 50) && x < (root.width / 2 - 50) && Config.barConfig.popouts.activeWindow ) { } else if ( id === "activeWindow" && Config.barConfig.popouts.activeWindow ) {
popouts.currentName = "dash"; popouts.currentName = "dash";
popouts.currentCenter = root.width / 2; popouts.currentCenter = root.width / 2;
popouts.hasCurrent = true; popouts.hasCurrent = true;
@@ -153,7 +154,10 @@ RowLayout {
DelegateChoice { DelegateChoice {
roleValue: "activeWindow" roleValue: "activeWindow"
delegate: WrappedLoader { delegate: WrappedLoader {
sourceComponent: WindowTitle {} sourceComponent: WindowTitle {
bar: root
monitor: Brightness.getMonitorForScreen(root.screen)
}
} }
} }
} }
+1 -33
View File
@@ -30,7 +30,7 @@ GridLayout {
Rect { Rect {
Layout.row: 0 Layout.row: 0
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.preferredWidth: Config.dashboard.sizes.weatherWidth Layout.preferredWidth: 250
Layout.fillHeight: true Layout.fillHeight: true
radius: 8 radius: 8
@@ -50,38 +50,6 @@ GridLayout {
} }
} }
Rect {
Layout.row: 1
Layout.column: 1
Layout.columnSpan: 3
Layout.fillWidth: true
Layout.preferredHeight: 100
radius: 8
}
Rect {
Layout.row: 1
Layout.column: 4
Layout.preferredWidth: 100
Layout.fillHeight: true
radius: 8
}
Rect {
Layout.row: 0
Layout.column: 5
Layout.rowSpan: 2
Layout.preferredWidth: 100
Layout.fillHeight: true
radius: 8
}
component Rect: CustomRect { component Rect: CustomRect {
color: DynamicColors.tPalette.m3surfaceContainer color: DynamicColors.tPalette.m3surfaceContainer
} }
+3 -14
View File
@@ -12,32 +12,21 @@ Item {
required property var wrapper required property var wrapper
readonly property PersistentProperties state: PersistentProperties { readonly property PersistentProperties state: PersistentProperties {
property int currentTab property int currentTab: 0
property date currentDate: new Date() property date currentDate: new Date()
reloadableId: "dashboardState" reloadableId: "dashboardState"
} }
readonly property real nonAnimWidth: view.implicitWidth + viewWrapper.anchors.margins * 2 readonly property real nonAnimWidth: view.implicitWidth + viewWrapper.anchors.margins * 2
readonly property real nonAnimHeight: tabs.implicitHeight + tabs.anchors.topMargin + view.implicitHeight + viewWrapper.anchors.margins * 2 readonly property real nonAnimHeight: view.implicitHeight + viewWrapper.anchors.margins * 2
implicitWidth: nonAnimWidth implicitWidth: nonAnimWidth
implicitHeight: nonAnimHeight implicitHeight: nonAnimHeight
Tabs {
id: tabs
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
nonAnimWidth: root.nonAnimWidth - anchors.margins * 2
state: root.state
}
ClippingRectangle { ClippingRectangle {
id: viewWrapper id: viewWrapper
anchors.top: tabs.bottom anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
+68 -51
View File
@@ -1,68 +1,85 @@
pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Layouts
import Quickshell.Hyprland
import qs.Helpers
import qs.Config
import qs.Components import qs.Components
import qs.Config
import qs.Helpers
Item { Item {
id: root id: root
property string currentTitle: Hypr.activeName
Layout.fillHeight: true required property var bar
Layout.preferredWidth: Math.max( titleText1.implicitWidth, titleText2.implicitWidth ) + 10 required property Brightness.Monitor monitor
property color colour: DynamicColors.palette.m3primary
readonly property int maxHeight: {
const otherModules = bar.children.filter(c => c.id && c.item !== this && c.id !== "spacer");
const otherHeight = otherModules.reduce((acc, curr) => acc + (curr.item.nonAnimHeight ?? curr.height), 0);
// Length - 2 cause repeater counts as a child
return bar.height - otherHeight - bar.spacing * (bar.children.length - 1) - bar.vPadding * 2;
}
property Title current: text1
clip: true clip: true
implicitWidth: current.implicitWidth + current.anchors.leftMargin
implicitHeight: current.implicitHeight
property bool showFirst: true // MaterialIcon {
property color textColor: Config.useDynamicColors ? DynamicColors.palette.m3primary : "white" // id: icon
//
// Component.onCompleted: { // anchors.verticalCenter: parent.verticalCenter
// Hyprland.rawEvent.connect(( event ) => { //
// if (event.name === "activewindow") { // animate: true
// InitialTitle.getInitialTitle( function( initialTitle ) { // text: Icons.getAppCategoryIcon(Hypr.activeToplevel?.lastIpcObject.class, "desktop_windows")
// root.currentTitle = initialTitle // color: root.colour
// })
// }
// })
// } // }
onCurrentTitleChanged: { Title {
if (showFirst) { id: text1
titleText2.text = currentTitle }
showFirst = false
} else { Title {
titleText1.text = currentTitle id: text2
showFirst = true }
TextMetrics {
id: metrics
text: Hypr.activeToplevel?.title ?? qsTr("Desktop")
font.pointSize: 12
font.family: "Rubik"
onTextChanged: {
const next = root.current === text1 ? text2 : text1;
next.text = elidedText;
root.current = next;
}
onElideWidthChanged: root.current.text = elidedText
}
Behavior on implicitWidth {
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
} }
} }
CustomText { component Title: CustomText {
id: titleText1 id: text
anchors.fill: parent
anchors.margins: 5 anchors.verticalCenter: parent.verticalCenter
text: root.currentTitle anchors.leftMargin: 7
color: root.textColor
elide: Text.ElideRight font.pointSize: metrics.font.pointSize
font.pixelSize: 16 font.family: metrics.font.family
horizontalAlignment: Text.AlignHCenter color: root.colour
verticalAlignment: Text.AlignVCenter opacity: root.current === this ? 1 : 0
opacity: root.showFirst ? 1 : 0
Behavior on opacity { width: implicitWidth
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad } height: implicitHeight
}
}
CustomText {
id: titleText2
anchors.fill: parent
anchors.margins: 5
color: root.textColor
elide: Text.ElideRight
font.pixelSize: 16
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
opacity: root.showFirst ? 0 : 1
Behavior on opacity { Behavior on opacity {
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad } Anim {}
} }
} }
} }
+68
View File
@@ -0,0 +1,68 @@
import QtQuick
import QtQuick.Layouts
import Quickshell.Hyprland
import qs.Helpers
import qs.Config
import qs.Components
Item {
id: root
property string currentTitle: Hypr.activeName
Layout.fillHeight: true
Layout.preferredWidth: Math.max( titleText1.implicitWidth, titleText2.implicitWidth ) + 10
clip: true
property bool showFirst: true
property color textColor: Config.useDynamicColors ? DynamicColors.palette.m3primary : "white"
// Component.onCompleted: {
// Hyprland.rawEvent.connect(( event ) => {
// if (event.name === "activewindow") {
// InitialTitle.getInitialTitle( function( initialTitle ) {
// root.currentTitle = initialTitle
// })
// }
// })
// }
onCurrentTitleChanged: {
if (showFirst) {
titleText2.text = currentTitle
showFirst = false
} else {
titleText1.text = currentTitle
showFirst = true
}
}
CustomText {
id: titleText1
anchors.fill: parent
anchors.margins: 5
text: root.currentTitle
color: root.textColor
elide: Text.ElideRight
font.pixelSize: 16
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
opacity: root.showFirst ? 1 : 0
Behavior on opacity {
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
}
}
CustomText {
id: titleText2
anchors.fill: parent
anchors.margins: 5
color: root.textColor
elide: Text.ElideRight
font.pixelSize: 16
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
opacity: root.showFirst ? 0 : 1
Behavior on opacity {
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
}
}
}
+27 -2
View File
@@ -1,3 +1,28 @@
#!/bin/zsh #!/usr/bin/env bash
exec yay -Sy OS="arch"
if [[ $(ls ./tmp) ]]; then
exec mkdir ./tmp
fi
cd ./tmp
main() {
local OPTARG OPTIND opt
while getopts "arch:nix:" opt; do
case "$opt" in
arch) OS=$OPTARG ;;
nix) OS=$OPTARG ;;
*) fatal 'bad option' ;;
esac
done
if [[ $OS = "arch" ]]; then
exec yay -Sy
elif [[ $OS = "nix" ]]; then
exec nixos-rebuild build --flake $HOME/Gits/NixOS/#nixos
PKGS=$(exec nix store diff-closures /run/current-system ./result)
fi
}
main "$@"