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