Skip to content

Commit f8bd770

Browse files
committed
feat(URI): add mime type handler for opening ogui:// uri's
1 parent 4c0b931 commit f8bd770

File tree

13 files changed

+426
-3
lines changed

13 files changed

+426
-3
lines changed

addons/core/core.gdextension

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@ ResourceProcessor = "res://addons/core/assets/icons/clarity--process-on-vm-line.
1818
ResourceRegistry = "res://addons/core/assets/icons/carbon--cloud-registry.svg"
1919
UPowerInstance = "res://assets/editor-icons/material-symbols--battery-profile-sharp.svg"
2020
Vdf = "res://addons/core/assets/icons/material-symbols-light--valve.svg"
21+
FifoReader = "res://assets/editor-icons/fluent--pipeline-20-filled.svg"
Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
[remap]
2+
3+
importer="texture"
4+
type="CompressedTexture2D"
5+
uid="uid://cgl7d0purhfh2"
6+
path="res://.godot/imported/fluent--pipeline-20-filled.svg-6cd997c93f64610de8c42827f96b306b.ctex"
7+
metadata={
8+
"vram_texture": false
9+
}
10+
11+
[deps]
12+
13+
source_file="res://assets/editor-icons/fluent--pipeline-20-filled.svg"
14+
dest_files=["res://.godot/imported/fluent--pipeline-20-filled.svg-6cd997c93f64610de8c42827f96b306b.ctex"]
15+
16+
[params]
17+
18+
compress/mode=0
19+
compress/high_quality=false
20+
compress/lossy_quality=0.7
21+
compress/hdr_compression=1
22+
compress/normal_map=0
23+
compress/channel_pack=0
24+
mipmaps/generate=false
25+
mipmaps/limit=-1
26+
roughness/mode=0
27+
roughness/src_normal=""
28+
process/fix_alpha_border=true
29+
process/premult_alpha=false
30+
process/normal_map_invert_y=false
31+
process/hdr_as_srgb=false
32+
process/hdr_clamp_exposure=false
33+
process/size_limit=0
34+
detect_3d/compress_to=1
35+
svg/scale=1.0
36+
editor/scale_with_editor_scale=false
37+
editor/convert_colors_with_editor_theme=false

core/systems/ipc/pipe_manager.gd

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
@icon("res://assets/editor-icons/fluent--pipeline-20-filled.svg")
2+
extends Node
3+
class_name PipeManager
4+
5+
## Class for managing control messages sent through a named pipe
6+
##
7+
## The [PipeManager] creates a named pipe in `/run/user/<uid>/opengamepadui`
8+
## that can be used as a communication mechanism to send OpenGamepadUI commands
9+
## from another process. This is mostly done to handle custom `ogui://` URIs
10+
## which can be used to react in different ways.
11+
12+
const RUN_FOLDER: String = "/run/user/{}/opengamepadui"
13+
const URI_PREFIX: String = "ogui://"
14+
15+
signal line_written(line: String)
16+
17+
var launch_manager := load("res://core/global/launch_manager.tres") as LaunchManager
18+
var library_manager := load("res://core/global/library_manager.tres") as LibraryManager
19+
var settings_manager := load("res://core/global/settings_manager.tres") as SettingsManager
20+
21+
var pipe: FifoReader
22+
var pipe_path: String
23+
var logger := Log.get_logger("PipeManager", Log.LEVEL.DEBUG)
24+
25+
26+
func _ready() -> void:
27+
# Ensure the run directory exists
28+
var run_path := get_run_path()
29+
DirAccess.make_dir_recursive_absolute(run_path)
30+
31+
# Create a unix named pipe
32+
pipe_path = get_pipe_path()
33+
if pipe_path.is_empty():
34+
logger.error("Failed to get pipe path!")
35+
return
36+
logger.info("Opening pipe:", pipe_path)
37+
pipe = FifoReader.new()
38+
if pipe.open(pipe_path) != OK:
39+
return
40+
pipe.line_written.connect(_on_line_written)
41+
add_child(pipe)
42+
43+
44+
## Returns the path to the named pipe (e.g. /run/user/1000/opengamepadui/opengamepadui-0)
45+
func get_pipe_path() -> String:
46+
var run_path := get_run_path()
47+
if run_path.is_empty():
48+
return ""
49+
var path := "/".join([run_path, "opengamepadui-0"])
50+
return path
51+
52+
53+
## Returns the run path for the current user (e.g. /run/user/1000/opengamepadui)
54+
func get_run_path() -> String:
55+
var uid := get_uid()
56+
if uid < 0:
57+
return ""
58+
var run_folder := RUN_FOLDER.format([uid], "{}")
59+
60+
return run_folder
61+
62+
63+
## Returns the current user id (e.g. 1000)
64+
func get_uid() -> int:
65+
var output: Array = []
66+
if OS.execute("id", ["-u"], output) != OK:
67+
return -1
68+
if output.is_empty():
69+
return -1
70+
var data := output[0] as String
71+
var uid := data.to_int()
72+
73+
return uid
74+
75+
76+
func _on_line_written(line: String) -> void:
77+
if line.is_empty():
78+
return
79+
logger.debug("Received piped message:", line)
80+
line_written.emit(line)
81+
82+
# Check to see if the message is an ogui URI
83+
if line.begins_with(URI_PREFIX):
84+
_handle_uri_request(line)
85+
86+
87+
func _handle_uri_request(uri: String) -> void:
88+
var path := uri.replace(URI_PREFIX, "")
89+
var parts: Array[String]
90+
parts.assign(path.split("/"))
91+
if parts.is_empty():
92+
return
93+
var cmd := parts.pop_front() as String
94+
logger.info("Received URI command:", cmd)
95+
match cmd:
96+
"run":
97+
_handle_run_cmd(parts)
98+
99+
100+
func _handle_run_cmd(args: Array[String]) -> void:
101+
if args.is_empty():
102+
logger.debug("No game name was found in URI")
103+
return
104+
var game_name := args[0]
105+
var library_item := library_manager.get_app_by_name(game_name)
106+
if not library_item:
107+
logger.warn("Unable to find game with name:", game_name)
108+
return
109+
110+
# Select the provider
111+
var launch_item := library_item.launch_items[0] as LibraryLaunchItem
112+
var section := "game.{0}".format([library_item.name.to_lower()])
113+
var provider_id = settings_manager.get_value(section, "provider", "")
114+
if provider_id != "":
115+
var p := library_item.get_launch_item(provider_id)
116+
if p != null:
117+
launch_item = p
118+
119+
# Start the game
120+
logger.info("Starting game:", game_name)
121+
launch_manager.launch(launch_item)
122+
123+
124+
func _exit_tree() -> void:
125+
if not pipe:
126+
return
127+
pipe.close()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uid://ompm0veql0ng
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
extends GutTest
2+
3+
4+
func test_pipe() -> void:
5+
var pipe_manager := PipeManager.new()
6+
add_child_autoqfree(pipe_manager)
7+
watch_signals(pipe_manager)
8+
gut.p("Got manager: " + str(pipe_manager))
9+
10+
var run_path := pipe_manager.get_run_path()
11+
if not DirAccess.dir_exists_absolute(run_path):
12+
pass_test("Unable to create pipe directory for test. Skipping.")
13+
return
14+
15+
var on_line_written := func(line: String):
16+
gut.p("Got message: " + line)
17+
assert_eq(line, "test")
18+
pipe_manager.line_written.connect(on_line_written)
19+
20+
OS.execute("sh", ["-c", "echo -n test > " + pipe_manager.get_pipe_path()])
21+
await get_tree().process_frame
22+
await get_tree().process_frame
23+
await get_tree().process_frame
24+
25+
assert_signal_emitted(pipe_manager, "line_written", "should emit line")
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uid://dx7fwtl7k300t

core/ui/card_ui/card_ui.tscn

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
[gd_scene load_steps=47 format=3 uid="uid://fhriwlhm0lcj"]
1+
[gd_scene load_steps=48 format=3 uid="uid://fhriwlhm0lcj"]
22

33
[ext_resource type="PackedScene" uid="uid://n83wlhmmsu3j" path="res://core/systems/input/input_manager.tscn" id="1_34t85"]
44
[ext_resource type="Script" uid="uid://dcv2u3sn10dg5" path="res://core/ui/card_ui/card_ui.gd" id="1_f8851"]
@@ -31,6 +31,7 @@
3131
[ext_resource type="PackedScene" uid="uid://dj1fooc3gh13l" path="res://core/ui/card_ui/help/help_menu.tscn" id="15_m1wp2"]
3232
[ext_resource type="ResourceRegistry" uid="uid://bsr58xihnpn1j" path="res://core/systems/resource/resource_registry.tres" id="15_ne50o"]
3333
[ext_resource type="PackedScene" uid="uid://cu4l0d1joc37w" path="res://core/ui/card_ui/navigation/in-game_notification.tscn" id="16_bcudw"]
34+
[ext_resource type="Script" uid="uid://ompm0veql0ng" path="res://core/systems/ipc/pipe_manager.gd" id="17_s6ods"]
3435
[ext_resource type="PackedScene" uid="uid://vf4sij64f82b" path="res://core/ui/common/osk/on_screen_keyboard.tscn" id="18_462u5"]
3536
[ext_resource type="PackedScene" uid="uid://doft5r1y37j1" path="res://core/ui/components/volume_indicator.tscn" id="18_g4maw"]
3637
[ext_resource type="PackedScene" uid="uid://bbcd5tclmp2ux" path="res://core/ui/card_ui/navigation/library_loading_notification.tscn" id="20_7tnsn"]
@@ -126,6 +127,10 @@ instance = ExtResource("15_k47ua")
126127
[node name="ResourceProcessor" type="ResourceProcessor" parent="."]
127128
registry = ExtResource("15_ne50o")
128129

130+
[node name="PipeManager" type="Node" parent="."]
131+
script = ExtResource("17_s6ods")
132+
metadata/_custom_type_script = "uid://ompm0veql0ng"
133+
129134
[node name="PowerTimer" type="Timer" parent="."]
130135
unique_name_in_owner = true
131136
wait_time = 1.5

extensions/core/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ godot = { version = "0.2.4", features = [
1212
"experimental-threads",
1313
"register-docs",
1414
] }
15-
nix = { version = "0.29.0", features = ["term", "process"] }
15+
nix = { version = "0.29.0", features = ["term", "process", "fs"] }
1616
once_cell = "1.21.0"
1717
tokio = { version = "1.44.0", features = ["full"] }
1818
zbus = "5.5.0"

extensions/core/src/system.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
pub mod command;
2+
pub mod fifo;
23
pub mod pty;
34
pub mod subreaper;

0 commit comments

Comments
 (0)