Skip to content

Commit 40e83f8

Browse files
committed
examples/gui_vm, gtk_display: Add support for input (keyboard+touch)
Signed-off-by: Matej Hrica <[email protected]>
1 parent 7e781d7 commit 40e83f8

File tree

9 files changed

+1324
-49
lines changed

9 files changed

+1324
-49
lines changed

examples/Cargo.lock

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/external_kernel

27.4 KB
Binary file not shown.

examples/gui_vm/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ edition = "2024"
66
[dependencies]
77
gtk_display = { path = "../krun_gtk_display" }
88
krun-sys = { path = "../../krun-sys" }
9+
krun_input = { path = "../../src/krun_input" }
910
anyhow = "1.0.98"
1011
clap = "4.5.39"
1112
clap_derive = "4.5.32"

examples/gui_vm/src/main.rs

Lines changed: 154 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,31 @@
11
use clap::Parser;
22
use clap_derive::Parser;
3-
use gtk_display::DisplayBackendHandle;
3+
use gtk_display::{
4+
Axis, DisplayBackendHandle, DisplayInputOptions, InputBackendHandle, TouchArea,
5+
TouchScreenOptions,
6+
};
7+
48
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,
713
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,
1017
};
1118
use log::LevelFilter;
1219
use regex::{Captures, Regex};
1320
use std::ffi::{CString, c_void};
1421
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;
1529
use std::process::exit;
1630
use std::ptr::null;
1731
use std::str::FromStr;
@@ -32,19 +46,20 @@ struct DisplayArg {
3246
height: u32,
3347
refresh_rate: Option<u32>,
3448
physical_size: Option<PhysicalSize>,
49+
touch: bool,
3550
}
3651

3752
/// Parses a display settings string.
3853
/// The expected format is "WIDTHxHEIGHT[@FPS][:DPIdpi|:PHYSICAL_WIDTHxPHYSICAL_HEIGHTmm]".
3954
fn parse_display(display_string: &str) -> Result<DisplayArg, String> {
4055
static RE: LazyLock<Regex> = LazyLock::new(|| {
4156
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)?)?$",
4358
).unwrap()
4459
});
4560

4661
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'")
4863
})?;
4964

5065
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> {
7893
(None, None, None) => None,
7994
_ => unreachable!("regex bug"),
8095
},
96+
touch: captures.name("touch").is_some(),
8197
})
8298
}
8399

@@ -86,41 +102,98 @@ struct Args {
86102
#[arg(long)]
87103
root_dir: Option<CString>,
88104

105+
// Set disk to mount after boot
106+
#[arg(long)]
107+
remount_disk: Option<CString>,
108+
89109
executable: Option<CString>,
90110
argv: Vec<CString>,
111+
91112
// Display specifications in the format WIDTHxHEIGHT[@FPS][:DPIdpi|:PHYSICAL_WIDTHxPHYSICAL_HEIGHTmm]
92113
#[clap(long, value_parser = parse_display)]
93114
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>,
94127
}
95128

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<()> {
97134
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+
99155
let ctx = krun_call_u32!(krun_create_ctx())?;
100156

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(
102160
ctx,
103161
VIRGLRENDERER_USE_EGL
104162
| VIRGLRENDERER_VENUS
105163
| VIRGLRENDERER_RENDER_SERVER
106164
| VIRGLRENDERER_THREAD_SYNC
107-
| VIRGLRENDERER_USE_ASYNC_FENCE_CB
165+
| VIRGLRENDERER_USE_ASYNC_FENCE_CB,
166+
4096
108167
))?;
109168

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(),
116181
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()))?;
122185
}
123186

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+
124197
for display in &args.display {
125198
let display_id = krun_call_u32!(krun_add_display(ctx, display.width, display.height))?;
126199
if let Some(refresh_rate) = display.refresh_rate {
@@ -144,21 +217,76 @@ fn krun_thread(args: &Args, display_backend_handle: DisplayBackendHandle) -> any
144217
&raw const display_backend as *const c_void,
145218
size_of_val(&display_backend),
146219
))?;
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+
147243
krun_call!(krun_start_enter(ctx))?;
148244
};
149245
Ok(())
150246
}
151247

152248
fn main() -> anyhow::Result<()> {
153-
env_logger::builder().filter_level(LevelFilter::Info).init();
249+
env_logger::builder()
250+
.filter_level(LevelFilter::Debug)
251+
.init();
154252
let args = Args::parse();
155253

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+
)?;
158286

159287
thread::scope(|s| {
160288
s.spawn(|| {
161-
if let Err(e) = krun_thread(&args, display_backend) {
289+
if let Err(e) = krun_thread(&args, display_backend, input_backends) {
162290
eprintln!("{e}");
163291
exit(1);
164292
}

examples/krun_gtk_display/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ edition = "2024"
66

77
[dependencies]
88
utils = { path = "../../src/utils" } # Our version of rust vmm-sys-util
9-
crossbeam-channel = "0.5.15"
109
gtk = { version = "0.10", package = "gtk4", features = ["v4_16"] }
1110
krun_display = { path = "../../src/krun_display" }
11+
krun_input = { path = "../../src/krun_input" }
1212
anyhow = "1.0.98"
1313
log = "0.4.27"
1414
libc = "0.2.174"
15+
crossbeam-channel = "0.5.15"

0 commit comments

Comments
 (0)