Add event system for SteamClient callbacks (untested; WIP)
This commit is contained in:
parent
a52309484e
commit
17c61907a9
18 changed files with 446 additions and 33 deletions
32
README.md
32
README.md
|
@ -1,6 +1,8 @@
|
|||
# React-Frontend Plugin Template
|
||||
# Caylon
|
||||
|
||||
Reference example for using [decky-frontend-lib](https://github.com/SteamDeckHomebrew/decky-frontend-lib) in a [PluginLoader](https://github.com/SteamDeckHomebrew/PluginLoader) plugin.
|
||||
Custom widgets for simple tasks.
|
||||
|
||||
Adapted from a template for using [decky-frontend-lib](https://github.com/SteamDeckHomebrew/decky-frontend-lib) in a [Decky Loader](https://github.com/SteamDeckHomebrew/PluginLoader) plugin.
|
||||
|
||||
## PluginLoader Discord [![Chat](https://img.shields.io/badge/chat-on%20discord-7289da.svg)](https://discord.gg/ZU74G2NJzk)
|
||||
|
||||
|
@ -8,30 +10,18 @@ Reference example for using [decky-frontend-lib](https://github.com/SteamDeckHom
|
|||
|
||||
### Dependencies
|
||||
|
||||
This template relies on the user having `pnpm` installed on their system.
|
||||
This can be downloaded from `npm` itself which is recommended.
|
||||
|
||||
#### Linux
|
||||
|
||||
```bash
|
||||
sudo npm i -g pnpm
|
||||
```
|
||||
This project relies on `npm` and `rustup`.
|
||||
For building on another Linux PC, Rust toolchain `x86_64-unknown-linux-musl` must also be installed.
|
||||
|
||||
### Getting Started
|
||||
|
||||
1. Clone the repository to use as an example for making your plugin.
|
||||
2. In your clone of the repository run these commands:
|
||||
1. ``pnpm i``
|
||||
2. ``pnpm run build``
|
||||
3. You should do this every time you make changes to your plugin.
|
||||
|
||||
Note: If you are recieveing build errors due to an out of date library, you should run this command inside of your repository:
|
||||
|
||||
```bash
|
||||
pnpm update decky-frontend-lib --latest
|
||||
```
|
||||
2. In your clone of the repository run these commands to build the front-end:
|
||||
1. ``npm install``
|
||||
2. ``npm run build``
|
||||
3. From `backend/`, Run `./build.sh` to build the back-end.
|
||||
|
||||
### Distribution
|
||||
|
||||
WIP. Check back in later.
|
||||
IDK, maybe a bit of custom spins as well as a barebones offering from me
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ clap = { version = "3.2", features = ["derive", "std"], default-features = false
|
|||
|
||||
# async
|
||||
tokio = { version = "*", features = ["time"] }
|
||||
async-trait = { version = "0.1.57" }
|
||||
async-trait = { version = "0.1" }
|
||||
|
||||
# json
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
|
|
@ -2,6 +2,7 @@ mod about;
|
|||
pub(crate) mod async_utils;
|
||||
mod get_display;
|
||||
mod get_items;
|
||||
mod on_event;
|
||||
mod on_javascript_result;
|
||||
mod on_update;
|
||||
mod reload;
|
||||
|
@ -12,6 +13,7 @@ mod types;
|
|||
pub use about::get_about;
|
||||
pub use get_display::GetDisplayEndpoint;
|
||||
pub use get_items::get_items;
|
||||
pub use on_event::on_event;
|
||||
pub use on_javascript_result::on_javascript_result;
|
||||
pub use on_update::on_update;
|
||||
pub use reload::reload;
|
||||
|
|
36
backend/src/api/on_event.rs
Normal file
36
backend/src/api/on_event.rs
Normal file
|
@ -0,0 +1,36 @@
|
|||
use std::sync::{Mutex, mpsc::Sender};
|
||||
|
||||
use usdpl_back::core::serdes::Primitive;
|
||||
|
||||
use super::ApiParameterType;
|
||||
|
||||
use crate::runtime::{QueueAction, QueueItem};
|
||||
|
||||
/// API web method to notify the back-end of a steam event (i.e. callback through SteamClient API)
|
||||
pub fn on_event(sender: Sender<QueueItem>) -> impl Fn(ApiParameterType) -> ApiParameterType {
|
||||
let sender = Mutex::new(sender);
|
||||
move |params: ApiParameterType| {
|
||||
log::debug!("API: on_event");
|
||||
if let Some(Primitive::Json(event_data)) = params.get(0) {
|
||||
match serde_json::from_str(event_data) {
|
||||
Ok(event_obj) => {
|
||||
sender.lock().unwrap().send(
|
||||
QueueItem {
|
||||
action: QueueAction::DoSteamEvent {
|
||||
event: event_obj,
|
||||
}
|
||||
}
|
||||
).unwrap();
|
||||
log::info!("Sent steam event");
|
||||
vec![true.into()]
|
||||
},
|
||||
Err(e) => {
|
||||
log::warn!("Failed to parse event json: {}", e);
|
||||
vec![false.into()]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
vec![false.into()]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,44 @@
|
|||
#![allow(non_snake_case)]
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[serde(tag = "event_type", content = "event_data")]
|
||||
pub enum SteamEvent {
|
||||
#[serde(rename = "download-items")]
|
||||
DownloadItems(SteamDownloadInfo),
|
||||
#[serde(rename = "download-overview")]
|
||||
DownloadOverview(SteamDownloadOverview),
|
||||
#[serde(rename = "achievement-notification")]
|
||||
AchievementNotification(SteamAchievementNotification),
|
||||
#[serde(rename = "bluetooth-state")]
|
||||
BluetoothState(SteamBluetoothState),
|
||||
#[serde(rename = "connectivity-test-change")]
|
||||
ConnectivityTestChange(SteamConnectivityTestChange),
|
||||
#[serde(rename = "network-diagnostic")]
|
||||
NetworkDiagnostic(SteamNetworkDiagnostic),
|
||||
#[serde(rename = "audio-device-added")]
|
||||
AudioDeviceAdded(SteamAudioDevice),
|
||||
#[serde(rename = "audio-device-removed")]
|
||||
AudioDeviceRemoved(SteamAudioDevice),
|
||||
#[serde(rename = "brightness")]
|
||||
Brightness(SteamBrightness),
|
||||
#[serde(rename = "airplane")]
|
||||
Airplane(SteamAirplane),
|
||||
#[serde(rename = "battery")]
|
||||
Battery(SteamBattery),
|
||||
#[serde(rename = "screenshot-notification")]
|
||||
ScreenshotNotification(SteamScreenshotNotification),
|
||||
#[serde(rename = "controller-input-message")]
|
||||
ControllerInputMessage(Vec<SteamControllerInputMessage>),
|
||||
#[serde(rename = "app-lifetime-notification")]
|
||||
AppLifetimeNotification(SteamAppLifetimeNotification),
|
||||
#[serde(rename = "game-action-start")]
|
||||
GameActionStart(SteamGameAction),
|
||||
}
|
||||
|
||||
// list of these is second callback param for SteamClient.Downloads.RegisterForDownloadItems
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct SteamDownloadInfo {
|
||||
pub struct SteamDownloadItem {
|
||||
pub active: bool,
|
||||
pub appid: usize,
|
||||
pub buildid: usize,
|
||||
|
@ -11,6 +46,13 @@ pub struct SteamDownloadInfo {
|
|||
pub paused: bool,
|
||||
}
|
||||
|
||||
// both params for callback for SteamClient.Downloads.RegisterForDownloadItems
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct SteamDownloadInfo {
|
||||
pub paused: bool,
|
||||
pub items: Vec<SteamDownloadItem>,
|
||||
}
|
||||
|
||||
// only callback param for SteamClient.Downloads.RegisterForDownloadOverview
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct SteamDownloadOverview {
|
||||
|
@ -103,7 +145,7 @@ pub struct SteamNetworkDiagnostic {
|
|||
}
|
||||
|
||||
// only param of callback for SteamClient.System.Audio.RegisterForDeviceAdded
|
||||
// and SteamClient.System.Audio.RegisterForDeviceAdded
|
||||
// and SteamClient.System.Audio.RegisterForDeviceRemoved
|
||||
// Also type of vecDevices of await SteamClient.System.Audio.GetDevices()
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct SteamAudioDevice {
|
||||
|
@ -196,3 +238,19 @@ pub struct SteamControllerInputMessage {
|
|||
pub nController: usize,
|
||||
pub strActionName: String,
|
||||
}
|
||||
|
||||
// only param of callback for SteamClient.GameSessions.RegisterForAppLifetimeNotifications
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct SteamAppLifetimeNotification {
|
||||
pub bRunning: bool,
|
||||
pub nInstanceID: usize,
|
||||
pub unAppID: usize,
|
||||
}
|
||||
|
||||
// params of callback for SteamClient.Apps.RegisterForGameActionStart
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct SteamGameAction {
|
||||
pub param0: usize, // idk what this is supposed to indicate
|
||||
pub gameID: usize,
|
||||
pub action: String, // idk what possible values are
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use serde::{Serialize, Deserialize};
|
||||
|
||||
use super::{ButtonConfig, ToggleConfig, SliderConfig, ReadingConfig, ResultDisplayConfig};
|
||||
use super::{ButtonConfig, ToggleConfig, SliderConfig, ReadingConfig, ResultDisplayConfig, EventDisplayConfig};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[serde(tag = "element")]
|
||||
|
@ -15,4 +15,6 @@ pub enum ElementConfig {
|
|||
ReadingDisplay(ReadingConfig),
|
||||
#[serde(rename = "result-display")]
|
||||
ResultDisplay(ResultDisplayConfig),
|
||||
#[serde(rename = "event-display")]
|
||||
EventDisplay(EventDisplayConfig)
|
||||
}
|
||||
|
|
67
backend/src/config/event_display.rs
Normal file
67
backend/src/config/event_display.rs
Normal file
|
@ -0,0 +1,67 @@
|
|||
use serde::{Serialize, Deserialize};
|
||||
|
||||
use super::TopLevelActionConfig;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct EventDisplayConfig {
|
||||
pub title: String,
|
||||
/// Type of event to listen for
|
||||
pub event: EventType,
|
||||
/// Action to perform when the event occurs
|
||||
pub on_event: TopLevelActionConfig,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub enum EventType {
|
||||
#[serde(rename = "achievement")]
|
||||
Achievement,
|
||||
#[serde(rename = "airplane", alias = "airplane mode")]
|
||||
Airplane,
|
||||
#[serde(rename = "bluetooth")]
|
||||
Bluetooth,
|
||||
#[serde(rename = "brightness")]
|
||||
Brightness,
|
||||
#[serde(rename = "screenshot")]
|
||||
Screenshot,
|
||||
#[serde(rename = "game-start", alias = "game start")]
|
||||
GameStart,
|
||||
#[serde(rename = "game-lifetime", alias = "game lifetime")]
|
||||
GameLifetime,
|
||||
}
|
||||
|
||||
impl EventType {
|
||||
#[inline]
|
||||
pub fn is_achievement(&self) -> bool {
|
||||
matches!(self, Self::Achievement)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_airplane(&self) -> bool {
|
||||
matches!(self, Self::Airplane)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_bluetooth(&self) -> bool {
|
||||
matches!(self, Self::Bluetooth)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_brightness(&self) -> bool {
|
||||
matches!(self, Self::Brightness)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_screenshot(&self) -> bool {
|
||||
matches!(self, Self::Screenshot)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_game_start(&self) -> bool {
|
||||
matches!(self, Self::GameStart)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_game_lifetime(&self) -> bool {
|
||||
matches!(self, Self::GameLifetime)
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ mod action;
|
|||
mod base;
|
||||
mod button;
|
||||
mod element;
|
||||
mod event_display;
|
||||
mod reading;
|
||||
mod result_display;
|
||||
mod slider;
|
||||
|
@ -14,6 +15,7 @@ pub use action::{TopLevelActionConfig, ActionConfig, CommandAction, MirrorAction
|
|||
pub use base::BaseConfig;
|
||||
pub use button::ButtonConfig;
|
||||
pub use element::ElementConfig;
|
||||
pub use event_display::{EventDisplayConfig, EventType};
|
||||
pub use reading::ReadingConfig;
|
||||
pub use result_display::ResultDisplayConfig;
|
||||
pub use slider::SliderConfig;
|
||||
|
|
|
@ -7,5 +7,6 @@ pub struct ReadingConfig {
|
|||
pub title: String,
|
||||
/// Period in milliseconds, or None/null for non-repeating actions
|
||||
pub period_ms: Option<u64>,
|
||||
/// Action to perform on every period
|
||||
pub on_period: TopLevelActionConfig,
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@ use serde::{Serialize, Deserialize};
|
|||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct ResultDisplayConfig {
|
||||
pub title: String,
|
||||
/// index of element who's action's result will be used
|
||||
/// Index of element who's action's result will be used
|
||||
pub result_of: usize,
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ fn main() -> Result<(), ()> {
|
|||
.register_blocking("get_items", api::get_items(sender.clone()))
|
||||
.register("on_javascript_result", api::on_javascript_result(sender.clone()))
|
||||
.register("on_update", api::on_update(sender.clone()))
|
||||
.register("on_event", api::on_event(sender.clone()))
|
||||
.register_blocking("reload", api::reload(sender.clone()));
|
||||
let _exec_handle = executor.spawn();
|
||||
instance.run_blocking()
|
||||
|
|
|
@ -31,6 +31,7 @@ impl<'a> SeqAct<'a> for Actor {
|
|||
ElementConfig::Slider(s) => TopLevelActorType::build(&s.on_set, parameter.1),
|
||||
ElementConfig::ReadingDisplay(r) => TopLevelActorType::build(&r.on_period, parameter.1),
|
||||
ElementConfig::ResultDisplay(_) => Err(format!("Item #{} is a ResultDisplay, which can't act", i)),
|
||||
ElementConfig::EventDisplay(e) => TopLevelActorType::build(&e.on_event, parameter.1),
|
||||
}?;
|
||||
Ok(Self {
|
||||
actor_type: a_type,
|
||||
|
@ -168,6 +169,7 @@ where
|
|||
outputs: std::collections::VecDeque<Expected>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub enum Expected {
|
||||
Output(Primitive),
|
||||
BuildErr(ActError),
|
||||
|
|
|
@ -85,6 +85,7 @@ mod test {
|
|||
#[test]
|
||||
fn json_actor_test() {
|
||||
//let (runtime_io, _result_rx, _js_rx) = crate::runtime::RuntimeIO::mock();
|
||||
// test data """borrowed""" from https://jmespath.org/
|
||||
SeqActTestHarness::builder(JsonActor::build)
|
||||
// test 1 ---
|
||||
.with_io(
|
||||
|
|
|
@ -2,6 +2,7 @@ use std::sync::mpsc::Sender;
|
|||
|
||||
use usdpl_back::core::serdes::Primitive;
|
||||
|
||||
use crate::api::SteamEvent;
|
||||
use crate::config::{AboutConfig, ElementConfig};
|
||||
|
||||
/// An API operation for the executor to perform
|
||||
|
@ -27,6 +28,9 @@ pub enum QueueAction {
|
|||
id: usize,
|
||||
value: Primitive,
|
||||
},
|
||||
DoSteamEvent {
|
||||
event: SteamEvent,
|
||||
},
|
||||
}
|
||||
|
||||
/// Wrapper for an executor command
|
||||
|
|
|
@ -2,10 +2,43 @@ use std::thread;
|
|||
use std::path::{Path, PathBuf};
|
||||
use std::sync::mpsc::{self, Receiver, Sender};
|
||||
|
||||
use usdpl_back::core::serdes::Primitive;
|
||||
|
||||
use crate::config::{BaseConfig, ElementConfig};
|
||||
use crate::api::SteamEvent;
|
||||
use super::{QueueItem, QueueAction, Act, SeqAct};
|
||||
use super::{ResultRouter, RouterCommand, JavascriptRouter, JavascriptCommand};
|
||||
|
||||
macro_rules! wire_steam_event {
|
||||
(
|
||||
$func_name: ident,
|
||||
$display_name: literal,
|
||||
$self: ident,
|
||||
$conf: ident,
|
||||
$item: ident,
|
||||
$index: ident,
|
||||
$event: ident,
|
||||
$event_json_cache: ident,
|
||||
) => {
|
||||
if $conf.event.$func_name() {
|
||||
let value = cache_event_maybe(&$event, &mut $event_json_cache);
|
||||
match super::Actor::build($item, ($index, &$self.handlers)) {
|
||||
Ok(act) => {
|
||||
let respond_to = $self.handlers.result.clone();
|
||||
thread::spawn(move || {
|
||||
let result = act.run(value);
|
||||
match respond_to.send(RouterCommand::HandleResult{$index, result}) {
|
||||
Ok(_) => {},
|
||||
Err(_) => log::warn!("Failed to send DoSteamEvent `{}` response for item #{}", $display_name, $index),
|
||||
}
|
||||
});
|
||||
},
|
||||
Err(e) => log::error!("Failed to build DoSteamEvent `{}` actor for item #{}: {}", $display_name, $index, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RuntimeIO {
|
||||
pub result: Sender<RouterCommand>,
|
||||
|
@ -83,7 +116,7 @@ impl ExecutorState {
|
|||
},
|
||||
QueueAction::DoUpdate { index, value } => {
|
||||
// trigger update event for element
|
||||
// i.e. on_click, on_toggle, etc. action
|
||||
// e.g. on_click, on_toggle, etc. action
|
||||
if let Some(item) = self.config_data.get_item(index) {
|
||||
match super::Actor::build(item, (index, &self.handlers)) {
|
||||
Ok(act) => {
|
||||
|
@ -151,6 +184,97 @@ impl ExecutorState {
|
|||
log::error!("Failed to send to JavascriptRouter again, did not SetJavascriptSubscriber");
|
||||
}
|
||||
}
|
||||
},
|
||||
QueueAction::DoSteamEvent { event } => {
|
||||
// handle steam event for all elements that may be listening
|
||||
let mut event_json_cache: Option<String> = None;
|
||||
for (index, item) in self.config_data.items().iter().enumerate() {
|
||||
match item {
|
||||
ElementConfig::EventDisplay(conf) => {
|
||||
match &event {
|
||||
SteamEvent::DownloadItems(_x) => log::error!("Unsupported event"),
|
||||
SteamEvent::DownloadOverview(_x) => log::error!("Unsupported event"),
|
||||
SteamEvent::AchievementNotification(x) => wire_steam_event!{
|
||||
is_achievement,
|
||||
"achievement",
|
||||
self,
|
||||
conf,
|
||||
item,
|
||||
index,
|
||||
x,
|
||||
event_json_cache,
|
||||
},
|
||||
SteamEvent::BluetoothState(x) => wire_steam_event!{
|
||||
is_bluetooth,
|
||||
"bluetooth",
|
||||
self,
|
||||
conf,
|
||||
item,
|
||||
index,
|
||||
x,
|
||||
event_json_cache,
|
||||
},
|
||||
SteamEvent::ConnectivityTestChange(_x) => log::error!("Unsupported event"),
|
||||
SteamEvent::NetworkDiagnostic(_x) => log::error!("Unsupported event"),
|
||||
SteamEvent::AudioDeviceAdded(_x) => log::error!("Unsupported event"),
|
||||
SteamEvent::AudioDeviceRemoved(_x) => log::error!("Unsupported event"),
|
||||
SteamEvent::Brightness(x) => wire_steam_event!{
|
||||
is_brightness,
|
||||
"brightness",
|
||||
self,
|
||||
conf,
|
||||
item,
|
||||
index,
|
||||
x,
|
||||
event_json_cache,
|
||||
},
|
||||
SteamEvent::Airplane(x) => wire_steam_event!{
|
||||
is_airplane,
|
||||
"airplane",
|
||||
self,
|
||||
conf,
|
||||
item,
|
||||
index,
|
||||
x,
|
||||
event_json_cache,
|
||||
},
|
||||
SteamEvent::Battery(_x) => log::error!("Unsupported event"),
|
||||
SteamEvent::ScreenshotNotification(x) => wire_steam_event!{
|
||||
is_screenshot,
|
||||
"screenshot",
|
||||
self,
|
||||
conf,
|
||||
item,
|
||||
index,
|
||||
x,
|
||||
event_json_cache,
|
||||
},
|
||||
SteamEvent::ControllerInputMessage(_x) => log::error!("Unsupported event"),
|
||||
SteamEvent::AppLifetimeNotification(x) => wire_steam_event!{
|
||||
is_game_lifetime,
|
||||
"game-lifetime",
|
||||
self,
|
||||
conf,
|
||||
item,
|
||||
index,
|
||||
x,
|
||||
event_json_cache,
|
||||
},
|
||||
SteamEvent::GameActionStart(x) => wire_steam_event!{
|
||||
is_game_start,
|
||||
"game-start",
|
||||
self,
|
||||
conf,
|
||||
item,
|
||||
index,
|
||||
x,
|
||||
event_json_cache,
|
||||
},
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -183,3 +307,14 @@ impl ExecutorState {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn cache_event_maybe<T: serde::Serialize>(event: &T, cache: &mut Option<String>) -> Primitive {
|
||||
if let Some(cached) = cache {
|
||||
Primitive::Json(cached.to_owned())
|
||||
} else {
|
||||
let dump = serde_json::to_string(event).unwrap();
|
||||
*cache = Some(dump.clone());
|
||||
Primitive::Json(dump)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ const USDPL_PORT: number = 25717;
|
|||
|
||||
// Utility
|
||||
|
||||
export function resolve(promise: Promise<any>, setter: any) {
|
||||
export function resolve<T>(promise: Promise<T>, setter: (x: T) => void) {
|
||||
(async function () {
|
||||
let data = await promise;
|
||||
if (data != null) {
|
||||
|
@ -64,7 +64,18 @@ export type CResultDisplay = {
|
|||
result_of: number;
|
||||
}
|
||||
|
||||
export type CElement = CButton | CToggle | CSlider | CReading | CResultDisplay;
|
||||
export type CEventDisplay = {
|
||||
element: string; // "event-display"
|
||||
title: string;
|
||||
event: string;
|
||||
}
|
||||
|
||||
export type CSteamEvent = {
|
||||
event_type: string; // enum; see steam_types.rs
|
||||
event_data: any;
|
||||
}
|
||||
|
||||
export type CElement = CButton | CToggle | CSlider | CReading | CResultDisplay | CEventDisplay;
|
||||
|
||||
export type CErrorResult = {
|
||||
result: string; // "error"
|
||||
|
@ -91,7 +102,7 @@ export async function getElements(): Promise<CElement[]> {
|
|||
return (await call_backend("get_items", []))[0];
|
||||
}
|
||||
|
||||
export async function onUpdate(index: number, value: any): Promise<any> {
|
||||
export async function onUpdate(index: number, value: any): Promise<boolean> {
|
||||
return (await call_backend("on_update", [index, value]))[0];
|
||||
}
|
||||
|
||||
|
@ -111,6 +122,10 @@ export async function getJavascriptToRun(): Promise<CJavascriptResponse> {
|
|||
return (await call_backend("get_javascript_to_run", []))[0];
|
||||
}
|
||||
|
||||
export async function onJavascriptResult(id: number, value: any): Promise<any> {
|
||||
export async function onJavascriptResult(id: number, value: any): Promise<boolean> {
|
||||
return (await call_backend("on_javascript_result", [id, value]))[0];
|
||||
}
|
||||
|
||||
export async function onSteamEvent(data: CSteamEvent): Promise<boolean> {
|
||||
return (await call_backend("on_javascript_result", [data]))[0];
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import { GiWashingMachine } from "react-icons/gi";
|
|||
|
||||
import { get_value, set_value } from "usdpl-front";
|
||||
import * as backend from "./backend";
|
||||
import {register_for_steam_events, unregister_for_steam_events} from "./steam_events";
|
||||
|
||||
const FieldWithSeparator = joinClassNames(gamepadDialogClasses.Field, gamepadDialogClasses.WithBottomSeparatorStandard);
|
||||
|
||||
|
@ -65,6 +66,7 @@ function onGetElements() {
|
|||
backend.resolve(backend.getDisplay(i), displayCallback(i));
|
||||
}
|
||||
backend.resolve(backend.getJavascriptToRun(), jsCallback());
|
||||
register_for_steam_events();
|
||||
}
|
||||
|
||||
const eval2 = eval;
|
||||
|
@ -110,6 +112,7 @@ function jsCallback() {
|
|||
console.warn("CAYLON: backend connection failed");
|
||||
}
|
||||
backend.resolve(backend.getJavascriptToRun(), jsCallback());
|
||||
register_for_steam_events();
|
||||
})();
|
||||
|
||||
const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => {
|
||||
|
@ -181,6 +184,8 @@ function buildHtmlElement(element: backend.CElement, index: number, updateIdc: a
|
|||
return buildReading(element as backend.CReading, index, updateIdc);
|
||||
case "result-display":
|
||||
return buildResultDisplay(element as backend.CResultDisplay, index, updateIdc);
|
||||
case "event-display":
|
||||
return buildEventDisplay(element as backend.CEventDisplay, index, updateIdc);
|
||||
}
|
||||
console.error("CAYLON: Unsupported element", element);
|
||||
return <div>Unsupported</div>;
|
||||
|
@ -256,6 +261,17 @@ function buildResultDisplay(element: backend.CResultDisplay, index: number, _upd
|
|||
);
|
||||
}
|
||||
|
||||
function buildEventDisplay(element: backend.CEventDisplay, index: number, _updateIdc: any) {
|
||||
return (
|
||||
<div className={FieldWithSeparator}>
|
||||
<div className={gamepadDialogClasses.FieldLabelRow}>
|
||||
<div className={gamepadDialogClasses.FieldLabel}>{element.title}</div>
|
||||
<div className={gamepadDialogClasses.FieldChildren}>{get_value(DISPLAY_KEY + index.toString())}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function buildAbout() {
|
||||
if (about == null) {
|
||||
return [];
|
||||
|
@ -360,12 +376,13 @@ function buildAbout() {
|
|||
}
|
||||
|
||||
export default definePlugin((serverApi: ServerAPI) => {
|
||||
register_for_steam_events()
|
||||
return {
|
||||
title: <div className={staticClasses.Title}>{about == null? "Kaylon": about.name}</div>,
|
||||
title: <div className={staticClasses.Title}>{about == null? "Caylon": about.name}</div>,
|
||||
content: <Content serverAPI={serverApi} />,
|
||||
icon: <GiWashingMachine />,
|
||||
onDismount() {
|
||||
//serverApi.routerHook.removeRoute("/decky-plugin-test");
|
||||
unregister_for_steam_events();
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
80
src/steam_events.ts
Normal file
80
src/steam_events.ts
Normal file
|
@ -0,0 +1,80 @@
|
|||
import * as backend from "./backend";
|
||||
|
||||
type Unregisterer = {
|
||||
unregister: () => void;
|
||||
}
|
||||
|
||||
let callbacks: Unregisterer[] = [];
|
||||
|
||||
export function register_for_steam_events() {
|
||||
unregister_for_steam_events();
|
||||
|
||||
//@ts-ignore
|
||||
SteamClient.Apps.RegisterForGameActionStart((p0, p1, p2) => {
|
||||
backend.onSteamEvent( {
|
||||
event_type: "game-action-start",
|
||||
event_data: {
|
||||
param0: p0,
|
||||
gameID: p1,
|
||||
action: p2,
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
//@ts-ignore
|
||||
SteamClient.GameSessions.RegisterForAppLifetimeNotifications((p0) => {
|
||||
backend.onSteamEvent( {
|
||||
event_type: "app-lifetime-notification",
|
||||
event_data: p0
|
||||
});
|
||||
});
|
||||
|
||||
//@ts-ignore
|
||||
SteamClient.GameSessions.RegisterForAchievementNotification((p0) => {
|
||||
backend.onSteamEvent( {
|
||||
event_type: "achievement-notification",
|
||||
event_data: p0
|
||||
});
|
||||
});
|
||||
|
||||
//@ts-ignore
|
||||
SteamClient.System.Bluetooth.RegisterForStateChanges((p0) => {
|
||||
backend.onSteamEvent( {
|
||||
event_type: "bluetooth-state",
|
||||
event_data: p0
|
||||
});
|
||||
});
|
||||
|
||||
//@ts-ignore
|
||||
SteamClient.System.Display.RegisterForBrightnessChanges((p0) => {
|
||||
backend.onSteamEvent( {
|
||||
event_type: "brightness",
|
||||
event_data: p0
|
||||
});
|
||||
});
|
||||
|
||||
//@ts-ignore
|
||||
SteamClient.System.RegisterForAirplaneModeChanges((p0) => {
|
||||
backend.onSteamEvent( {
|
||||
event_type: "airplane",
|
||||
event_data: p0
|
||||
});
|
||||
});
|
||||
|
||||
//@ts-ignore
|
||||
SteamClient.GameSessions.RegisterForScreenshotNotification((p0) => {
|
||||
backend.onSteamEvent( {
|
||||
event_type: "screenshot-notification",
|
||||
event_data: p0
|
||||
});
|
||||
});
|
||||
|
||||
// TODO add more events
|
||||
}
|
||||
|
||||
export function unregister_for_steam_events() {
|
||||
for (let i = 0; i < callbacks.length; i++) {
|
||||
callbacks[i].unregister();
|
||||
}
|
||||
callbacks = [];
|
||||
}
|
Reference in a new issue