1
1
use clap:: Parser ;
2
2
use clap_derive:: Parser ;
3
- use gtk_display:: DisplayBackendHandle ;
3
+ use gtk_display:: {
4
+ Axis , DisplayBackendHandle , DisplayInputOptions , InputBackendHandle , TouchArea ,
5
+ TouchScreenOptions ,
6
+ } ;
7
+
4
8
use krun_sys:: {
9
+ KRUN_LOG_LEVEL_TRACE , KRUN_LOG_LEVEL_WARN , KRUN_LOG_STYLE_ALWAYS , KRUN_LOG_TARGET_DEFAULT ,
5
10
VIRGLRENDERER_RENDER_SERVER , VIRGLRENDERER_THREAD_SYNC , VIRGLRENDERER_USE_ASYNC_FENCE_CB ,
6
- VIRGLRENDERER_USE_EGL , VIRGLRENDERER_VENUS , krun_add_display, krun_create_ctx,
7
- krun_display_set_dpi, krun_display_set_physical_size, krun_display_set_refresh_rate,
8
- krun_set_display_backend, krun_set_exec, krun_set_gpu_options, krun_set_log_level,
9
- krun_set_root, krun_start_enter,
11
+ VIRGLRENDERER_USE_EGL , VIRGLRENDERER_VENUS , krun_add_display, krun_add_input_device,
12
+ krun_add_input_device_fd, krun_create_ctx, krun_display_set_dpi,
13
+ krun_display_set_physical_size, krun_display_set_refresh_rate, krun_init_log,
14
+ krun_set_display_backend, krun_set_exec, krun_set_gpu_options2, krun_set_root,
15
+ krun_set_vm_config, krun_start_enter,
10
16
} ;
11
17
use log:: LevelFilter ;
12
18
use regex:: { Captures , Regex } ;
13
19
use std:: ffi:: { CString , c_void} ;
14
20
use std:: fmt:: Display ;
21
+ use std:: fs:: { File , OpenOptions } ;
22
+ use std:: mem:: size_of_val;
23
+
24
+ use anyhow:: Context ;
25
+ use std:: os:: fd:: IntoRawFd ;
26
+ use std:: path:: PathBuf ;
15
27
use std:: process:: exit;
16
28
use std:: ptr:: null;
17
29
use std:: str:: FromStr ;
@@ -32,19 +44,20 @@ struct DisplayArg {
32
44
height : u32 ,
33
45
refresh_rate : Option < u32 > ,
34
46
physical_size : Option < PhysicalSize > ,
47
+ touch : bool ,
35
48
}
36
49
37
50
/// Parses a display settings string.
38
51
/// The expected format is "WIDTHxHEIGHT[@FPS][:DPIdpi|:PHYSICAL_WIDTHxPHYSICAL_HEIGHTmm]".
39
52
fn parse_display ( display_string : & str ) -> Result < DisplayArg , String > {
40
53
static RE : LazyLock < Regex > = LazyLock :: new ( || {
41
54
Regex :: new (
42
- r"^(?P<width>\d+)x(?P<height>\d+)(?:@(?P<refresh_rate>\d+))?(?::(?P<dpi>\d+)dpi|:(?P<width_mm>\d+)x(?P<height_mm>\d+)mm)?$" ,
55
+ r"^(?P<width>\d+)x(?P<height>\d+)(?:@(?P<refresh_rate>\d+))?(?::(?P<dpi>\d+)dpi|:(?P<width_mm>\d+)x(?P<height_mm>\d+)mm)?(?P<touch>\+touch(screen)?)? $" ,
43
56
) . unwrap ( )
44
57
} ) ;
45
58
46
59
let captures = RE . captures ( display_string) . ok_or_else ( || {
47
- format ! ( "Invalid display string '{display_string}' format. Examples of valid values:\n '1920x1080', '1920x1080@60', '1920x1080:162x91mm', '1920x1080:300dpi', '1920x1080@90:300dpi'" )
60
+ format ! ( "Invalid display string '{display_string}' format. Examples of valid values:\n '1920x1080', '1920x1080+touch','1920x1080 @60', '1920x1080:162x91mm', '1920x1080:300dpi', '1920x1080@90:300dpi+touch '" )
48
61
} ) ?;
49
62
50
63
fn parse_group < T : FromStr > ( captures : & Captures , name : & str ) -> Result < Option < T > , String >
@@ -78,48 +91,86 @@ fn parse_display(display_string: &str) -> Result<DisplayArg, String> {
78
91
( None , None , None ) => None ,
79
92
_ => unreachable ! ( "regex bug" ) ,
80
93
} ,
94
+ touch : captures. name ( "touch" ) . is_some ( ) ,
81
95
} )
82
96
}
83
97
84
98
#[ derive( Parser , Debug ) ]
85
99
struct Args {
86
100
#[ arg( long) ]
87
- root_dir : Option < CString > ,
101
+ root_dir : CString ,
88
102
89
103
executable : Option < CString > ,
90
104
argv : Vec < CString > ,
105
+
91
106
// Display specifications in the format WIDTHxHEIGHT[@FPS][:DPIdpi|:PHYSICAL_WIDTHxPHYSICAL_HEIGHTmm]
92
107
#[ clap( long, value_parser = parse_display) ]
93
108
display : Vec < DisplayArg > ,
109
+
110
+ /// Attach a virtual keyboard input device
111
+ #[ arg( long) ]
112
+ keyboard_input : bool ,
113
+
114
+ /// Pipe (or file) where to write log (with terminal color formatting)
115
+ #[ arg( long) ]
116
+ color_log : Option < PathBuf > ,
117
+
118
+ /// Passthrough an input device (e.g. /dev/input/event0)
119
+ #[ arg( long) ]
120
+ input : Vec < PathBuf > ,
94
121
}
95
122
96
- fn krun_thread ( args : & Args , display_backend_handle : DisplayBackendHandle ) -> anyhow:: Result < ( ) > {
123
+ fn krun_thread (
124
+ args : & Args ,
125
+ display_backend_handle : DisplayBackendHandle ,
126
+ input_device_handles : Vec < InputBackendHandle > ,
127
+ ) -> anyhow:: Result < ( ) > {
97
128
unsafe {
98
- krun_call ! ( krun_set_log_level( 3 ) ) ?;
129
+ if let Some ( path) = & args. color_log {
130
+ krun_call ! ( krun_init_log(
131
+ OpenOptions :: new( )
132
+ . write( true )
133
+ . open( path)
134
+ . context( "Failed to open log output" ) ?
135
+ . into_raw_fd( ) ,
136
+ KRUN_LOG_LEVEL_TRACE ,
137
+ KRUN_LOG_STYLE_ALWAYS ,
138
+ 0
139
+ ) ) ?;
140
+ } else {
141
+ krun_call ! ( krun_init_log(
142
+ KRUN_LOG_TARGET_DEFAULT ,
143
+ KRUN_LOG_LEVEL_WARN ,
144
+ 0 ,
145
+ 0 ,
146
+ ) ) ?;
147
+ }
148
+
99
149
let ctx = krun_call_u32 ! ( krun_create_ctx( ) ) ?;
100
150
101
- krun_call ! ( krun_set_gpu_options(
151
+ krun_call ! ( krun_set_vm_config( ctx, 4 , 4096 ) ) ?;
152
+
153
+ krun_call ! ( krun_set_gpu_options2(
102
154
ctx,
103
155
VIRGLRENDERER_USE_EGL
104
156
| VIRGLRENDERER_VENUS
105
157
| VIRGLRENDERER_RENDER_SERVER
106
158
| VIRGLRENDERER_THREAD_SYNC
107
- | VIRGLRENDERER_USE_ASYNC_FENCE_CB
159
+ | VIRGLRENDERER_USE_ASYNC_FENCE_CB ,
160
+ 4096
108
161
) ) ?;
109
162
110
- if let Some ( root_dir) = & args. root_dir {
111
- krun_call ! ( krun_set_root( ctx, root_dir. as_ptr( ) ) ) ?;
112
- // Executable variable should be set if we have root_dir, this is verified by clap
113
- let executable = args. executable . as_ref ( ) . unwrap ( ) . as_ptr ( ) ;
114
- let argv: Vec < _ > = args. argv . iter ( ) . map ( |a| a. as_ptr ( ) ) . collect ( ) ;
115
- let argv_ptr = if argv. is_empty ( ) {
116
- null ( )
117
- } else {
118
- argv. as_ptr ( )
119
- } ;
120
- let envp = [ null ( ) ] ;
121
- krun_call ! ( krun_set_exec( ctx, executable, argv_ptr, envp. as_ptr( ) ) ) ?;
122
- }
163
+ krun_call ! ( krun_set_root( ctx, args. root_dir. as_ptr( ) ) ) ?;
164
+
165
+ let executable = args. executable . as_ref ( ) . unwrap ( ) . as_ptr ( ) ;
166
+ let argv: Vec < _ > = args. argv . iter ( ) . map ( |a| a. as_ptr ( ) ) . collect ( ) ;
167
+ let argv_ptr = if argv. is_empty ( ) {
168
+ null ( )
169
+ } else {
170
+ argv. as_ptr ( )
171
+ } ;
172
+ let envp = [ null ( ) ] ;
173
+ krun_call ! ( krun_set_exec( ctx, executable, argv_ptr, envp. as_ptr( ) ) ) ?;
123
174
124
175
for display in & args. display {
125
176
let display_id = krun_call_u32 ! ( krun_add_display( ctx, display. width, display. height) ) ?;
@@ -144,21 +195,76 @@ fn krun_thread(args: &Args, display_backend_handle: DisplayBackendHandle) -> any
144
195
& raw const display_backend as * const c_void,
145
196
size_of_val( & display_backend) ,
146
197
) ) ?;
198
+
199
+ for input in & args. input {
200
+ let fd = File :: open ( input)
201
+ . with_context ( || format ! ( "Failed to open input device {input:?}" ) ) ?
202
+ . into_raw_fd ( ) ;
203
+ krun_call ! ( krun_add_input_device_fd( ctx, fd) )
204
+ . context ( "Failed to attach input device" ) ?;
205
+ }
206
+
207
+ // Configure all input devices
208
+ for handle in & input_device_handles {
209
+ let config_backend = handle. get_config ( ) ;
210
+ let event_provider_backend = handle. get_events ( ) ;
211
+
212
+ krun_call ! ( krun_add_input_device(
213
+ ctx,
214
+ & raw const config_backend as * const c_void,
215
+ size_of_val( & config_backend) ,
216
+ & raw const event_provider_backend as * const c_void,
217
+ size_of_val( & event_provider_backend) ,
218
+ ) ) ?;
219
+ }
220
+
147
221
krun_call ! ( krun_start_enter( ctx) ) ?;
148
222
} ;
149
223
Ok ( ( ) )
150
224
}
151
225
152
226
fn main ( ) -> anyhow:: Result < ( ) > {
153
- env_logger:: builder ( ) . filter_level ( LevelFilter :: Info ) . init ( ) ;
227
+ env_logger:: builder ( )
228
+ . filter_level ( LevelFilter :: Debug )
229
+ . init ( ) ;
154
230
let args = Args :: parse ( ) ;
155
231
156
- let ( display_backend, display_worker) =
157
- gtk_display:: crate_display ( "libkrun examples/gui_vm" . to_string ( ) ) ;
232
+ let mut per_display_inputs = vec ! [ vec![ ] ; args. display. len( ) ] ;
233
+ for ( idx, display) in args. display . iter ( ) . enumerate ( ) {
234
+ if display. touch {
235
+ per_display_inputs[ idx] . push ( DisplayInputOptions :: TouchScreen ( TouchScreenOptions {
236
+ // There is no specific reason for these axis sizes, just picked what my
237
+ // physical hardware had
238
+ area : TouchArea {
239
+ x : Axis {
240
+ max : 13764 ,
241
+ res : 40 ,
242
+ fuzz : 40 ,
243
+ ..Default :: default ( )
244
+ } ,
245
+ y : Axis {
246
+ max : 7740 ,
247
+ res : 40 ,
248
+ fuzz : 40 ,
249
+ ..Default :: default ( )
250
+ } ,
251
+ } ,
252
+ emit_mt : true ,
253
+ emit_non_mt : false ,
254
+ triggered_by_mouse : true ,
255
+ } ) ) ;
256
+ }
257
+ }
258
+
259
+ let ( display_backend, input_backends, display_worker) = gtk_display:: init (
260
+ "libkrun examples/gui_vm" . to_string ( ) ,
261
+ args. keyboard_input ,
262
+ per_display_inputs,
263
+ ) ?;
158
264
159
265
thread:: scope ( |s| {
160
266
s. spawn ( || {
161
- if let Err ( e) = krun_thread ( & args, display_backend) {
267
+ if let Err ( e) = krun_thread ( & args, display_backend, input_backends ) {
162
268
eprintln ! ( "{e}" ) ;
163
269
exit ( 1 ) ;
164
270
}
0 commit comments