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