This commit is contained in:
James Eversole
2026-04-29 13:55:55 -05:00
commit 35d8b5e466
6 changed files with 282 additions and 0 deletions

7
LICENSE Normal file
View File

@@ -0,0 +1,7 @@
ISC LICENSE
Copyright James Eversole (james@eversole.co)
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

49
README.md Normal file
View File

@@ -0,0 +1,49 @@
100% slopcoded
---
A KDE Plasma 6 applet (panel icon) to toggle [voxtype](https://voxtype.io) voice typing on and off.
## Prerequisites
[voxtype](https://voxtype.io) must be installed and its daemon running. The applet reads state from `/run/user/1000/voxtype/state` and checks for the daemon via `/run/user/1000/voxtype/pid` — the same locations used by the voxtype GNOME extension.
## What this does
- Shows a panel icon reflecting the current voxtype state.
- Click to toggle recording on/off (sends `voxtype record toggle` when the daemon is running).
- Polls the state file periodically to update the icon and tooltip.
- Icons change by state: idle, recording, transcribing, or not-running.
## How to use it
### Nix Flake + Home Manager
Add the flake to your `flake.nix` inputs:
```nix
voxtype-toggle = {
url = "git+ssh://git@git.eversole.co:23231/James/voxtype-toggle-plasmashell.git";
inputs.nixpkgs.follows = "nixpkgs";
};
```
Then pass it through `extraSpecialArgs` and install the applet into `xdg.dataFile`:
```nix
{
voxtype-toggle,
system,
# ... other args
}:
# In extraSpecialArgs:
extraSpecialArgs = { inherit voxtype-toggle system; };
# In your home-manager module:
xdg.dataFile."plasma/plasmoids/org.eversole.voxtype-toggle" = {
source = "${voxtype-toggle.packages.${system}.plasmaAppletVoxtypeToggle}/share/plasma/plasmoids/org.eversole.voxtype-toggle";
};
```
This copies the applet files to the standard KDE Plasma applet directory (`~/.local/share/plasma/plasmoids/`) so the applet is available immediately after `home-manager switch` — no manual symlink or copy step needed.

161
contents/ui/main.qml Normal file
View File

@@ -0,0 +1,161 @@
// 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()
}
}

25
default.nix Normal file
View File

@@ -0,0 +1,25 @@
{ stdenvNoCC, lib }:
stdenvNoCC.mkDerivation {
pname = "plasma-applet-voxtype-toggle";
version = "1.0.1";
src = ./.;
installPhase = ''
runHook preInstall
install -D -t "$out/share/plasma/plasmoids/org.eversole.voxtype-toggle/" \
metadata.json
install -D -t "$out/share/plasma/plasmoids/org.eversole.voxtype-toggle/contents/ui" \
contents/ui/main.qml
runHook postInstall
'';
meta = with lib; {
description = "KDE Plasma 6 applet to toggle voxtype voice typing on/off";
license = licenses.isc;
platforms = platforms.linux;
};
}

15
flake.nix Normal file
View File

@@ -0,0 +1,15 @@
{
description = "Voxtype Toggle - Plasma 6 applet to toggle voxtype voice typing on/off";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
outputs = { self, nixpkgs, ... }: let
system = "x86_64-linux";
pkgs = import nixpkgs { inherit system; };
in {
packages.${system} = {
plasmaAppletVoxtypeToggle = pkgs.callPackage ./. {};
default = self.packages.${system}.plasmaAppletVoxtypeToggle;
};
};
}

25
metadata.json Normal file
View File

@@ -0,0 +1,25 @@
{
"KPackageStructure": "Plasma/Applet",
"KPlugin": {
"Name": "Voxtype Toggle",
"Name[en]": "Voxtype Toggle",
"Description": "Toggle voxtype voice typing on/off",
"Description[en]": "Toggle voxtype voice typing on/off",
"Id": "org.eversole.voxtype-toggle",
"Icon": "accessories-voice",
"Name[en_US]": "Voxtype Toggle For plasmashell",
"Version": "1.0.0",
"License": "ISC",
"Category": "System Information",
"Authors": [
{
"Name": "James Eversole",
"Email": "james@eversole.co"
}
],
"Website": "https://git.eversole.co/James/voxtype-toggle-plasmashell"
},
"X-Plasma-API-Minimum-Version": "6.0",
"X-Plasma-Required-Data-Engines": "executable",
"X-Plasma-MainFile": "main.qml"
}