Skip to content

Commit e06c2f8

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 e06c2f8

File tree

9 files changed

+1323
-49
lines changed

9 files changed

+1323
-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: 152 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,29 @@
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, krun_set_root,
15+
krun_set_root_disk_remount, krun_set_vm_config, krun_start_enter,
1016
};
1117
use log::LevelFilter;
1218
use regex::{Captures, Regex};
1319
use std::ffi::{CString, c_void};
1420
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;
1527
use std::process::exit;
1628
use std::ptr::null;
1729
use std::str::FromStr;
@@ -32,19 +44,20 @@ struct DisplayArg {
3244
height: u32,
3345
refresh_rate: Option<u32>,
3446
physical_size: Option<PhysicalSize>,
47+
touch: bool,
3548
}
3649

3750
/// Parses a display settings string.
3851
/// The expected format is "WIDTHxHEIGHT[@FPS][:DPIdpi|:PHYSICAL_WIDTHxPHYSICAL_HEIGHTmm]".
3952
fn parse_display(display_string: &str) -> Result<DisplayArg, String> {
4053
static RE: LazyLock<Regex> = LazyLock::new(|| {
4154
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)?)?$",
4356
).unwrap()
4457
});
4558

4659
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'")
4861
})?;
4962

5063
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> {
7891
(None, None, None) => None,
7992
_ => unreachable!("regex bug"),
8093
},
94+
touch: captures.name("touch").is_some(),
8195
})
8296
}
8397

@@ -86,41 +100,98 @@ struct Args {
86100
#[arg(long)]
87101
root_dir: Option<CString>,
88102

103+
// Set disk to mount after boot
104+
#[arg(long)]
105+
remount_disk: Option<CString>,
106+
89107
executable: Option<CString>,
90108
argv: Vec<CString>,
109+
91110
// Display specifications in the format WIDTHxHEIGHT[@FPS][:DPIdpi|:PHYSICAL_WIDTHxPHYSICAL_HEIGHTmm]
92111
#[clap(long, value_parser = parse_display)]
93112
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>,
94125
}
95126

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<()> {
97132
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+
99153
let ctx = krun_call_u32!(krun_create_ctx())?;
100154

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(
102158
ctx,
103159
VIRGLRENDERER_USE_EGL
104160
| VIRGLRENDERER_VENUS
105161
| VIRGLRENDERER_RENDER_SERVER
106162
| VIRGLRENDERER_THREAD_SYNC
107-
| VIRGLRENDERER_USE_ASYNC_FENCE_CB
163+
| VIRGLRENDERER_USE_ASYNC_FENCE_CB,
164+
4096
108165
))?;
109166

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(),
116179
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()))?;
122183
}
123184

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+
124195
for display in &args.display {
125196
let display_id = krun_call_u32!(krun_add_display(ctx, display.width, display.height))?;
126197
if let Some(refresh_rate) = display.refresh_rate {
@@ -144,21 +215,76 @@ fn krun_thread(args: &Args, display_backend_handle: DisplayBackendHandle) -> any
144215
&raw const display_backend as *const c_void,
145216
size_of_val(&display_backend),
146217
))?;
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+
147241
krun_call!(krun_start_enter(ctx))?;
148242
};
149243
Ok(())
150244
}
151245

152246
fn main() -> anyhow::Result<()> {
153-
env_logger::builder().filter_level(LevelFilter::Info).init();
247+
env_logger::builder()
248+
.filter_level(LevelFilter::Debug)
249+
.init();
154250
let args = Args::parse();
155251

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

159285
thread::scope(|s| {
160286
s.spawn(|| {
161-
if let Err(e) = krun_thread(&args, display_backend) {
287+
if let Err(e) = krun_thread(&args, display_backend, input_backends) {
162288
eprintln!("{e}");
163289
exit(1);
164290
}

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)