Initial Commit

This commit is contained in:
2026-02-22 09:55:19 +01:00
commit a68403d3ae
15 changed files with 851 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

292
Cargo.lock generated Normal file
View File

@@ -0,0 +1,292 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "bitflags"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
[[package]]
name = "bumpalo"
version = "3.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
[[package]]
name = "bytes"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "deunicode"
version = "1.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04"
[[package]]
name = "dirs"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys",
]
[[package]]
name = "getrandom"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "libc"
version = "0.2.182"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
[[package]]
name = "libredox"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
dependencies = [
"bitflags",
"libc",
]
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "option-ext"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "otd-ipc"
version = "0.1.0"
dependencies = [
"bytes",
"dirs",
"serde",
"serde_bytes",
"slug",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_users"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
dependencies = [
"getrandom",
"libredox",
"thiserror",
]
[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_bytes"
version = "0.11.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8"
dependencies = [
"serde",
"serde_core",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "slug"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724"
dependencies = [
"deunicode",
"wasm-bindgen",
]
[[package]]
name = "syn"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasm-bindgen"
version = "0.2.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec1adf1535672f5b7824f817792b1afd731d7e843d2d04ec8f27e8cb51edd8ac"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19e638317c08b21663aed4d2b9a2091450548954695ff4efa75bff5fa546b3b1"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c64760850114d03d5f65457e96fc988f11f01d38fbaa51b254e4ab5809102af"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60eecd4fe26177cfa3339eb00b4a36445889ba3ad37080c2429879718e20ca41"
dependencies = [
"unicode-ident",
]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]

12
Cargo.toml Normal file
View File

@@ -0,0 +1,12 @@
[package]
name = "otd-ipc"
version = "0.1.0"
edition = "2024"
authors = ["AviiNL <otd-ipc@avii.nl>"]
[dependencies]
bytes = "1.11.1"
dirs = "6.0.0"
serde = { version = "1.0.228", features = ["derive"] }
serde_bytes = "0.11.19"
slug = "0.1.6"

68
src/lib.rs Normal file
View File

@@ -0,0 +1,68 @@
mod messages;
mod metadata;
use std::{
io::{Read, Write},
os::unix::net::UnixStream,
};
use crate::{messages::*, metadata::OtdIpcMetadata};
pub struct OtdIpc {
stream: UnixStream,
}
impl OtdIpc {
pub fn new(name: &str, version: &str) -> Result<Self, Box<dyn std::error::Error>> {
let meta = OtdIpcMetadata::new()?;
let mut stream = UnixStream::connect(meta.socket)?;
let hello = Hello::new(
0x022026020501,
name,
version,
&format!("{}.{}", slug::slugify(name), meta.id),
1,
)?;
stream.write_all(&Vec::from(hello))?;
Ok(Self { stream })
}
}
impl Iterator for OtdIpc {
type Item = Message;
fn next(&mut self) -> Option<Self::Item> {
let mut raw_header = [0; size_of::<Header>()];
self.stream.read_exact(&mut raw_header).ok()?;
let header: Header = raw_header[..].try_into().ok()?;
let mut raw_buffer = vec![0; (header.size as usize) - size_of::<Header>()];
self.stream.read_exact(&mut raw_buffer).ok()?;
match header.message_type {
messages::MessageType::None => None,
messages::MessageType::DeviceInfo => Some(Message::DeviceInfo(Box::new(
DeviceInfo::try_from(&raw_buffer[..]).ok()?,
))),
messages::MessageType::State => {
Some(Message::State(State::try_from(&raw_buffer[..]).ok()?))
}
messages::MessageType::Ping => {
Some(Message::Ping(Ping::try_from(&raw_buffer[..]).ok()?))
}
messages::MessageType::DebugMessage => {
Some(Message::DebugMessage(DebugMessage::from(&raw_buffer[..])))
}
messages::MessageType::Experimental => {
Some(Message::Experimental(Experimental::from(&raw_buffer[..])))
}
messages::MessageType::Hello => {
// Some(Message::Hello(Hello::from(&raw_buffer[..])))
None
}
}
}
}

11
src/main.rs Normal file
View File

@@ -0,0 +1,11 @@
use otd_ipc::OtdIpc;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let otd_ipc = OtdIpc::new("otd-ipc-rs", "master")?;
for msg in otd_ipc {
dbg!(msg);
}
Ok(())
}

View File

@@ -0,0 +1,16 @@
use std::fmt::Debug;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DebugMessage {
pub message: String, // this can not be known at compile time
}
impl From<&[u8]> for DebugMessage {
fn from(value: &[u8]) -> Self {
Self {
message: String::from_utf8_lossy(value).to_string(),
}
}
}

View File

@@ -0,0 +1,47 @@
use std::fmt::Debug;
use super::take;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeviceInfo {
pub max_x: f32,
pub max_y: f32,
pub max_pressure: u32,
pub persistent_id: String,
pub name: String,
}
impl TryFrom<&[u8]> for DeviceInfo {
type Error = &'static str;
fn try_from(mut bytes: &[u8]) -> Result<Self, Self::Error> {
if bytes.len() < 16 + 256 + 256 {
return Err("buffer too small");
}
let max_x = f32::from_le_bytes(take::<4>(&mut bytes)?);
let max_y = f32::from_le_bytes(take::<4>(&mut bytes)?);
let max_pressure = u32::from_le_bytes(take::<4>(&mut bytes)?);
// skip ignored field
let _ = take::<4>(&mut bytes)?;
let (id_bytes, name_bytes) = bytes.split_at(256);
let persistent_id = String::from_utf8_lossy(id_bytes)
.trim_end_matches('\0')
.to_string();
let name = String::from_utf8_lossy(name_bytes)
.trim_end_matches('\0')
.to_string();
Ok(Self {
max_x,
max_y,
max_pressure,
persistent_id,
name,
})
}
}

View File

@@ -0,0 +1,16 @@
use std::fmt::Debug;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Experimental {
// pub sequence_number: u32,
}
impl From<&[u8]> for Experimental {
fn from(_value: &[u8]) -> Self {
Self {
// ..
}
}
}

28
src/messages/header.rs Normal file
View File

@@ -0,0 +1,28 @@
use serde::{Deserialize, Serialize};
use super::take;
use super::MessageType;
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct Header {
pub message_type: MessageType,
pub size: u32,
pub non_persistent_tablet_id: u32,
}
impl TryFrom<&[u8]> for Header {
type Error = &'static str;
fn try_from(mut bytes: &[u8]) -> Result<Self, Self::Error> {
if bytes.len() < 12 {
return Err("buffer too small");
}
Ok(Self {
message_type: u32::from_le_bytes(take::<4>(&mut bytes)?).into(),
size: u32::from_le_bytes(take::<4>(&mut bytes)?),
non_persistent_tablet_id: u32::from_le_bytes(take::<4>(&mut bytes)?),
})
}
}

68
src/messages/hello.rs Normal file
View File

@@ -0,0 +1,68 @@
use std::fmt::Debug;
use serde::{Deserialize, Serialize};
use super::MessageType;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Hello {
pub protocol_version: u64,
pub human_readable_name: String,
pub human_readable_version: String,
pub implementation_id: String,
pub compatibility_version: u8,
}
impl Hello {
pub fn new(
protocol_version: u64,
human_readable_name: &str,
human_readable_version: &str,
implementation_id: &str,
compatibility_version: u8,
) -> Result<Self, Box<dyn std::error::Error>> {
Ok(Hello {
protocol_version,
human_readable_name: human_readable_name.to_string(),
human_readable_version: human_readable_version.to_string(),
implementation_id: implementation_id.to_string(),
compatibility_version,
})
}
}
impl From<Hello> for Vec<u8> {
fn from(value: Hello) -> Self {
let mut output = Vec::with_capacity(std::mem::size_of::<Hello>());
output.extend_from_slice(&(MessageType::Hello as u32).to_le_bytes());
output.extend_from_slice(&(std::mem::size_of::<Hello>() as u32).to_le_bytes());
output.push(0u8); // non_persistent_tablet_id
output.extend_from_slice(&value.protocol_version.to_le_bytes());
output.extend_from_slice(&str_to_fixed(&value.human_readable_name).unwrap());
output.extend_from_slice(&str_to_fixed(&value.human_readable_version).unwrap());
output.extend_from_slice(&str_to_fixed(&value.implementation_id).unwrap());
output.push(value.compatibility_version);
output
}
}
fn str_to_fixed(input: &str) -> Result<[u8; 256], String> {
let bytes = input.as_bytes();
if bytes.len() > 256 {
return Err(format!(
"String too long ({} bytes), max is 256",
bytes.len()
));
}
let mut buffer = [0u8; 256];
buffer[..bytes.len()].copy_from_slice(bytes);
Ok(buffer)
}

View File

@@ -0,0 +1,33 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub enum MessageType {
None,
DeviceInfo,
State,
Ping,
DebugMessage,
Experimental,
Hello,
}
impl From<MessageType> for u32 {
fn from(value: MessageType) -> Self {
value as u32
}
}
impl From<u32> for MessageType {
fn from(value: u32) -> Self {
match value {
0 => Self::None,
1 => Self::DeviceInfo,
2 => Self::State,
3 => Self::Ping,
4 => Self::DebugMessage,
5 => Self::Experimental,
6 => Self::Hello,
_ => panic!("Invalid value"),
}
}
}

37
src/messages/mod.rs Normal file
View File

@@ -0,0 +1,37 @@
mod debug_message;
mod device_info;
mod experimental;
mod header;
mod hello;
mod message_type;
mod ping;
mod state;
pub use debug_message::*;
pub use device_info::*;
pub use experimental::*;
pub use header::*;
pub use hello::*;
pub use message_type::*;
pub use ping::*;
pub use state::*;
#[derive(Debug)]
pub enum Message {
DeviceInfo(Box<DeviceInfo>),
State(State),
Ping(Ping),
DebugMessage(DebugMessage),
Experimental(Experimental),
// Hello(Hello),
}
// helper to read fixed-size chunks
pub(super) fn take<const N: usize>(bytes: &mut &[u8]) -> Result<[u8; N], &'static str> {
if bytes.len() < N {
return Err("buffer too small");
}
let (head, tail) = bytes.split_at(N);
*bytes = tail;
Ok(head.try_into().unwrap())
}

24
src/messages/ping.rs Normal file
View File

@@ -0,0 +1,24 @@
use std::fmt::Debug;
use serde::{Deserialize, Serialize};
use super::take;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Ping {
pub sequence_number: u32,
}
impl TryFrom<&[u8]> for Ping {
type Error = &'static str;
fn try_from(mut bytes: &[u8]) -> Result<Self, Self::Error> {
if bytes.len() < 4 {
return Err("buffer too small");
}
Ok(Self {
sequence_number: u32::from_le_bytes(take::<4>(&mut bytes)?),
})
}
}

145
src/messages/state.rs Normal file
View File

@@ -0,0 +1,145 @@
use std::fmt::Debug;
use serde::{Deserialize, Serialize};
use super::take;
#[repr(transparent)]
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct ValidMask(pub u32);
impl ValidMask {
// pub const NONE: Self = Self(0);
pub const POSITION_X: Self = Self(1 << 0);
pub const POSITION_Y: Self = Self(1 << 1);
pub const PRESSURE: Self = Self(1 << 2);
pub const PEN_BUTTONS: Self = Self(1 << 3);
pub const AUX_BUTTONS: Self = Self(1 << 4);
pub const PEN_IS_NEAR_SURFACE: Self = Self(1 << 5);
pub const HOVER_DISTANCE: Self = Self(1 << 6);
// pub const POSITION: Self = Self(Self::POSITION_X.0 | Self::POSITION_Y.0);
#[inline]
pub fn has(self, mask: ValidMask) -> bool {
(self.0 & mask.0) == mask.0
}
}
impl From<u32> for ValidMask {
fn from(value: u32) -> Self {
Self(value)
}
}
// Bitwise operators
impl std::ops::BitOr for ValidMask {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
Self(self.0 | rhs.0)
}
}
impl std::ops::BitAnd for ValidMask {
type Output = Self;
fn bitand(self, rhs: Self) -> Self {
Self(self.0 & rhs.0)
}
}
impl std::ops::Not for ValidMask {
type Output = Self;
fn not(self) -> Self {
Self(!self.0)
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct State {
valid_bits: ValidMask,
x: f32,
y: f32,
pressure: u32,
pen_buttons: u32,
aux_buttons: u32,
hover_distance: u32,
pen_is_near_surface: bool,
}
impl State {
pub fn x(&self) -> f32 {
if !self.valid_bits.has(ValidMask::POSITION_X) {
return 0.0;
}
self.x
}
pub fn y(&self) -> f32 {
if !self.valid_bits.has(ValidMask::POSITION_Y) {
return 0.0;
}
self.y
}
pub fn pressure(&self) -> u32 {
if !self.valid_bits.has(ValidMask::PRESSURE) {
return 0;
}
self.pressure
}
pub fn pen_buttons(&self) -> u32 {
if !self.valid_bits.has(ValidMask::PEN_BUTTONS) {
return 0;
}
self.pen_buttons
}
pub fn aux_buttons(&self) -> u32 {
if !self.valid_bits.has(ValidMask::AUX_BUTTONS) {
return 0;
}
self.aux_buttons
}
pub fn hover_distance(&self) -> u32 {
if !self.valid_bits.has(ValidMask::HOVER_DISTANCE) {
return 0;
}
self.hover_distance
}
pub fn pen_is_near_surface(&self) -> bool {
if !self.valid_bits.has(ValidMask::PEN_IS_NEAR_SURFACE) {
return true;
}
self.pen_is_near_surface
}
}
impl TryFrom<&[u8]> for State {
type Error = &'static str;
fn try_from(mut bytes: &[u8]) -> Result<Self, Self::Error> {
if bytes.len() < 29 {
return Err("buffer too small");
}
Ok(Self {
valid_bits: u32::from_le_bytes(take::<4>(&mut bytes)?).into(),
x: f32::from_le_bytes(take::<4>(&mut bytes)?),
y: f32::from_le_bytes(take::<4>(&mut bytes)?),
pressure: u32::from_le_bytes(take::<4>(&mut bytes)?),
pen_buttons: u32::from_le_bytes(take::<4>(&mut bytes)?),
aux_buttons: u32::from_le_bytes(take::<4>(&mut bytes)?),
hover_distance: u32::from_le_bytes(take::<4>(&mut bytes)?),
pen_is_near_surface: u8::from_le_bytes(take::<1>(&mut bytes)?) != 0,
})
}
}

53
src/metadata.rs Normal file
View File

@@ -0,0 +1,53 @@
use std::{collections::HashMap, fs::read_to_string, path::PathBuf};
#[derive(Debug)]
#[allow(unused)]
pub(crate) struct OtdIpcMetadata {
pub id: String,
pub name: String,
pub semver: String,
pub debug_version: String,
pub homepage: String,
pub socket: PathBuf,
}
impl OtdIpcMetadata {
pub fn new() -> std::io::Result<Self> {
let Some(mut otd_ipc_dir) = dirs::data_local_dir() else {
panic!("User data local dir should exist!");
};
otd_ipc_dir.push("otd-ipc");
otd_ipc_dir.push("servers");
otd_ipc_dir.push("v2");
let mut default = otd_ipc_dir.clone();
default.push("default.txt");
let default_id = read_to_string(default)?;
let mut available = otd_ipc_dir.clone();
available.push("available");
available.push(format!("{}.txt", default_id));
let metadata = read_to_string(available)?;
let mut data = HashMap::new();
for line in metadata.lines() {
let Some((key, value)) = line.split_once('=') else {
continue; // not a valid kvp
};
data.insert(key, value.to_owned());
}
Ok(Self {
id: data["ID"].clone(),
name: data["NAME"].clone(),
semver: data["SEMVER"].clone(),
debug_version: data["DEBUG_VERSION"].clone(),
homepage: data["HOMEPAGE"].clone(),
socket: data["SOCKET"].clone().into(),
})
}
}