diff --git a/README.md b/README.md index 04401c8..fd4e7a7 100644 --- a/README.md +++ b/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 diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 88c53e2..2fe5aae 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -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"] } diff --git a/backend/src/api/mod.rs b/backend/src/api/mod.rs index 37805bc..8ad0f7b 100644 --- a/backend/src/api/mod.rs +++ b/backend/src/api/mod.rs @@ -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; diff --git a/backend/src/api/on_event.rs b/backend/src/api/on_event.rs new file mode 100644 index 0000000..851c84d --- /dev/null +++ b/backend/src/api/on_event.rs @@ -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) -> 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()] + } + } +} diff --git a/backend/src/api/steam_types.rs b/backend/src/api/steam_types.rs index c76e6e3..1bb4c94 100644 --- a/backend/src/api/steam_types.rs +++ b/backend/src/api/steam_types.rs @@ -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), + #[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, +} + // 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 +} diff --git a/backend/src/config/element.rs b/backend/src/config/element.rs index 0d10d13..d796deb 100644 --- a/backend/src/config/element.rs +++ b/backend/src/config/element.rs @@ -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) } diff --git a/backend/src/config/event_display.rs b/backend/src/config/event_display.rs new file mode 100644 index 0000000..67e24a7 --- /dev/null +++ b/backend/src/config/event_display.rs @@ -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) + } +} diff --git a/backend/src/config/mod.rs b/backend/src/config/mod.rs index d36cc79..2955fc5 100644 --- a/backend/src/config/mod.rs +++ b/backend/src/config/mod.rs @@ -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; diff --git a/backend/src/config/reading.rs b/backend/src/config/reading.rs index 270acc5..fdd4e7a 100644 --- a/backend/src/config/reading.rs +++ b/backend/src/config/reading.rs @@ -7,5 +7,6 @@ pub struct ReadingConfig { pub title: String, /// Period in milliseconds, or None/null for non-repeating actions pub period_ms: Option, + /// Action to perform on every period pub on_period: TopLevelActionConfig, } diff --git a/backend/src/config/result_display.rs b/backend/src/config/result_display.rs index d2e9146..783c67f 100644 --- a/backend/src/config/result_display.rs +++ b/backend/src/config/result_display.rs @@ -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, } diff --git a/backend/src/main.rs b/backend/src/main.rs index 56f3ccb..de4d755 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -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() diff --git a/backend/src/runtime/actors/actor.rs b/backend/src/runtime/actors/actor.rs index 3893025..14a0ee7 100644 --- a/backend/src/runtime/actors/actor.rs +++ b/backend/src/runtime/actors/actor.rs @@ -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, } +#[cfg(test)] pub enum Expected { Output(Primitive), BuildErr(ActError), diff --git a/backend/src/runtime/actors/json_actor.rs b/backend/src/runtime/actors/json_actor.rs index d97f727..aa36694 100644 --- a/backend/src/runtime/actors/json_actor.rs +++ b/backend/src/runtime/actors/json_actor.rs @@ -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( diff --git a/backend/src/runtime/communication.rs b/backend/src/runtime/communication.rs index b8ded53..0d5b878 100644 --- a/backend/src/runtime/communication.rs +++ b/backend/src/runtime/communication.rs @@ -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 diff --git a/backend/src/runtime/executor.rs b/backend/src/runtime/executor.rs index 7289bf2..cc05d40 100644 --- a/backend/src/runtime/executor.rs +++ b/backend/src/runtime/executor.rs @@ -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, @@ -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 = 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(event: &T, cache: &mut Option) -> 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) + } +} diff --git a/src/backend.ts b/src/backend.ts index 826f619..05c6a75 100644 --- a/src/backend.ts +++ b/src/backend.ts @@ -4,7 +4,7 @@ const USDPL_PORT: number = 25717; // Utility -export function resolve(promise: Promise, setter: any) { +export function resolve(promise: Promise, 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 { return (await call_backend("get_items", []))[0]; } -export async function onUpdate(index: number, value: any): Promise { +export async function onUpdate(index: number, value: any): Promise { return (await call_backend("on_update", [index, value]))[0]; } @@ -111,6 +122,10 @@ export async function getJavascriptToRun(): Promise { return (await call_backend("get_javascript_to_run", []))[0]; } -export async function onJavascriptResult(id: number, value: any): Promise { +export async function onJavascriptResult(id: number, value: any): Promise { return (await call_backend("on_javascript_result", [id, value]))[0]; } + +export async function onSteamEvent(data: CSteamEvent): Promise { + return (await call_backend("on_javascript_result", [data]))[0]; +} diff --git a/src/index.tsx b/src/index.tsx index b3ba287..287ea04 100755 --- a/src/index.tsx +++ b/src/index.tsx @@ -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
Unsupported
; @@ -256,6 +261,17 @@ function buildResultDisplay(element: backend.CResultDisplay, index: number, _upd ); } +function buildEventDisplay(element: backend.CEventDisplay, index: number, _updateIdc: any) { + return ( +
+
+
{element.title}
+
{get_value(DISPLAY_KEY + index.toString())}
+
+
+ ); +} + function buildAbout() { if (about == null) { return []; @@ -360,12 +376,13 @@ function buildAbout() { } export default definePlugin((serverApi: ServerAPI) => { + register_for_steam_events() return { - title:
{about == null? "Kaylon": about.name}
, + title:
{about == null? "Caylon": about.name}
, content: , icon: , onDismount() { - //serverApi.routerHook.removeRoute("/decky-plugin-test"); + unregister_for_steam_events(); }, }; }); diff --git a/src/steam_events.ts b/src/steam_events.ts new file mode 100644 index 0000000..7ebcd06 --- /dev/null +++ b/src/steam_events.ts @@ -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 = []; +}