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:: {
5
- VIRGLRENDERER_RENDER_SERVER , VIRGLRENDERER_THREAD_SYNC , VIRGLRENDERER_USE_ASYNC_FENCE_CB ,
6
- VIRGLRENDERER_USE_EGL , VIRGLRENDERER_VENUS , krun_add_display, krun_create_ctx,
9
+ KRUN_DISK_FORMAT_QCOW2 , KRUN_LOG_LEVEL_TRACE , KRUN_LOG_LEVEL_WARN , KRUN_LOG_STYLE_ALWAYS ,
10
+ KRUN_LOG_TARGET_DEFAULT , VIRGLRENDERER_RENDER_SERVER , VIRGLRENDERER_THREAD_SYNC ,
11
+ VIRGLRENDERER_USE_ASYNC_FENCE_CB , VIRGLRENDERER_USE_EGL , VIRGLRENDERER_VENUS , krun_add_disk2,
12
+ krun_add_display, krun_add_input_device, krun_add_input_device_fd, krun_create_ctx,
7
13
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,
14
+ krun_init_log, krun_set_display_backend, krun_set_exec, krun_set_gpu_options2,
15
+ krun_set_passt_fd, krun_set_root, krun_set_root_disk_remount, krun_set_vm_config,
16
+ krun_start_enter,
10
17
} ;
11
18
use log:: LevelFilter ;
12
19
use regex:: { Captures , Regex } ;
13
20
use std:: ffi:: { CString , c_void} ;
14
21
use std:: fmt:: Display ;
22
+ use std:: fs:: { File , OpenOptions } ;
23
+ use std:: mem:: size_of_val;
24
+
25
+ use anyhow:: Context ;
26
+ use std:: os:: fd:: IntoRawFd ;
27
+ use std:: os:: unix:: net:: UnixStream ;
28
+ use std:: path:: PathBuf ;
15
29
use std:: process:: exit;
16
30
use std:: ptr:: null;
17
31
use std:: str:: FromStr ;
@@ -32,19 +46,20 @@ struct DisplayArg {
32
46
height : u32 ,
33
47
refresh_rate : Option < u32 > ,
34
48
physical_size : Option < PhysicalSize > ,
49
+ touch : bool ,
35
50
}
36
51
37
52
/// Parses a display settings string.
38
53
/// The expected format is "WIDTHxHEIGHT[@FPS][:DPIdpi|:PHYSICAL_WIDTHxPHYSICAL_HEIGHTmm]".
39
54
fn parse_display ( display_string : & str ) -> Result < DisplayArg , String > {
40
55
static RE : LazyLock < Regex > = LazyLock :: new ( || {
41
56
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)?$" ,
57
+ 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
58
) . unwrap ( )
44
59
} ) ;
45
60
46
61
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'" )
62
+ 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
63
} ) ?;
49
64
50
65
fn parse_group < T : FromStr > ( captures : & Captures , name : & str ) -> Result < Option < T > , String >
@@ -78,6 +93,7 @@ fn parse_display(display_string: &str) -> Result<DisplayArg, String> {
78
93
( None , None , None ) => None ,
79
94
_ => unreachable ! ( "regex bug" ) ,
80
95
} ,
96
+ touch : captures. name ( "touch" ) . is_some ( ) ,
81
97
} )
82
98
}
83
99
@@ -86,41 +102,98 @@ struct Args {
86
102
#[ arg( long) ]
87
103
root_dir : Option < CString > ,
88
104
105
+ // Set disk to mount after boot
106
+ #[ arg( long) ]
107
+ remount_disk : Option < CString > ,
108
+
89
109
executable : Option < CString > ,
90
110
argv : Vec < CString > ,
111
+
91
112
// Display specifications in the format WIDTHxHEIGHT[@FPS][:DPIdpi|:PHYSICAL_WIDTHxPHYSICAL_HEIGHTmm]
92
113
#[ clap( long, value_parser = parse_display) ]
93
114
display : Vec < DisplayArg > ,
115
+
116
+ /// Attach a virtual keyboard input device
117
+ #[ arg( long) ]
118
+ keyboard_input : bool ,
119
+
120
+ /// Pipe (or file) where to write log (with terminal color formatting)
121
+ #[ arg( long) ]
122
+ color_log : Option < PathBuf > ,
123
+
124
+ /// Passthrough an input device (e.g. /dev/input/event0)
125
+ #[ arg( long) ]
126
+ input : Vec < PathBuf > ,
94
127
}
95
128
96
- fn krun_thread ( args : & Args , display_backend_handle : DisplayBackendHandle ) -> anyhow:: Result < ( ) > {
129
+ fn krun_thread (
130
+ args : & Args ,
131
+ display_backend_handle : DisplayBackendHandle ,
132
+ input_device_handles : Vec < InputBackendHandle > ,
133
+ ) -> anyhow:: Result < ( ) > {
97
134
unsafe {
98
- krun_call ! ( krun_set_log_level( 3 ) ) ?;
135
+ if let Some ( path) = & args. color_log {
136
+ krun_call ! ( krun_init_log(
137
+ OpenOptions :: new( )
138
+ . write( true )
139
+ . open( path)
140
+ . context( "Failed to open log output" ) ?
141
+ . into_raw_fd( ) ,
142
+ KRUN_LOG_LEVEL_TRACE ,
143
+ KRUN_LOG_STYLE_ALWAYS ,
144
+ 0
145
+ ) ) ?;
146
+ } else {
147
+ krun_call ! ( krun_init_log(
148
+ KRUN_LOG_TARGET_DEFAULT ,
149
+ KRUN_LOG_LEVEL_WARN ,
150
+ 0 ,
151
+ 0 ,
152
+ ) ) ?;
153
+ }
154
+
99
155
let ctx = krun_call_u32 ! ( krun_create_ctx( ) ) ?;
100
156
101
- krun_call ! ( krun_set_gpu_options(
157
+ krun_call ! ( krun_set_vm_config( ctx, 4 , 4096 ) ) ?;
158
+
159
+ krun_call ! ( krun_set_gpu_options2(
102
160
ctx,
103
161
VIRGLRENDERER_USE_EGL
104
162
| VIRGLRENDERER_VENUS
105
163
| VIRGLRENDERER_RENDER_SERVER
106
164
| VIRGLRENDERER_THREAD_SYNC
107
- | VIRGLRENDERER_USE_ASYNC_FENCE_CB
165
+ | VIRGLRENDERER_USE_ASYNC_FENCE_CB ,
166
+ 4096
108
167
) ) ?;
109
168
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 ( ) {
169
+ if let Some ( remount_disk) = & args. remount_disk {
170
+ krun_call ! ( krun_add_disk2(
171
+ ctx,
172
+ c"/dev/vda" . as_ptr( ) ,
173
+ remount_disk. as_ptr( ) ,
174
+ KRUN_DISK_FORMAT_QCOW2 ,
175
+ false
176
+ ) ) ?;
177
+ krun_call ! ( krun_set_root_disk_remount(
178
+ ctx,
179
+ c"/dev/vda" . as_ptr( ) ,
180
+ c"btrfs" . as_ptr( ) ,
116
181
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( ) ) ) ?;
182
+ ) ) ?;
183
+ } else if let Some ( root_dir) = & args. root_dir {
184
+ krun_call ! ( krun_set_root( ctx, root_dir. as_ptr( ) ) ) ?;
122
185
}
123
186
187
+ let executable = args. executable . as_ref ( ) . unwrap ( ) . as_ptr ( ) ;
188
+ let argv: Vec < _ > = args. argv . iter ( ) . map ( |a| a. as_ptr ( ) ) . collect ( ) ;
189
+ let argv_ptr = if argv. is_empty ( ) {
190
+ null ( )
191
+ } else {
192
+ argv. as_ptr ( )
193
+ } ;
194
+ let envp = [ null ( ) ] ;
195
+ krun_call ! ( krun_set_exec( ctx, executable, argv_ptr, envp. as_ptr( ) ) ) ?;
196
+
124
197
for display in & args. display {
125
198
let display_id = krun_call_u32 ! ( krun_add_display( ctx, display. width, display. height) ) ?;
126
199
if let Some ( refresh_rate) = display. refresh_rate {
@@ -144,21 +217,76 @@ fn krun_thread(args: &Args, display_backend_handle: DisplayBackendHandle) -> any
144
217
& raw const display_backend as * const c_void,
145
218
size_of_val( & display_backend) ,
146
219
) ) ?;
220
+
221
+ for input in & args. input {
222
+ let fd = File :: open ( input)
223
+ . with_context ( || format ! ( "Failed to open input device {input:?}" ) ) ?
224
+ . into_raw_fd ( ) ;
225
+ krun_call ! ( krun_add_input_device_fd( ctx, fd) )
226
+ . context ( "Failed to attach input device" ) ?;
227
+ }
228
+
229
+ // Configure all input devices
230
+ for handle in & input_device_handles {
231
+ let config_backend = handle. get_config ( ) ;
232
+ let event_provider_backend = handle. get_events ( ) ;
233
+
234
+ krun_call ! ( krun_add_input_device(
235
+ ctx,
236
+ & raw const config_backend as * const c_void,
237
+ size_of_val( & config_backend) ,
238
+ & raw const event_provider_backend as * const c_void,
239
+ size_of_val( & event_provider_backend) ,
240
+ ) ) ?;
241
+ }
242
+
147
243
krun_call ! ( krun_start_enter( ctx) ) ?;
148
244
} ;
149
245
Ok ( ( ) )
150
246
}
151
247
152
248
fn main ( ) -> anyhow:: Result < ( ) > {
153
- env_logger:: builder ( ) . filter_level ( LevelFilter :: Info ) . init ( ) ;
249
+ env_logger:: builder ( )
250
+ . filter_level ( LevelFilter :: Debug )
251
+ . init ( ) ;
154
252
let args = Args :: parse ( ) ;
155
253
156
- let ( display_backend, display_worker) =
157
- gtk_display:: crate_display ( "libkrun examples/gui_vm" . to_string ( ) ) ;
254
+ let mut per_display_inputs = vec ! [ vec![ ] ; args. display. len( ) ] ;
255
+ for ( idx, display) in args. display . iter ( ) . enumerate ( ) {
256
+ if display. touch {
257
+ per_display_inputs[ idx] . push ( DisplayInputOptions :: TouchScreen ( TouchScreenOptions {
258
+ // There is no specific reason for these axis sizes, just picked what my
259
+ // physical hardware had
260
+ area : TouchArea {
261
+ x : Axis {
262
+ max : 13764 ,
263
+ res : 40 ,
264
+ fuzz : 40 ,
265
+ ..Default :: default ( )
266
+ } ,
267
+ y : Axis {
268
+ max : 7740 ,
269
+ res : 40 ,
270
+ fuzz : 40 ,
271
+ ..Default :: default ( )
272
+ } ,
273
+ } ,
274
+ emit_mt : true ,
275
+ emit_non_mt : false ,
276
+ triggered_by_mouse : true ,
277
+ } ) ) ;
278
+ }
279
+ }
280
+
281
+ let ( display_backend, input_backends, display_worker) = gtk_display:: init (
282
+ "libkrun examples/gui_vm" . to_string ( ) ,
283
+ args. keyboard_input ,
284
+ per_display_inputs,
285
+ ) ?;
158
286
159
287
thread:: scope ( |s| {
160
288
s. spawn ( || {
161
- if let Err ( e) = krun_thread ( & args, display_backend) {
289
+ if let Err ( e) = krun_thread ( & args, display_backend, input_backends ) {
162
290
eprintln ! ( "{e}" ) ;
163
291
exit ( 1 ) ;
164
292
}
0 commit comments