diff --git a/Makefile b/Makefile index 51c7784e..d3fcbea5 100644 --- a/Makefile +++ b/Makefile @@ -15,12 +15,12 @@ EXPORT_TEMPLATE ?= $(HOME)/.local/share/godot/export_templates/$(GODOT_REVISION) #EXPORT_TEMPLATE_URL ?= https://downloads.tuxfamily.org/godotengine/$(GODOT_VERSION)/Godot_v$(GODOT_VERSION)-$(GODOT_RELEASE)_export_templates.tpz EXPORT_TEMPLATE_URL ?= https://github.com/godotengine/godot/releases/download/$(GODOT_VERSION)-$(GODOT_RELEASE)/Godot_v$(GODOT_VERSION)-$(GODOT_RELEASE)_export_templates.tpz -ALL_ADDONS := ./addons/dbus/bin/libdbus.linux.template_$(BUILD_TYPE).x86_64.so ./addons/linuxthread/bin/liblinuxthread.linux.template_$(BUILD_TYPE).x86_64.so ./addons/pty/bin/libpty.linux.template_$(BUILD_TYPE).x86_64.so ./addons/unixsock/bin/libunixsock.linux.template_$(BUILD_TYPE).x86_64.so ./addons/xlib/bin/libxlib.linux.template_$(BUILD_TYPE).x86_64.so -ALL_ADDON_FILES := $(shell find ./addons -regex '.*\(\.cpp\|\.h\|\.hpp\)$$') +ALL_EXTENSIONS := ./addons/core/bin/libopengamepadui-core.linux.template_$(BUILD_TYPE).x86_64.so +ALL_EXTENSION_FILES := $(shell find ./extensions/ -regex '.*\(\.rs|\.toml\|\.lock\)$$') ALL_GDSCRIPT := $(shell find ./ -name '*.gd') ALL_SCENES := $(shell find ./ -name '*.tscn') ALL_RESOURCES := $(shell find ./ -regex '.*\(\.tres\|\.svg\|\.png\)$$') -PROJECT_FILES := $(ALL_ADDONS) $(ALL_GDSCRIPT) $(ALL_SCENES) $(ALL_RESOURCES) +PROJECT_FILES := $(ALL_EXTENSIONS) $(ALL_GDSCRIPT) $(ALL_SCENES) $(ALL_RESOURCES) # Docker image variables IMAGE_NAME ?= ghcr.io/shadowblip/opengamepadui-builder @@ -144,24 +144,24 @@ build/metadata.json: build/opengamepad-ui.x86_64 assets/crypto/keys/opengamepadu .PHONY: import import: $(IMPORT_DIR) ## Import project assets -$(IMPORT_DIR): $(ALL_ADDONS) +$(IMPORT_DIR): $(ALL_EXTENSIONS) @echo "Importing project assets. This will take some time..." command -v $(GODOT) > /dev/null 2>&1 timeout --foreground 40 $(GODOT) --headless --editor . > /dev/null 2>&1 || echo "Finished" touch $(IMPORT_DIR) .PHONY: force-import -force-import: $(ALL_ADDONS) +force-import: $(ALL_EXTENSIONS) @echo "Force importing project assets. This will take some time..." command -v $(GODOT) > /dev/null 2>&1 timeout --foreground 40 $(GODOT) --headless --editor . > /dev/null 2>&1 || echo "Finished" timeout --foreground 40 $(GODOT) --headless --editor . > /dev/null 2>&1 || echo "Finished" -.PHONY: addons -addons: $(ALL_ADDONS) ## Build GDExtension addons -$(ALL_ADDONS) &: $(ALL_ADDON_FILES) - @echo "Building native GDExtension addons..." - cd ./gdext && $(MAKE) build +.PHONY: extensions +extensions: $(ALL_EXTENSIONS) ## Build native extensions +$(ALL_EXTENSIONS) &: $(ALL_EXTENSION_FILES) + @echo "Building native extensions..." + cd ./extensions/core && $(MAKE) build .PHONY: edit edit: $(IMPORT_DIR) ## Open the project in the Godot editor @@ -174,7 +174,7 @@ clean: ## Remove build artifacts rm -rf $(CACHE_DIR) rm -rf dist rm -rf $(IMPORT_DIR) - cd ./gdext && $(MAKE) clean + cd ./extensions/core && $(MAKE) clean .PHONY: run run-force run: build/opengamepad-ui.x86_64 run-force ## Run the project in gamescope diff --git a/addons/.gitignore b/addons/.gitignore index 99eef491..140f8cf8 100644 --- a/addons/.gitignore +++ b/addons/.gitignore @@ -1,5 +1 @@ -dbus/ -linuxthread/ -pty/ -unixsock/ -xlib/ +*.so diff --git a/addons/core/assets/icons/inputplumber.svg b/addons/core/assets/icons/inputplumber.svg new file mode 100644 index 00000000..4ea3ae1e --- /dev/null +++ b/addons/core/assets/icons/inputplumber.svg @@ -0,0 +1,76 @@ + + diff --git a/addons/core/assets/icons/inputplumber.svg.import b/addons/core/assets/icons/inputplumber.svg.import new file mode 100644 index 00000000..b1198507 --- /dev/null +++ b/addons/core/assets/icons/inputplumber.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dbdyxgqmyeg1f" +path="res://.godot/imported/inputplumber.svg-930e8ab4c0d3c4d458c32ef96742bdaf.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/core/assets/icons/inputplumber.svg" +dest_files=["res://.godot/imported/inputplumber.svg-930e8ab4c0d3c4d458c32ef96742bdaf.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/core/assets/icons/library.svg b/addons/core/assets/icons/library.svg new file mode 100644 index 00000000..a4c3cef4 --- /dev/null +++ b/addons/core/assets/icons/library.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addons/core/assets/icons/library.svg.import b/addons/core/assets/icons/library.svg.import new file mode 100644 index 00000000..36d12ed4 --- /dev/null +++ b/addons/core/assets/icons/library.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bn4jiyx7afl3b" +path="res://.godot/imported/library.svg-8fcfe8437951fa126b5ab9f50dba6f13.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/core/assets/icons/library.svg" +dest_files=["res://.godot/imported/library.svg-8fcfe8437951fa126b5ab9f50dba6f13.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/core/core.gdextension b/addons/core/core.gdextension new file mode 100644 index 00000000..3248941d --- /dev/null +++ b/addons/core/core.gdextension @@ -0,0 +1,12 @@ +[configuration] +entry_symbol = "gdext_rust_init" +compatibility_minimum = 4.3 +reloadable = true + +[libraries] +linux.debug.x86_64 = "res://addons/core/bin/libopengamepadui-core.linux.template_debug.x86_64.so" +linux.release.x86_64 = "res://addons/core/bin/libopengamepadui-core.linux.template_release.x86_64.so" + +[icons] +LibraryItem = "res://addons/core/assets/icons/library.svg" +LibraryLaunchItem = "res://addons/core/assets/icons/library.svg" diff --git a/addons/gut/fonts/AnonymousPro-Bold.ttf.import b/addons/gut/fonts/AnonymousPro-Bold.ttf.import index a3eb4791..de1351f6 100644 --- a/addons/gut/fonts/AnonymousPro-Bold.ttf.import +++ b/addons/gut/fonts/AnonymousPro-Bold.ttf.import @@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/AnonymousPro-Bold.ttf-9d8fef4d357af5b52cd60af Rendering=null antialiasing=1 generate_mipmaps=false +disable_embedded_bitmaps=true multichannel_signed_distance_field=false msdf_pixel_range=8 msdf_size=48 diff --git a/addons/gut/fonts/AnonymousPro-BoldItalic.ttf.import b/addons/gut/fonts/AnonymousPro-BoldItalic.ttf.import index ef28dd80..bdde2072 100644 --- a/addons/gut/fonts/AnonymousPro-BoldItalic.ttf.import +++ b/addons/gut/fonts/AnonymousPro-BoldItalic.ttf.import @@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/AnonymousPro-BoldItalic.ttf-4274bf704d3d6b9cd Rendering=null antialiasing=1 generate_mipmaps=false +disable_embedded_bitmaps=true multichannel_signed_distance_field=false msdf_pixel_range=8 msdf_size=48 diff --git a/addons/gut/fonts/AnonymousPro-Italic.ttf.import b/addons/gut/fonts/AnonymousPro-Italic.ttf.import index 1779af17..ce3e5b91 100644 --- a/addons/gut/fonts/AnonymousPro-Italic.ttf.import +++ b/addons/gut/fonts/AnonymousPro-Italic.ttf.import @@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/AnonymousPro-Italic.ttf-9989590b02137b799e13d Rendering=null antialiasing=1 generate_mipmaps=false +disable_embedded_bitmaps=true multichannel_signed_distance_field=false msdf_pixel_range=8 msdf_size=48 diff --git a/addons/gut/fonts/AnonymousPro-Regular.ttf.import b/addons/gut/fonts/AnonymousPro-Regular.ttf.import index 1e2975b1..a567498c 100644 --- a/addons/gut/fonts/AnonymousPro-Regular.ttf.import +++ b/addons/gut/fonts/AnonymousPro-Regular.ttf.import @@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/AnonymousPro-Regular.ttf-856c843fd6f89964d2ca Rendering=null antialiasing=1 generate_mipmaps=false +disable_embedded_bitmaps=true multichannel_signed_distance_field=false msdf_pixel_range=8 msdf_size=48 diff --git a/addons/gut/fonts/CourierPrime-Bold.ttf.import b/addons/gut/fonts/CourierPrime-Bold.ttf.import index 7d60fb0a..cb05171d 100644 --- a/addons/gut/fonts/CourierPrime-Bold.ttf.import +++ b/addons/gut/fonts/CourierPrime-Bold.ttf.import @@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/CourierPrime-Bold.ttf-1f003c66d63ebed70964e77 Rendering=null antialiasing=1 generate_mipmaps=false +disable_embedded_bitmaps=true multichannel_signed_distance_field=false msdf_pixel_range=8 msdf_size=48 diff --git a/addons/gut/fonts/CourierPrime-BoldItalic.ttf.import b/addons/gut/fonts/CourierPrime-BoldItalic.ttf.import index 4678c9eb..0a9a7b77 100644 --- a/addons/gut/fonts/CourierPrime-BoldItalic.ttf.import +++ b/addons/gut/fonts/CourierPrime-BoldItalic.ttf.import @@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/CourierPrime-BoldItalic.ttf-65ebcc61dd5e1dfa8 Rendering=null antialiasing=1 generate_mipmaps=false +disable_embedded_bitmaps=true multichannel_signed_distance_field=false msdf_pixel_range=8 msdf_size=48 diff --git a/addons/gut/fonts/CourierPrime-Italic.ttf.import b/addons/gut/fonts/CourierPrime-Italic.ttf.import index 522e2950..89412fc9 100644 --- a/addons/gut/fonts/CourierPrime-Italic.ttf.import +++ b/addons/gut/fonts/CourierPrime-Italic.ttf.import @@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/CourierPrime-Italic.ttf-baa9156a73770735a0f72 Rendering=null antialiasing=1 generate_mipmaps=false +disable_embedded_bitmaps=true multichannel_signed_distance_field=false msdf_pixel_range=8 msdf_size=48 diff --git a/addons/gut/fonts/CourierPrime-Regular.ttf.import b/addons/gut/fonts/CourierPrime-Regular.ttf.import index 38174660..9fde40b1 100644 --- a/addons/gut/fonts/CourierPrime-Regular.ttf.import +++ b/addons/gut/fonts/CourierPrime-Regular.ttf.import @@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/CourierPrime-Regular.ttf-3babe7e4a7a588dfc9a8 Rendering=null antialiasing=1 generate_mipmaps=false +disable_embedded_bitmaps=true multichannel_signed_distance_field=false msdf_pixel_range=8 msdf_size=48 diff --git a/addons/gut/fonts/LobsterTwo-Bold.ttf.import b/addons/gut/fonts/LobsterTwo-Bold.ttf.import index 7548ad04..673d1515 100644 --- a/addons/gut/fonts/LobsterTwo-Bold.ttf.import +++ b/addons/gut/fonts/LobsterTwo-Bold.ttf.import @@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/LobsterTwo-Bold.ttf-7c7f734103b58a32491a47881 Rendering=null antialiasing=1 generate_mipmaps=false +disable_embedded_bitmaps=true multichannel_signed_distance_field=false msdf_pixel_range=8 msdf_size=48 diff --git a/addons/gut/fonts/LobsterTwo-BoldItalic.ttf.import b/addons/gut/fonts/LobsterTwo-BoldItalic.ttf.import index 4b609e80..62048b0e 100644 --- a/addons/gut/fonts/LobsterTwo-BoldItalic.ttf.import +++ b/addons/gut/fonts/LobsterTwo-BoldItalic.ttf.import @@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/LobsterTwo-BoldItalic.ttf-227406a33e84448e6aa Rendering=null antialiasing=1 generate_mipmaps=false +disable_embedded_bitmaps=true multichannel_signed_distance_field=false msdf_pixel_range=8 msdf_size=48 diff --git a/addons/gut/fonts/LobsterTwo-Italic.ttf.import b/addons/gut/fonts/LobsterTwo-Italic.ttf.import index 5899b797..d3ca2728 100644 --- a/addons/gut/fonts/LobsterTwo-Italic.ttf.import +++ b/addons/gut/fonts/LobsterTwo-Italic.ttf.import @@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/LobsterTwo-Italic.ttf-f93abf6c25390c85ad5fb6c Rendering=null antialiasing=1 generate_mipmaps=false +disable_embedded_bitmaps=true multichannel_signed_distance_field=false msdf_pixel_range=8 msdf_size=48 diff --git a/addons/gut/fonts/LobsterTwo-Regular.ttf.import b/addons/gut/fonts/LobsterTwo-Regular.ttf.import index 45a12c8a..9cc75421 100644 --- a/addons/gut/fonts/LobsterTwo-Regular.ttf.import +++ b/addons/gut/fonts/LobsterTwo-Regular.ttf.import @@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/LobsterTwo-Regular.ttf-f3fcfa01cd671c8da433dd Rendering=null antialiasing=1 generate_mipmaps=false +disable_embedded_bitmaps=true multichannel_signed_distance_field=false msdf_pixel_range=8 msdf_size=48 diff --git a/gdext/.gdignore b/extensions/.gdignore similarity index 100% rename from gdext/.gdignore rename to extensions/.gdignore diff --git a/extensions/core/.gitignore b/extensions/core/.gitignore new file mode 100644 index 00000000..54b02da4 --- /dev/null +++ b/extensions/core/.gitignore @@ -0,0 +1,2 @@ +/target +*.so diff --git a/extensions/core/Cargo.lock b/extensions/core/Cargo.lock new file mode 100644 index 00000000..7ab833db --- /dev/null +++ b/extensions/core/Cargo.lock @@ -0,0 +1,1426 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "async-broadcast" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cd0e2e25ea8e5f7e9df04578dc6cf5c83577fd09b1a46aaf5c85e1c33f2a7e" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7ebdfa2ebdab6b1760375fa7d6f382b9f486eac35fc994625a00e89280bdbb7" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-io" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" +dependencies = [ + "async-lock", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a07789659a4d385b79b18b9127fc27e1a59e1e89117c78c5ea3b806f016374" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-signal" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "backtrace" +version = "0.3.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "blocking" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" + +[[package]] +name = "cc" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "cpufeatures" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "endi" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" + +[[package]] +name = "enumflags2" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "event-listener" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-lite" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "gdextension-api" +version = "0.2.0" +source = "git+https://github.com/godot-rust/godot4-prebuilt?branch=releases#6d902e8a6060007f4ab94cd78882247ae2558d96" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "gensym" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "913dce4c5f06c2ea40fc178c06f777ac89fc6b1383e90c254fafb1abe4ba3c82" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "uuid", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" + +[[package]] +name = "glam" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "779ae4bf7e8421cf91c0b3b64e7e8b40b862fba4d393f59150042de7c4965a94" + +[[package]] +name = "godot" +version = "0.1.3" +source = "git+https://github.com/godot-rust/gdext?branch=master#406a5d9b6c44b42b4134251c9b732b4fdbb343a1" +dependencies = [ + "godot-core", + "godot-macros", +] + +[[package]] +name = "godot-bindings" +version = "0.1.3" +source = "git+https://github.com/godot-rust/gdext?branch=master#406a5d9b6c44b42b4134251c9b732b4fdbb343a1" +dependencies = [ + "gdextension-api", +] + +[[package]] +name = "godot-cell" +version = "0.1.3" +source = "git+https://github.com/godot-rust/gdext?branch=master#406a5d9b6c44b42b4134251c9b732b4fdbb343a1" + +[[package]] +name = "godot-codegen" +version = "0.1.3" +source = "git+https://github.com/godot-rust/gdext?branch=master#406a5d9b6c44b42b4134251c9b732b4fdbb343a1" +dependencies = [ + "godot-bindings", + "heck", + "nanoserde", + "proc-macro2", + "quote", + "regex", +] + +[[package]] +name = "godot-core" +version = "0.1.3" +source = "git+https://github.com/godot-rust/gdext?branch=master#406a5d9b6c44b42b4134251c9b732b4fdbb343a1" +dependencies = [ + "glam", + "godot-bindings", + "godot-cell", + "godot-codegen", + "godot-ffi", +] + +[[package]] +name = "godot-ffi" +version = "0.1.3" +source = "git+https://github.com/godot-rust/gdext?branch=master#406a5d9b6c44b42b4134251c9b732b4fdbb343a1" +dependencies = [ + "gensym", + "godot-bindings", + "godot-codegen", + "libc", + "paste", +] + +[[package]] +name = "godot-macros" +version = "0.1.3" +source = "git+https://github.com/godot-rust/gdext?branch=master#406a5d9b6c44b42b4134251c9b732b4fdbb343a1" +dependencies = [ + "godot-bindings", + "markdown", + "proc-macro2", + "quote", + "venial", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "indexmap" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "libc" +version = "0.2.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "markdown" +version = "1.0.0-alpha.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "911a8325e6fb87b89890cd4529a2ab34c2669c026279e61c26b7140a3d821ccb" +dependencies = [ + "unicode-id", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "nanoserde" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de9cf844ab1e25a0353525bd74cb889843a6215fa4a0d156fd446f4857a1b99" +dependencies = [ + "nanoserde-derive", +] + +[[package]] +name = "nanoserde-derive" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e943b2c21337b7e3ec6678500687cdc741b7639ad457f234693352075c082204" + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + +[[package]] +name = "object" +version = "0.36.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "opengamepadui-core" +version = "0.1.0" +dependencies = [ + "futures-util", + "godot", + "nix", + "once_cell", + "tokio", + "zbus", + "zvariant", +] + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "polling" +version = "3.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.4.0", + "pin-project-lite", + "rustix", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.208" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.208" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_repr" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "2.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "tokio" +version = "1.39.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" + +[[package]] +name = "toml_edit" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset", + "tempfile", + "winapi", +] + +[[package]] +name = "unicode-id" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1b6def86329695390197b82c1e244a54a131ceb66c996f2088a3876e2ae083f" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "uuid" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +dependencies = [ + "getrandom", +] + +[[package]] +name = "venial" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6816bc32f30bf8dd1b3adb04de8406c7bf187d2f923bd9e4c0b99365d012613f" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "xdg-home" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "zbus" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725" +dependencies = [ + "async-broadcast", + "async-executor", + "async-fs", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix", + "ordered-stream", + "rand", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tracing", + "uds_windows", + "windows-sys 0.52.0", + "xdg-home", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" +dependencies = [ + "serde", + "static_assertions", + "zvariant", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zvariant" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe" +dependencies = [ + "endi", + "enumflags2", + "serde", + "static_assertions", + "zvariant_derive", +] + +[[package]] +name = "zvariant_derive" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/extensions/core/Cargo.toml b/extensions/core/Cargo.toml new file mode 100644 index 00000000..6424198b --- /dev/null +++ b/extensions/core/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "opengamepadui-core" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] # Compile this crate to a dynamic C library. + +[dependencies] +futures-util = "0.3.30" +godot = { git = "https://github.com/godot-rust/gdext", branch = "master", features = [ + "experimental-threads", + "register-docs", +] } +nix = { version = "0.29.0", features = ["term"] } +once_cell = "1.19.0" +tokio = { version = "1.39.3", features = ["full"] } +zbus = "4.4.0" +zvariant = "4.2.0" diff --git a/extensions/core/Makefile b/extensions/core/Makefile new file mode 100644 index 00000000..917abf11 --- /dev/null +++ b/extensions/core/Makefile @@ -0,0 +1,50 @@ +PREFIX ?= addons +EXT_NAME := $(shell grep 'name =' Cargo.toml | head -n 1 | cut -d'"' -f2) +LIB_NAME := $(shell grep 'name =' Cargo.toml | head -n 1 | cut -d'"' -f2 | sed 's/-/_/g') +ALL_RS := $(shell find ./src -name '*.rs') +ADDON_PATH := ../../addons/core +RELEASE_TARGET := $(ADDON_PATH)/bin/lib$(EXT_NAME).linux.template_release.x86_64.so +DEBUG_TARGET := $(ADDON_PATH)/bin/lib$(EXT_NAME).linux.template_debug.x86_64.so + +##@ General + +# The help target prints out all targets with their descriptions organized +# beneath their categories. The categories are represented by '##@' and the +# target descriptions by '##'. The awk commands is responsible for reading the +# entire set of makefiles included in this invocation, looking for lines of the +# file as xyz: ## something, and then pretty-format the target and help. Then, +# if there's a line with ##@ something, that gets pretty-printed as a category. +# More info on the usage of ANSI control characters for terminal formatting: +# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters +# More info on the awk command: +# http://linuxcommand.org/lc3_adv_awk.php + +.PHONY: help +help: ## Display this help. + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + + +.PHONY: build +build: $(RELEASE_TARGET) $(DEBUG_TARGET) ## Build release and debug binaries + + +.PHONY: clean +clean: ## Clean build artifacts + rm $(RELEASE_TARGET) $(DEBUG_TARGET) + rm -rf target + + +.PHONY: release +release: $(RELEASE_TARGET) ## Build release binary +$(RELEASE_TARGET): $(ALL_RS) + cargo build --release + mkdir -p $(@D) + cp target/release/lib$(LIB_NAME).so $@ + + +.PHONY: debug +debug: $(DEBUG_TARGET) ## Build binary with debug symbols +$(DEBUG_TARGET): $(ALL_RS) + cargo build + mkdir -p $(@D) + cp target/debug/lib$(LIB_NAME).so $@ diff --git a/gdext/README.md b/extensions/core/src/bluetooth.rs similarity index 100% rename from gdext/README.md rename to extensions/core/src/bluetooth.rs diff --git a/extensions/core/src/dbus.rs b/extensions/core/src/dbus.rs new file mode 100644 index 00000000..613ea199 --- /dev/null +++ b/extensions/core/src/dbus.rs @@ -0,0 +1,98 @@ +use godot::prelude::*; +use zvariant::NoneValue; + +pub mod inputplumber; +pub mod upower; + +/// Possible DBus runtime errors +#[derive(Debug)] +pub enum RunError { + Zbus(zbus::Error), + ZbusFdo(zbus::fdo::Error), +} + +impl From for RunError { + fn from(value: zbus::Error) -> Self { + RunError::Zbus(value) + } +} + +impl From for RunError { + fn from(value: zbus::fdo::Error) -> Self { + RunError::ZbusFdo(value) + } +} + +pub trait DBusVariant { + fn as_zvariant(&self) -> Option; +} + +impl DBusVariant for Variant { + /// Convert the Godot variant type into a DBus variant type + fn as_zvariant(&self) -> Option { + match self.get_type() { + VariantType::NIL => { + let value = zvariant::Optional::<&str>::null_value(); + Some(zvariant::Value::new(value)) + } + VariantType::BOOL => { + let value: bool = self.to(); + Some(zvariant::Value::new(value)) + } + VariantType::INT => { + let value: i64 = self.to(); + Some(zvariant::Value::new(value)) + } + VariantType::FLOAT => { + let value: f64 = self.to(); + Some(zvariant::Value::new(value)) + } + VariantType::STRING => { + let value: GString = self.to(); + let value: String = value.into(); + Some(zvariant::Value::new(value)) + } + VariantType::VECTOR2 => todo!(), + VariantType::VECTOR2I => todo!(), + VariantType::RECT2 => todo!(), + VariantType::RECT2I => todo!(), + VariantType::VECTOR3 => todo!(), + VariantType::VECTOR3I => todo!(), + VariantType::TRANSFORM2D => todo!(), + VariantType::VECTOR4 => todo!(), + VariantType::VECTOR4I => todo!(), + VariantType::PLANE => todo!(), + VariantType::QUATERNION => todo!(), + VariantType::AABB => todo!(), + VariantType::BASIS => todo!(), + VariantType::TRANSFORM3D => todo!(), + VariantType::PROJECTION => todo!(), + VariantType::COLOR => todo!(), + VariantType::STRING_NAME => todo!(), + VariantType::NODE_PATH => todo!(), + VariantType::RID => { + let value: i64 = self.to(); + Some(zvariant::Value::new(value)) + } + VariantType::OBJECT => todo!(), + VariantType::CALLABLE => todo!(), + VariantType::SIGNAL => todo!(), + VariantType::DICTIONARY => todo!(), + VariantType::ARRAY => todo!(), + VariantType::PACKED_BYTE_ARRAY => todo!(), + VariantType::PACKED_INT32_ARRAY => todo!(), + VariantType::PACKED_INT64_ARRAY => todo!(), + VariantType::PACKED_FLOAT32_ARRAY => todo!(), + VariantType::PACKED_FLOAT64_ARRAY => todo!(), + VariantType::PACKED_STRING_ARRAY => todo!(), + VariantType::PACKED_VECTOR2_ARRAY => todo!(), + VariantType::PACKED_VECTOR3_ARRAY => todo!(), + VariantType::PACKED_COLOR_ARRAY => todo!(), + VariantType::PACKED_VECTOR4_ARRAY => todo!(), + VariantType::MAX => todo!(), + + // Unsupported conversion + _ => None, + } + } +} diff --git a/extensions/core/src/dbus/inputplumber.rs b/extensions/core/src/dbus/inputplumber.rs new file mode 100644 index 00000000..e722fce8 --- /dev/null +++ b/extensions/core/src/dbus/inputplumber.rs @@ -0,0 +1,6 @@ +pub mod composite_device; +pub mod dbus_device; +pub mod event_device; +pub mod input_manager; +pub mod keyboard; +pub mod mouse; diff --git a/extensions/core/src/dbus/inputplumber/composite_device.rs b/extensions/core/src/dbus/inputplumber/composite_device.rs new file mode 100644 index 00000000..bfb9ea4b --- /dev/null +++ b/extensions/core/src/dbus/inputplumber/composite_device.rs @@ -0,0 +1,87 @@ +//! # D-Bus interface proxy for: `org.shadowblip.Input.CompositeDevice` +//! +//! This code was generated by `zbus-xmlgen` `4.1.0` from D-Bus introspection data. +//! Source: `Interface '/org/shadowblip/InputPlumber/CompositeDevice0' from service 'org.shadowblip.InputPlumber' on system bus`. +//! +//! You may prefer to adapt it, instead of using it verbatim. +//! +//! More information can be found in the [Writing a client proxy] section of the zbus +//! documentation. +//! +//! This type implements the [D-Bus standard interfaces], (`org.freedesktop.DBus.*`) for which the +//! following zbus API can be used: +//! +//! * [`zbus::fdo::PeerProxy`] +//! * [`zbus::fdo::PropertiesProxy`] +//! * [`zbus::fdo::IntrospectableProxy`] +//! +//! Consequently `zbus-xmlgen` did not generate code for the above interfaces. +//! +//! [Writing a client proxy]: https://dbus2.github.io/zbus/client.html +//! [D-Bus standard interfaces]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces, +use zbus::proxy; +#[proxy( + interface = "org.shadowblip.Input.CompositeDevice", + default_service = "org.shadowblip.InputPlumber", + default_path = "/org/shadowblip/InputPlumber/CompositeDevice0" +)] +trait CompositeDevice { + /// LoadProfileFromYaml method + fn load_profile_from_yaml(&self, profile: &str) -> zbus::Result<()>; + + /// LoadProfilePath method + fn load_profile_path(&self, path: &str) -> zbus::Result<()>; + + /// SendButtonChord method + fn send_button_chord(&self, events: &[&str]) -> zbus::Result<()>; + + /// SendEvent method + fn send_event(&self, event: &str, value: &zbus::zvariant::Value<'_>) -> zbus::Result<()>; + + /// SetInterceptActivation method + fn set_intercept_activation( + &self, + activation_events: &[&str], + target_event: &str, + ) -> zbus::Result<()>; + + /// SetTargetDevices method + fn set_target_devices(&self, target_device_types: &[&str]) -> zbus::Result<()>; + + /// Stop method + fn stop(&self) -> zbus::Result<()>; + + /// Capabilities property + #[zbus(property)] + fn capabilities(&self) -> zbus::Result>; + + /// DbusDevices property + #[zbus(property)] + fn dbus_devices(&self) -> zbus::Result>; + + /// InterceptMode property + #[zbus(property)] + fn intercept_mode(&self) -> zbus::Result; + #[zbus(property)] + fn set_intercept_mode(&self, value: u32) -> zbus::Result<()>; + + /// Name property + #[zbus(property)] + fn name(&self) -> zbus::Result; + + /// ProfileName property + #[zbus(property)] + fn profile_name(&self) -> zbus::Result; + + /// SourceDevicePaths property + #[zbus(property)] + fn source_device_paths(&self) -> zbus::Result>; + + /// TargetCapabilities property + #[zbus(property)] + fn target_capabilities(&self) -> zbus::Result>; + + /// TargetDevices property + #[zbus(property)] + fn target_devices(&self) -> zbus::Result>; +} diff --git a/extensions/core/src/dbus/inputplumber/dbus_device.rs b/extensions/core/src/dbus/inputplumber/dbus_device.rs new file mode 100644 index 00000000..56f05450 --- /dev/null +++ b/extensions/core/src/dbus/inputplumber/dbus_device.rs @@ -0,0 +1,48 @@ +//! # D-Bus interface proxy for: `org.shadowblip.Input.DBusDevice` +//! +//! This code was generated by `zbus-xmlgen` `4.1.0` from D-Bus introspection data. +//! Source: `Interface '/org/shadowblip/InputPlumber/devices/target/dbus0' from service 'org.shadowblip.InputPlumber' on system bus`. +//! +//! You may prefer to adapt it, instead of using it verbatim. +//! +//! More information can be found in the [Writing a client proxy] section of the zbus +//! documentation. +//! +//! This type implements the [D-Bus standard interfaces], (`org.freedesktop.DBus.*`) for which the +//! following zbus API can be used: +//! +//! * [`zbus::fdo::PeerProxy`] +//! * [`zbus::fdo::PropertiesProxy`] +//! * [`zbus::fdo::IntrospectableProxy`] +//! +//! Consequently `zbus-xmlgen` did not generate code for the above interfaces. +//! +//! [Writing a client proxy]: https://dbus2.github.io/zbus/client.html +//! [D-Bus standard interfaces]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces, +use zbus::proxy; +#[proxy( + interface = "org.shadowblip.Input.DBusDevice", + default_service = "org.shadowblip.InputPlumber", + default_path = "/org/shadowblip/InputPlumber/devices/target/dbus0" +)] +trait DBusDevice { + /// InputEvent signal + #[zbus(signal)] + fn input_event(&self, event: &str, value: f64) -> zbus::Result<()>; + + /// TouchEvent signal + #[zbus(signal)] + fn touch_event( + &self, + event: &str, + index: u32, + is_touching: bool, + pressure: f64, + x: f64, + y: f64, + ) -> zbus::Result<()>; + + /// Name property + #[zbus(property)] + fn name(&self) -> zbus::Result; +} diff --git a/extensions/core/src/dbus/inputplumber/event_device.rs b/extensions/core/src/dbus/inputplumber/event_device.rs new file mode 100644 index 00000000..5b435da7 --- /dev/null +++ b/extensions/core/src/dbus/inputplumber/event_device.rs @@ -0,0 +1,64 @@ +//! # D-Bus interface proxy for: `org.shadowblip.Input.Source.EventDevice` +//! +//! This code was generated by `zbus-xmlgen` `4.1.0` from D-Bus introspection data. +//! Source: `Interface '/org/shadowblip/InputPlumber/devices/source/event9' from service 'org.shadowblip.InputPlumber' on system bus`. +//! +//! You may prefer to adapt it, instead of using it verbatim. +//! +//! More information can be found in the [Writing a client proxy] section of the zbus +//! documentation. +//! +//! This type implements the [D-Bus standard interfaces], (`org.freedesktop.DBus.*`) for which the +//! following zbus API can be used: +//! +//! * [`zbus::fdo::PeerProxy`] +//! * [`zbus::fdo::IntrospectableProxy`] +//! * [`zbus::fdo::PropertiesProxy`] +//! +//! Consequently `zbus-xmlgen` did not generate code for the above interfaces. +//! +//! [Writing a client proxy]: https://dbus2.github.io/zbus/client.html +//! [D-Bus standard interfaces]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces, +use zbus::proxy; +#[proxy( + interface = "org.shadowblip.Input.Source.EventDevice", + default_service = "org.shadowblip.InputPlumber", + default_path = "/org/shadowblip/InputPlumber/devices/source/event9" +)] +trait EventDevice { + /// DevicePath property + #[zbus(property)] + fn device_path(&self) -> zbus::Result; + + /// IdBustype property + #[zbus(property)] + fn id_bustype(&self) -> zbus::Result; + + /// IdProduct property + #[zbus(property)] + fn id_product(&self) -> zbus::Result; + + /// IdVendor property + #[zbus(property)] + fn id_vendor(&self) -> zbus::Result; + + /// IdVersion property + #[zbus(property)] + fn id_version(&self) -> zbus::Result; + + /// Name property + #[zbus(property)] + fn name(&self) -> zbus::Result; + + /// PhysPath property + #[zbus(property)] + fn phys_path(&self) -> zbus::Result; + + /// SysfsPath property + #[zbus(property)] + fn sysfs_path(&self) -> zbus::Result; + + /// UniqueId property + #[zbus(property)] + fn unique_id(&self) -> zbus::Result; +} diff --git a/extensions/core/src/dbus/inputplumber/gamepad.rs b/extensions/core/src/dbus/inputplumber/gamepad.rs new file mode 100644 index 00000000..207174b1 --- /dev/null +++ b/extensions/core/src/dbus/inputplumber/gamepad.rs @@ -0,0 +1,32 @@ +//! # D-Bus interface proxy for: `org.shadowblip.Input.Gamepad` +//! +//! This code was generated by `zbus-xmlgen` `4.1.0` from D-Bus introspection data. +//! Source: `Interface '/org/shadowblip/InputPlumber/devices/target/gamepad0' from service 'org.shadowblip.InputPlumber' on system bus`. +//! +//! You may prefer to adapt it, instead of using it verbatim. +//! +//! More information can be found in the [Writing a client proxy] section of the zbus +//! documentation. +//! +//! This type implements the [D-Bus standard interfaces], (`org.freedesktop.DBus.*`) for which the +//! following zbus API can be used: +//! +//! * [`zbus::fdo::PeerProxy`] +//! * [`zbus::fdo::PropertiesProxy`] +//! * [`zbus::fdo::IntrospectableProxy`] +//! +//! Consequently `zbus-xmlgen` did not generate code for the above interfaces. +//! +//! [Writing a client proxy]: https://dbus2.github.io/zbus/client.html +//! [D-Bus standard interfaces]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces, +use zbus::proxy; +#[proxy( + interface = "org.shadowblip.Input.Gamepad", + default_service = "org.shadowblip.InputPlumber", + default_path = "/org/shadowblip/InputPlumber/devices/target/gamepad0" +)] +trait Gamepad { + /// Name property + #[zbus(property)] + fn name(&self) -> zbus::Result; +} diff --git a/extensions/core/src/dbus/inputplumber/hidraw_device.rs b/extensions/core/src/dbus/inputplumber/hidraw_device.rs new file mode 100644 index 00000000..f0a50da5 --- /dev/null +++ b/extensions/core/src/dbus/inputplumber/hidraw_device.rs @@ -0,0 +1,64 @@ +//! # D-Bus interface proxy for: `org.shadowblip.Input.Source.HIDRawDevice` +//! +//! This code was generated by `zbus-xmlgen` `4.1.0` from D-Bus introspection data. +//! Source: `Interface '/org/shadowblip/InputPlumber/devices/source/hidraw0' from service 'org.shadowblip.InputPlumber' on system bus`. +//! +//! You may prefer to adapt it, instead of using it verbatim. +//! +//! More information can be found in the [Writing a client proxy] section of the zbus +//! documentation. +//! +//! This type implements the [D-Bus standard interfaces], (`org.freedesktop.DBus.*`) for which the +//! following zbus API can be used: +//! +//! * [`zbus::fdo::PropertiesProxy`] +//! * [`zbus::fdo::PeerProxy`] +//! * [`zbus::fdo::IntrospectableProxy`] +//! +//! Consequently `zbus-xmlgen` did not generate code for the above interfaces. +//! +//! [Writing a client proxy]: https://dbus2.github.io/zbus/client.html +//! [D-Bus standard interfaces]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces, +use zbus::proxy; +#[proxy( + interface = "org.shadowblip.Input.Source.HIDRawDevice", + default_service = "org.shadowblip.InputPlumber", + default_path = "/org/shadowblip/InputPlumber/devices/source/hidraw0" +)] +trait HIDRawDevice { + /// DevPath property + #[zbus(property)] + fn dev_path(&self) -> zbus::Result; + + /// IdProduct property + #[zbus(property)] + fn id_product(&self) -> zbus::Result; + + /// IdVendor property + #[zbus(property)] + fn id_vendor(&self) -> zbus::Result; + + /// InterfaceNumber property + #[zbus(property)] + fn interface_number(&self) -> zbus::Result; + + /// Manufacturer property + #[zbus(property)] + fn manufacturer(&self) -> zbus::Result; + + /// Name property + #[zbus(property)] + fn name(&self) -> zbus::Result; + + /// Product property + #[zbus(property)] + fn product(&self) -> zbus::Result; + + /// SerialNumber property + #[zbus(property)] + fn serial_number(&self) -> zbus::Result; + + /// SysfsPath property + #[zbus(property)] + fn sysfs_path(&self) -> zbus::Result; +} diff --git a/extensions/core/src/dbus/inputplumber/iioimudevice.rs b/extensions/core/src/dbus/inputplumber/iioimudevice.rs new file mode 100644 index 00000000..7dbfdfdf --- /dev/null +++ b/extensions/core/src/dbus/inputplumber/iioimudevice.rs @@ -0,0 +1,76 @@ +//! # D-Bus interface proxy for: `org.shadowblip.Input.Source.IIOIMUDevice` +//! +//! This code was generated by `zbus-xmlgen` `4.1.0` from D-Bus introspection data. +//! Source: `Interface '/org/shadowblip/InputPlumber/devices/source/iio_device0' from service 'org.shadowblip.InputPlumber' on system bus`. +//! +//! You may prefer to adapt it, instead of using it verbatim. +//! +//! More information can be found in the [Writing a client proxy] section of the zbus +//! documentation. +//! +//! This type implements the [D-Bus standard interfaces], (`org.freedesktop.DBus.*`) for which the +//! following zbus API can be used: +//! +//! * [`zbus::fdo::PeerProxy`] +//! * [`zbus::fdo::IntrospectableProxy`] +//! * [`zbus::fdo::PropertiesProxy`] +//! +//! Consequently `zbus-xmlgen` did not generate code for the above interfaces. +//! +//! [Writing a client proxy]: https://dbus2.github.io/zbus/client.html +//! [D-Bus standard interfaces]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces, +use zbus::proxy; +#[proxy( + interface = "org.shadowblip.Input.Source.IIOIMUDevice", + default_service = "org.shadowblip.InputPlumber", + default_path = "/org/shadowblip/InputPlumber/devices/source/iio_device0" +)] +trait IIOIMUDevice { + /// AccelSampleRate property + #[zbus(property)] + fn accel_sample_rate(&self) -> zbus::Result; + #[zbus(property)] + fn set_accel_sample_rate(&self, value: f64) -> zbus::Result<()>; + + /// AccelSampleRatesAvail property + #[zbus(property)] + fn accel_sample_rates_avail(&self) -> zbus::Result>; + + /// AccelScale property + #[zbus(property)] + fn accel_scale(&self) -> zbus::Result; + #[zbus(property)] + fn set_accel_scale(&self, value: f64) -> zbus::Result<()>; + + /// AccelScalesAvail property + #[zbus(property)] + fn accel_scales_avail(&self) -> zbus::Result>; + + /// AngvelSampleRate property + #[zbus(property)] + fn angvel_sample_rate(&self) -> zbus::Result; + #[zbus(property)] + fn set_angvel_sample_rate(&self, value: f64) -> zbus::Result<()>; + + /// AngvelSampleRatesAvail property + #[zbus(property)] + fn angvel_sample_rates_avail(&self) -> zbus::Result>; + + /// AngvelScale property + #[zbus(property)] + fn angvel_scale(&self) -> zbus::Result; + #[zbus(property)] + fn set_angvel_scale(&self, value: f64) -> zbus::Result<()>; + + /// AngvelScalesAvail property + #[zbus(property)] + fn angvel_scales_avail(&self) -> zbus::Result>; + + /// Id property + #[zbus(property)] + fn id(&self) -> zbus::Result; + + /// Name property + #[zbus(property)] + fn name(&self) -> zbus::Result; +} diff --git a/extensions/core/src/dbus/inputplumber/input_manager.rs b/extensions/core/src/dbus/inputplumber/input_manager.rs new file mode 100644 index 00000000..49b1c383 --- /dev/null +++ b/extensions/core/src/dbus/inputplumber/input_manager.rs @@ -0,0 +1,52 @@ +//! # D-Bus interface proxy for: `org.shadowblip.InputManager` +//! +//! This code was generated by `zbus-xmlgen` `4.1.0` from D-Bus introspection data. +//! Source: `Interface '/org/shadowblip/InputPlumber/Manager' from service 'org.shadowblip.InputPlumber' on system bus`. +//! +//! You may prefer to adapt it, instead of using it verbatim. +//! +//! More information can be found in the [Writing a client proxy] section of the zbus +//! documentation. +//! +//! This type implements the [D-Bus standard interfaces], (`org.freedesktop.DBus.*`) for which the +//! following zbus API can be used: +//! +//! * [`zbus::fdo::PropertiesProxy`] +//! * [`zbus::fdo::PeerProxy`] +//! * [`zbus::fdo::IntrospectableProxy`] +//! +//! Consequently `zbus-xmlgen` did not generate code for the above interfaces. +//! +//! [Writing a client proxy]: https://dbus2.github.io/zbus/client.html +//! [D-Bus standard interfaces]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces, +use zbus::proxy; +#[proxy( + interface = "org.shadowblip.InputManager", + default_service = "org.shadowblip.InputPlumber", + default_path = "/org/shadowblip/InputPlumber/Manager" +)] +trait InputManager { + /// AttachTargetDevice method + fn attach_target_device(&self, target_path: &str, composite_path: &str) -> zbus::Result<()>; + + /// CreateCompositeDevice method + fn create_composite_device(&self, config_path: &str) -> zbus::Result; + + /// CreateTargetDevice method + fn create_target_device(&self, kind: &str) -> zbus::Result; + + /// StopTargetDevice method + fn stop_target_device(&self, path: &str) -> zbus::Result<()>; + + /// InterceptMode property + #[zbus(property)] + fn intercept_mode(&self) -> zbus::Result; + + /// SupportedTargetDeviceIds property + #[zbus(property)] + fn supported_target_device_ids(&self) -> zbus::Result>; + + /// SupportedTargetDevices property + #[zbus(property)] + fn supported_target_devices(&self) -> zbus::Result>; +} diff --git a/extensions/core/src/dbus/inputplumber/keyboard.rs b/extensions/core/src/dbus/inputplumber/keyboard.rs new file mode 100644 index 00000000..3da27d52 --- /dev/null +++ b/extensions/core/src/dbus/inputplumber/keyboard.rs @@ -0,0 +1,35 @@ +//! # D-Bus interface proxy for: `org.shadowblip.Input.Keyboard` +//! +//! This code was generated by `zbus-xmlgen` `4.1.0` from D-Bus introspection data. +//! Source: `Interface '/org/shadowblip/InputPlumber/devices/target/keyboard0' from service 'org.shadowblip.InputPlumber' on system bus`. +//! +//! You may prefer to adapt it, instead of using it verbatim. +//! +//! More information can be found in the [Writing a client proxy] section of the zbus +//! documentation. +//! +//! This type implements the [D-Bus standard interfaces], (`org.freedesktop.DBus.*`) for which the +//! following zbus API can be used: +//! +//! * [`zbus::fdo::IntrospectableProxy`] +//! * [`zbus::fdo::PeerProxy`] +//! * [`zbus::fdo::PropertiesProxy`] +//! +//! Consequently `zbus-xmlgen` did not generate code for the above interfaces. +//! +//! [Writing a client proxy]: https://dbus2.github.io/zbus/client.html +//! [D-Bus standard interfaces]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces, +use zbus::proxy; +#[proxy( + interface = "org.shadowblip.Input.Keyboard", + default_service = "org.shadowblip.InputPlumber", + default_path = "/org/shadowblip/InputPlumber/devices/target/keyboard0" +)] +trait Keyboard { + /// SendKey method + fn send_key(&self, key: &str, value: bool) -> zbus::Result<()>; + + /// Name property + #[zbus(property)] + fn name(&self) -> zbus::Result; +} diff --git a/extensions/core/src/dbus/inputplumber/mouse.rs b/extensions/core/src/dbus/inputplumber/mouse.rs new file mode 100644 index 00000000..8ffe1b2d --- /dev/null +++ b/extensions/core/src/dbus/inputplumber/mouse.rs @@ -0,0 +1,35 @@ +//! # D-Bus interface proxy for: `org.shadowblip.Input.Mouse` +//! +//! This code was generated by `zbus-xmlgen` `4.1.0` from D-Bus introspection data. +//! Source: `Interface '/org/shadowblip/InputPlumber/devices/target/mouse0' from service 'org.shadowblip.InputPlumber' on system bus`. +//! +//! You may prefer to adapt it, instead of using it verbatim. +//! +//! More information can be found in the [Writing a client proxy] section of the zbus +//! documentation. +//! +//! This type implements the [D-Bus standard interfaces], (`org.freedesktop.DBus.*`) for which the +//! following zbus API can be used: +//! +//! * [`zbus::fdo::IntrospectableProxy`] +//! * [`zbus::fdo::PropertiesProxy`] +//! * [`zbus::fdo::PeerProxy`] +//! +//! Consequently `zbus-xmlgen` did not generate code for the above interfaces. +//! +//! [Writing a client proxy]: https://dbus2.github.io/zbus/client.html +//! [D-Bus standard interfaces]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces, +use zbus::proxy; +#[proxy( + interface = "org.shadowblip.Input.Mouse", + default_service = "org.shadowblip.InputPlumber", + default_path = "/org/shadowblip/InputPlumber/devices/target/mouse0" +)] +trait Mouse { + /// MoveCursor method + fn move_cursor(&self, x: i32, y: i32) -> zbus::Result<()>; + + /// Name property + #[zbus(property)] + fn name(&self) -> zbus::Result; +} diff --git a/extensions/core/src/dbus/upower.rs b/extensions/core/src/dbus/upower.rs new file mode 100644 index 00000000..18cf2051 --- /dev/null +++ b/extensions/core/src/dbus/upower.rs @@ -0,0 +1,2 @@ +pub mod device; +pub mod upower; diff --git a/extensions/core/src/dbus/upower/device.rs b/extensions/core/src/dbus/upower/device.rs new file mode 100644 index 00000000..5ea61037 --- /dev/null +++ b/extensions/core/src/dbus/upower/device.rs @@ -0,0 +1,162 @@ +//! # D-Bus interface proxy for: `org.freedesktop.UPower.Device` +//! +//! This code was generated by `zbus-xmlgen` `4.1.0` from D-Bus introspection data. +//! Source: `Interface '/org/freedesktop/UPower/devices/DisplayDevice' from service 'org.freedesktop.UPower' on system bus`. +//! +//! You may prefer to adapt it, instead of using it verbatim. +//! +//! More information can be found in the [Writing a client proxy] section of the zbus +//! documentation. +//! +//! This type implements the [D-Bus standard interfaces], (`org.freedesktop.DBus.*`) for which the +//! following zbus API can be used: +//! +//! * [`zbus::fdo::PropertiesProxy`] +//! * [`zbus::fdo::IntrospectableProxy`] +//! * [`zbus::fdo::PeerProxy`] +//! +//! Consequently `zbus-xmlgen` did not generate code for the above interfaces. +//! +//! [Writing a client proxy]: https://dbus2.github.io/zbus/client.html +//! [D-Bus standard interfaces]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces, +use zbus::proxy; +#[proxy( + interface = "org.freedesktop.UPower.Device", + default_service = "org.freedesktop.UPower", + default_path = "/org/freedesktop/UPower/devices/DisplayDevice" +)] +trait Device { + /// GetHistory method + fn get_history( + &self, + type_: &str, + timespan: u32, + resolution: u32, + ) -> zbus::Result>; + + /// GetStatistics method + fn get_statistics(&self, type_: &str) -> zbus::Result>; + + /// Refresh method + fn refresh(&self) -> zbus::Result<()>; + + /// BatteryLevel property + #[zbus(property)] + fn battery_level(&self) -> zbus::Result; + + /// Capacity property + #[zbus(property)] + fn capacity(&self) -> zbus::Result; + + /// ChargeCycles property + #[zbus(property)] + fn charge_cycles(&self) -> zbus::Result; + + /// Energy property + #[zbus(property)] + fn energy(&self) -> zbus::Result; + + /// EnergyEmpty property + #[zbus(property)] + fn energy_empty(&self) -> zbus::Result; + + /// EnergyFull property + #[zbus(property)] + fn energy_full(&self) -> zbus::Result; + + /// EnergyFullDesign property + #[zbus(property)] + fn energy_full_design(&self) -> zbus::Result; + + /// EnergyRate property + #[zbus(property)] + fn energy_rate(&self) -> zbus::Result; + + /// HasHistory property + #[zbus(property)] + fn has_history(&self) -> zbus::Result; + + /// HasStatistics property + #[zbus(property)] + fn has_statistics(&self) -> zbus::Result; + + /// IconName property + #[zbus(property)] + fn icon_name(&self) -> zbus::Result; + + /// IsPresent property + #[zbus(property)] + fn is_present(&self) -> zbus::Result; + + /// IsRechargeable property + #[zbus(property)] + fn is_rechargeable(&self) -> zbus::Result; + + /// Luminosity property + #[zbus(property)] + fn luminosity(&self) -> zbus::Result; + + /// Model property + #[zbus(property)] + fn model(&self) -> zbus::Result; + + /// NativePath property + #[zbus(property)] + fn native_path(&self) -> zbus::Result; + + /// Online property + #[zbus(property)] + fn online(&self) -> zbus::Result; + + /// Percentage property + #[zbus(property)] + fn percentage(&self) -> zbus::Result; + + /// PowerSupply property + #[zbus(property)] + fn power_supply(&self) -> zbus::Result; + + /// Serial property + #[zbus(property)] + fn serial(&self) -> zbus::Result; + + /// State property + #[zbus(property)] + fn state(&self) -> zbus::Result; + + /// Technology property + #[zbus(property)] + fn technology(&self) -> zbus::Result; + + /// Temperature property + #[zbus(property)] + fn temperature(&self) -> zbus::Result; + + /// TimeToEmpty property + #[zbus(property)] + fn time_to_empty(&self) -> zbus::Result; + + /// TimeToFull property + #[zbus(property)] + fn time_to_full(&self) -> zbus::Result; + + /// Type property + #[zbus(property)] + fn type_(&self) -> zbus::Result; + + /// UpdateTime property + #[zbus(property)] + fn update_time(&self) -> zbus::Result; + + /// Vendor property + #[zbus(property)] + fn vendor(&self) -> zbus::Result; + + /// Voltage property + #[zbus(property)] + fn voltage(&self) -> zbus::Result; + + /// WarningLevel property + #[zbus(property)] + fn warning_level(&self) -> zbus::Result; +} diff --git a/extensions/core/src/dbus/upower/upower.rs b/extensions/core/src/dbus/upower/upower.rs new file mode 100644 index 00000000..b6f80298 --- /dev/null +++ b/extensions/core/src/dbus/upower/upower.rs @@ -0,0 +1,61 @@ +//! # D-Bus interface proxy for: `org.freedesktop.UPower` +//! +//! This code was generated by `zbus-xmlgen` `4.1.0` from D-Bus introspection data. +//! Source: `Interface '/org/freedesktop/UPower' from service 'org.freedesktop.UPower' on system bus`. +//! +//! You may prefer to adapt it, instead of using it verbatim. +//! +//! More information can be found in the [Writing a client proxy] section of the zbus +//! documentation. +//! +//! This type implements the [D-Bus standard interfaces], (`org.freedesktop.DBus.*`) for which the +//! following zbus API can be used: +//! +//! * [`zbus::fdo::PropertiesProxy`] +//! * [`zbus::fdo::IntrospectableProxy`] +//! * [`zbus::fdo::PeerProxy`] +//! +//! Consequently `zbus-xmlgen` did not generate code for the above interfaces. +//! +//! [Writing a client proxy]: https://dbus2.github.io/zbus/client.html +//! [D-Bus standard interfaces]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces, +use zbus::proxy; +#[proxy( + interface = "org.freedesktop.UPower", + default_service = "org.freedesktop.UPower", + default_path = "/org/freedesktop/UPower" +)] +trait UPower { + /// EnumerateDevices method + fn enumerate_devices(&self) -> zbus::Result>; + + /// GetCriticalAction method + fn get_critical_action(&self) -> zbus::Result; + + /// GetDisplayDevice method + fn get_display_device(&self) -> zbus::Result; + + /// DeviceAdded signal + #[zbus(signal)] + fn device_added(&self, device: zbus::zvariant::ObjectPath<'_>) -> zbus::Result<()>; + + /// DeviceRemoved signal + #[zbus(signal)] + fn device_removed(&self, device: zbus::zvariant::ObjectPath<'_>) -> zbus::Result<()>; + + /// DaemonVersion property + #[zbus(property)] + fn daemon_version(&self) -> zbus::Result; + + /// LidIsClosed property + #[zbus(property)] + fn lid_is_closed(&self) -> zbus::Result; + + /// LidIsPresent property + #[zbus(property)] + fn lid_is_present(&self) -> zbus::Result; + + /// OnBattery property + #[zbus(property)] + fn on_battery(&self) -> zbus::Result; +} diff --git a/extensions/core/src/input.rs b/extensions/core/src/input.rs new file mode 100644 index 00000000..f672b911 --- /dev/null +++ b/extensions/core/src/input.rs @@ -0,0 +1 @@ +pub mod inputplumber; diff --git a/extensions/core/src/input/inputplumber.rs b/extensions/core/src/input/inputplumber.rs new file mode 100644 index 00000000..f3da3737 --- /dev/null +++ b/extensions/core/src/input/inputplumber.rs @@ -0,0 +1,468 @@ +pub mod composite_device; +pub mod dbus_device; +pub mod event_device; +pub mod keyboard_device; +pub mod mouse_device; + +use dbus_device::DBusDevice; +use futures_util::stream::StreamExt; +use std::collections::HashMap; +use std::sync::mpsc::{channel, Receiver, Sender, TryRecvError}; +use std::time::Duration; + +use composite_device::CompositeDevice; +use godot::prelude::*; + +use godot::classes::Resource; +use zbus::fdo::ObjectManagerProxy; +use zbus::names::BusName; +use zbus::Connection; + +use crate::dbus::RunError; +use crate::RUNTIME; + +const INPUT_PLUMBER_BUS: &str = "org.shadowblip.InputPlumber"; +const INPUT_PLUMBER_PATH: &str = "/org/shadowblip/InputPlumber"; + +/// Supported InputPlumber DBus objects +#[derive(Debug)] +enum ObjectType { + Unknown, + CompositeDevice, + SourceEventDevice, + SourceHidRawDevice, + SourceIioDevice, + TargetDBusDevice, + TargetGamepadDevice, + TargetKeyboardDevice, + TargetMouseDevice, +} + +impl ObjectType { + fn from_dbus_path(path: &str) -> Self { + if path.contains("CompositeDevice") { + return Self::CompositeDevice; + } + if path.contains("dbus") { + return Self::TargetDBusDevice; + } + if path.contains("target") && path.contains("mouse") { + return Self::TargetMouseDevice; + } + if path.contains("target") && path.contains("keyboard") { + return Self::TargetKeyboardDevice; + } + if path.contains("target") && path.contains("gamepad") { + return Self::TargetGamepadDevice; + } + if path.contains("source") && path.contains("event") { + return Self::SourceEventDevice; + } + if path.contains("source") && path.contains("hidraw") { + return Self::SourceHidRawDevice; + } + if path.contains("source") && path.contains("iio") { + return Self::SourceIioDevice; + } + Self::Unknown + } +} + +/// Signals that can be emitted +#[derive(Debug)] +enum Signal { + Started, + Stopped, + ObjectAdded { path: String, kind: ObjectType }, + ObjectRemoved { path: String, kind: ObjectType }, +} + +/// Instance representing a client connection to InputPlumber over DBus. This +/// is represented as a resource so it can be accessed from anywhere in the scene +/// tree, but there must be a node that calls 'process()' on this resource every +/// frame in order to emit signals and process messages. +#[derive(GodotClass)] +#[class(base=Resource)] +pub struct InputPlumberInstance { + base: Base, + rx: Receiver, + conn: Option, + /// Map of DBus path to composite device resource. E.g. + /// {"/org/shadowblip/InputPlumber/CompositeDevice0": } + composite_devices: HashMap>, + /// Map of DBus path to dbus device resource. E.g. + /// {"/org/shadowblip/InputPlumber/target/dbus0": } + dbus_devices: HashMap>, + /// The current intercept mode set for all devices + #[var(get = get_intercept_mode, set = set_intercept_mode)] + intercept_mode: i64, + /// The current events that will trigger intercept mode + #[var(get = get_intercept_triggers, set = set_intercept_triggers)] + intercept_triggers: PackedStringArray, + /// The current target event for intercept mode + #[var(get = get_intercept_target, set = set_intercept_target)] + intercept_target: GString, +} + +#[godot_api] +impl InputPlumberInstance { + #[constant] + const INTERCEPT_MODE_NONE: i32 = 0; + #[constant] + const INTERCEPT_MODE_PASS: i32 = 1; + #[constant] + const INTERCEPT_MODE_ALL: i32 = 2; + + /// Emitted when InputPlumber is detected as running + #[signal] + fn started(); + + /// Emitted when InputPlumber is detected as stopped + #[signal] + fn stopped(); + + /// Emitted when a CompositeDevice is dicovered and identified as a new device + #[signal] + fn composite_device_added(device: Gd); + + /// Emitted when a CompositeDevice is removed + #[signal] + fn composite_device_removed(dbus_path: GString); + + /// Returns true if the InputPlumber service is currently running + #[func] + fn is_running(&self) -> bool { + let Some(conn) = self.conn.as_ref() else { + return false; + }; + let bus = BusName::from_static_str(INPUT_PLUMBER_BUS).unwrap(); + let dbus = zbus::blocking::fdo::DBusProxy::new(conn).ok(); + let Some(dbus) = dbus else { + return false; + }; + dbus.name_has_owner(bus.clone()).unwrap_or_default() + } + + /// Return all current composite devices + #[func] + fn get_composite_devices(&mut self) -> Array> { + let mut devices = array![]; + let Some(conn) = self.conn.as_ref() else { + return devices; + }; + + let bus = BusName::from_static_str(INPUT_PLUMBER_BUS).unwrap(); + let object_manager = zbus::blocking::fdo::ObjectManagerProxy::builder(conn) + .destination(bus) + .ok() + .and_then(|builder| builder.path(INPUT_PLUMBER_PATH).ok()) + .and_then(|builder| builder.build().ok()); + let Some(object_manager) = object_manager else { + return devices; + }; + + let objects = match object_manager.get_managed_objects() { + Ok(objs) => objs, + Err(_) => { + return devices; + } + }; + + for path in objects.keys() { + if !path.contains("CompositeDevice") { + continue; + } + + let device = CompositeDevice::new(path); + devices.push(device); + } + + devices + } + + /// Process InputPlumber signals and emit them as Godot signals. This method + /// should be called every frame in the "_process" loop of a node. + #[func] + fn process(&mut self) { + // Drain all messages from the channel to process them + loop { + let signal = match self.rx.try_recv() { + Ok(value) => value, + Err(e) => match e { + TryRecvError::Empty => break, + TryRecvError::Disconnected => { + godot_error!("Backend thread is not running!"); + return; + } + }, + }; + self.process_signal(signal); + } + + // Process any composite devices + for (_, device) in self.composite_devices.iter_mut() { + device.bind_mut().process(); + } + for (_, device) in self.dbus_devices.iter_mut() { + device.bind_mut().process(); + } + } + + /// Gets the current intercept mode for all composite devices + #[func] + fn get_intercept_mode(&self) -> i64 { + self.intercept_mode + } + + /// Sets all composite devices to the specified intercept mode. + #[func] + fn set_intercept_mode(&mut self, mode: i64) { + if !(0..=2).contains(&mode) { + godot_error!("Invalid intercept mode: {mode}"); + return; + } + self.intercept_mode = mode; + for (_, device) in self.composite_devices.iter() { + device.bind().set_intercept_mode(mode as i32); + } + } + + /// Gets the current triggers for activating intercept mode for all devices + #[func] + fn get_intercept_triggers(&self) -> PackedStringArray { + self.intercept_triggers.clone() + } + + /// Sets the current triggers for activating intercept mode for all devices + #[func] + fn set_intercept_triggers(&mut self, triggers: PackedStringArray) { + self.intercept_triggers = triggers; + } + + /// Gets the current target event for activating intercept mode for all devices + #[func] + fn get_intercept_target(&self) -> GString { + self.intercept_target.clone() + } + + /// Sets the current target event for activating intercept mode for all devices + #[func] + fn set_intercept_target(&mut self, target_event: GString) { + self.intercept_target = target_event; + } + + /// Sets all composite devices to use the specified intercept actions. + #[func] + fn set_intercept_activation(&mut self, triggers: PackedStringArray, target_event: GString) { + self.set_intercept_triggers(triggers.clone()); + self.set_intercept_target(target_event.clone()); + for (_, device) in self.composite_devices.iter() { + device + .bind() + .set_intercept_activation(triggers.clone(), target_event.clone()) + } + } + + /// Process and dispatch the given signal + fn process_signal(&mut self, signal: Signal) { + match signal { + Signal::Started => { + self.base_mut().emit_signal("started".into(), &[]); + } + Signal::Stopped => { + // Clear all known devices + self.composite_devices.clear(); + self.dbus_devices.clear(); + self.base_mut().emit_signal("stopped".into(), &[]); + } + Signal::ObjectAdded { path, kind } => { + self.on_object_added(path, kind); + } + Signal::ObjectRemoved { path, kind } => { + self.on_object_removed(path, kind); + } + } + } + + /// Track the given object and emit signals + fn on_object_added(&mut self, path: String, kind: ObjectType) { + match kind { + ObjectType::Unknown => (), + ObjectType::CompositeDevice => { + let device = CompositeDevice::new(path.as_str()); + self.composite_devices.insert(path, device.clone()); + self.base_mut() + .emit_signal("composite_device_added".into(), &[device.to_variant()]); + } + ObjectType::SourceEventDevice => (), + ObjectType::SourceHidRawDevice => (), + ObjectType::SourceIioDevice => (), + ObjectType::TargetDBusDevice => { + let device = DBusDevice::new(path.as_str()); + self.dbus_devices.insert(path, device); + } + ObjectType::TargetGamepadDevice => (), + ObjectType::TargetKeyboardDevice => (), + ObjectType::TargetMouseDevice => (), + } + } + + /// Remove the given object and emit signals + fn on_object_removed(&mut self, path: String, kind: ObjectType) { + match kind { + ObjectType::Unknown => (), + ObjectType::CompositeDevice => { + self.composite_devices.remove(&path); + self.base_mut().emit_signal( + "composite_device_removed".into(), + &[GString::from(path).to_variant()], + ); + } + ObjectType::SourceEventDevice => (), + ObjectType::SourceHidRawDevice => (), + ObjectType::SourceIioDevice => (), + ObjectType::TargetDBusDevice => { + self.dbus_devices.remove(&path); + } + ObjectType::TargetGamepadDevice => (), + ObjectType::TargetKeyboardDevice => (), + ObjectType::TargetMouseDevice => (), + } + } +} + +#[godot_api] +impl IResource for InputPlumberInstance { + /// Called upon object initialization in the engine + fn init(base: Base) -> Self { + godot_print!("Initializing InputPlumber instance"); + + // Create a channel to communicate with the service + let (tx, rx) = channel(); + + // Spawn a task using the shared tokio runtime to listen for signals + RUNTIME.spawn(async move { + if let Err(e) = run(tx).await { + godot_error!("Failed to run InputPlumber task: ${e:?}"); + } + }); + + // Create a new InputPlumber instance + let conn = zbus::blocking::Connection::system().ok(); + let mut instance = Self { + base, + rx, + conn, + composite_devices: HashMap::new(), + dbus_devices: HashMap::new(), + intercept_mode: 0, + intercept_triggers: PackedStringArray::from(&["Gamepad:Button:Guide".into()]), + intercept_target: "Gamepad:Button:Guide".into(), + }; + + // Do initial device discovery + let devices = instance.get_composite_devices(); + for device in devices.iter_shared() { + let path = device.bind().get_dbus_path(); + instance.composite_devices.insert(path.into(), device); + } + + instance + } +} + +/// Runs InputPlumber tasks in Tokio to listen for DBus signals and send them +/// over the given channel so they can be processed during each engine frame. +async fn run(tx: Sender) -> Result<(), RunError> { + godot_print!("Spawning inputplumber"); + // Establish a connection to the system bus + let conn = Connection::system().await?; + + // Spawn a task to listen for InputPlumber start/stop + let dbus_conn = conn.clone(); + let signals_tx = tx.clone(); + RUNTIME.spawn(async move { + let bus = BusName::from_static_str(INPUT_PLUMBER_BUS).unwrap(); + let mut is_running = { + let dbus = zbus::fdo::DBusProxy::new(&dbus_conn).await.ok(); + let Some(dbus) = dbus else { + return; + }; + dbus.name_has_owner(bus.clone()).await.unwrap_or_default() + }; + + loop { + let dbus = zbus::fdo::DBusProxy::new(&dbus_conn).await.ok(); + let Some(dbus) = dbus else { + break; + }; + let running = dbus.name_has_owner(bus.clone()).await.unwrap_or_default(); + if running != is_running { + let signal = if running { + Signal::Started + } else { + Signal::Stopped + }; + if signals_tx.send(signal).is_err() { + break; + } + } + is_running = running; + tokio::time::sleep(Duration::from_secs(5)).await; + } + }); + + // Get a proxy instance to ObjectManager + let bus = BusName::from_static_str(INPUT_PLUMBER_BUS).unwrap(); + let object_manager: ObjectManagerProxy = ObjectManagerProxy::builder(&conn) + .destination(bus)? + .path(INPUT_PLUMBER_PATH)? + .build() + .await?; + + // Spawn a task to listen for objects added + let mut ifaces_added = object_manager.receive_interfaces_added().await?; + let signals_tx = tx.clone(); + RUNTIME.spawn(async move { + while let Some(signal) = ifaces_added.next().await { + let args = match signal.args() { + Ok(args) => args, + Err(e) => { + godot_warn!("Failed to get signal args: ${e:?}"); + continue; + } + }; + + let path = args.object_path.to_string(); + let kind = ObjectType::from_dbus_path(path.as_str()); + let signal = Signal::ObjectAdded { path, kind }; + if signals_tx.send(signal).is_err() { + break; + } + } + }); + + // Spawn a task to listen for objects removed + let mut ifaces_removed = object_manager.receive_interfaces_removed().await?; + let signals_tx = tx.clone(); + RUNTIME.spawn(async move { + while let Some(signal) = ifaces_removed.next().await { + let args = match signal.args() { + Ok(args) => args, + Err(e) => { + godot_warn!("Failed to get signal args: ${e:?}"); + continue; + } + }; + + let path = args.object_path.to_string(); + let kind = ObjectType::from_dbus_path(path.as_str()); + let signal = Signal::ObjectRemoved { path, kind }; + if signals_tx.send(signal).is_err() { + break; + } + } + }); + + Ok(()) +} diff --git a/extensions/core/src/input/inputplumber/composite_device.rs b/extensions/core/src/input/inputplumber/composite_device.rs new file mode 100644 index 00000000..25f97043 --- /dev/null +++ b/extensions/core/src/input/inputplumber/composite_device.rs @@ -0,0 +1,322 @@ +use godot::prelude::*; + +use godot::classes::{ProjectSettings, Resource, ResourceLoader}; + +use crate::dbus::inputplumber::composite_device::CompositeDeviceProxyBlocking; +use crate::dbus::DBusVariant; + +use super::dbus_device::DBusDevice; +use super::INPUT_PLUMBER_BUS; + +#[derive(GodotClass)] +#[class(no_init, base=Resource)] +pub struct CompositeDevice { + base: Base, + + conn: Option, + path: String, + + /// The DBus path of the [CompositeDevice] + #[allow(dead_code)] + #[var(get = get_dbus_path)] + dbus_path: GString, + /// Name of the [CompositeDevice] + #[allow(dead_code)] + #[var(get = get_name)] + name: GString, + /// Name of the input profile that the [CompositeDevice] is using + #[allow(dead_code)] + #[var(get = get_profile_name)] + profile_name: GString, + /// Intercept mode of the [CompositeDevice] + #[allow(dead_code)] + #[var(get = get_intercept_mode, set = set_intercept_mode)] + intercept_mode: i32, + /// Capabilities from all source devices + #[allow(dead_code)] + #[var(get = get_capabilities)] + capabilities: PackedStringArray, + /// Capabilities from all target devices + #[allow(dead_code)] + #[var(get = get_target_capabilities)] + target_capabilities: PackedStringArray, + /// Target DBus devices associated with this composite device + #[allow(dead_code)] + #[var(get = get_dbus_devices)] + dbus_devices: Array>, + /// The source device paths of the composite device (e.g. /dev/input/event0) + #[allow(dead_code)] + #[var(get = get_source_device_paths)] + source_device_paths: PackedStringArray, + /// Get the target device types for the composite device (e.g. "keyboard", "mouse", etc.) + #[allow(dead_code)] + #[var(get = get_target_devices, set = set_target_devices)] + target_devices: PackedStringArray, +} + +#[godot_api] +impl CompositeDevice { + /// Create a new [CompositeDevice] with the given DBus path + pub fn from_path(path: GString) -> Gd { + Gd::from_init_fn(|base| { + // Create a connection to DBus + let conn = zbus::blocking::Connection::system().ok(); + + // Accept a base of type Base and directly forward it. + Self { + conn, + path: path.clone().into(), // Convert GString -> String. + dbus_path: path, + name: Default::default(), + profile_name: Default::default(), + intercept_mode: Default::default(), + capabilities: Default::default(), + target_capabilities: Default::default(), + dbus_devices: Default::default(), + source_device_paths: Default::default(), + target_devices: Default::default(), + base, + } + }) + } + + /// Return a proxy instance to the composite device + fn get_proxy(&self) -> Option { + if let Some(conn) = self.conn.as_ref() { + CompositeDeviceProxyBlocking::builder(conn) + .path(self.path.clone()) + .ok() + .and_then(|builder| builder.build().ok()) + } else { + None + } + } + + /// Get or create a [CompositeDevice] with the given DBus path. If an instance + /// already exists with the given path, then it will be loaded from the resource + /// cache. + pub fn new(path: &str) -> Gd { + let res_path = format!("dbus://{INPUT_PLUMBER_BUS}{path}"); + + // Check to see if a resource already exists for this device + let mut resource_loader = ResourceLoader::singleton(); + if resource_loader.exists(res_path.clone().into()) { + if let Some(res) = resource_loader.load(res_path.clone().into()) { + godot_print!("Resource already exists, loading that instead"); + let device: Gd = res.cast(); + device + } else { + let mut device = CompositeDevice::from_path(path.to_string().into()); + device.take_over_path(res_path.into()); + device + } + } else { + let mut device = CompositeDevice::from_path(path.to_string().into()); + device.take_over_path(res_path.into()); + device + } + } + + /// Get the name of the [CompositeDevice] + #[func] + pub fn get_name(&self) -> GString { + let Some(proxy) = self.get_proxy() else { + return "".into(); + }; + proxy.name().ok().unwrap_or_default().into() + } + + #[func] + pub fn get_profile_name(&self) -> GString { + let Some(proxy) = self.get_proxy() else { + return "".into(); + }; + proxy.profile_name().ok().unwrap_or_default().into() + } + + /// Get the intercept mode of the composite device + #[func] + pub fn get_intercept_mode(&self) -> i32 { + let Some(proxy) = self.get_proxy() else { + return -1; + }; + proxy.intercept_mode().ok().unwrap_or_default() as i32 + } + + /// Set the intercept mode of the composite device + #[func] + pub fn set_intercept_mode(&self, mode: i32) { + let Some(proxy) = self.get_proxy() else { + return; + }; + let mode = mode as u32; + proxy.set_intercept_mode(mode).ok(); + } + + /// Get capabilities from all source devices + #[func] + pub fn get_capabilities(&self) -> PackedStringArray { + let Some(proxy) = self.get_proxy() else { + return PackedStringArray::new(); + }; + let caps: Vec = proxy + .capabilities() + .ok() + .unwrap_or_default() + .into_iter() + .map(GString::from) + .collect(); + PackedStringArray::from(caps.as_slice()) + } + + /// Get capabilities from all target devices + #[func] + pub fn get_target_capabilities(&self) -> PackedStringArray { + let Some(proxy) = self.get_proxy() else { + return PackedStringArray::new(); + }; + let caps: Vec = proxy + .target_capabilities() + .ok() + .unwrap_or_default() + .into_iter() + .map(GString::from) + .collect(); + PackedStringArray::from(caps.as_slice()) + } + + #[func] + pub fn get_dbus_devices(&self) -> Array> { + let mut devices = array![]; + let paths = self.get_dbus_devices_paths(); + for path in paths.as_slice() { + let dbus_path = String::from(path); + let device = DBusDevice::new(dbus_path.as_str()); + devices.push(device); + } + devices + } + + #[func] + pub fn get_dbus_devices_paths(&self) -> PackedStringArray { + let Some(proxy) = self.get_proxy() else { + return PackedStringArray::new(); + }; + let values: Vec = proxy + .dbus_devices() + .ok() + .unwrap_or_default() + .into_iter() + .map(GString::from) + .collect(); + PackedStringArray::from(values.as_slice()) + } + + /// Get the source device paths of the composite device (e.g. /dev/input/event0) + #[func] + pub fn get_source_device_paths(&self) -> PackedStringArray { + let Some(proxy) = self.get_proxy() else { + return PackedStringArray::new(); + }; + let values: Vec = proxy + .source_device_paths() + .ok() + .unwrap_or_default() + .into_iter() + .map(GString::from) + .collect(); + PackedStringArray::from(values.as_slice()) + } + + /// Get the target device types for the composite device (e.g. "keyboard", "mouse", etc.) + #[func] + pub fn get_target_devices(&self) -> PackedStringArray { + let Some(proxy) = self.get_proxy() else { + return PackedStringArray::new(); + }; + let values: Vec = proxy + .target_devices() + .ok() + .unwrap_or_default() + .into_iter() + .map(GString::from) + .collect(); + PackedStringArray::from(values.as_slice()) + } + + /// get the target device types for the composite device (e.g. "keyboard", "mouse", etc.) + #[func] + pub fn set_target_devices(&self, devices: PackedStringArray) { + let Some(proxy) = self.get_proxy() else { + return; + }; + let device_types: Vec = devices.to_vec().into_iter().map(|v| v.into()).collect(); + let target_devices: Vec<&str> = device_types.iter().map(|v| v.as_str()).collect(); + proxy.set_target_devices(target_devices.as_slice()).ok(); + } + + /// Returns the DBus path to the [CompositeDevice] + #[func] + pub fn get_dbus_path(&self) -> GString { + self.path.clone().into() + } + + /// Load the device profile from the given path + #[func] + pub fn load_profile_path(&self, path: GString) { + let Some(proxy) = self.get_proxy() else { + return; + }; + let path = String::from(path); + let absolute_path = if path.starts_with("res://") || path.starts_with("user://") { + let project_settings = ProjectSettings::singleton(); + project_settings.globalize_path(path.into()).into() + } else { + path + }; + proxy.load_profile_path(absolute_path.as_str()).ok(); + } + + /// Write the given event to the appropriate target device, bypassing intercept + /// logic. + #[func] + pub fn send_event(&self, action: GString, value: Variant) { + let Some(proxy) = self.get_proxy() else { + return; + }; + let Some(value) = value.as_zvariant() else { + return; + }; + let event = String::from(action); + proxy.send_event(event.as_str(), &value).ok(); + } + + /// Write the given set of events as a button chord + #[func] + pub fn send_button_chord(&self, actions: PackedStringArray) { + let Some(proxy) = self.get_proxy() else { + return; + }; + let values: Vec = actions.to_vec().into_iter().map(|v| v.into()).collect(); + let str_values: Vec<&str> = values.iter().map(|v| v.as_str()).collect(); + proxy.send_button_chord(str_values.as_slice()).ok(); + } + + /// Set the events to look for to activate input interception while in + /// "PASS" mode. + #[func] + pub fn set_intercept_activation(&self, triggers: PackedStringArray, target_event: GString) { + let Some(proxy) = self.get_proxy() else { + return; + }; + let values: Vec = triggers.to_vec().into_iter().map(|v| v.into()).collect(); + let str_values: Vec<&str> = values.iter().map(|v| v.as_str()).collect(); + let target_event: String = target_event.into(); + proxy + .set_intercept_activation(str_values.as_slice(), target_event.as_str()) + .ok(); + } + + /// Dispatches signals + pub fn process(&mut self) {} +} diff --git a/extensions/core/src/input/inputplumber/dbus_device.rs b/extensions/core/src/input/inputplumber/dbus_device.rs new file mode 100644 index 00000000..2d84bec6 --- /dev/null +++ b/extensions/core/src/input/inputplumber/dbus_device.rs @@ -0,0 +1,208 @@ +use std::sync::mpsc::{channel, Receiver, Sender, TryRecvError}; + +use futures_util::StreamExt; +use godot::prelude::*; + +use godot::classes::{Resource, ResourceLoader}; +use zbus::Connection; + +use crate::dbus::inputplumber::dbus_device::DBusDeviceProxy; +use crate::RUNTIME; + +use super::{RunError, INPUT_PLUMBER_BUS}; + +/// Signals that can be emitted +#[derive(Debug)] +enum Signal { + InputEvent { + type_code: String, + value: f64, + }, + TouchEvent { + type_code: String, + index: u32, + is_touching: bool, + pressure: f64, + x: f64, + y: f64, + }, +} + +#[derive(GodotClass)] +#[class(no_init, base=Resource)] +pub struct DBusDevice { + base: Base, + path: String, + rx: Receiver, + + #[allow(dead_code)] + #[var(get = get_dbus_path)] + dbus_path: GString, +} + +#[godot_api] +impl DBusDevice { + #[signal] + fn input_event(type_code: GString, value: f64); + + #[signal] + fn touch_event( + type_code: GString, + index: i64, + is_touching: bool, + pressure: f64, + x: f64, + y: f64, + ); + + /// Create a new [DBusDevice] with the given DBus path + pub fn from_path(path: GString) -> Gd { + // Create a channel to communicate with the signals task + let (tx, rx) = channel(); + let dbus_path = path.clone().into(); + + // Spawn a task using the shared tokio runtime to listen for signals + RUNTIME.spawn(async move { + if let Err(e) = run(tx, dbus_path).await { + godot_error!("Failed to run DBusDevice task: ${e:?}"); + } + }); + + Gd::from_init_fn(|base| { + // Accept a base of type Base and directly forward it. + Self { + base, + path: path.clone().into(), // Convert GString -> String. + rx, + dbus_path: path, + } + }) + } + + /// Get or create a [DBusDevice] with the given DBus path. If an instance + /// already exists with the given path, then it will be loaded from the resource + /// cache. + pub fn new(path: &str) -> Gd { + let res_path = format!("dbus://{INPUT_PLUMBER_BUS}{path}"); + + // Check to see if a resource already exists for this device + let mut resource_loader = ResourceLoader::singleton(); + if resource_loader.exists(res_path.clone().into()) { + if let Some(res) = resource_loader.load(res_path.clone().into()) { + godot_print!("Resource already exists, loading that instead"); + let device: Gd = res.cast(); + device + } else { + let mut device = DBusDevice::from_path(path.to_string().into()); + device.take_over_path(res_path.into()); + device + } + } else { + let mut device = DBusDevice::from_path(path.to_string().into()); + device.take_over_path(res_path.into()); + device + } + } + + #[func] + pub fn get_dbus_path(&self) -> GString { + self.path.clone().into() + } + + /// Dispatches signals + pub fn process(&mut self) { + // Drain all messages from the channel to process them + loop { + let signal = match self.rx.try_recv() { + Ok(value) => value, + Err(e) => match e { + TryRecvError::Empty => break, + TryRecvError::Disconnected => { + godot_error!("Backend thread is not running!"); + return; + } + }, + }; + self.process_signal(signal); + } + } + + /// Process and dispatch the given signal + fn process_signal(&mut self, signal: Signal) { + match signal { + Signal::InputEvent { type_code, value } => { + self.base_mut().emit_signal( + "input_event".into(), + &[type_code.into_godot().to_variant(), value.to_variant()], + ); + } + Signal::TouchEvent { + type_code, + index, + is_touching, + pressure, + x, + y, + } => { + self.base_mut().emit_signal( + "touch_event".into(), + &[ + type_code.into_godot().to_variant(), + index.to_variant(), + is_touching.to_variant(), + pressure.to_variant(), + x.to_variant(), + y.to_variant(), + ], + ); + } + } + } +} + +/// Run the signals task +async fn run(tx: Sender, path: String) -> Result<(), RunError> { + // Establish a connection to the system bus + let conn = Connection::system().await?; + let proxy = DBusDeviceProxy::builder(&conn).path(path)?.build().await?; + + let signals_tx = tx.clone(); + let mut input_events = proxy.receive_input_event().await?; + RUNTIME.spawn(async move { + while let Some(event) = input_events.next().await { + let Some(args) = event.args().ok() else { + break; + }; + let signal = Signal::InputEvent { + type_code: args.event.to_string(), + value: args.value, + }; + if signals_tx.send(signal).is_err() { + break; + } + } + }); + + let signals_tx = tx.clone(); + let mut touch_events = proxy.receive_touch_event().await?; + RUNTIME.spawn(async move { + while let Some(event) = touch_events.next().await { + let Some(args) = event.args().ok() else { + break; + }; + let signal = Signal::TouchEvent { + type_code: args.event.to_string(), + index: args.index, + is_touching: args.is_touching, + pressure: args.pressure, + x: args.x, + y: args.y, + }; + if signals_tx.send(signal).is_err() { + break; + } + } + }); + + Ok(()) +} diff --git a/extensions/core/src/input/inputplumber/event_device.rs b/extensions/core/src/input/inputplumber/event_device.rs new file mode 100644 index 00000000..a2cd1650 --- /dev/null +++ b/extensions/core/src/input/inputplumber/event_device.rs @@ -0,0 +1,139 @@ +use godot::{classes::ResourceLoader, prelude::*}; + +use crate::dbus::inputplumber::event_device::EventDeviceProxyBlocking; + +use super::INPUT_PLUMBER_BUS; + +#[derive(GodotClass)] +#[class(no_init, base=Resource)] +pub struct EventDevice { + base: Base, + path: String, + conn: Option, + + #[allow(dead_code)] + #[var(get = get_dbus_path)] + dbus_path: GString, + #[allow(dead_code)] + #[var(get = get_name)] + name: GString, + #[allow(dead_code)] + #[var(get = get_device_path)] + device_path: GString, + #[allow(dead_code)] + #[var(get = get_phys_path)] + phys_path: GString, + #[allow(dead_code)] + #[var(get = get_sysfs_path)] + sysfs_path: GString, + #[allow(dead_code)] + #[var(get = get_unique_id)] + unique_id: GString, +} + +#[godot_api] +impl EventDevice { + /// Create a new [EventDevice] with the given DBus path + fn from_path(path: GString) -> Gd { + Gd::from_init_fn(|base| { + // Create a connection to DBus + let conn = zbus::blocking::Connection::system().ok(); + + // Accept a base of type Base and directly forward it. + Self { + base, + conn, + path: path.clone().into(), + dbus_path: path, + name: Default::default(), + device_path: Default::default(), + phys_path: Default::default(), + sysfs_path: Default::default(), + unique_id: Default::default(), + } + }) + } + + /// Return a proxy instance to the composite device + fn get_proxy(&self) -> Option { + if let Some(conn) = self.conn.as_ref() { + EventDeviceProxyBlocking::builder(conn) + .path(self.path.clone()) + .ok() + .and_then(|builder| builder.build().ok()) + } else { + None + } + } + + /// Get or create a [DBusDevice] with the given DBus path. If an instance + /// already exists with the given path, then it will be loaded from the resource + /// cache. + pub fn new(path: &str) -> Gd { + let res_path = format!("dbus://{INPUT_PLUMBER_BUS}{path}"); + + // Check to see if a resource already exists for this device + let mut resource_loader = ResourceLoader::singleton(); + if resource_loader.exists(res_path.clone().into()) { + if let Some(res) = resource_loader.load(res_path.clone().into()) { + godot_print!("Resource already exists, loading that instead"); + let device: Gd = res.cast(); + device + } else { + let mut device = EventDevice::from_path(path.to_string().into()); + device.take_over_path(res_path.into()); + device + } + } else { + let mut device = EventDevice::from_path(path.to_string().into()); + device.take_over_path(res_path.into()); + device + } + } + + #[func] + pub fn get_dbus_path(&self) -> GString { + self.path.clone().into() + } + + /// Get the name of the [EventDevice] + #[func] + pub fn get_name(&self) -> GString { + let Some(proxy) = self.get_proxy() else { + return "".into(); + }; + proxy.name().unwrap_or_default().into() + } + + #[func] + pub fn get_device_path(&self) -> GString { + let Some(proxy) = self.get_proxy() else { + return "".into(); + }; + proxy.device_path().unwrap_or_default().into() + } + + #[func] + pub fn get_phys_path(&self) -> GString { + let Some(proxy) = self.get_proxy() else { + return "".into(); + }; + proxy.phys_path().unwrap_or_default().into() + } + + #[func] + pub fn get_sysfs_path(&self) -> GString { + let Some(proxy) = self.get_proxy() else { + return "".into(); + }; + proxy.sysfs_path().unwrap_or_default().into() + } + + #[func] + pub fn get_unique_id(&self) -> GString { + let Some(proxy) = self.get_proxy() else { + return "".into(); + }; + proxy.unique_id().unwrap_or_default().into() + } +} diff --git a/extensions/core/src/input/inputplumber/keyboard_device.rs b/extensions/core/src/input/inputplumber/keyboard_device.rs new file mode 100644 index 00000000..07bdd427 --- /dev/null +++ b/extensions/core/src/input/inputplumber/keyboard_device.rs @@ -0,0 +1,100 @@ +use godot::{classes::ResourceLoader, prelude::*}; + +use crate::dbus::inputplumber::keyboard::KeyboardProxyBlocking; + +use super::INPUT_PLUMBER_BUS; + +#[derive(GodotClass)] +#[class(no_init, base=Resource)] +pub struct KeyboardDevice { + base: Base, + path: String, + conn: Option, + + #[allow(dead_code)] + #[var(get = get_dbus_path)] + dbus_path: GString, + #[allow(dead_code)] + #[var(get = get_name)] + name: GString, +} + +#[godot_api] +impl KeyboardDevice { + /// Create a new [KeyboardDevice] with the given DBus path + fn from_path(path: GString) -> Gd { + Gd::from_init_fn(|base| { + // Create a connection to DBus + let conn = zbus::blocking::Connection::system().ok(); + + // Accept a base of type Base and directly forward it. + Self { + base, + conn, + path: path.clone().into(), + dbus_path: path, + name: Default::default(), + } + }) + } + + /// Return a proxy instance to the composite device + fn get_proxy(&self) -> Option { + if let Some(conn) = self.conn.as_ref() { + KeyboardProxyBlocking::builder(conn) + .path(self.path.clone()) + .ok() + .and_then(|builder| builder.build().ok()) + } else { + None + } + } + + /// Get or create a [KeyboardDevice] with the given DBus path. If an instance + /// already exists with the given path, then it will be loaded from the resource + /// cache. + pub fn new(path: &str) -> Gd { + let res_path = format!("dbus://{INPUT_PLUMBER_BUS}{path}"); + + // Check to see if a resource already exists for this device + let mut resource_loader = ResourceLoader::singleton(); + if resource_loader.exists(res_path.clone().into()) { + if let Some(res) = resource_loader.load(res_path.clone().into()) { + godot_print!("Resource already exists, loading that instead"); + let device: Gd = res.cast(); + device + } else { + let mut device = KeyboardDevice::from_path(path.to_string().into()); + device.take_over_path(res_path.into()); + device + } + } else { + let mut device = KeyboardDevice::from_path(path.to_string().into()); + device.take_over_path(res_path.into()); + device + } + } + + #[func] + pub fn get_dbus_path(&self) -> GString { + self.path.clone().into() + } + + /// Get the name of the [KeyboardDevice] + #[func] + pub fn get_name(&self) -> GString { + let Some(proxy) = self.get_proxy() else { + return "".into(); + }; + proxy.name().unwrap_or_default().into() + } + + #[func] + pub fn send_key(&self, key: GString, value: bool) { + let Some(proxy) = self.get_proxy() else { + return; + }; + let key_code: String = key.into(); + proxy.send_key(key_code.as_str(), value).ok(); + } +} diff --git a/extensions/core/src/input/inputplumber/mouse_device.rs b/extensions/core/src/input/inputplumber/mouse_device.rs new file mode 100644 index 00000000..4fae1bcc --- /dev/null +++ b/extensions/core/src/input/inputplumber/mouse_device.rs @@ -0,0 +1,99 @@ +use godot::{classes::ResourceLoader, prelude::*}; + +use crate::dbus::inputplumber::mouse::MouseProxyBlocking; + +use super::INPUT_PLUMBER_BUS; + +#[derive(GodotClass)] +#[class(no_init, base=Resource)] +pub struct MouseDevice { + base: Base, + path: String, + conn: Option, + + #[allow(dead_code)] + #[var(get = get_dbus_path)] + dbus_path: GString, + #[allow(dead_code)] + #[var(get = get_name)] + name: GString, +} + +#[godot_api] +impl MouseDevice { + /// Create a new [MouseDevice] with the given DBus path + fn from_path(path: GString) -> Gd { + Gd::from_init_fn(|base| { + // Create a connection to DBus + let conn = zbus::blocking::Connection::system().ok(); + + // Accept a base of type Base and directly forward it. + Self { + base, + conn, + path: path.clone().into(), + dbus_path: path, + name: Default::default(), + } + }) + } + + /// Return a proxy instance to the composite device + fn get_proxy(&self) -> Option { + if let Some(conn) = self.conn.as_ref() { + MouseProxyBlocking::builder(conn) + .path(self.path.clone()) + .ok() + .and_then(|builder| builder.build().ok()) + } else { + None + } + } + + /// Get or create a [KeyboardDevice] with the given DBus path. If an instance + /// already exists with the given path, then it will be loaded from the resource + /// cache. + pub fn new(path: &str) -> Gd { + let res_path = format!("dbus://{INPUT_PLUMBER_BUS}{path}"); + + // Check to see if a resource already exists for this device + let mut resource_loader = ResourceLoader::singleton(); + if resource_loader.exists(res_path.clone().into()) { + if let Some(res) = resource_loader.load(res_path.clone().into()) { + godot_print!("Resource already exists, loading that instead"); + let device: Gd = res.cast(); + device + } else { + let mut device = MouseDevice::from_path(path.to_string().into()); + device.take_over_path(res_path.into()); + device + } + } else { + let mut device = MouseDevice::from_path(path.to_string().into()); + device.take_over_path(res_path.into()); + device + } + } + + #[func] + pub fn get_dbus_path(&self) -> GString { + self.path.clone().into() + } + + /// Get the name of the [KeyboardDevice] + #[func] + pub fn get_name(&self) -> GString { + let Some(proxy) = self.get_proxy() else { + return "".into(); + }; + proxy.name().unwrap_or_default().into() + } + + #[func] + pub fn move_cursor(&self, x: i64, y: i64) { + let Some(proxy) = self.get_proxy() else { + return; + }; + proxy.move_cursor(x as i32, y as i32).ok(); + } +} diff --git a/extensions/core/src/lib.rs b/extensions/core/src/lib.rs new file mode 100644 index 00000000..614879d1 --- /dev/null +++ b/extensions/core/src/lib.rs @@ -0,0 +1,75 @@ +pub mod dbus; +pub mod input; +pub mod power; +pub mod system; + +use std::{sync::Arc, time::Duration}; + +use godot::prelude::*; +use once_cell::sync::Lazy; +use tokio::{ + runtime::{Builder, Handle}, + sync::{ + mpsc::{channel, Receiver, Sender}, + Mutex, + }, +}; + +/// Channel for shutting down the tokio runtime +type Channel = (Sender<()>, Arc>>); + +/// Global tokio runtime instance +pub static RUNTIME: Lazy = Lazy::new(tokio_init); +static CHANNEL: Lazy = Lazy::new(get_channel); + +struct OpenGamepadUICore {} + +#[gdextension] +unsafe impl ExtensionLibrary for OpenGamepadUICore { + fn on_level_init(level: InitLevel) { + if level != InitLevel::Scene { + return; + } + godot_print!("Initializing OpenGamepadUI Core"); + } + + fn on_level_deinit(level: InitLevel) { + if level != InitLevel::Scene { + return; + } + godot_print!("De-initializing OpenGamepadUI Core"); + tokio_deinit(); + } +} + +fn tokio_init() -> Handle { + godot_print!("Initializing tokio runtime"); + let runtime = Builder::new_multi_thread().enable_all().build().unwrap(); + let handle = runtime.handle().clone(); + + let rx = CHANNEL.1.clone(); + + std::thread::spawn(move || { + runtime.block_on(async { + godot_print!("Tokio runtime started"); + let _ = rx.lock().await.recv().await; + }); + godot_print!("Shutting down Tokio runtime"); + runtime.shutdown_timeout(Duration::from_secs(1)); + godot_print!("Tokio runtime stopped"); + }); + + handle +} + +fn tokio_deinit() { + let result = CHANNEL.0.clone().blocking_send(()); + if let Err(e) = result { + godot_print!("Failed to shut down tokio runtime: {e}"); + } +} + +fn get_channel() -> (Sender<()>, Arc>>) { + let (tx, rx) = channel(1); + (tx, Arc::new(Mutex::new(rx))) +} diff --git a/extensions/core/src/performance.rs b/extensions/core/src/performance.rs new file mode 100644 index 00000000..e69de29b diff --git a/extensions/core/src/power.rs b/extensions/core/src/power.rs new file mode 100644 index 00000000..18cf2051 --- /dev/null +++ b/extensions/core/src/power.rs @@ -0,0 +1,2 @@ +pub mod device; +pub mod upower; diff --git a/extensions/core/src/power/device.rs b/extensions/core/src/power/device.rs new file mode 100644 index 00000000..3362e9c7 --- /dev/null +++ b/extensions/core/src/power/device.rs @@ -0,0 +1,669 @@ +use std::sync::mpsc::{channel, Receiver, Sender, TryRecvError}; + +use futures_util::StreamExt; +use godot::{classes::ResourceLoader, prelude::*}; +use zbus::Connection; + +use crate::{ + dbus::{ + upower::device::{DeviceProxy, DeviceProxyBlocking}, + RunError, + }, + RUNTIME, +}; + +use super::upower::UPOWER_BUS; + +/// Signals that can be emitted +#[derive(Debug)] +enum Signal { + Updated, +} + +#[derive(GodotClass)] +#[class(no_init, base=Resource)] +pub struct UPowerDevice { + base: Base, + rx: Receiver, + conn: Option, + #[var] + dbus_path: GString, + #[allow(dead_code)] + #[var(get = get_battery_level)] + battery_level: u32, + #[allow(dead_code)] + #[var(get = get_charge_cycles)] + charge_cycles: i32, + #[allow(dead_code)] + #[var(get = get_energy)] + energy: f64, + #[allow(dead_code)] + #[var(get = get_energy_empty)] + energy_empty: f64, + #[allow(dead_code)] + #[var(get = get_energy_full)] + energy_full: f64, + #[allow(dead_code)] + #[var(get = get_energy_full_design)] + energy_full_design: f64, + #[allow(dead_code)] + #[var(get = get_energy_rate)] + energy_rate: f64, + #[allow(dead_code)] + #[var(get = get_has_history)] + has_history: bool, + #[allow(dead_code)] + #[var(get = get_has_statistics)] + has_statistics: bool, + #[allow(dead_code)] + #[var(get = get_icon_name)] + icon_name: GString, + #[allow(dead_code)] + #[var(get = get_is_present)] + is_present: bool, + #[allow(dead_code)] + #[var(get = get_is_rechargeable)] + is_rechargeable: bool, + #[allow(dead_code)] + #[var(get = get_luminosity)] + luminosity: f64, + #[allow(dead_code)] + #[var(get = get_model)] + model: GString, + #[allow(dead_code)] + #[var(get = get_native_path)] + native_path: GString, + #[allow(dead_code)] + #[var(get = get_online)] + online: bool, + #[allow(dead_code)] + #[var(get = get_percentage)] + percentage: f64, + #[allow(dead_code)] + #[var(get = get_power_supply)] + power_supply: bool, + #[allow(dead_code)] + #[var(get = get_serial)] + serial: GString, + #[allow(dead_code)] + #[var(get = get_state)] + state: u32, + #[allow(dead_code)] + #[var(get = get_technology)] + technology: u32, + #[allow(dead_code)] + #[var(get = get_temperature)] + temperature: f64, + #[allow(dead_code)] + #[var(get = get_time_to_empty)] + time_to_empty: i64, + #[allow(dead_code)] + #[var(get = get_time_to_full)] + time_to_full: i64, + #[allow(dead_code)] + #[var(get = get_type)] + type_: u32, + #[allow(dead_code)] + #[var(get = get_update_time)] + update_time: i64, + #[allow(dead_code)] + #[var(get = get_vendor)] + vendor: GString, + #[allow(dead_code)] + #[var(get = get_voltage)] + voltage: f64, + #[allow(dead_code)] + #[var(get = get_warning_level)] + warning_level: u32, +} + +#[godot_api] +impl UPowerDevice { + #[constant] + const TYPE_UNKNOWN: i32 = 0; + #[constant] + const TYPE_LINE_POWER: i32 = 1; + #[constant] + const TYPE_BATTERY: i32 = 2; + #[constant] + const TYPE_UPS: i32 = 3; + #[constant] + const TYPE_MONITOR: i32 = 4; + #[constant] + const TYPE_MOUSE: i32 = 5; + #[constant] + const TYPE_KEYBOARD: i32 = 6; + #[constant] + const TYPE_PDA: i32 = 7; + #[constant] + const TYPE_PHONE: i32 = 8; + #[constant] + const TYPE_MEDIA_PLAYER: i32 = 9; + #[constant] + const TYPE_TABLET: i32 = 10; + #[constant] + const TYPE_COMPUTER: i32 = 11; + #[constant] + const TYPE_GAMING_INPUT: i32 = 12; + #[constant] + const TYPE_PEN: i32 = 13; + #[constant] + const TYPE_TOUCHPAD: i32 = 14; + #[constant] + const TYPE_MODEM: i32 = 15; + #[constant] + const TYPE_NETWORK: i32 = 16; + #[constant] + const TYPE_HEADSET: i32 = 17; + #[constant] + const TYPE_SPEAKERS: i32 = 18; + #[constant] + const TYPE_HEADPHONES: i32 = 19; + #[constant] + const TYPE_VIDEO: i32 = 20; + #[constant] + const TYPE_OTHER_AUDIO: i32 = 21; + #[constant] + const TYPE_REMOTE_CONTROL: i32 = 22; + #[constant] + const TYPE_PRINTER: i32 = 23; + #[constant] + const TYPE_SCANNER: i32 = 24; + #[constant] + const TYPE_CAMERA: i32 = 25; + #[constant] + const TYPE_WEARABLE: i32 = 26; + #[constant] + const TYPE_TOY: i32 = 27; + #[constant] + const TYPE_BLUETOOTH_GENREIC: i32 = 28; + + #[constant] + const STATE_UNKNOWN: i32 = 0; + #[constant] + const STATE_CHARGING: i32 = 1; + #[constant] + const STATE_DISCHARGING: i32 = 2; + #[constant] + const STATE_EMPTY: i32 = 3; + #[constant] + const STATE_FULLY_CHARGED: i32 = 4; + #[constant] + const STATE_PENDING_CHARGE: i32 = 5; + #[constant] + const STATE_PENDING_DISCHARGE: i32 = 6; + + #[constant] + const TECHNOLOGY_UNKNOWN: i32 = 0; + #[constant] + const TECHNOLOGY_LITHIUM_ION: i32 = 1; + #[constant] + const TECHNOLOGY_LITHIUM_POLYMER: i32 = 2; + #[constant] + const TECHNOLOGY_LITHIUM_IRON_PHOSPHATE: i32 = 3; + #[constant] + const TECHNOLOGY_LEAD_ACID: i32 = 4; + #[constant] + const TECHNOLOGY_NICKEL_CADMIUM: i32 = 5; + #[constant] + const TECHNOLOGY_NICKEL_METAL_HYDRIDE: i32 = 6; + + #[constant] + const WARNING_LEVEL_UNKNOWN: i32 = 0; + #[constant] + const WARNING_LEVEL_NONE: i32 = 1; + #[constant] + const WARNING_LEVEL_DISCHARGING: i32 = 2; + #[constant] + const WARNING_LEVEL_LOW: i32 = 3; + #[constant] + const WARNING_LEVEL_CRITICAL: i32 = 4; + #[constant] + const WARNING_LEVEL_ACTION: i32 = 5; + + #[constant] + const BATTERY_LEVEL_UNKNOWN: i32 = 0; + #[constant] + const BATTERY_LEVEL_NONE: i32 = 1; + #[constant] + const BATTERY_LEVEL_LOW: i32 = 3; + #[constant] + const BATTERY_LEVEL_CRITICAL: i32 = 4; + #[constant] + const BATTERY_LEVEL_NORMAL: i32 = 6; + #[constant] + const BATTERY_LEVEL_HIGH: i32 = 7; + #[constant] + const BATTERY_LEVEL_FULL: i32 = 8; + + #[signal] + fn updated(); + + /// Create a new [UPowerDevice] with the given DBus path + pub fn from_path(path: GString) -> Gd { + // Create a channel to communicate with the signals task + let (tx, rx) = channel(); + let dbus_path = path.clone().into(); + + // Spawn a task using the shared tokio runtime to listen for signals + RUNTIME.spawn(async move { + if let Err(e) = run(tx, dbus_path).await { + godot_error!("Failed to run UPowerDevice task: ${e:?}"); + } + }); + + Gd::from_init_fn(|base| { + // Create a connection to DBus + let conn = zbus::blocking::Connection::system().ok(); + + // Accept a base of type Base and directly forward it. + Self { + base, + rx, + conn, + dbus_path: path, + battery_level: Default::default(), + charge_cycles: Default::default(), + energy: Default::default(), + energy_empty: Default::default(), + energy_full: Default::default(), + energy_full_design: Default::default(), + energy_rate: Default::default(), + has_history: Default::default(), + has_statistics: Default::default(), + icon_name: Default::default(), + is_present: Default::default(), + is_rechargeable: Default::default(), + luminosity: Default::default(), + model: Default::default(), + native_path: Default::default(), + online: Default::default(), + percentage: Default::default(), + power_supply: Default::default(), + serial: Default::default(), + state: Default::default(), + technology: Default::default(), + temperature: Default::default(), + time_to_empty: Default::default(), + time_to_full: Default::default(), + type_: Default::default(), + update_time: Default::default(), + vendor: Default::default(), + voltage: Default::default(), + warning_level: Default::default(), + } + }) + } + + /// Return a proxy instance to the composite device + fn get_proxy(&self) -> Option { + if let Some(conn) = self.conn.as_ref() { + let path: String = self.dbus_path.clone().into(); + DeviceProxyBlocking::builder(conn) + .path(path) + .ok() + .and_then(|builder| builder.build().ok()) + } else { + None + } + } + + /// Get or create a [UPowerDevice] with the given DBus path. If an instance + /// already exists with the given path, then it will be loaded from the resource + /// cache. + pub fn new(path: &str) -> Gd { + let res_path = format!("dbus://{UPOWER_BUS}{path}"); + + // Check to see if a resource already exists for this device + let mut resource_loader = ResourceLoader::singleton(); + if resource_loader.exists(res_path.clone().into()) { + if let Some(res) = resource_loader.load(res_path.clone().into()) { + godot_print!("Resource already exists, loading that instead"); + let device: Gd = res.cast(); + device + } else { + let mut device = UPowerDevice::from_path(path.to_string().into()); + device.take_over_path(res_path.into()); + device + } + } else { + let mut device = UPowerDevice::from_path(path.to_string().into()); + device.take_over_path(res_path.into()); + device + } + } + + #[func] + pub fn get_battery_level(&self) -> u32 { + let Some(proxy) = self.get_proxy() else { + return Default::default(); + }; + proxy.battery_level().ok().unwrap_or_default() + } + + #[func] + pub fn get_charge_cycles(&self) -> i32 { + let Some(proxy) = self.get_proxy() else { + return Default::default(); + }; + proxy.charge_cycles().ok().unwrap_or_default() + } + + #[func] + pub fn get_energy(&self) -> f64 { + let Some(proxy) = self.get_proxy() else { + return Default::default(); + }; + proxy.energy().ok().unwrap_or_default() + } + + #[func] + pub fn get_energy_empty(&self) -> f64 { + let Some(proxy) = self.get_proxy() else { + return Default::default(); + }; + proxy.energy_empty().ok().unwrap_or_default() + } + + #[func] + pub fn get_energy_full(&self) -> f64 { + let Some(proxy) = self.get_proxy() else { + return Default::default(); + }; + proxy.energy_full().ok().unwrap_or_default() + } + + #[func] + pub fn get_energy_full_design(&self) -> f64 { + let Some(proxy) = self.get_proxy() else { + return Default::default(); + }; + proxy.energy_full_design().ok().unwrap_or_default() + } + + #[func] + pub fn get_energy_rate(&self) -> f64 { + let Some(proxy) = self.get_proxy() else { + return Default::default(); + }; + proxy.energy_rate().ok().unwrap_or_default() + } + + #[func] + pub fn get_has_history(&self) -> bool { + let Some(proxy) = self.get_proxy() else { + return Default::default(); + }; + proxy.has_history().ok().unwrap_or_default() + } + + #[func] + pub fn get_has_statistics(&self) -> bool { + let Some(proxy) = self.get_proxy() else { + return Default::default(); + }; + proxy.has_statistics().ok().unwrap_or_default() + } + + #[func] + pub fn get_icon_name(&self) -> GString { + let Some(proxy) = self.get_proxy() else { + return Default::default(); + }; + proxy.icon_name().ok().unwrap_or_default().into() + } + + #[func] + pub fn get_is_present(&self) -> bool { + let Some(proxy) = self.get_proxy() else { + return Default::default(); + }; + proxy.is_present().ok().unwrap_or_default() + } + + #[func] + pub fn get_is_rechargeable(&self) -> bool { + let Some(proxy) = self.get_proxy() else { + return Default::default(); + }; + proxy.is_rechargeable().ok().unwrap_or_default() + } + + #[func] + pub fn get_luminosity(&self) -> f64 { + let Some(proxy) = self.get_proxy() else { + return Default::default(); + }; + proxy.luminosity().ok().unwrap_or_default() + } + + #[func] + pub fn get_model(&self) -> GString { + let Some(proxy) = self.get_proxy() else { + return Default::default(); + }; + proxy.model().ok().unwrap_or_default().into() + } + + #[func] + pub fn get_native_path(&self) -> GString { + let Some(proxy) = self.get_proxy() else { + return Default::default(); + }; + proxy.native_path().ok().unwrap_or_default().into() + } + + #[func] + pub fn get_online(&self) -> bool { + let Some(proxy) = self.get_proxy() else { + return Default::default(); + }; + proxy.online().ok().unwrap_or_default() + } + + #[func] + pub fn get_percentage(&self) -> f64 { + let Some(proxy) = self.get_proxy() else { + return Default::default(); + }; + proxy.percentage().ok().unwrap_or_default() + } + + #[func] + pub fn get_power_supply(&self) -> bool { + let Some(proxy) = self.get_proxy() else { + return Default::default(); + }; + proxy.power_supply().ok().unwrap_or_default() + } + + #[func] + pub fn get_serial(&self) -> GString { + let Some(proxy) = self.get_proxy() else { + return Default::default(); + }; + proxy.serial().ok().unwrap_or_default().into() + } + + #[func] + pub fn get_state(&self) -> u32 { + let Some(proxy) = self.get_proxy() else { + return Default::default(); + }; + proxy.state().ok().unwrap_or_default() + } + + #[func] + pub fn get_technology(&self) -> u32 { + let Some(proxy) = self.get_proxy() else { + return Default::default(); + }; + proxy.technology().ok().unwrap_or_default() + } + + #[func] + pub fn get_temperature(&self) -> f64 { + let Some(proxy) = self.get_proxy() else { + return Default::default(); + }; + proxy.temperature().ok().unwrap_or_default() + } + + #[func] + pub fn get_time_to_empty(&self) -> i64 { + let Some(proxy) = self.get_proxy() else { + return Default::default(); + }; + proxy.time_to_empty().ok().unwrap_or_default() + } + + #[func] + pub fn get_time_to_full(&self) -> i64 { + let Some(proxy) = self.get_proxy() else { + return Default::default(); + }; + proxy.time_to_full().ok().unwrap_or_default() + } + + #[func] + pub fn get_type(&self) -> u32 { + let Some(proxy) = self.get_proxy() else { + return Default::default(); + }; + proxy.type_().ok().unwrap_or_default() + } + + #[func] + pub fn get_update_time(&self) -> i64 { + let Some(proxy) = self.get_proxy() else { + return Default::default(); + }; + proxy.update_time().ok().unwrap_or_default() as i64 + } + + #[func] + pub fn get_vendor(&self) -> GString { + let Some(proxy) = self.get_proxy() else { + return Default::default(); + }; + proxy.vendor().ok().unwrap_or_default().into() + } + + #[func] + pub fn get_voltage(&self) -> f64 { + let Some(proxy) = self.get_proxy() else { + return Default::default(); + }; + proxy.voltage().ok().unwrap_or_default() + } + + #[func] + pub fn get_warning_level(&self) -> u32 { + let Some(proxy) = self.get_proxy() else { + return Default::default(); + }; + proxy.warning_level().ok().unwrap_or_default() + } + + /// Dispatches signals + pub fn process(&mut self) { + // Drain all messages from the channel to process them + loop { + let signal = match self.rx.try_recv() { + Ok(value) => value, + Err(e) => match e { + TryRecvError::Empty => break, + TryRecvError::Disconnected => { + godot_error!("Backend thread is not running!"); + return; + } + }, + }; + self.process_signal(signal); + } + } + + /// Process and dispatch the given signal + fn process_signal(&mut self, signal: Signal) { + match signal { + Signal::Updated => { + self.base_mut().emit_signal("updated".into(), &[]); + } + } + } +} + +/// Run the signals task +async fn run(tx: Sender, path: String) -> Result<(), RunError> { + // Establish a connection to the system bus + let conn = Connection::system().await?; + let proxy = DeviceProxy::builder(&conn).path(path)?.build().await?; + + let signals_tx = tx.clone(); + let mut events = proxy.receive_percentage_changed().await; + RUNTIME.spawn(async move { + while let Some(_event) = events.next().await { + let signal = Signal::Updated; + if signals_tx.send(signal).is_err() { + break; + } + } + }); + + let signals_tx = tx.clone(); + let mut events = proxy.receive_icon_name_changed().await; + RUNTIME.spawn(async move { + while let Some(_event) = events.next().await { + let signal = Signal::Updated; + if signals_tx.send(signal).is_err() { + break; + } + } + }); + + let signals_tx = tx.clone(); + let mut events = proxy.receive_state_changed().await; + RUNTIME.spawn(async move { + while let Some(_event) = events.next().await { + let signal = Signal::Updated; + if signals_tx.send(signal).is_err() { + break; + } + } + }); + + let signals_tx = tx.clone(); + let mut events = proxy.receive_time_to_full_changed().await; + RUNTIME.spawn(async move { + while let Some(_event) = events.next().await { + let signal = Signal::Updated; + if signals_tx.send(signal).is_err() { + break; + } + } + }); + + let signals_tx = tx.clone(); + let mut events = proxy.receive_time_to_empty_changed().await; + RUNTIME.spawn(async move { + while let Some(_event) = events.next().await { + let signal = Signal::Updated; + if signals_tx.send(signal).is_err() { + break; + } + } + }); + + let signals_tx = tx.clone(); + let mut events = proxy.receive_battery_level_changed().await; + RUNTIME.spawn(async move { + while let Some(_event) = events.next().await { + let signal = Signal::Updated; + if signals_tx.send(signal).is_err() { + break; + } + } + }); + + Ok(()) +} diff --git a/extensions/core/src/power/upower.rs b/extensions/core/src/power/upower.rs new file mode 100644 index 00000000..c6eb64c4 --- /dev/null +++ b/extensions/core/src/power/upower.rs @@ -0,0 +1,189 @@ +use std::{ + collections::HashMap, + sync::mpsc::{channel, Receiver, Sender, TryRecvError}, + time::Duration, +}; + +use godot::prelude::*; +use zbus::{names::BusName, Connection}; + +use crate::{ + dbus::{upower::upower::UPowerProxyBlocking, RunError}, + RUNTIME, +}; + +use super::device::UPowerDevice; + +pub const UPOWER_BUS: &str = "org.freedesktop.UPower"; +const UPOWER_PATH: &str = "/org/freedesktop/UPower"; +const DISPLAY_DEVICE_PATH: &str = "/org/freedesktop/UPower/devices/DisplayDevice"; + +/// Signals that can be emitted +#[derive(Debug)] +enum Signal { + Started, + Stopped, +} + +#[derive(GodotClass)] +#[class(base=Resource)] +pub struct UPowerInstance { + base: Base, + rx: Receiver, + conn: Option, + devices: HashMap>, + #[allow(dead_code)] + #[var(get = get_on_battery)] + on_battery: bool, +} + +#[godot_api] +impl UPowerInstance { + /// Emitted when UPower is detected as running + #[signal] + fn started(); + + /// Emitted when UPower is detected as stopped + #[signal] + fn stopped(); + + /// Returns whether or not the device is running on battery power + #[func] + pub fn get_on_battery(&self) -> bool { + let Some(proxy) = self.get_proxy() else { + return false; + }; + proxy.on_battery().ok().unwrap_or_default() + } + + /// Get the object to the "display device", a composite device that represents + /// the status icon to show in desktop environments. + #[func] + pub fn get_display_device(&mut self) -> Gd { + let device = UPowerDevice::new(DISPLAY_DEVICE_PATH); + self.devices + .insert(DISPLAY_DEVICE_PATH.to_string(), device.clone()); + device + } + + /// Process UPower signals and emit them as Godot signals. This method + /// should be called every frame in the "_process" loop of a node. + #[func] + fn process(&mut self) { + // Drain all messages from the channel to process them + loop { + let signal = match self.rx.try_recv() { + Ok(value) => value, + Err(e) => match e { + TryRecvError::Empty => break, + TryRecvError::Disconnected => { + godot_error!("Backend thread is not running!"); + return; + } + }, + }; + self.process_signal(signal); + } + + // Process signals for other known DBus objects + for (_, device) in self.devices.iter_mut() { + device.bind_mut().process(); + } + } + + /// Process and dispatch the given signal + fn process_signal(&mut self, signal: Signal) { + match signal { + Signal::Started => { + self.base_mut().emit_signal("started".into(), &[]); + } + Signal::Stopped => { + self.base_mut().emit_signal("stopped".into(), &[]); + } + } + } + + /// Return a proxy instance to the composite device + fn get_proxy(&self) -> Option { + if let Some(conn) = self.conn.as_ref() { + UPowerProxyBlocking::builder(conn) + .path(UPOWER_PATH) + .ok() + .and_then(|builder| builder.build().ok()) + } else { + None + } + } +} + +#[godot_api] +impl IResource for UPowerInstance { + /// Called upon object initialization in the engine + fn init(base: Base) -> Self { + godot_print!("Initializing UPower instance"); + + // Create a channel to communicate with the service + let (tx, rx) = channel(); + + // Spawn a task using the shared tokio runtime to listen for signals + RUNTIME.spawn(async move { + if let Err(e) = run(tx).await { + godot_error!("Failed to run UPower task: ${e:?}"); + } + }); + + // Create a new UPower instance + let conn = zbus::blocking::Connection::system().ok(); + Self { + base, + rx, + conn, + devices: HashMap::new(), + on_battery: false, + } + } +} + +/// Runs UPower tasks in Tokio to listen for DBus signals and send them +/// over the given channel so they can be processed during each engine frame. +async fn run(tx: Sender) -> Result<(), RunError> { + godot_print!("Spawning UPower tasks"); + // Establish a connection to the system bus + let conn = Connection::system().await?; + + // Spawn a task to listen for UPower start/stop + let dbus_conn = conn.clone(); + let signals_tx = tx.clone(); + RUNTIME.spawn(async move { + let bus = BusName::from_static_str(UPOWER_BUS).unwrap(); + let mut is_running = { + let dbus = zbus::fdo::DBusProxy::new(&dbus_conn).await.ok(); + let Some(dbus) = dbus else { + return; + }; + dbus.name_has_owner(bus.clone()).await.unwrap_or_default() + }; + + loop { + let dbus = zbus::fdo::DBusProxy::new(&dbus_conn).await.ok(); + let Some(dbus) = dbus else { + break; + }; + let running = dbus.name_has_owner(bus.clone()).await.unwrap_or_default(); + if running != is_running { + let signal = if running { + Signal::Started + } else { + Signal::Stopped + }; + if signals_tx.send(signal).is_err() { + break; + } + } + is_running = running; + tokio::time::sleep(Duration::from_secs(5)).await; + } + }); + + Ok(()) +} diff --git a/extensions/core/src/system.rs b/extensions/core/src/system.rs new file mode 100644 index 00000000..cfd505c1 --- /dev/null +++ b/extensions/core/src/system.rs @@ -0,0 +1,2 @@ +pub mod command; +pub mod pty; diff --git a/extensions/core/src/system/command.rs b/extensions/core/src/system/command.rs new file mode 100644 index 00000000..72a783a2 --- /dev/null +++ b/extensions/core/src/system/command.rs @@ -0,0 +1,44 @@ +use std::sync::mpsc::Receiver; + +use godot::prelude::*; + +//// Signals that can be emitted +//#[derive(Debug)] +//enum Signal { +// InputEvent { +// type_code: String, +// value: f64, +// }, +// TouchEvent { +// type_code: String, +// index: u32, +// is_touching: bool, +// pressure: f64, +// x: f64, +// y: f64, +// }, +//} +// +//#[derive(GodotClass)] +//#[class(base=RefCounted)] +//pub struct Command { +// base: Base, +// path: String, +// rx: Receiver, +//} +// +//#[godot_api] +//impl Command { +// #[signal] +// fn input_event(type_code: GString, value: f64); +// +// #[signal] +// fn touch_event( +// type_code: GString, +// index: i64, +// is_touching: bool, +// pressure: f64, +// x: f64, +// y: f64, +// ); +//} diff --git a/extensions/core/src/system/pty.rs b/extensions/core/src/system/pty.rs new file mode 100644 index 00000000..f4d06c83 --- /dev/null +++ b/extensions/core/src/system/pty.rs @@ -0,0 +1,316 @@ +use nix::pty::{openpty, Winsize}; +use std::{ + ffi::OsString, + sync::mpsc::{channel, Receiver, Sender, TryRecvError}, +}; +use tokio::{ + fs::File, + io::{AsyncReadExt, AsyncWriteExt, BufReader, BufWriter}, + process::Command, + select, +}; + +use godot::{obj::WithBaseField, prelude::*}; + +use crate::RUNTIME; + +/// Signals that can be emitted +#[derive(Debug)] +enum Signal { + Started { pid: u32 }, + Finished { exit_code: i32 }, + LineWritten { line: String }, +} + +/// Commands that can be sent to a running PTY session +#[derive(Debug)] +enum PtyCommand { + Write { data: Vec }, + WriteLine { line: String }, +} + +#[derive(GodotClass)] +#[class(base=Node)] +pub struct Pty { + base: Base, + rx: Receiver, + tx: Sender, + pty_tx: Option>, + + /// Whether or not a process is currently running in the PTY + #[var(get = get_running)] + running: bool, + /// Number of rows the pseudo terminal should have + #[export] + rows: i32, + /// Number of columns the psuedo terminal should have + #[export] + columns: i32, + /// Width of the pseudo terminal in pixels + #[export] + width_px: i32, + /// Height of the pseudo terminal in pixels + #[export] + height_px: i32, +} + +#[godot_api] +impl Pty { + /// Emitted when a process is started in the PTY. Returns the PID of the + /// started process. + #[signal] + fn started(pid: i32); + + /// Emitted when a line is written to the PTY stdout + #[signal] + fn line_written(line: GString); + + /// Emitted when the underlying command has exited. Returns the exit code + /// of the child process. + #[signal] + fn finished(exit_code: i32); + + /// Returns whether or not the PTY is currently executing a process + #[func] + fn get_running(&self) -> bool { + self.running + } + + #[func] + fn write(&self, data: PackedByteArray) -> i32 { + let Some(pty_tx) = self.pty_tx.as_ref() else { + godot_error!("PTY is not open to write line"); + return -1; + }; + let slice = data.as_slice(); + let data = slice.to_vec(); + let command = PtyCommand::Write { data }; + if let Err(e) = pty_tx.blocking_send(command) { + println!("Error sending write line to PTY: {e:?}"); + return -1; + } + + 0 + } + + /// Write the given line to the running PTY. Returns an error code if the + /// PTY is not currently executing a process. + #[func] + fn write_line(&self, line: GString) -> i32 { + let Some(pty_tx) = self.pty_tx.as_ref() else { + godot_error!("PTY is not open to write line"); + return -1; + }; + let command = PtyCommand::WriteLine { line: line.into() }; + if let Err(e) = pty_tx.blocking_send(command) { + println!("Error sending write line to PTY: {e:?}"); + return -1; + } + + 0 + } + + /// Execute the given command inside the PTY. This command is executed + /// asyncronously and will emit signals whenever new output is available. + #[func] + fn exec(&mut self, command: GString, args: PackedStringArray) -> i32 { + if self.running { + godot_error!("PTY is already running a process"); + return -1; + } + + // Open a new PTY with the given dimensions + let window_size = Winsize { + ws_row: self.rows as u16, + ws_col: self.columns as u16, + ws_xpixel: self.width_px as u16, + ws_ypixel: self.height_px as u16, + }; + let pty = match openpty(Some(&window_size), None) { + Ok(pty) => pty, + Err(e) => { + godot_error!("Failed to open pty: {e}"); + return -1; + } + }; + + godot_print!("Executing command async in pty"); + let command: String = command.into(); + let command = OsString::from(command); + let args: Vec = args.as_slice().iter().map(String::from).collect(); + + // Assign the different sides of the PTY + let master = pty.master; + let slave = pty.slave; + let stdin = slave.try_clone().unwrap(); + let stdout = slave.try_clone().unwrap(); + let stderr = slave; + + // Spawn a task to run the command + let signals_tx = self.tx.clone(); + RUNTIME.spawn(async move { + let mut binding = Command::new(command); + let cmd = binding + .args(args) + .stdin(stdin) + .stdout(stdout) + .stderr(stderr); + let mut child = cmd.spawn().unwrap(); + + // Get the PID of the process and emit a started signal + let pid = child.id(); + if let Some(pid) = pid { + let signal = Signal::Started { pid }; + if let Err(e) = signals_tx.send(signal) { + println!("Error sending started signal: {e:?}"); + } + } + + // Wait for the process to finish + let exit_code = match child.wait().await { + Ok(code) => code, + Err(e) => { + println!("Error executing child: {e:?}"); + return; + } + }; + let exit_code = exit_code.code().unwrap_or(0); + + // Send the exit code with the finished signal + let signal = Signal::Finished { exit_code }; + if let Err(e) = signals_tx.send(signal) { + println!("Error sending exit code: {e:?}"); + } + }); + + // Create a channel so input commands can be sent to the running PTY + let (pty_tx, mut pty_rx) = tokio::sync::mpsc::channel(8192); + self.pty_tx = Some(pty_tx); + + // Spawn a task to read/write from/to the PTY + let signals_tx = self.tx.clone(); + RUNTIME.spawn(async move { + println!("Task spawned to read/write PTY"); + + // Create readers/writers + let output = std::fs::File::from(master.try_clone().unwrap()); + let output: File = output.into(); + let input = std::fs::File::from(master); + let input: File = input.into(); + + let mut reader = BufReader::new(output); + let mut writer = BufWriter::new(input); + + // Select between read and write operations in a loop + loop { + let mut buffer = [0; 4096]; + select! { + // Handle stdout output + read_result = reader.read(&mut buffer[..]) => { + let bytes_read = match read_result { + Ok(n) => n, + Err(_e) => break, + }; + Pty::process_read(&buffer, bytes_read, &signals_tx); + } + // Handle stdin commands over channel + Some(cmd) = pty_rx.recv() => { + Pty::process_write(&mut writer, cmd).await; + } + } + } + println!("Finished"); + }); + self.running = true; + + 0 + } + + /// Process reading output from the PTY + fn process_read(buffer: &[u8], bytes_read: usize, signals_tx: &Sender) { + let data = &buffer[..bytes_read]; + let text = String::from_utf8_lossy(data).to_string(); + let text = text.replace('\r', ""); + let lines = text.split('\n'); + for line in lines { + let line = line.to_string(); + let signal = Signal::LineWritten { line }; + if let Err(e) = signals_tx.send(signal) { + println!("Error sending line: {e:?}"); + } + } + } + + /// Process writing input to the PTY + async fn process_write(writer: &mut BufWriter, cmd: PtyCommand) { + match cmd { + PtyCommand::Write { data } => { + writer.write_all(data.as_slice()).await.unwrap(); + } + PtyCommand::WriteLine { line } => { + let line = format!("{line}\r"); + writer.write_all(line.as_bytes()).await.unwrap(); + } + }; + writer.flush().await.unwrap(); + } + + /// Process and dispatch the given signal + fn process_signal(&mut self, signal: Signal) { + match signal { + Signal::Started { pid } => { + self.base_mut() + .emit_signal("started".into(), &[pid.to_variant()]); + } + Signal::Finished { exit_code } => { + self.running = false; + self.pty_tx = None; + self.base_mut() + .emit_signal("finished".into(), &[exit_code.to_variant()]); + } + Signal::LineWritten { line } => { + self.base_mut() + .emit_signal("line_written".into(), &[line.to_godot().to_variant()]); + } + } + } +} + +#[godot_api] +impl INode for Pty { + /// Called upon object initialization in the engine + fn init(base: Base) -> Self { + // Create a channel to communicate with the async runtime + let (tx, rx) = channel(); + + Self { + base, + rx, + tx, + pty_tx: None, + running: false, + rows: 8000, + columns: 8000, + width_px: 8000, + height_px: 8000, + } + } + + /// Executed every engine frame + fn process(&mut self, _delta: f64) { + // Drain all messages from the channel to process them + loop { + let signal = match self.rx.try_recv() { + Ok(value) => value, + Err(e) => match e { + TryRecvError::Empty => break, + TryRecvError::Disconnected => { + godot_error!("Backend thread is not running!"); + return; + } + }, + }; + self.process_signal(signal); + } + } +} diff --git a/gdext/.gitignore b/gdext/.gitignore deleted file mode 100644 index 5b2d4627..00000000 --- a/gdext/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.sconsign.dblite diff --git a/gdext/Makefile b/gdext/Makefile deleted file mode 100644 index e540f8b0..00000000 --- a/gdext/Makefile +++ /dev/null @@ -1,129 +0,0 @@ -NUM_CPU := $(shell nproc) - -# Variables to define all the extensions to build. If a new extension is added, -# they should be added to these lists. -ADDONS_PATH = ../addons -ALL_EXT_PATHS = ../addons/dbus ../addons/linuxthread ../addons/pty ../addons/unixsock ../addons/xlib -ALL_CPP_FILES = $(shell find ./godot-cpp -regex '.*\(\.cpp\|\.h\|\.hpp\)$$') godot-cpp/SConstruct - -ALL_SCONS_FILES = godot-cpp/SConstruct \ - godot-dbus/SConstruct \ - godot-linuxthread/SConstruct \ - godot-pty/SConstruct \ - godot-unix-socket/SConstruct \ - godot-xlib/SConstruct - -ALL_DEBUG_EXT = $(ADDONS_PATH)/dbus/bin/libdbus.linux.template_debug.x86_64.so \ - $(ADDONS_PATH)/linuxthread/bin/liblinuxthread.linux.template_debug.x86_64.so \ - $(ADDONS_PATH)/pty/bin/libpty.linux.template_debug.x86_64.so \ - $(ADDONS_PATH)/unixsock/bin/libunixsock.linux.template_debug.x86_64.so \ - $(ADDONS_PATH)/xlib/bin/libxlib.linux.template_debug.x86_64.so - -ALL_RELEASE_EXT = $(ADDONS_PATH)/dbus/bin/libdbus.linux.template_release.x86_64.so \ - $(ADDONS_PATH)/linuxthread/bin/liblinuxthread.linux.template_release.x86_64.so \ - $(ADDONS_PATH)/pty/bin/libpty.linux.template_release.x86_64.so \ - $(ADDONS_PATH)/unixsock/bin/libunixsock.linux.template_release.x86_64.so \ - $(ADDONS_PATH)/xlib/bin/libxlib.linux.template_release.x86_64.so - -ALL_GDEXT_FILES = $(ADDONS_PATH)/dbus/dbus.gdextension \ - $(ADDONS_PATH)/linuxthread/linuxthread.gdextension \ - $(ADDONS_PATH)/pty/pty.gdextension \ - $(ADDONS_PATH)/unixsock/unixsock.gdextension \ - $(ADDONS_PATH)/xlib/xlib.gdextension - -##@ General - -# The help target prints out all targets with their descriptions organized -# beneath their categories. The categories are represented by '##@' and the -# target descriptions by '##'. The awk commands is responsible for reading the -# entire set of makefiles included in this invocation, looking for lines of the -# file as xyz: ## something, and then pretty-format the target and help. Then, -# if there's a line with ##@ something, that gets pretty-printed as a category. -# More info on the usage of ANSI control characters for terminal formatting: -# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters -# More info on the awk command: -# http://linuxcommand.org/lc3_adv_awk.php - -.PHONY: help -help: ## Display this help. - @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) - -.PHONY: build -build: ## Build all GDExtensions - $(MAKE) release debug - -.PHONY: release -release: $(ALL_RELEASE_EXT) -$(ALL_RELEASE_EXT) &: $(ALL_GDEXT_FILES) $(ALL_CPP_FILES) - scons platform=linux -j$(NUM_CPU) target=template_release - -.PHONY: debug -debug: $(ALL_DEBUG_EXT) -$(ALL_DEBUG_EXT) &: $(ALL_GDEXT_FILES) $(ALL_CPP_FILES) - scons platform=linux -j$(NUM_CPU) target=template_debug - -$(ALL_GDEXT_FILES) &: $(ALL_SCONS_FILES) - mkdir -p $(ALL_EXT_PATHS) - cp ./godot-dbus/addons/dbus/dbus.gdextension $(ADDONS_PATH)/dbus - cp ./godot-linuxthread/addons/linuxthread/linuxthread.gdextension $(ADDONS_PATH)/linuxthread - cp ./godot-pty/addons/pty/pty.gdextension $(ADDONS_PATH)/pty - cp ./godot-unix-socket/addons/unixsock/unixsock.gdextension $(ADDONS_PATH)/unixsock - cp ./godot-xlib/addons/xlib/xlib.gdextension $(ADDONS_PATH)/xlib - -.PHONY: clean -clean: ## Clean all build artifacts - rm -rf $(ALL_EXT_PATHS) - find ./ -type f -name '*.o' -delete - find ./ -type f -name '*.a' -delete - find ./ -type f -name '*.os' -delete - find ./ -type f -name '*.so' -delete - -godot-cpp/SConstruct: - git submodule update --init godot-cpp - -godot-dbus/SConstruct: - git submodule update --init godot-dbus - -godot-linuxthread/SConstruct: - git submodule update --init godot-linuxthread - -godot-pty/SConstruct: - git submodule update --init godot-pty - -godot-unix-socket/SConstruct: - git submodule update --init godot-unix-socket - -godot-xlib/SConstruct: - git submodule update --init godot-xlib - -##@ Updates - -.PHONY: update-dbus -update-dbus: ## Update godot-dbus - cd godot-dbus - git fetch - git rebase origin/main - -.PHONY: update-linuxthread -update-linuxthread: ## Update godot-linuxthread - cd godot-linuxthread - git fetch - git rebase origin/main - -.PHONY: update-pty -update-pty: ## Update godot-pty - cd godot-pty - git fetch - git rebase origin/main - -.PHONY: update-unixsock -update-unixsock: ## Update godot-unixsock - cd godot-unixsock - git fetch - git rebase origin/main - -.PHONY: update-xlib -update-xlib: ## Update godot-xlib - cd godot-xlib - git fetch - git rebase origin/main diff --git a/gdext/SConstruct b/gdext/SConstruct deleted file mode 100644 index df56d6dd..00000000 --- a/gdext/SConstruct +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/env python -from SCons import __version__ as scons_raw_version -import os -import sys - -# Define path to godot-cpp dependency -godot_cpp_path = "godot-cpp" -if 'GODOT_CPP_PATH' in os.environ: - godot_cpp_path = os.environ['GODOT_CPP_PATH'] - -# Setup a standard path to output the extension -EXT_PATH = "../addons/{}/bin/lib{}{}{}" - -# Setup the environments from godot-cpp -env = SConscript(godot_cpp_path + "/SConstruct") -dbus_env = env.Clone() -thread_env = env.Clone() -pty_env = env.Clone() -unixsock_env = env.Clone() -xlib_env = env.Clone() - - -# --- godot-dbus --- - -# tweak this if you want to use different folders, or more folders, to store your source code in. -dbus_env.Append(CPPPATH=["godot-dbus/src/"]) -dbus_sources = Glob("godot-dbus/src/*.cpp") - -# Include dependency libraries for dbus -if 'PKG_CONFIG_PATH' in os.environ: - dbus_env['ENV']['PKG_CONFIG_PATH'] = os.environ['PKG_CONFIG_PATH'] -dbus_env.ParseConfig("pkg-config dbus-1 --cflags --libs") - -# Build the shared library -libdbus = dbus_env.SharedLibrary( - EXT_PATH.format( - "dbus", "dbus", dbus_env["suffix"], dbus_env["SHLIBSUFFIX"] - ), - source=dbus_sources, -) - -Default(libdbus) - - -# --- godot-linuxthread --- - -# tweak this if you want to use different folders, or more folders, to store your source code in. -thread_env.Append(CPPPATH=["godot-linuxthread/src/"]) -thread_sources = Glob("godot-linuxthread/src/*.cpp") - -# Build the shared library -libthread = thread_env.SharedLibrary( - EXT_PATH.format("linuxthread", - "linuxthread", thread_env["suffix"], thread_env["SHLIBSUFFIX"]), - source=thread_sources, -) - -Default(libthread) - - -# --- godot-pty --- - -# tweak this if you want to use different folders, or more folders, to store your source code in. -pty_env.Append(CPPPATH=["godot-pty/src/"]) -pty_sources = Glob("godot-pty/src/*.cpp") - -# Build the shared library -libpty = pty_env.SharedLibrary( - EXT_PATH.format("pty", - "pty", pty_env["suffix"], pty_env["SHLIBSUFFIX"]), - source=pty_sources, -) - -Default(libpty) - - -# --- godot-unix-socket --- - -# tweak this if you want to use different folders, or more folders, to store your source code in. -unixsock_env.Append(CPPPATH=["godot-unix-socket/src/"]) -unixsock_sources = Glob("godot-unix-socket/src/*.cpp") - -# Build the shared library -libunixsock = unixsock_env.SharedLibrary( - EXT_PATH.format("unixsock", - "unixsock", unixsock_env["suffix"], unixsock_env["SHLIBSUFFIX"]), - source=unixsock_sources, -) - -Default(libunixsock) - - -# --- godot-unix-socket --- - -# tweak this if you want to use different folders, or more folders, to store your source code in. -xlib_env.Append(CPPPATH=["godot-xlib/src/"]) -xlib_sources = Glob("godot-xlib/src/*.cpp") - -# Include dependency libraries for the extension -if 'PKG_CONFIG_PATH' in os.environ: - xlib_env['ENV']['PKG_CONFIG_PATH'] = os.environ['PKG_CONFIG_PATH'] -xlib_env.ParseConfig("pkg-config x11 --cflags --libs") -xlib_env.ParseConfig("pkg-config xres --cflags --libs") -xlib_env.ParseConfig("pkg-config xtst --cflags --libs") -xlib_env.ParseConfig("pkg-config xi --cflags --libs") - -# Build the shared library -libx11 = xlib_env.SharedLibrary( - EXT_PATH.format("xlib", - "xlib", xlib_env["suffix"], xlib_env["SHLIBSUFFIX"]), - source=xlib_sources, -) - -Default(libx11) diff --git a/gdext/godot-cpp b/gdext/godot-cpp deleted file mode 160000 index d6e5286c..00000000 --- a/gdext/godot-cpp +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d6e5286cc19bbd5b2c626207d3b01a8f145c0f76 diff --git a/gdext/godot-dbus b/gdext/godot-dbus deleted file mode 160000 index a8f62141..00000000 --- a/gdext/godot-dbus +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a8f62141612046b9bfff9f3b7d153ab7a122c899 diff --git a/gdext/godot-linuxthread b/gdext/godot-linuxthread deleted file mode 160000 index dbe78542..00000000 --- a/gdext/godot-linuxthread +++ /dev/null @@ -1 +0,0 @@ -Subproject commit dbe785424ed5eb62aca2ee33a87922f22c017641 diff --git a/gdext/godot-pty b/gdext/godot-pty deleted file mode 160000 index cd3128ac..00000000 --- a/gdext/godot-pty +++ /dev/null @@ -1 +0,0 @@ -Subproject commit cd3128ac07eb3407e877dacea4e93cc524039112 diff --git a/gdext/godot-unix-socket b/gdext/godot-unix-socket deleted file mode 160000 index 3ce07d78..00000000 --- a/gdext/godot-unix-socket +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3ce07d7868cc6cd1028ce0ec681b2f6789b04221 diff --git a/gdext/godot-xlib b/gdext/godot-xlib deleted file mode 160000 index 95e8237d..00000000 --- a/gdext/godot-xlib +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 95e8237dd472bbed6acb1d18fa8b9e9514cbd33b diff --git a/project.godot b/project.godot index 2919adce..399a1e4a 100644 --- a/project.godot +++ b/project.godot @@ -20,7 +20,7 @@ config/name="Open Gamepad UI" run/main_scene="res://entrypoint.tscn" config/use_custom_user_dir=true config/custom_user_dir_name="opengamepadui" -config/features=PackedStringArray("4.2", "Forward Plus") +config/features=PackedStringArray("4.3", "Forward Plus") run/low_processor_mode=true boot_splash/bg_color=Color(0, 0, 0, 0) boot_splash/show_image=false @@ -56,53 +56,53 @@ import/blender/enabled=false ui_accept={ "deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194309,"physical_keycode":0,"key_label":0,"unicode":4194309,"echo":false,"script":null) -, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194310,"physical_keycode":0,"key_label":0,"unicode":4194310,"echo":false,"script":null) -, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":32,"physical_keycode":0,"key_label":0,"unicode":32,"echo":false,"script":null) +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194309,"physical_keycode":0,"key_label":0,"unicode":4194309,"location":0,"echo":false,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194310,"physical_keycode":0,"key_label":0,"unicode":4194310,"location":0,"echo":false,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":32,"physical_keycode":0,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null) , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":0,"pressure":0.0,"pressed":true,"script":null) ] } ui_select={ "deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":32,"physical_keycode":0,"key_label":0,"unicode":32,"echo":false,"script":null) +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":32,"physical_keycode":0,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null) ] } ui_focus_next={ "deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194306,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null) +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194306,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":10,"pressure":0.0,"pressed":true,"script":null) ] } ui_focus_prev={ "deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":true,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194306,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null) +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":true,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194306,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":9,"pressure":0.0,"pressed":true,"script":null) ] } ui_left={ "deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194319,"physical_keycode":0,"key_label":0,"unicode":4194319,"echo":false,"script":null) +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194319,"physical_keycode":0,"key_label":0,"unicode":4194319,"location":0,"echo":false,"script":null) , null, null, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":13,"pressure":0.0,"pressed":true,"script":null) , Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":-1.0,"script":null) ] } ui_right={ "deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194321,"physical_keycode":0,"key_label":0,"unicode":4194321,"echo":false,"script":null) +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194321,"physical_keycode":0,"key_label":0,"unicode":4194321,"location":0,"echo":false,"script":null) , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":14,"pressure":0.0,"pressed":true,"script":null) , Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":1.0,"script":null) ] } ui_up={ "deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194320,"physical_keycode":0,"key_label":0,"unicode":4194320,"echo":false,"script":null) +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194320,"physical_keycode":0,"key_label":0,"unicode":4194320,"location":0,"echo":false,"script":null) , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":11,"pressure":0.0,"pressed":true,"script":null) , Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":-1.0,"script":null) ] } ui_down={ "deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194322,"physical_keycode":0,"key_label":0,"unicode":4194322,"echo":false,"script":null) +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194322,"physical_keycode":0,"key_label":0,"unicode":4194322,"location":0,"echo":false,"script":null) , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":12,"pressure":0.0,"pressed":true,"script":null) , Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":1.0,"script":null) ] @@ -114,13 +114,13 @@ ogui_guide={ } ogui_tab_right={ "deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":true,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194324,"key_label":0,"unicode":0,"echo":false,"script":null) +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":true,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194324,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":10,"pressure":0.0,"pressed":true,"script":null) ] } ogui_tab_left={ "deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":true,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194323,"key_label":0,"unicode":0,"echo":false,"script":null) +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":true,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194323,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":9,"pressure":0.0,"pressed":true,"script":null) ] } @@ -141,7 +141,7 @@ ogui_west={ } ogui_east={ "deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194305,"key_label":0,"unicode":0,"echo":false,"script":null) +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194305,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":1,"pressure":0.0,"pressed":true,"script":null) ] } @@ -151,23 +151,23 @@ ogui_guide_action={ } ogui_osk={ "deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":true,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":52,"key_label":0,"unicode":0,"echo":false,"script":null) +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":true,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":52,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) ] } ogui_qb={ "deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":true,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194333,"key_label":0,"unicode":0,"echo":false,"script":null) +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":true,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194333,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) ] } ogui_menu={ "deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":true,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194332,"key_label":0,"unicode":0,"echo":false,"script":null) +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":true,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194332,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) ] } ogui_back={ "deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194308,"key_label":0,"unicode":0,"echo":false,"script":null) -, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194305,"key_label":0,"unicode":0,"echo":false,"script":null) +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194308,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194305,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":1,"pressure":0.0,"pressed":true,"script":null) ] } @@ -183,12 +183,12 @@ ogui_right_trigger={ } ogui_modifier={ "deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194326,"key_label":0,"unicode":0,"echo":false,"script":null) +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194326,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) ] } ogui_guide_action_qb={ "deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194333,"key_label":0,"unicode":0,"echo":false,"script":null) +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194333,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) ] } ogui_power={ @@ -213,22 +213,22 @@ ogui_scroll_right={ } ogui_search={ "deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194336,"key_label":0,"unicode":0,"echo":false,"script":null) +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194336,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) ] } ogui_volume_up={ "deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194382,"key_label":0,"unicode":0,"echo":false,"script":null) +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194382,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) ] } ogui_volume_down={ "deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194380,"key_label":0,"unicode":0,"echo":false,"script":null) +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194380,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) ] } ogui_volume_mute={ "deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194381,"key_label":0,"unicode":0,"echo":false,"script":null) +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194381,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) ] } ogui_qam_ov={