317 lines
9.3 KiB
QML
317 lines
9.3 KiB
QML
import QtQuick
|
|
import Quickshell.Bluetooth
|
|
import Quickshell.Services.Pipewire
|
|
import "../.."
|
|
|
|
Item {
|
|
id: root
|
|
width: parent ? parent.width : 1280
|
|
height: parent ? parent.height : 820
|
|
focus: true
|
|
|
|
/* ------------------------------
|
|
PIXEL CONSTANTS (DO NOT TOUCH)
|
|
------------------------------ */
|
|
|
|
property int menuLeft: 64
|
|
property int menuTop: 140
|
|
property int lineHeight: 38 + 40 + 1
|
|
|
|
property int nameFontSize: 32
|
|
property int stateFontSize: 28
|
|
|
|
property int stateColumnX: 824
|
|
|
|
property int soulOffsetX: -36 - 32
|
|
property int soulOffsetY: -26
|
|
|
|
/* ------------------------------ */
|
|
|
|
property ShellStateManager manager: null
|
|
property int activeSelection: 0
|
|
property bool inBluetoothMenu: false
|
|
|
|
property bool isSelected: false
|
|
|
|
function clamp(v, lo, hi) {
|
|
return Math.max(lo, Math.min(hi, v));
|
|
}
|
|
|
|
function menuLength() {
|
|
if (root.inBluetoothMenu && bluetoothRepeater)
|
|
return bluetoothRepeater.count;
|
|
if (!menuModel)
|
|
return 0;
|
|
if (typeof menuModel.count === "function")
|
|
return menuModel.count();
|
|
if (menuModel.count !== undefined)
|
|
return menuModel.count;
|
|
if (menuModel.length !== undefined)
|
|
return menuModel.length;
|
|
return 0;
|
|
}
|
|
|
|
function menuAt(index) {
|
|
if (!menuModel)
|
|
return null;
|
|
if (menuModel.get)
|
|
return menuModel.get(index);
|
|
if (menuModel.length !== undefined)
|
|
return menuModel[index];
|
|
return null;
|
|
}
|
|
|
|
function wrapIndex(i) {
|
|
const length = menuLength();
|
|
if (length === 0)
|
|
return 0;
|
|
return (i + length) % length;
|
|
}
|
|
|
|
function currentAction() {
|
|
if (root.inBluetoothMenu)
|
|
return null;
|
|
return menuLength() > 0 ? menuAt(activeSelection) : null;
|
|
}
|
|
|
|
function clampSelection() {
|
|
const length = menuLength();
|
|
if (length === 0) {
|
|
activeSelection = 0;
|
|
return;
|
|
}
|
|
activeSelection = clamp(activeSelection, 0, length - 1);
|
|
}
|
|
|
|
function bluetoothDisplayName(device) {
|
|
if (!device)
|
|
return "";
|
|
if (device.name && device.name.length > 0)
|
|
return device.name;
|
|
return device.deviceName || "";
|
|
}
|
|
|
|
function bluetoothDisplayState(device) {
|
|
if (!device)
|
|
return "";
|
|
// dont change symbols
|
|
return device.connected ? "✓" : "";
|
|
}
|
|
|
|
function bluetoothDeviceAt(index) {
|
|
const item = bluetoothRepeater.itemAt(index);
|
|
if (!item)
|
|
return null;
|
|
return item.device || null;
|
|
}
|
|
|
|
PwObjectTracker {
|
|
objects: [Pipewire.defaultAudioSink]
|
|
}
|
|
|
|
property int bluetoothActionIndex: 1
|
|
property var actions: [
|
|
{
|
|
name: "Master Volume",
|
|
arr: function (dir) {
|
|
const sink = Pipewire.defaultAudioSink;
|
|
if (!sink || !sink.audio)
|
|
return;
|
|
const step = 0.05;
|
|
sink.audio.muted = false;
|
|
sink.audio.volume = clamp(sink.audio.volume + dir * step, 0, 1);
|
|
},
|
|
getState: function () {
|
|
const sink = Pipewire.defaultAudioSink;
|
|
if (!sink || !sink.audio)
|
|
return "—";
|
|
return Math.round(sink.audio.volume * 100) + "%";
|
|
}
|
|
},
|
|
{
|
|
name: "Bluetooth",
|
|
ent: function () {
|
|
root.inBluetoothMenu = true;
|
|
root.isSelected = false;
|
|
root.activeSelection = 0;
|
|
root.clampSelection();
|
|
},
|
|
getState: function () {
|
|
return "";
|
|
}
|
|
}
|
|
]
|
|
|
|
property var menuModel: root.inBluetoothMenu ? Bluetooth.devices : actions
|
|
|
|
Connections {
|
|
target: Bluetooth.devices
|
|
ignoreUnknownSignals: true
|
|
function onCountChanged() {
|
|
if (root.inBluetoothMenu)
|
|
root.clampSelection();
|
|
}
|
|
function onModelReset() {
|
|
if (root.inBluetoothMenu)
|
|
root.clampSelection();
|
|
}
|
|
function onRowsInserted() {
|
|
if (root.inBluetoothMenu)
|
|
root.clampSelection();
|
|
}
|
|
function onRowsRemoved() {
|
|
if (root.inBluetoothMenu)
|
|
root.clampSelection();
|
|
}
|
|
}
|
|
|
|
Text {
|
|
text: root.inBluetoothMenu ? "BLUETOOTH" : "CONFIG"
|
|
font.family: "8bitoperator JVE"
|
|
font.pixelSize: 71
|
|
renderType: Text.NativeRendering
|
|
font.hintingPreference: Font.PreferNoHinting
|
|
smooth: false
|
|
antialiasing: false
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
color: "#ffffff"
|
|
y: 32
|
|
}
|
|
|
|
Repeater {
|
|
id: bluetoothRepeater
|
|
model: root.menuModel
|
|
|
|
delegate: Item {
|
|
width: root.width
|
|
height: lineHeight
|
|
x: 0
|
|
y: menuTop + index * lineHeight
|
|
|
|
property var device: root.inBluetoothMenu ? modelData : null
|
|
|
|
Image {
|
|
source: "./soul.png"
|
|
width: 36
|
|
height: 36
|
|
x: 182
|
|
y: 8 + 14
|
|
visible: root.activeSelection == index
|
|
}
|
|
|
|
Text {
|
|
x: 239
|
|
y: 0
|
|
text: root.inBluetoothMenu ? root.bluetoothDisplayName(modelData) : modelData.name
|
|
font.family: "8bitoperator JVE"
|
|
font.pixelSize: 71
|
|
font.letterSpacing: 1
|
|
renderType: Text.NativeRendering
|
|
font.hintingPreference: Font.PreferNoHinting
|
|
smooth: false
|
|
antialiasing: false
|
|
color: (root.activeSelection == index && root.isSelected == true) ? "#fefe00" : "#ffffff"
|
|
}
|
|
|
|
// Option state
|
|
Text {
|
|
x: menuLeft + stateColumnX
|
|
y: 4
|
|
text: root.inBluetoothMenu ? root.bluetoothDisplayState(modelData) : (modelData.getState ? modelData.getState() : "")
|
|
font.family: "8bitoperator JVE"
|
|
font.pixelSize: 71
|
|
font.letterSpacing: 1
|
|
renderType: Text.NativeRendering
|
|
font.hintingPreference: Font.PreferNoHinting
|
|
smooth: false
|
|
antialiasing: false
|
|
color: (root.activeSelection == index && root.isSelected == true) ? "#fefe00" : "#ffffff"
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ------------------------------
|
|
INPUT HANDLING
|
|
------------------------------ */
|
|
|
|
function handleKey(key) {
|
|
switch (key) {
|
|
case Qt.Key_Up:
|
|
if (root.inBluetoothMenu || root.isSelected === false)
|
|
activeSelection = wrapIndex(activeSelection - 1);
|
|
return true;
|
|
case Qt.Key_Down:
|
|
if (root.inBluetoothMenu || root.isSelected === false)
|
|
activeSelection = wrapIndex(activeSelection + 1);
|
|
return true;
|
|
case Qt.Key_Left:
|
|
{
|
|
const a = currentAction();
|
|
if (a && a.arr && root.isSelected === true)
|
|
a.arr(-1);
|
|
return true;
|
|
}
|
|
case Qt.Key_Right:
|
|
{
|
|
const a = currentAction();
|
|
if (a && a.arr && root.isSelected === true)
|
|
a.arr(1);
|
|
return true;
|
|
}
|
|
case Qt.Key_Z:
|
|
case Qt.Key_Return:
|
|
case Qt.Key_Enter:
|
|
{
|
|
if (root.inBluetoothMenu) {
|
|
const device = bluetoothDeviceAt(activeSelection);
|
|
console.log(root.inBluetoothMenu, activeSelection, device);
|
|
if (device) {
|
|
if (!device.connected) {
|
|
device.connect();
|
|
} else {
|
|
device.disconnect();
|
|
}
|
|
// device.connected = !device.connected;
|
|
}
|
|
} else {
|
|
const a = currentAction();
|
|
if (a && a.ent) {
|
|
a.ent();
|
|
} else {
|
|
root.isSelected = !root.isSelected;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
case Qt.Key_X:
|
|
case Qt.Key_Shift:
|
|
case Qt.Key_Escape:
|
|
if (root.inBluetoothMenu) {
|
|
root.inBluetoothMenu = false;
|
|
root.isSelected = false;
|
|
root.activeSelection = root.bluetoothActionIndex;
|
|
root.clampSelection();
|
|
} else if (root.isSelected === true) {
|
|
root.isSelected = false;
|
|
} else {
|
|
if (manager && manager.closeQuickSettings) {
|
|
manager.closeQuickSettings();
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
root.activeSelection = 0;
|
|
root.isSelected = false;
|
|
root.inBluetoothMenu = false;
|
|
ShellInputManager.registerHandler("quickSettings", handleKey);
|
|
}
|
|
|
|
Component.onDestruction: {
|
|
ShellInputManager.unregisterHandler("quickSettings");
|
|
}
|
|
}
|