up su Gitea
This commit is contained in:
@@ -0,0 +1,455 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
spacing: Style.marginL
|
||||
|
||||
property var pluginApi: null
|
||||
|
||||
// Requirement check states
|
||||
property bool evtestInstalled: false
|
||||
property bool inInputGroup: false
|
||||
property string currentUser: ""
|
||||
|
||||
// Editable settings properties
|
||||
property string editCatColor: {
|
||||
let saved = pluginApi?.pluginSettings?.catColor
|
||||
if (saved && saved.length > 0) return saved
|
||||
return pluginApi?.manifest?.metadata?.defaultSettings?.catColor ?? "none"
|
||||
}
|
||||
|
||||
property real editCatSize: {
|
||||
let saved = pluginApi?.pluginSettings?.catSize
|
||||
if (saved !== undefined && saved !== null) return saved
|
||||
return pluginApi?.manifest?.metadata?.defaultSettings?.catSize ?? 1.0
|
||||
}
|
||||
|
||||
property real editCatOffsetY: {
|
||||
let saved = pluginApi?.pluginSettings?.catOffsetY
|
||||
if (saved !== undefined && saved !== null) return saved
|
||||
return pluginApi?.manifest?.metadata?.defaultSettings?.catOffsetY ?? 0.11
|
||||
}
|
||||
|
||||
property var editInputDevices: {
|
||||
let saved = pluginApi?.pluginSettings?.inputDevices
|
||||
if (saved && saved.length > 0) return saved
|
||||
let legacy = pluginApi?.pluginSettings?.inputDevice
|
||||
?? pluginApi?.manifest?.metadata?.defaultSettings?.inputDevice
|
||||
return legacy ? [legacy] : []
|
||||
}
|
||||
|
||||
property bool editRaveMode: {
|
||||
let saved = pluginApi?.pluginSettings?.raveMode
|
||||
if (saved !== undefined && saved !== null) return saved
|
||||
return pluginApi?.manifest?.metadata?.defaultSettings?.raveMode ?? false
|
||||
}
|
||||
|
||||
property bool editTappyMode: {
|
||||
let saved = pluginApi?.pluginSettings?.tappyMode
|
||||
if (saved !== undefined && saved !== null) return saved
|
||||
return pluginApi?.manifest?.metadata?.defaultSettings?.tappyMode ?? false
|
||||
}
|
||||
|
||||
property bool editUseMprisFilter: {
|
||||
let saved = pluginApi?.pluginSettings?.useMprisFilter
|
||||
if (saved !== undefined && saved !== null) return saved
|
||||
return pluginApi?.manifest?.metadata?.defaultSettings?.useMprisFilter ?? false
|
||||
}
|
||||
|
||||
// Status colors (with fallback for theme compatibility)
|
||||
readonly property color statusSuccessColor: Color.mPrimary
|
||||
readonly property color statusErrorColor: Color.mError ?? "#c00202"
|
||||
|
||||
property var inputDevices: []
|
||||
|
||||
function isSelected(key) {
|
||||
return root.editInputDevices.indexOf(key) >= 0
|
||||
}
|
||||
|
||||
function toggleDevice(key) {
|
||||
let list = root.editInputDevices.slice()
|
||||
let idx = list.indexOf(key)
|
||||
if (idx >= 0)
|
||||
list.splice(idx, 1)
|
||||
else
|
||||
list.push(key)
|
||||
root.editInputDevices = list
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
evtestCheck.running = true
|
||||
userCheck.running = true
|
||||
byIdListProcess.running = true
|
||||
}
|
||||
|
||||
Process {
|
||||
id: evtestCheck
|
||||
command: ["which", "evtest"]
|
||||
stdout: StdioCollector {}
|
||||
stderr: StdioCollector {}
|
||||
onExited: function(exitCode, exitStatus) {
|
||||
root.evtestInstalled = (exitCode == 0)
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: userCheck
|
||||
command: ["id", "-un"]
|
||||
stdout: SplitParser {
|
||||
onRead: data => {
|
||||
root.currentUser = data.trim()
|
||||
}
|
||||
}
|
||||
stderr: StdioCollector {}
|
||||
onExited: function(exitCode, exitStatus) {
|
||||
if (exitCode == 0 && root.currentUser.length > 0)
|
||||
groupCheck.running = true
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: groupCheck
|
||||
command: ["sh", "-c", "id -nG '" + root.currentUser + "' | tr ' ' '\\n' | grep -qx input"]
|
||||
stdout: StdioCollector {}
|
||||
stderr: StdioCollector {}
|
||||
onExited: function(exitCode, exitStatus) {
|
||||
root.inInputGroup = (exitCode == 0)
|
||||
}
|
||||
}
|
||||
|
||||
// Try by-id first
|
||||
Process {
|
||||
id: byIdListProcess
|
||||
command: ["sh", "-c", "[ -d /dev/input/by-id ] && for f in /dev/input/by-id/*-event-*; do [ -e \"$f\" ] && echo \"$(basename \"$f\")|$(readlink -f \"$f\")\"; done || true"]
|
||||
|
||||
stdout: SplitParser {
|
||||
onRead: data => {
|
||||
const line = data.trim()
|
||||
if (line.length === 0) return
|
||||
const parts = line.split("|")
|
||||
if (parts.length !== 2) return
|
||||
const name = parts[0]
|
||||
const resolved = parts[1]
|
||||
if (!resolved.startsWith("/dev/input/event")) return
|
||||
|
||||
const eventNum = resolved.replace(/.*\//, "")
|
||||
let friendly = name
|
||||
.replace(/^usb-/, "")
|
||||
.replace(/-event-\w+$/, "")
|
||||
.replace(/-if\d+$/, "")
|
||||
.replace(/_/g, " ")
|
||||
|
||||
root.inputDevices = root.inputDevices.concat([{
|
||||
key: resolved,
|
||||
name: friendly,
|
||||
eventDev: eventNum
|
||||
}])
|
||||
}
|
||||
}
|
||||
|
||||
stderr: StdioCollector {}
|
||||
|
||||
onExited: function(exitCode, exitStatus) {
|
||||
// Always try to get names from sysfs
|
||||
sysfsListProcess.running = true
|
||||
}
|
||||
}
|
||||
|
||||
// Get device names from sysfs
|
||||
Process {
|
||||
id: sysfsListProcess
|
||||
command: ["sh", "-c", "for f in /dev/input/event*; do [ -c \"$f\" ] && echo \"$f|$(cat /sys/class/input/$(basename $f)/device/name 2>/dev/null || basename $f)\"; done"]
|
||||
running: false
|
||||
|
||||
stdout: SplitParser {
|
||||
onRead: data => {
|
||||
const line = data.trim()
|
||||
if (line.length === 0) return
|
||||
const parts = line.split("|")
|
||||
if (parts.length !== 2) return
|
||||
const device = parts[0]
|
||||
const name = parts[1]
|
||||
const eventNum = device.replace(/.*\//, "")
|
||||
|
||||
// Filter out non-keyboardy devices
|
||||
const nameLower = name.toLowerCase()
|
||||
const excludePatterns = [
|
||||
/power button/i,
|
||||
/sleep button/i,
|
||||
/lid switch/i,
|
||||
/video bus/i,
|
||||
/audio/i,
|
||||
/hdmi/i,
|
||||
/speaker/i,
|
||||
/headphone/i,
|
||||
/mic\b/i
|
||||
]
|
||||
|
||||
const shouldExclude = excludePatterns.some(pattern => pattern.test(name))
|
||||
if (shouldExclude) return
|
||||
|
||||
// Check if we already have this device from by-id
|
||||
const exists = root.inputDevices.some(d => d.key === device)
|
||||
if (!exists) {
|
||||
root.inputDevices = root.inputDevices.concat([{
|
||||
key: device,
|
||||
name: name,
|
||||
eventDev: eventNum
|
||||
}])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stderr: StdioCollector {}
|
||||
}
|
||||
|
||||
// Requirements Section
|
||||
Text {
|
||||
text: pluginApi?.tr("settings.requirements")
|
||||
color: Color.mOnSurface
|
||||
font.pointSize: Style.fontSizeM
|
||||
font.weight: Font.DemiBold
|
||||
}
|
||||
|
||||
NBox {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: reqContent.implicitHeight + Style.marginM * 2
|
||||
|
||||
ColumnLayout {
|
||||
id: reqContent
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: Style.marginM
|
||||
spacing: Style.marginS
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginS
|
||||
NIcon {
|
||||
icon: root.evtestInstalled ? "circle-check-filled" : "circle-x-filled"
|
||||
color: root.evtestInstalled ? root.statusSuccessColor : root.statusErrorColor
|
||||
pointSize: Style.fontSizeM
|
||||
}
|
||||
Text {
|
||||
text: root.evtestInstalled
|
||||
? pluginApi?.tr("settings.evtest-installed")
|
||||
: pluginApi?.tr("settings.evtest-not-installed")
|
||||
color: root.evtestInstalled ? root.statusSuccessColor : root.statusErrorColor
|
||||
font.pointSize: Style.fontSizeM
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: Style.marginS
|
||||
NIcon {
|
||||
icon: root.inInputGroup ? "circle-check-filled" : "circle-x-filled"
|
||||
color: root.inInputGroup ? root.statusSuccessColor : root.statusErrorColor
|
||||
pointSize: Style.fontSizeM
|
||||
}
|
||||
Text {
|
||||
text: root.inInputGroup
|
||||
? pluginApi?.tr("settings.in-input-group")
|
||||
: pluginApi?.tr("settings.not-in-input-group")
|
||||
color: root.inInputGroup ? root.statusSuccessColor : root.statusErrorColor
|
||||
font.pointSize: Style.fontSizeM
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Widget Color
|
||||
NColorChoice {
|
||||
label: pluginApi?.tr("settings.colours")
|
||||
currentKey: root.editCatColor
|
||||
onSelected: key => { root.editCatColor = key; }
|
||||
}
|
||||
|
||||
// Cat Size Section
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: pluginApi?.tr("settings.cat-size")
|
||||
value: root.editCatSize
|
||||
from: 0.5
|
||||
to: 1.5
|
||||
stepSize: 0.01
|
||||
defaultValue: 1.0
|
||||
showReset: true
|
||||
text: Math.round(root.editCatSize * 100) + "%"
|
||||
onMoved: value => root.editCatSize = value
|
||||
}
|
||||
|
||||
// Vertical Position Section
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
label: pluginApi?.tr("settings.vertical-position")
|
||||
value: root.editCatOffsetY
|
||||
from: -0.39
|
||||
to: 0.61
|
||||
stepSize: 0.01
|
||||
defaultValue: 0.11
|
||||
showReset: true
|
||||
text: { let v = Math.round(-(root.editCatOffsetY - 0.11) * 100) / 100; return (v > 0 ? "+" : "") + v.toFixed(2) }
|
||||
onMoved: value => root.editCatOffsetY = value
|
||||
}
|
||||
|
||||
// Rave Mode
|
||||
NToggle {
|
||||
label: pluginApi?.tr("settings.rave-mode")
|
||||
description: pluginApi?.tr("settings.rave-mode-desc")
|
||||
checked: root.editRaveMode
|
||||
onToggled: checked => root.editRaveMode = checked
|
||||
defaultValue: pluginApi?.manifest?.metadata?.defaultSettings?.raveMode ?? false
|
||||
}
|
||||
|
||||
// Tappy Mode
|
||||
NToggle {
|
||||
label: pluginApi?.tr("settings.tappy-mode")
|
||||
description: pluginApi?.tr("settings.tappy-mode-desc")
|
||||
checked: root.editTappyMode
|
||||
onToggled: checked => root.editTappyMode = checked
|
||||
defaultValue: pluginApi?.manifest?.metadata?.defaultSettings?.tappyMode ?? false
|
||||
}
|
||||
|
||||
// MPRIS Filtering
|
||||
NToggle {
|
||||
label: pluginApi?.tr("settings.mpris-filter")
|
||||
description: pluginApi?.tr("settings.mpris-filter-desc")
|
||||
checked: root.editUseMprisFilter
|
||||
onToggled: checked => root.editUseMprisFilter = checked
|
||||
defaultValue: pluginApi?.manifest?.metadata?.defaultSettings?.useMprisFilter ?? false
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Input Devices Section
|
||||
Text {
|
||||
text: pluginApi?.tr("settings.input-devices") || "Input Devices"
|
||||
color: Color.mOnSurface
|
||||
font.pointSize: Style.fontSizeM
|
||||
font.weight: Font.DemiBold
|
||||
}
|
||||
|
||||
NBox {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: devContent.implicitHeight + Style.marginM * 2
|
||||
|
||||
ColumnLayout {
|
||||
id: devContent
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: Style.marginM
|
||||
spacing: Style.marginS
|
||||
|
||||
Repeater {
|
||||
model: root.inputDevices
|
||||
|
||||
Rectangle {
|
||||
required property var modelData
|
||||
|
||||
property bool isChecked: root.isSelected(modelData.key)
|
||||
property bool isHovered: mouseArea.containsMouse
|
||||
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: rowContent.implicitHeight + Style.marginS * 2
|
||||
radius: Style.iRadiusXS
|
||||
color: isHovered ? Color.mSurfaceVariant : "transparent"
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: Style.animationFast }
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
onClicked: root.toggleDevice(modelData.key)
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: rowContent
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Style.marginS
|
||||
anchors.rightMargin: Style.marginS
|
||||
spacing: Style.marginM
|
||||
|
||||
Rectangle {
|
||||
id: checkBox
|
||||
implicitWidth: Math.round(Style.baseWidgetSize * 0.7)
|
||||
implicitHeight: Math.round(Style.baseWidgetSize * 0.7)
|
||||
radius: Style.iRadiusXS
|
||||
color: parent.parent.isChecked ? Color.mPrimary : Color.mSurface
|
||||
border.color: parent.parent.isHovered ? Color.mPrimary : Color.mOutline
|
||||
border.width: Style.borderS
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: Style.animationFast }
|
||||
}
|
||||
Behavior on border.color {
|
||||
ColorAnimation { duration: Style.animationFast }
|
||||
}
|
||||
|
||||
NIcon {
|
||||
visible: parent.parent.parent.isChecked
|
||||
anchors.centerIn: parent
|
||||
anchors.horizontalCenterOffset: -1
|
||||
icon: "check"
|
||||
color: Color.mOnPrimary
|
||||
pointSize: Math.max(Style.fontSizeXS, checkBox.implicitWidth * 0.5)
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 2
|
||||
|
||||
Text {
|
||||
text: parent.parent.parent.modelData.name
|
||||
color: Color.mOnSurface
|
||||
font.pointSize: Style.fontSizeM
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Text {
|
||||
text: parent.parent.parent.modelData.eventDev
|
||||
color: Color.mOnSurfaceVariant
|
||||
font.pointSize: Style.fontSizeS
|
||||
visible: text !== ""
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function saveSettings() {
|
||||
if (!pluginApi) {
|
||||
Logger.e("Slow Bongo", "Cannot save settings: pluginApi is null")
|
||||
return
|
||||
}
|
||||
pluginApi.pluginSettings.inputDevices = root.editInputDevices
|
||||
pluginApi.pluginSettings.catColor = root.editCatColor
|
||||
pluginApi.pluginSettings.catSize = root.editCatSize
|
||||
pluginApi.pluginSettings.catOffsetY = root.editCatOffsetY
|
||||
pluginApi.pluginSettings.raveMode = root.editRaveMode
|
||||
pluginApi.pluginSettings.tappyMode = root.editTappyMode
|
||||
pluginApi.pluginSettings.useMprisFilter = root.editUseMprisFilter
|
||||
pluginApi.saveSettings()
|
||||
Logger.i("Slow Bongo", "Settings saved successfully")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user