+1
package.json
+1
package.json
+15
pnpm-lock.yaml
+15
pnpm-lock.yaml
···
11
11
'@tauri-apps/api':
12
12
specifier: ^2
13
13
version: 2.7.0
14
+
'@tauri-apps/plugin-dialog':
15
+
specifier: ~2
16
+
version: 2.4.0
14
17
'@tauri-apps/plugin-opener':
15
18
specifier: ^2
16
19
version: 2.4.0
···
396
399
'@tauri-apps/api@2.7.0':
397
400
resolution: {integrity: sha512-v7fVE8jqBl8xJFOcBafDzXFc8FnicoH3j8o8DNNs0tHuEBmXUDqrCOAzMRX0UkfpwqZLqvrvK0GNQ45DfnoVDg==}
398
401
402
+
'@tauri-apps/api@2.8.0':
403
+
resolution: {integrity: sha512-ga7zdhbS2GXOMTIZRT0mYjKJtR9fivsXzsyq5U3vjDL0s6DTMwYRm0UHNjzTY5dh4+LSC68Sm/7WEiimbQNYlw==}
404
+
399
405
'@tauri-apps/cli-darwin-arm64@2.7.1':
400
406
resolution: {integrity: sha512-j2NXQN6+08G03xYiyKDKqbCV2Txt+hUKg0a8hYr92AmoCU8fgCjHyva/p16lGFGUG3P2Yu0xiNe1hXL9ZuRMzA==}
401
407
engines: {node: '>= 10'}
···
466
472
resolution: {integrity: sha512-RcGWR4jOUEl92w3uvI0h61Llkfj9lwGD1iwvDRD2isMrDhOzjeeeVn9aGzeW1jubQ/kAbMYfydcA4BA0Cy733Q==}
467
473
engines: {node: '>= 10'}
468
474
hasBin: true
475
+
476
+
'@tauri-apps/plugin-dialog@2.4.0':
477
+
resolution: {integrity: sha512-OvXkrEBfWwtd8tzVCEXIvRfNEX87qs2jv6SqmVPiHcJjBhSF/GUvjqUNIDmKByb5N8nvDqVUM7+g1sXwdC/S9w==}
469
478
470
479
'@tauri-apps/plugin-opener@2.4.0':
471
480
resolution: {integrity: sha512-43VyN8JJtvKWJY72WI/KNZszTpDpzHULFxQs0CJBIYUdCRowQ6Q1feWTDb979N7nldqSuDOaBupZ6wz2nvuWwQ==}
···
991
1000
992
1001
'@tauri-apps/api@2.7.0': {}
993
1002
1003
+
'@tauri-apps/api@2.8.0': {}
1004
+
994
1005
'@tauri-apps/cli-darwin-arm64@2.7.1':
995
1006
optional: true
996
1007
···
1037
1048
'@tauri-apps/cli-win32-arm64-msvc': 2.7.1
1038
1049
'@tauri-apps/cli-win32-ia32-msvc': 2.7.1
1039
1050
'@tauri-apps/cli-win32-x64-msvc': 2.7.1
1051
+
1052
+
'@tauri-apps/plugin-dialog@2.4.0':
1053
+
dependencies:
1054
+
'@tauri-apps/api': 2.8.0
1040
1055
1041
1056
'@tauri-apps/plugin-opener@2.4.0':
1042
1057
dependencies:
+1
public/assets/icons/floppy-disk-solid-full.svg
+1
public/assets/icons/floppy-disk-solid-full.svg
···
1
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path fill="#fff" d="M160 96C124.7 96 96 124.7 96 160L96 480C96 515.3 124.7 544 160 544L480 544C515.3 544 544 515.3 544 480L544 237.3C544 220.3 537.3 204 525.3 192L448 114.7C436 102.7 419.7 96 402.7 96L160 96zM192 192C192 174.3 206.3 160 224 160L384 160C401.7 160 416 174.3 416 192L416 256C416 273.7 401.7 288 384 288L224 288C206.3 288 192 273.7 192 256L192 192zM320 352C355.3 352 384 380.7 384 416C384 451.3 355.3 480 320 480C284.7 480 256 451.3 256 416C256 380.7 284.7 352 320 352z"/></svg>
+213
-1
src-tauri/Cargo.lock
+213
-1
src-tauri/Cargo.lock
···
13
13
"serde_json",
14
14
"tauri",
15
15
"tauri-build",
16
+
"tauri-plugin-dialog",
16
17
"tauri-plugin-opener",
17
18
"tokio",
18
19
]
···
76
77
version = "1.0.99"
77
78
source = "registry+https://github.com/rust-lang/crates.io-index"
78
79
checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
80
+
81
+
[[package]]
82
+
name = "ashpd"
83
+
version = "0.11.0"
84
+
source = "registry+https://github.com/rust-lang/crates.io-index"
85
+
checksum = "6cbdf310d77fd3aaee6ea2093db7011dc2d35d2eb3481e5607f1f8d942ed99df"
86
+
dependencies = [
87
+
"enumflags2",
88
+
"futures-channel",
89
+
"futures-util",
90
+
"rand 0.9.2",
91
+
"raw-window-handle",
92
+
"serde",
93
+
"serde_repr",
94
+
"tokio",
95
+
"url",
96
+
"wayland-backend",
97
+
"wayland-client",
98
+
"wayland-protocols",
99
+
"zbus",
100
+
]
79
101
80
102
[[package]]
81
103
name = "async-broadcast"
···
758
780
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
759
781
dependencies = [
760
782
"bitflags 2.9.1",
783
+
"block2 0.6.1",
784
+
"libc",
761
785
"objc2 0.6.1",
762
786
]
763
787
···
773
797
]
774
798
775
799
[[package]]
800
+
name = "dlib"
801
+
version = "0.5.2"
802
+
source = "registry+https://github.com/rust-lang/crates.io-index"
803
+
checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
804
+
dependencies = [
805
+
"libloading",
806
+
]
807
+
808
+
[[package]]
776
809
name = "dlopen2"
777
810
version = "0.7.0"
778
811
source = "registry+https://github.com/rust-lang/crates.io-index"
···
794
827
"quote",
795
828
"syn 2.0.104",
796
829
]
830
+
831
+
[[package]]
832
+
name = "downcast-rs"
833
+
version = "1.2.1"
834
+
source = "registry+https://github.com/rust-lang/crates.io-index"
835
+
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
797
836
798
837
[[package]]
799
838
name = "dpi"
···
2634
2673
dependencies = [
2635
2674
"base64 0.22.1",
2636
2675
"indexmap 2.10.0",
2637
-
"quick-xml",
2676
+
"quick-xml 0.38.0",
2638
2677
"serde",
2639
2678
"time",
2640
2679
]
···
2765
2804
2766
2805
[[package]]
2767
2806
name = "quick-xml"
2807
+
version = "0.37.5"
2808
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2809
+
checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb"
2810
+
dependencies = [
2811
+
"memchr",
2812
+
]
2813
+
2814
+
[[package]]
2815
+
name = "quick-xml"
2768
2816
version = "0.38.0"
2769
2817
source = "registry+https://github.com/rust-lang/crates.io-index"
2770
2818
checksum = "8927b0664f5c5a98265138b7e3f90aa19a6b21353182469ace36d4ac527b7b1b"
···
2813
2861
]
2814
2862
2815
2863
[[package]]
2864
+
name = "rand"
2865
+
version = "0.9.2"
2866
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2867
+
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
2868
+
dependencies = [
2869
+
"rand_chacha 0.9.0",
2870
+
"rand_core 0.9.3",
2871
+
]
2872
+
2873
+
[[package]]
2816
2874
name = "rand_chacha"
2817
2875
version = "0.2.2"
2818
2876
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2833
2891
]
2834
2892
2835
2893
[[package]]
2894
+
name = "rand_chacha"
2895
+
version = "0.9.0"
2896
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2897
+
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
2898
+
dependencies = [
2899
+
"ppv-lite86",
2900
+
"rand_core 0.9.3",
2901
+
]
2902
+
2903
+
[[package]]
2836
2904
name = "rand_core"
2837
2905
version = "0.5.1"
2838
2906
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2851
2919
]
2852
2920
2853
2921
[[package]]
2922
+
name = "rand_core"
2923
+
version = "0.9.3"
2924
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2925
+
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
2926
+
dependencies = [
2927
+
"getrandom 0.3.3",
2928
+
]
2929
+
2930
+
[[package]]
2854
2931
name = "rand_hc"
2855
2932
version = "0.2.0"
2856
2933
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2979
3056
]
2980
3057
2981
3058
[[package]]
3059
+
name = "rfd"
3060
+
version = "0.15.4"
3061
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3062
+
checksum = "ef2bee61e6cffa4635c72d7d81a84294e28f0930db0ddcb0f66d10244674ebed"
3063
+
dependencies = [
3064
+
"ashpd",
3065
+
"block2 0.6.1",
3066
+
"dispatch2",
3067
+
"glib-sys",
3068
+
"gobject-sys",
3069
+
"gtk-sys",
3070
+
"js-sys",
3071
+
"log",
3072
+
"objc2 0.6.1",
3073
+
"objc2-app-kit",
3074
+
"objc2-core-foundation",
3075
+
"objc2-foundation 0.3.1",
3076
+
"raw-window-handle",
3077
+
"wasm-bindgen",
3078
+
"wasm-bindgen-futures",
3079
+
"web-sys",
3080
+
"windows-sys 0.59.0",
3081
+
]
3082
+
3083
+
[[package]]
2982
3084
name = "rustc-demangle"
2983
3085
version = "0.1.26"
2984
3086
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3079
3181
]
3080
3182
3081
3183
[[package]]
3184
+
name = "scoped-tls"
3185
+
version = "1.0.1"
3186
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3187
+
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
3188
+
3189
+
[[package]]
3082
3190
name = "scopeguard"
3083
3191
version = "1.2.0"
3084
3192
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3680
3788
]
3681
3789
3682
3790
[[package]]
3791
+
name = "tauri-plugin-dialog"
3792
+
version = "2.3.2"
3793
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3794
+
checksum = "37e5858cc7b455a73ab4ea2ebc08b5be33682c00ff1bf4cad5537d4fb62499d9"
3795
+
dependencies = [
3796
+
"log",
3797
+
"raw-window-handle",
3798
+
"rfd",
3799
+
"serde",
3800
+
"serde_json",
3801
+
"tauri",
3802
+
"tauri-plugin",
3803
+
"tauri-plugin-fs",
3804
+
"thiserror 2.0.12",
3805
+
"url",
3806
+
]
3807
+
3808
+
[[package]]
3809
+
name = "tauri-plugin-fs"
3810
+
version = "2.4.1"
3811
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3812
+
checksum = "8c6ef84ee2f2094ce093e55106d90d763ba343fad57566992962e8f76d113f99"
3813
+
dependencies = [
3814
+
"anyhow",
3815
+
"dunce",
3816
+
"glob",
3817
+
"percent-encoding",
3818
+
"schemars 0.8.22",
3819
+
"serde",
3820
+
"serde_json",
3821
+
"serde_repr",
3822
+
"tauri",
3823
+
"tauri-plugin",
3824
+
"tauri-utils",
3825
+
"thiserror 2.0.12",
3826
+
"toml 0.8.23",
3827
+
"url",
3828
+
]
3829
+
3830
+
[[package]]
3683
3831
name = "tauri-plugin-opener"
3684
3832
version = "2.4.0"
3685
3833
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3916
4064
"libc",
3917
4065
"mio",
3918
4066
"pin-project-lite",
4067
+
"signal-hook-registry",
3919
4068
"slab",
3920
4069
"socket2",
3921
4070
"tokio-macros",
4071
+
"tracing",
3922
4072
"windows-sys 0.59.0",
3923
4073
]
3924
4074
···
4433
4583
]
4434
4584
4435
4585
[[package]]
4586
+
name = "wayland-backend"
4587
+
version = "0.3.11"
4588
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4589
+
checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35"
4590
+
dependencies = [
4591
+
"cc",
4592
+
"downcast-rs",
4593
+
"rustix",
4594
+
"scoped-tls",
4595
+
"smallvec",
4596
+
"wayland-sys",
4597
+
]
4598
+
4599
+
[[package]]
4600
+
name = "wayland-client"
4601
+
version = "0.31.11"
4602
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4603
+
checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d"
4604
+
dependencies = [
4605
+
"bitflags 2.9.1",
4606
+
"rustix",
4607
+
"wayland-backend",
4608
+
"wayland-scanner",
4609
+
]
4610
+
4611
+
[[package]]
4612
+
name = "wayland-protocols"
4613
+
version = "0.32.9"
4614
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4615
+
checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901"
4616
+
dependencies = [
4617
+
"bitflags 2.9.1",
4618
+
"wayland-backend",
4619
+
"wayland-client",
4620
+
"wayland-scanner",
4621
+
]
4622
+
4623
+
[[package]]
4624
+
name = "wayland-scanner"
4625
+
version = "0.31.7"
4626
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4627
+
checksum = "54cb1e9dc49da91950bdfd8b848c49330536d9d1fb03d4bfec8cae50caa50ae3"
4628
+
dependencies = [
4629
+
"proc-macro2",
4630
+
"quick-xml 0.37.5",
4631
+
"quote",
4632
+
]
4633
+
4634
+
[[package]]
4635
+
name = "wayland-sys"
4636
+
version = "0.31.7"
4637
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4638
+
checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142"
4639
+
dependencies = [
4640
+
"dlib",
4641
+
"log",
4642
+
"pkg-config",
4643
+
]
4644
+
4645
+
[[package]]
4436
4646
name = "web-sys"
4437
4647
version = "0.3.77"
4438
4648
source = "registry+https://github.com/rust-lang/crates.io-index"
···
5057
5267
"ordered-stream",
5058
5268
"serde",
5059
5269
"serde_repr",
5270
+
"tokio",
5060
5271
"tracing",
5061
5272
"uds_windows",
5062
5273
"windows-sys 0.59.0",
···
5176
5387
"endi",
5177
5388
"enumflags2",
5178
5389
"serde",
5390
+
"url",
5179
5391
"winnow 0.7.12",
5180
5392
"zvariant_derive",
5181
5393
"zvariant_utils",
+1
src-tauri/Cargo.toml
+1
src-tauri/Cargo.toml
+6
-3
src-tauri/capabilities/default.json
+6
-3
src-tauri/capabilities/default.json
···
2
2
"$schema": "../gen/schemas/desktop-schema.json",
3
3
"identifier": "default",
4
4
"description": "Capability for the main window",
5
-
"windows": ["main"],
5
+
"windows": [
6
+
"main"
7
+
],
6
8
"permissions": [
7
9
"core:default",
8
-
"opener:default"
10
+
"opener:default",
11
+
"dialog:default"
9
12
]
10
-
}
13
+
}
+4
-4
src-tauri/src/frontend_calls/get_addresses.rs
+4
-4
src-tauri/src/frontend_calls/get_addresses.rs
···
5
5
use crate::osc::OSCMessage;
6
6
7
7
#[tauri::command]
8
-
pub fn get_addresses( addresses: State<&Mutex<Vec<OSCMessage>>> ) -> Vec<OSCMessage> {
9
-
let addresses = addresses.lock().unwrap();
10
-
addresses.clone()
11
-
}
8
+
pub fn get_addresses(addresses: State<&Mutex<Vec<OSCMessage>>>) -> Vec<OSCMessage> {
9
+
let addresses = addresses.lock().unwrap();
10
+
addresses.clone()
11
+
}
+1
-1
src-tauri/src/frontend_calls/mod.rs
+1
-1
src-tauri/src/frontend_calls/mod.rs
+13
-10
src-tauri/src/frontend_calls/save_graph.rs
+13
-10
src-tauri/src/frontend_calls/save_graph.rs
···
1
-
use std::{ fs::File, io::Write };
1
+
use std::{fs::File, io::Write};
2
2
3
-
use flate2::{ write::GzEncoder, Compression };
3
+
use flate2::{write::GzEncoder, Compression};
4
4
5
5
#[tauri::command]
6
-
pub fn save_graph( tab_name: String, graph: String ) {
7
-
dbg!(&graph);
6
+
pub fn save_graph(tab_name: String, graph: String) {
7
+
dbg!(&graph);
8
8
9
-
let path = dirs::config_dir().unwrap().join("VRCMacros").join(format!("{}.macro", tab_name));
9
+
let path = dirs::config_dir()
10
+
.unwrap()
11
+
.join("VRCMacros")
12
+
.join(format!("{}.macro", tab_name));
10
13
11
-
let file = File::create(&path).unwrap();
12
-
let mut encoder = GzEncoder::new(file, Compression::default());
14
+
let file = File::create(&path).unwrap();
15
+
let mut encoder = GzEncoder::new(file, Compression::default());
13
16
14
-
encoder.write_all(graph.as_bytes()).unwrap();
15
-
encoder.finish().unwrap();
16
-
}
17
+
encoder.write_all(graph.as_bytes()).unwrap();
18
+
encoder.finish().unwrap();
19
+
}
+33
-33
src-tauri/src/lib.rs
+33
-33
src-tauri/src/lib.rs
···
1
-
use std::{ fs, sync::Mutex };
1
+
use std::{fs, sync::Mutex};
2
2
3
3
use frontend_calls::*;
4
4
5
-
use crate::{ osc::OSCMessage, setup::setup, utils::config::Config };
5
+
use crate::{osc::OSCMessage, setup::setup, utils::config::Config};
6
6
7
7
mod frontend_calls;
8
-
mod structs;
8
+
mod osc;
9
9
mod setup;
10
+
mod structs;
10
11
mod utils;
11
-
mod osc;
12
12
13
13
#[cfg_attr(mobile, tauri::mobile_entry_point)]
14
14
#[tokio::main]
15
15
pub async fn run() {
16
-
let container_folder = dirs::config_dir().unwrap().join("VRCMacros");
16
+
let container_folder = dirs::config_dir().unwrap().join("VRCMacros");
17
17
18
-
match fs ::metadata(&container_folder){
19
-
Ok(meta) => {
20
-
if meta.is_file(){
21
-
panic!("document.write('Cannot launch app as the container path is a file not a directory')");
22
-
}
23
-
}
24
-
Err(_) => {
25
-
fs::create_dir(&container_folder).unwrap();
18
+
match fs::metadata(&container_folder) {
19
+
Ok(meta) => {
20
+
if meta.is_file() {
21
+
panic!("document.write('Cannot launch app as the container path is a file not a directory')");
22
+
}
23
+
}
24
+
Err(_) => {
25
+
fs::create_dir(&container_folder).unwrap();
26
+
}
26
27
}
27
-
}
28
28
29
-
let conf_file = container_folder.join("conf");
30
-
let conf = Config::new(conf_file);
31
-
32
-
static ADDRESSES: Mutex<Vec<OSCMessage>> = Mutex::new(Vec::new());
29
+
let conf_file = container_folder.join("conf");
30
+
let conf = Config::new(conf_file);
33
31
34
-
tauri::Builder::default()
35
-
.plugin(tauri_plugin_opener::init())
36
-
.invoke_handler(tauri::generate_handler![
37
-
get_addresses::get_addresses,
32
+
static ADDRESSES: Mutex<Vec<OSCMessage>> = Mutex::new(Vec::new());
38
33
39
-
save_graph::save_graph
40
-
])
41
-
.manage(conf)
42
-
.manage(&ADDRESSES)
43
-
.setup(| app | {
44
-
setup(app, &ADDRESSES);
45
-
Ok(())
46
-
})
47
-
.run(tauri::generate_context!())
48
-
.expect("error while running tauri application");
49
-
}
34
+
tauri::Builder::default()
35
+
.plugin(tauri_plugin_dialog::init())
36
+
.plugin(tauri_plugin_opener::init())
37
+
.invoke_handler(tauri::generate_handler![
38
+
get_addresses::get_addresses,
39
+
save_graph::save_graph
40
+
])
41
+
.manage(conf)
42
+
.manage(&ADDRESSES)
43
+
.setup(|app| {
44
+
setup(app, &ADDRESSES);
45
+
Ok(())
46
+
})
47
+
.run(tauri::generate_context!())
48
+
.expect("error while running tauri application");
49
+
}
+3
-3
src-tauri/src/main.rs
+3
-3
src-tauri/src/main.rs
···
2
2
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
3
3
4
4
fn main() {
5
-
#[cfg(target_os = "linux")]
6
-
std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1"); // Fix webkit being shit
5
+
#[cfg(target_os = "linux")]
6
+
std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1"); // Fix webkit being shit
7
7
8
-
vrcmacros_lib::run()
8
+
vrcmacros_lib::run()
9
9
}
+117
-109
src-tauri/src/osc.rs
+117
-109
src-tauri/src/osc.rs
···
1
1
// https://gist.github.com/phaze-the-dumb/634daacb5141eae2f846e20987dba7a8
2
2
3
-
use std::{ net::UdpSocket, sync::mpsc::Sender };
3
+
use std::{net::UdpSocket, sync::mpsc::Sender};
4
4
5
5
use serde::Serialize;
6
6
7
7
use crate::structs::parameter_types::ParameterType;
8
8
9
9
#[derive(Debug, Clone, Serialize)]
10
-
pub struct OSCMessage{
11
-
pub address: String,
12
-
pub values: Vec<ParameterType>
10
+
pub struct OSCMessage {
11
+
pub address: String,
12
+
pub values: Vec<ParameterType>,
13
13
}
14
14
15
-
impl PartialEq for OSCMessage{
16
-
// Technically this isn't exactly equal, but the only time i'm checking if OSCMessage's are equal
17
-
// Is when i'm checking for the "address" value
15
+
impl PartialEq for OSCMessage {
16
+
// Technically this isn't exactly equal, but the only time i'm checking if OSCMessage's are equal
17
+
// Is when i'm checking for the "address" value
18
18
19
-
fn eq(&self, other: &Self) -> bool {
20
-
self.address == other.address
21
-
}
19
+
fn eq(&self, other: &Self) -> bool {
20
+
self.address == other.address
21
+
}
22
22
23
-
fn ne(&self, other: &Self) -> bool {
24
-
self.address != other.address
25
-
}
23
+
fn ne(&self, other: &Self) -> bool {
24
+
self.address != other.address
25
+
}
26
26
}
27
27
28
28
// TODO: Implement osc bundles
29
-
pub fn start_server( sender: Sender<OSCMessage>, addr: &str ) {
30
-
let socket = UdpSocket::bind(addr).unwrap();
29
+
pub fn start_server(sender: Sender<OSCMessage>, addr: &str) {
30
+
let socket = UdpSocket::bind(addr).unwrap();
31
31
32
-
loop {
33
-
let mut buf = [0; 1024];
34
-
let (amt, _src) = socket.recv_from(&mut buf).unwrap();
32
+
loop {
33
+
let mut buf = [0; 1024];
34
+
let (amt, _src) = socket.recv_from(&mut buf).unwrap();
35
35
36
-
let buf = &mut buf[..amt];
37
-
if buf[0] != 0x2F { panic!("Packet is not an OSC Message"); }
36
+
let buf = &mut buf[..amt];
37
+
if buf[0] != 0x2F {
38
+
panic!("Packet is not an OSC Message");
39
+
}
38
40
39
-
let mut addr: Vec<u8> = Vec::new();
40
-
let mut value_start = 0;
41
+
let mut addr: Vec<u8> = Vec::new();
42
+
let mut value_start = 0;
41
43
42
-
loop{
43
-
let byte = buf[value_start];
44
-
if byte == 0x00{ break; }
44
+
loop {
45
+
let byte = buf[value_start];
46
+
if byte == 0x00 {
47
+
break;
48
+
}
45
49
46
-
value_start += 1;
47
-
addr.push(byte);
48
-
}
50
+
value_start += 1;
51
+
addr.push(byte);
52
+
}
49
53
50
-
loop{
51
-
let byte = buf[value_start];
52
-
value_start += 1;
54
+
loop {
55
+
let byte = buf[value_start];
56
+
value_start += 1;
53
57
54
-
if byte == 0x2C{ break; }
55
-
}
58
+
if byte == 0x2C {
59
+
break;
60
+
}
61
+
}
56
62
57
-
let mut types: Vec<u8> = Vec::new();
63
+
let mut types: Vec<u8> = Vec::new();
58
64
59
-
loop{
60
-
let byte = buf[value_start];
61
-
if byte == 0x00{ break; }
65
+
loop {
66
+
let byte = buf[value_start];
67
+
if byte == 0x00 {
68
+
break;
69
+
}
62
70
63
-
types.push(byte);
64
-
value_start += 1;
65
-
}
71
+
types.push(byte);
72
+
value_start += 1;
73
+
}
66
74
67
-
value_start = ((value_start as f32 / 4.0).ceil() * 4.0) as usize;
68
-
let mut values = Vec::new();
75
+
value_start = ((value_start as f32 / 4.0).ceil() * 4.0) as usize;
76
+
let mut values = Vec::new();
69
77
70
-
for tp in types{
71
-
match tp{
72
-
0x69 => {
73
-
let val_buf = &buf[value_start..value_start + 4];
78
+
for tp in types {
79
+
match tp {
80
+
0x69 => {
81
+
let val_buf = &buf[value_start..value_start + 4];
74
82
75
-
let bytes = <&[u8; 4]>::try_from(val_buf).unwrap().clone();
76
-
let int = i32::from_be_bytes(bytes);
83
+
let bytes = <&[u8; 4]>::try_from(val_buf).unwrap().clone();
84
+
let int = i32::from_be_bytes(bytes);
77
85
78
-
values.push(ParameterType::Int(int));
79
-
value_start += 4;
80
-
},
81
-
0x66 => {
82
-
let val_buf = &buf[value_start..value_start + 4];
86
+
values.push(ParameterType::Int(int));
87
+
value_start += 4;
88
+
}
89
+
0x66 => {
90
+
let val_buf = &buf[value_start..value_start + 4];
83
91
84
-
let bytes = <&[u8; 4]>::try_from(val_buf).unwrap().clone();
85
-
let float = f32::from_be_bytes(bytes);
92
+
let bytes = <&[u8; 4]>::try_from(val_buf).unwrap().clone();
93
+
let float = f32::from_be_bytes(bytes);
86
94
87
-
values.push(ParameterType::Float(float));
88
-
value_start += 4;
89
-
},
90
-
0x54 => values.push(ParameterType::Boolean(true)),
91
-
0x46 => values.push(ParameterType::Boolean(false)),
92
-
_ => {}
93
-
}
94
-
}
95
+
values.push(ParameterType::Float(float));
96
+
value_start += 4;
97
+
}
98
+
0x54 => values.push(ParameterType::Boolean(true)),
99
+
0x46 => values.push(ParameterType::Boolean(false)),
100
+
_ => {}
101
+
}
102
+
}
95
103
96
-
let message = OSCMessage {
97
-
address: String::from_utf8(addr.clone()).unwrap(),
98
-
values: values
99
-
};
104
+
let message = OSCMessage {
105
+
address: String::from_utf8(addr.clone()).unwrap(),
106
+
values: values,
107
+
};
100
108
101
-
sender.send(message).unwrap();
102
-
}
109
+
sender.send(message).unwrap();
110
+
}
103
111
}
104
112
105
-
pub fn send_message( address: &str, values: Vec<ParameterType>, ip_addr: &str ){
106
-
let socket = UdpSocket::bind("127.0.0.1:0").unwrap();
107
-
let mut buf: Vec<u8> = Vec::new();
113
+
pub fn send_message(address: &str, values: Vec<ParameterType>, ip_addr: &str) {
114
+
let socket = UdpSocket::bind("127.0.0.1:0").unwrap();
115
+
let mut buf: Vec<u8> = Vec::new();
108
116
109
-
buf.append(&mut address.as_bytes().to_vec());
117
+
buf.append(&mut address.as_bytes().to_vec());
110
118
111
-
let rounded_len = ((((buf.len() as f64) + 1.0) / 4.0).ceil() * 4.0) as usize;
112
-
let original_len = buf.len();
119
+
let rounded_len = ((((buf.len() as f64) + 1.0) / 4.0).ceil() * 4.0) as usize;
120
+
let original_len = buf.len();
113
121
114
-
for _i in original_len..rounded_len {
115
-
buf.push(0);
116
-
}
122
+
for _i in original_len..rounded_len {
123
+
buf.push(0);
124
+
}
117
125
118
-
buf.push(0x2C);
126
+
buf.push(0x2C);
119
127
120
-
let mut value_count = 1;
121
-
for value in values.clone() {
122
-
match value {
123
-
ParameterType::Boolean( val ) => buf.push(if val { 0x54 } else { 0x46 }),
124
-
ParameterType::Float(_) => buf.push(0x66),
125
-
ParameterType::Int(_) => buf.push(0x69),
126
-
ParameterType::String(_) => buf.push(0x73),
127
-
_ => {}
128
-
};
128
+
let mut value_count = 1;
129
+
for value in values.clone() {
130
+
match value {
131
+
ParameterType::Boolean(val) => buf.push(if val { 0x54 } else { 0x46 }),
132
+
ParameterType::Float(_) => buf.push(0x66),
133
+
ParameterType::Int(_) => buf.push(0x69),
134
+
ParameterType::String(_) => buf.push(0x73),
135
+
_ => {}
136
+
};
129
137
130
-
value_count += 1;
131
-
}
138
+
value_count += 1;
139
+
}
132
140
133
-
for _i in 0..4 - (value_count % 4) {
134
-
buf.push(0);
135
-
}
141
+
for _i in 0..4 - (value_count % 4) {
142
+
buf.push(0);
143
+
}
136
144
137
-
for value in values{
138
-
match value{
139
-
ParameterType::Float( val ) => buf.append(&mut val.to_be_bytes().to_vec()),
140
-
ParameterType::Int( val ) => buf.append(&mut val.to_be_bytes().to_vec()),
141
-
ParameterType::String( val ) => {
142
-
let mut str_buf = val.as_bytes().to_vec();
143
-
let buf_len = str_buf.len().clone();
145
+
for value in values {
146
+
match value {
147
+
ParameterType::Float(val) => buf.append(&mut val.to_be_bytes().to_vec()),
148
+
ParameterType::Int(val) => buf.append(&mut val.to_be_bytes().to_vec()),
149
+
ParameterType::String(val) => {
150
+
let mut str_buf = val.as_bytes().to_vec();
151
+
let buf_len = str_buf.len().clone();
144
152
145
-
buf.append(&mut str_buf);
153
+
buf.append(&mut str_buf);
146
154
147
-
for _i in 0..4 - (buf_len % 4) {
148
-
buf.push(0);
155
+
for _i in 0..4 - (buf_len % 4) {
156
+
buf.push(0);
157
+
}
158
+
}
159
+
_ => {}
149
160
}
150
-
}
151
-
_ => {}
152
161
}
153
-
}
154
162
155
-
println!("{:X?}", &buf);
156
-
socket.send_to(&buf, ip_addr).unwrap();
157
-
}
163
+
println!("{:X?}", &buf);
164
+
socket.send_to(&buf, ip_addr).unwrap();
165
+
}
+35
-29
src-tauri/src/setup.rs
+35
-29
src-tauri/src/setup.rs
···
1
-
use std::{ fs::File, io::Read, sync::{ self, Mutex } };
1
+
use std::{
2
+
fs::File,
3
+
io::Read,
4
+
sync::{self, Mutex},
5
+
};
2
6
3
7
use flate2::read::GzDecoder;
4
8
use serde_json::Value;
5
-
use tauri::{ App, Emitter, Listener, Manager };
9
+
use tauri::{App, Emitter, Listener, Manager};
6
10
7
-
use crate::osc::{ self, OSCMessage };
11
+
use crate::osc::{self, OSCMessage};
8
12
9
-
pub fn setup( app: &mut App, addresses: &'static Mutex<Vec<OSCMessage>> ){
10
-
let window = app.get_webview_window("main").unwrap();
13
+
pub fn setup(app: &mut App, addresses: &'static Mutex<Vec<OSCMessage>>) {
14
+
let window = app.get_webview_window("main").unwrap();
11
15
12
-
let handle = window.clone();
13
-
window.listen("tauri://drag-drop", move | ev | {
14
-
let path: Value = serde_json::from_str(ev.payload()).unwrap();
15
-
let path = path["paths"][0].as_str().unwrap();
16
+
let handle = window.clone();
17
+
window.listen("tauri://drag-drop", move |ev| {
18
+
let path: Value = serde_json::from_str(ev.payload()).unwrap();
19
+
let path = path["paths"][0].as_str().unwrap();
16
20
17
-
let file = File::open(path).unwrap();
18
-
let mut decoder = GzDecoder::new(file);
19
-
let mut string = String::new();
21
+
let file = File::open(path).unwrap();
22
+
let mut decoder = GzDecoder::new(file);
23
+
let mut string = String::new();
20
24
21
-
decoder.read_to_string(&mut string).unwrap();
25
+
decoder.read_to_string(&mut string).unwrap();
22
26
23
-
handle.emit("load_new_tab", Value::String(string)).unwrap();
24
-
});
27
+
handle.emit("load_new_tab", Value::String(string)).unwrap();
28
+
});
25
29
26
-
let ( sender, receiver ) = sync::mpsc::channel();
30
+
let (sender, receiver) = sync::mpsc::channel();
27
31
28
-
tokio::spawn(async move {
29
-
osc::start_server(sender, "127.0.0.1:9001");
30
-
});
32
+
tokio::spawn(async move {
33
+
osc::start_server(sender, "127.0.0.1:9001");
34
+
});
31
35
32
-
tokio::spawn(async move {
33
-
loop {
34
-
let message = receiver.recv().unwrap();
36
+
tokio::spawn(async move {
37
+
loop {
38
+
let message = receiver.recv().unwrap();
35
39
36
-
window.emit("osc-message", &message).unwrap();
40
+
window.emit("osc-message", &message).unwrap();
37
41
38
-
let msg = message.clone();
39
-
let mut addresses = addresses.lock().unwrap();
40
-
if !addresses.contains(&msg){ addresses.push(msg); }
41
-
}
42
-
});
43
-
}
42
+
let msg = message.clone();
43
+
let mut addresses = addresses.lock().unwrap();
44
+
if !addresses.contains(&msg) {
45
+
addresses.push(msg);
46
+
}
47
+
}
48
+
});
49
+
}
+8
-8
src-tauri/src/structs/parameter_types.rs
+8
-8
src-tauri/src/structs/parameter_types.rs
···
2
2
3
3
#[derive(Serialize, Clone, Debug)]
4
4
#[serde(tag = "key", content = "value")]
5
-
pub enum ParameterType{
6
-
AnyType(String),
7
-
Label(&'static str),
5
+
pub enum ParameterType {
6
+
AnyType(String),
7
+
Label(&'static str),
8
8
9
-
Int(i32),
10
-
Float(f32),
11
-
Boolean(bool),
12
-
String(String),
13
-
}
9
+
Int(i32),
10
+
Float(f32),
11
+
Boolean(bool),
12
+
String(String),
13
+
}
+69
-63
src-tauri/src/utils/config.rs
+69
-63
src-tauri/src/utils/config.rs
···
1
-
use std::{ collections::HashMap, fs::File, io::{ Read, Write }, path::PathBuf, sync::Mutex };
2
-
3
-
use flate2::{ read::GzDecoder, write::GzEncoder, Compression };
4
-
use serde_json::Value;
5
-
6
-
pub struct Config{
7
-
store: Mutex<HashMap<String, Value>>,
8
-
path: PathBuf
9
-
}
10
-
11
-
impl Config{
12
-
pub fn new( path: PathBuf ) -> Self{
13
-
let json_string = if path.exists(){
14
-
let mut decoder = GzDecoder::new(File::open(&path).unwrap());
15
-
let mut string = String::new();
16
-
17
-
decoder.read_to_string(&mut string).unwrap();
18
-
string
19
-
} else{
20
-
"{}".into()
21
-
};
22
-
23
-
let json: Value = serde_json::from_str(&json_string).unwrap();
24
-
25
-
let mut hashmap = HashMap::new();
26
-
27
-
let obj = json.as_object().unwrap();
28
-
for ( key, val ) in obj{
29
-
hashmap.insert(key.clone(), val.clone());
30
-
}
31
-
32
-
Config {
33
-
store: Mutex::new(hashmap),
34
-
path: path
35
-
}
36
-
}
37
-
38
-
pub fn set( &self, key: &str, value: Value ){
39
-
self.store.lock().unwrap().insert(key.to_owned(), value);
40
-
}
41
-
42
-
pub fn get( &self, key: &str ) -> Option<Value>{
43
-
let store = self.store.lock().unwrap();
44
-
let val = store.get(&key.to_owned());
45
-
46
-
if val.is_none(){
47
-
None
48
-
} else{
49
-
Some(val.unwrap().clone())
50
-
}
51
-
}
52
-
53
-
pub fn save( &self ){
54
-
let dat = serde_json::to_string(&self.store.lock().unwrap().clone()).unwrap();
55
-
dbg!(&dat);
56
-
57
-
let file = File::create(&self.path).unwrap();
58
-
let mut encoder = GzEncoder::new(file, Compression::default());
59
-
60
-
encoder.write_all(dat.as_bytes()).unwrap();
61
-
encoder.finish().unwrap();
62
-
}
63
-
}
1
+
use std::{
2
+
collections::HashMap,
3
+
fs::File,
4
+
io::{Read, Write},
5
+
path::PathBuf,
6
+
sync::Mutex,
7
+
};
8
+
9
+
use flate2::{read::GzDecoder, write::GzEncoder, Compression};
10
+
use serde_json::Value;
11
+
12
+
pub struct Config {
13
+
store: Mutex<HashMap<String, Value>>,
14
+
path: PathBuf,
15
+
}
16
+
17
+
impl Config {
18
+
pub fn new(path: PathBuf) -> Self {
19
+
let json_string = if path.exists() {
20
+
let mut decoder = GzDecoder::new(File::open(&path).unwrap());
21
+
let mut string = String::new();
22
+
23
+
decoder.read_to_string(&mut string).unwrap();
24
+
string
25
+
} else {
26
+
"{}".into()
27
+
};
28
+
29
+
let json: Value = serde_json::from_str(&json_string).unwrap();
30
+
31
+
let mut hashmap = HashMap::new();
32
+
33
+
let obj = json.as_object().unwrap();
34
+
for (key, val) in obj {
35
+
hashmap.insert(key.clone(), val.clone());
36
+
}
37
+
38
+
Config {
39
+
store: Mutex::new(hashmap),
40
+
path: path,
41
+
}
42
+
}
43
+
44
+
pub fn set(&self, key: &str, value: Value) {
45
+
self.store.lock().unwrap().insert(key.to_owned(), value);
46
+
}
47
+
48
+
pub fn get(&self, key: &str) -> Option<Value> {
49
+
let store = self.store.lock().unwrap();
50
+
let val = store.get(&key.to_owned());
51
+
52
+
if val.is_none() {
53
+
None
54
+
} else {
55
+
Some(val.unwrap().clone())
56
+
}
57
+
}
58
+
59
+
pub fn save(&self) {
60
+
let dat = serde_json::to_string(&self.store.lock().unwrap().clone()).unwrap();
61
+
dbg!(&dat);
62
+
63
+
let file = File::create(&self.path).unwrap();
64
+
let mut encoder = GzEncoder::new(file, Compression::default());
65
+
66
+
encoder.write_all(dat.as_bytes()).unwrap();
67
+
encoder.finish().unwrap();
68
+
}
69
+
}
+22
-10
src/Mangers/NodeManager.tsx
+22
-10
src/Mangers/NodeManager.tsx
···
5
5
import { listen } from "@tauri-apps/api/event";
6
6
import { getVersion } from "@tauri-apps/api/app";
7
7
import { NodesByID } from "../Nodes/Nodes";
8
+
import { save } from "@tauri-apps/plugin-dialog";
8
9
9
10
export interface TabHashMap {
10
11
[details: string] : Tab;
···
17
18
private _tabs: TabHashMap = {};
18
19
19
20
private _nodes: Node[] = [];
20
-
private _needsSave = false;
21
21
22
22
constructor(){
23
23
NodeManager.Instance = this;
24
24
25
-
setInterval(() => {
26
-
// Save config every 1 second
27
-
if(this._needsSave)this._saveConfigToDisk();
28
-
}, 1_000);
29
-
30
25
listen('load_new_tab', ( ev: any ) => {
31
26
this._loadFromConfig(ev.payload);
32
27
})
···
38
33
39
34
public async AddTab( name: string ){
40
35
let [ selected, setSelected ] = createSignal(false);
36
+
let [ needsSave, setNeedsSave ] = createSignal(false);
41
37
42
38
let tab: Tab = {
43
39
name: name,
44
40
id: await NodeManager.Instance.GetNewNodeId(),
45
41
nodes: [],
46
-
selected: selected,
47
-
setSelected: setSelected
42
+
43
+
selected,
44
+
setSelected,
45
+
46
+
needsSave,
47
+
setNeedsSave
48
48
};
49
49
50
50
this._tabs[tab.id] = tab;
···
110
110
}
111
111
}
112
112
113
+
public async SaveTab( tab: Tab ){
114
+
let path = await save({ defaultPath: tab.name + '.macro', filters: [ { name: 'Macro Files', extensions: [ 'macro' ] } ] });
115
+
116
+
console.log(path);
117
+
118
+
// TODO: Add location metadata to tab interface so it knows where to save
119
+
// TODO: store file
120
+
}
121
+
113
122
public HookTabUpdate( cb: ( tabs: TabHashMap ) => void ){
114
123
this._tabUpdateHook = cb;
115
124
}
···
150
159
151
160
152
161
public UpdateConfig(){
153
-
this._needsSave = true;
162
+
if(!this._selectedTab)return;
163
+
let tab = this._tabs[this._selectedTab];
164
+
if(!tab)return;
165
+
166
+
tab.setNeedsSave(true);
154
167
}
155
168
156
169
private async _loadFromConfig( config: string ){
···
215
228
}
216
229
217
230
private async _saveConfigToDisk(){
218
-
this._needsSave = false;
219
231
// Convert it into a structure we can actually save...
220
232
221
233
if(!this._selectedTab)return;
-2
src/components/TabMenu.css
-2
src/components/TabMenu.css
+21
-10
src/components/TabMenu.tsx
+21
-10
src/components/TabMenu.tsx
···
1
-
import { createSignal, For, onMount } from 'solid-js';
1
+
import { createSignal, For, onMount, Show } from 'solid-js';
2
2
import './TabMenu.css';
3
3
import { NodeManager } from '../Mangers/NodeManager';
4
4
import { Tab } from '../structs/Tab';
···
21
21
<For each={Object.values(tabs())}>
22
22
{
23
23
tab =>
24
-
<div class={ tab.selected() ? 'tab-selected ' : 'tab' }>
25
-
<div class="tab-icon" onClick={() => {
26
-
NodeManager.Instance.SelectTab(tab.id);
27
-
}}><img src="/assets/icons/pen-to-square-regular-full.svg" width="15" /></div>
28
-
<div class="tab-meta" onClick={() => {
29
-
NodeManager.Instance.SelectTab(tab.id);
30
-
}} onDblClick={( e ) => {
24
+
<div class={ tab.selected() ? 'tab-selected ' : 'tab' } onClick={() => {
25
+
NodeManager.Instance.SelectTab(tab.id);
26
+
}}>
27
+
<div class="tab-icon" onClick={async () => {
28
+
if(tab.selected()){
29
+
NodeManager.Instance.SaveTab(tab);
30
+
}
31
+
}}>
32
+
<Show when={tab.selected() && tab.needsSave()} fallback={
33
+
<img src="/assets/icons/pen-to-square-regular-full.svg" width="15" />
34
+
}>
35
+
<img src="/assets/icons/floppy-disk-solid-full.svg" width="15" />
36
+
</Show>
37
+
38
+
</div>
39
+
<div class="tab-meta" onDblClick={( e ) => {
31
40
let input = <input class="tab-meta-input" value={ e.target.innerHTML } /> as HTMLInputElement;
32
41
33
42
e.target.innerHTML = '';
···
40
49
}
41
50
}}>{ tab.name }</div>
42
51
<div class="tab-close" onClick={() => {
43
-
NodeManager.Instance.CloseTab(tab.id);
52
+
setTimeout(() => {
53
+
NodeManager.Instance.CloseTab(tab.id);
54
+
}, 50)
44
55
}}><img src="/assets/icons/xmark-solid-full.svg" width="12" /></div>
45
56
</div>
46
57
}
···
54
65
55
66
window.addEventListener('click', closeTabImportMenu);
56
67
}}>
57
-
<div class="tab-new-dropdown" style={{ opacity: tabImportOpen() ? 1 : 0 }}>
68
+
<div class="tab-new-dropdown" style={{ display: tabImportOpen() ? 'block' : 'none' }}>
58
69
<div class="tab">Import from file</div>
59
70
<div class="tab">Import from URL</div>
60
71
</div>