Files
voxtype-toggle-plasmashell/contents/ui/main.qml
James Eversole 3f3b27caaf init
2026-04-29 14:18:37 -05:00

162 lines
4.7 KiB
QML

// SPDX-FileCopyrightText: James Eversole <james@eversole.co>
// SPDX-License-Identifier: ISC
import QtQuick
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.plasma.plasmoid
import org.kde.plasma.components as PlasmaComponents
import org.kde.plasma.plasma5support as Plasma5Support
PlasmoidItem {
id: root
// Panel size — small icon size for system tray / panel
Layout.preferredWidth: Kirigami.Units.iconSizes.small
Layout.preferredHeight: Kirigami.Units.iconSizes.small
// Tooltip
Plasmoid.title: "Voxtype Toggle"
toolTipSubText: statusText
// State tracking
property string currentState: "idle"
property string statusText: "Voxtype is idle"
// State file paths (same as voxtype GNOME extension)
readonly property string runtimeDir: "/run/user/1000"
readonly property string stateFilePath: runtimeDir + "/voxtype/state"
readonly property string pidFilePath: runtimeDir + "/voxtype/pid"
// Data source for executing voxtype record toggle
Plasma5Support.DataSource {
id: executable
engine: "executable"
function toggleRecording() {
if (daemonRunning) {
executable.connectSource("voxtype record toggle")
} else {
updateState("not-running")
}
}
function checkDaemon() {
// Check if the voxtype daemon process is running
var cmd = "if [ -f " + root.pidFilePath + " ]; then kill -0 $(cat " + root.pidFilePath + ") 2>/dev/null && echo running; fi"
executable.connectSource(cmd)
}
onNewData: function(sourceName, data) {
var exitCode = data["exit code"]
var output = data.stdout.trim()
// Handle the toggle command
if (sourceName.includes("voxtype record toggle")) {
executable.disconnectSource(sourceName)
// Re-read state from disk after toggling
refreshState()
return
}
// Handle daemon check
if (sourceName.includes("kill -0")) {
daemonRunning = output === "running"
executable.disconnectSource(sourceName)
}
}
}
// Data source for reading the state file from disk
Plasma5Support.DataSource {
id: stateReader
engine: "executable"
connectedSources: []
onNewData: function(sourceName, data) {
stateReader.disconnectSource(sourceName)
var output = data.stdout.trim()
// If the state file doesn't exist or is empty, default to idle
updateState(output || "idle")
}
}
// Periodic timer to refresh state from disk
Timer {
id: stateRefreshTimer
interval: 3000
running: true
repeat: true
onTriggered: {
if (currentState !== "not-running") {
refreshState()
}
}
}
// Re-trigger polling faster when transcribing
onCurrentStateChanged: {
if (currentState === "transcribing") {
stateRefreshTimer.interval = 1500
} else {
stateRefreshTimer.interval = 3000
}
}
// Whether the voxtype daemon is currently running
property bool daemonRunning: false
function updateState(newStatus) {
currentState = newStatus
switch (newStatus) {
case "idle":
statusText = "Voxtype is idle"
break
case "recording":
statusText = "Voxtype is recording"
break
case "transcribing":
statusText = "Voxtype is transcribing"
break
case "not-running":
statusText = "Voxtype daemon not running"
break
default:
statusText = "Voxtype is " + newStatus
}
}
function refreshState() {
stateReader.connectSource("cat " + root.stateFilePath + " 2>/dev/null || echo idle")
}
// Icon source based on current state
readonly property string iconSource: {
switch (currentState) {
case "recording":
return "media-record-symbolic"
case "transcribing":
return "content-loading-symbolic"
case "not-running":
return "dialog-warning-symbolic"
case "idle":
default:
return "audio-input-microphone-symbolic"
}
}
// Main clickable button
PlasmaComponents.ToolButton {
id: toggleButton
anchors.centerIn: parent
icon.name: root.iconSource
onClicked: executable.toggleRecording()
}
// Initial state check when the component is ready
Component.onCompleted: {
refreshState()
executable.checkDaemon()
}
}