refactor: bring starter pack plugin source in-tree

This commit is contained in:
nekename committed 2025-05-17 13:39:47 +01:00
1 parent dd4cd10672
commit 0b27ffa9e5
23 files changed
+3504 -6

No files matched your search

+1
View File
@@ -0,0 +1 @@
plugins/com.amansprojects.starterpack.sdPlugin/files/propertyInspector/sdpi.css linguist-vendored
+1 -1
View File
@@ -2,7 +2,7 @@
node_modules
/build
/.svelte-kit
/src-tauri/target
**/target
/src-tauri/gen
.env
.env.*
-3
View File
@@ -1,3 +0,0 @@
[submodule "plugins/com.amansprojects.starterpack.sdPlugin"]
path = plugins/com.amansprojects.starterpack.sdPlugin
url = https://github.com/nekename/opendeck-starterpack.git
+1 -1
View File
@@ -1,6 +1,6 @@
{
"fmt": {
"exclude": ["src-tauri/", "plugins/", "*.md"],
"exclude": ["src-tauri/", "*.md"],
"useTabs": true,
"lineWidth": 200
},
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,18 @@
[package]
name = "opendeck-starterpack"
version = "1.4.1"
authors = ["Aman Khanna (nekename)"]
license = "GPL-3.0-or-later"
edition = "2021"
[dependencies]
openaction = "1.1"
log = "0.4"
simplelog = "0.12"
serde = "1.0"
serde_json = "1.0"
ron = "0.8"
enigo = { version = "0.3", features = ["serde", "x11rb"] }
tokio = { version = "1.42", features = ["full"] }
tungstenite = "0.26"
os_pipe = "1.2"
@@ -0,0 +1,18 @@
import { copy } from "jsr:@std/fs";
import { join } from "jsr:@std/path";
if (Deno.args.length < 1) Deno.exit(1);
const outDir = Deno.args[0];
try {
Deno.remove(outDir, { recursive: true });
} catch (error: any) {
if (!(error instanceof Deno.errors.NotFound)) {
throw error;
}
}
copy("files", outDir);
if (!(await new Deno.Command("cargo", { args: ["build", "--release"] }).spawn().status).success) Deno.exit(1);
const builtExec = Deno.build.os == "windows" ? "opendeck-starterpack.exe" : "opendeck-starterpack";
const targetExec = Deno.build.os == "windows" ? "opendeck-starterpack.exe" : Deno.build.os == "darwin" ? "opendeck-starterpack-mac" : "opendeck-starterpack";
copy(join("target", "release", builtExec), join(outDir, targetExec));
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

@@ -0,0 +1,56 @@
{
"Name": "OpenDeck Starter Pack",
"Description": "OpenDeck starter pack plugin",
"Author": "Aman Khanna (nekename)",
"Version": "1.4.1",
"CodePathWin": "opendeck-starterpack.exe",
"CodePathMac": "opendeck-starterpack-mac",
"CodePathLin": "opendeck-starterpack",
"Icon": "icons/plugin",
"Category": "OpenDeck",
"OS": [
{
"Platform": "mac",
"MinimumVersion": "10.11"
},
{
"Platform": "windows",
"MinimumVersion": "10"
},
{
"Platform": "linux"
}
],
"Actions": [
{
"Icon": "icons/runCommand",
"Name": "Run Command",
"States": [{ "Image": "actionDefaultImage" }],
"Controllers": ["Keypad"],
"SupportedInMultiActions": true,
"Tooltip": "Run a command",
"UUID": "com.amansprojects.starterpack.runcommand",
"PropertyInspectorPath": "propertyInspector/runCommand.html"
},
{
"Icon": "icons/inputSimulation",
"Name": "Input Simulation",
"States": [{ "Image": "actionDefaultImage" }],
"Controllers": ["Keypad"],
"SupportedInMultiActions": true,
"Tooltip": "Simulate mouse or keyboard input",
"UUID": "com.amansprojects.starterpack.inputsimulation",
"PropertyInspectorPath": "propertyInspector/inputSimulation.html"
},
{
"Icon": "icons/switchProfile",
"Name": "Switch Profile",
"States": [{ "Image": "actionDefaultImage" }],
"Controllers": ["Keypad"],
"SupportedInMultiActions": true,
"Tooltip": "Switch the selected profile",
"UUID": "com.amansprojects.starterpack.switchprofile",
"PropertyInspectorPath": "propertyInspector/switchProfile.html"
}
]
}
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="10" viewBox="0 0 12 10">
<polygon fill="#FFF" points="7.2 7.5 7.2 -1.3 8.7 -1.3 8.6 9.1 2.7 8.7 2.7 7.2" transform="rotate(37 5.718 3.896)"/>
</svg>

After

Width:  |  Height:  |  Size: 212 B

@@ -0,0 +1,77 @@
<!DOCTYPE html>
<html lang="en">
<head>
<script>
let update = () => {};
function connectElgatoStreamDeckSocket(inPort, inPropertyInspectorUUID, inRegisterEvent, inInfo, inActionInfo) {
const websocket = new WebSocket("ws://localhost:" + inPort);
inActionInfo = JSON.parse(inActionInfo);
websocket.onopen = () => {
websocket.send(JSON.stringify({
event: inRegisterEvent,
uuid: inPropertyInspectorUUID,
}));
};
const down = document.getElementById("down");
down.value = inActionInfo.payload.settings.down ?? "";
const up = document.getElementById("up");
up.value = inActionInfo.payload.settings.up ?? "";
update = () => {
websocket.send(JSON.stringify({
event: "setSettings",
context: inActionInfo.context,
payload: {
down: down.value,
up: up.value,
},
}));
};
}
</script>
<link rel="stylesheet" href="sdpi.css" />
</head>
<body>
<div type="textarea" class="sdpi-item">
<div class="sdpi-item-label">Key down</div>
<span class="sdpi-item-value textarea">
<textarea oninput="update();" id="down" style="font-family: monospace" placeholder="Input to be executed on key down"></textarea>
</span>
</div>
<div type="textarea" class="sdpi-item">
<div class="sdpi-item-label">Key up</div>
<span class="sdpi-item-value textarea">
<textarea oninput="update();" id="up" style="font-family: monospace" placeholder="Input to be executed on key up"></textarea>
</span>
</div>
<details open>
<summary style="margin-bottom: 0.25rem; font-size: 1.25rem">Details</summary>
<code style="word-break: break-all">[t("hello world!"),m(10,10,r),s(5),b(l),k(ctrl,p),k(uni('a')),k(ctrl,r)]</code>
<p>The example above becomes something like this:</p>
<!-- deno-fmt-ignore -->
<code style="white-space: pre;">[
Text("hello world!"),
MoveMouse(10, 10, Relative),
Scroll(5, Vertical),
Button(Left, Click),
Key(Control, Press),
Key(Unicode('a'), Click),
Key(Control, Release),
]</code>
<p>
As you can see, <code>Vertical</code> is the default for the <code>Scroll</code> action if an axis is not specified,
<code>Click</code> (press followed by release) is the default for the <code>Button</code> and <code>Key</code> actions if a direction is not specified, and so on.
</p>
<!-- deno-fmt-ignore -->
<p>
You can find a list of all of the keycodes
<span style="color: dodgerblue; cursor: pointer;" onclick="window.open('https://github.com/enigo-rs/enigo/blob/v0.3.0/src/keycodes.rs', '_blank');">here</span>.
Please note that not all keycodes are available on all operating systems, as is marked on each such keycode on the line above it.
You can find the possible buttons, directions, axis and coordinate types
<span style="color: dodgerblue; cursor: pointer;" onclick="window.open('https://github.com/enigo-rs/enigo/blob/v0.3.0/src/lib.rs', '_blank');">here</span>.
</p>
</details>
</body>
</html>
@@ -0,0 +1,62 @@
<!DOCTYPE html>
<html lang="en">
<head>
<script>
let update = () => {};
function connectElgatoStreamDeckSocket(inPort, inPropertyInspectorUUID, inRegisterEvent, inInfo, inActionInfo) {
const websocket = new WebSocket("ws://localhost:" + inPort);
inActionInfo = JSON.parse(inActionInfo);
websocket.onopen = () => {
websocket.send(JSON.stringify({
event: inRegisterEvent,
uuid: inPropertyInspectorUUID,
}));
};
const down = document.getElementById("down");
down.value = inActionInfo.payload.settings.down ?? "";
const up = document.getElementById("up");
up.value = inActionInfo.payload.settings.up ?? "";
const file = document.getElementById("file");
file.value = inActionInfo.payload.settings.file ?? "";
const show = document.getElementById("show");
show.checked = inActionInfo.payload.settings.show ?? false;
update = () => {
websocket.send(JSON.stringify({
event: "setSettings",
context: inActionInfo.context,
payload: {
down: down.value,
up: up.value,
file: file.value,
show: show.checked,
},
}));
};
}
</script>
<link rel="stylesheet" href="sdpi.css" />
</head>
<body>
<div class="sdpi-item">
<div class="sdpi-item-label">Key down</div>
<input id="down" oninput="update();" class="sdpi-item-value" />
</div>
<div class="sdpi-item">
<div class="sdpi-item-label">Key up</div>
<input id="up" oninput="update();" class="sdpi-item-value" />
</div>
<br />
<div class="sdpi-item">
<div class="sdpi-item-label">Write to path</div>
<input id="file" oninput="update();" class="sdpi-item-value" />
</div>
<div class="sdpi-item">
<div class="sdpi-item-label">Show on key</div>
<input id="show" type="checkbox" oninput="update();" class="sdpi-item-value" />
<label for="show"><span></span></label>
</div>
</body>
</html>
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,46 @@
<!DOCTYPE html>
<html lang="en">
<head>
<script>
let update = () => {};
function connectElgatoStreamDeckSocket(inPort, inPropertyInspectorUUID, inRegisterEvent, inInfo, inActionInfo) {
const websocket = new WebSocket("ws://localhost:" + inPort);
inActionInfo = JSON.parse(inActionInfo);
websocket.onopen = () => {
websocket.send(JSON.stringify({
event: inRegisterEvent,
uuid: inPropertyInspectorUUID,
}));
};
const device = document.getElementById("device");
device.value = inActionInfo.payload.settings.device ?? inActionInfo.device;
const profile = document.getElementById("profile");
profile.value = inActionInfo.payload.settings.profile ?? "";
update = () => {
websocket.send(JSON.stringify({
event: "setSettings",
context: inActionInfo.context,
payload: {
device: device.value ?? inActionInfo.device,
profile: profile.value,
},
}));
};
}
</script>
<link rel="stylesheet" href="sdpi.css" />
</head>
<body>
<div class="sdpi-item">
<div class="sdpi-item-label">Device ID</div>
<input id="device" oninput="update();" class="sdpi-item-value" />
</div>
<div class="sdpi-item">
<div class="sdpi-item-label">Profile</div>
<input id="profile" oninput="update();" class="sdpi-item-value" />
</div>
</body>
</html>
@@ -0,0 +1,3 @@
edition = "2021"
hard_tabs = true
newline_style = "Unix"
@@ -0,0 +1,38 @@
use openaction::*;
use enigo::{
agent::{Agent, Token},
Enigo, Settings,
};
pub fn key_down(event: KeyEvent) -> EventHandlerResult {
if let Some(value) = event.payload.settings.as_object().unwrap().get("down") {
let value = value.as_str().unwrap();
if value.trim().is_empty() {
return Ok(());
}
let mut enigo = Enigo::new(&Settings::default())?;
let tokens: Vec<Token> = ron::from_str(value)?;
for token in tokens {
enigo.execute(&token).unwrap();
}
}
Ok(())
}
pub fn key_up(event: KeyEvent) -> EventHandlerResult {
if let Some(value) = event.payload.settings.as_object().unwrap().get("up") {
let value = value.as_str().unwrap();
if value.trim().is_empty() {
return Ok(());
}
let mut enigo = Enigo::new(&Settings::default())?;
let tokens: Vec<Token> = ron::from_str(value)?;
for token in tokens {
enigo.execute(&token).unwrap();
}
}
Ok(())
}
@@ -0,0 +1,51 @@
mod input_simulation;
mod run_command;
mod switch_profile;
use openaction::*;
struct GlobalEventHandler {}
impl openaction::GlobalEventHandler for GlobalEventHandler {}
struct ActionEventHandler {}
impl openaction::ActionEventHandler for ActionEventHandler {
async fn key_down(
&self,
event: KeyEvent,
_outbound: &mut openaction::OutboundEventManager,
) -> EventHandlerResult {
match &event.action[..] {
"com.amansprojects.starterpack.runcommand" => run_command::key_down(event),
"com.amansprojects.starterpack.inputsimulation" => input_simulation::key_down(event),
_ => Ok(()),
}
}
async fn key_up(
&self,
event: KeyEvent,
_outbound: &mut openaction::OutboundEventManager,
) -> EventHandlerResult {
match &event.action[..] {
"com.amansprojects.starterpack.runcommand" => run_command::key_up(event),
"com.amansprojects.starterpack.inputsimulation" => input_simulation::key_up(event),
"com.amansprojects.starterpack.switchprofile" => switch_profile::key_up(event),
_ => Ok(()),
}
}
}
#[tokio::main]
async fn main() {
simplelog::TermLogger::init(
simplelog::LevelFilter::Debug,
simplelog::Config::default(),
simplelog::TerminalMode::Stdout,
simplelog::ColorChoice::Never,
)
.unwrap();
if let Err(error) = init_plugin(GlobalEventHandler {}, ActionEventHandler {}).await {
log::error!("Failed to initialise plugin: {}", error);
}
}
@@ -0,0 +1,85 @@
use std::io::Read;
use std::process::{Command, Stdio};
use openaction::*;
async fn run_command(event: KeyEvent, action: &str) -> EventHandlerResult {
let settings = event.payload.settings.as_object().unwrap();
let Some(value) = settings.get(action).map(|v| v.as_str().unwrap()) else {
return Ok(());
};
if value.is_empty() {
return Ok(());
}
#[cfg(unix)]
let command = if std::env::var("container").is_ok() {
"flatpak-spawn"
} else {
"sh"
};
#[cfg(unix)]
let extra_args = if std::env::var("container").is_ok() {
vec!["--host", "sh", "-c"]
} else {
vec!["-c"]
};
#[cfg(windows)]
let command = "cmd";
#[cfg(windows)]
let extra_args = ["/C"];
let (mut reader, writer) = os_pipe::pipe()?;
let mut command = Command::new(command);
command
.args(extra_args)
.arg(value)
.stdout(Stdio::from(writer.try_clone()?))
.stderr(Stdio::from(writer));
command.spawn()?.wait()?;
drop(command);
let mut output = String::new();
reader.read_to_string(&mut output)?;
if let Some(path) = settings.get("file").map(|v| v.as_str().unwrap()) {
if !path.is_empty() {
tokio::fs::write(path, &output).await?;
}
}
if settings
.get("show")
.unwrap_or(&serde_json::Value::Bool(false))
.as_bool()
.unwrap()
{
let mut lock = OUTBOUND_EVENT_MANAGER.lock().await;
let outbound = lock.as_mut().unwrap();
outbound
.set_title(event.context, Some(output.trim().to_owned()), None)
.await?;
}
Ok(())
}
pub fn key_down(event: KeyEvent) -> EventHandlerResult {
tokio::spawn(async move {
if let Err(error) = run_command(event, "down").await {
log::warn!("Failed to run command: {error}");
}
});
Ok(())
}
pub fn key_up(event: KeyEvent) -> EventHandlerResult {
tokio::spawn(async move {
if let Err(error) = run_command(event, "up").await {
log::warn!("Failed to run command: {error}");
}
});
Ok(())
}
@@ -0,0 +1,39 @@
use openaction::*;
// Non-spec OpenDeck-specific protocols are used in this file.
#[derive(serde::Serialize)]
struct SwitchProfileEvent {
event: &'static str,
device: String,
profile: String,
}
pub fn key_up(event: KeyEvent) -> EventHandlerResult {
if let Some(value) = event.payload.settings.as_object().unwrap().get("profile") {
let (mut stream, _) = tungstenite::connect(format!(
"ws://localhost:{}",
std::env::args().nth(2).unwrap()
))?;
stream.write(tungstenite::Message::text(serde_json::to_string(
&SwitchProfileEvent {
event: "switchProfile",
device: event
.payload
.settings
.as_object()
.unwrap()
.get("device")
.unwrap()
.as_str()
.unwrap()
.to_owned(),
profile: value.as_str().unwrap().to_owned(),
},
)?))?;
stream.close(None)?;
stream.flush()?;
}
Ok(())
}