Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/code-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ jobs:
- name: Clippy (tdx)
run: cargo clippy --locked --features tdx -- -D warnings

- name: Clippy (net+blk+gpu+snd)
run: cargo clippy --locked --features net,blk,gpu,snd -- -D warnings
- name: Clippy (net+blk+gpu+snd+input)
run: cargo clippy --locked --features net,blk,gpu,snd,input -- -D warnings

code-quality-linux-aarch64:
name: libkrun (Linux aarch64)
Expand All @@ -41,8 +41,8 @@ jobs:
- name: Clippy (default)
run: cargo clippy --locked -- -D warnings

- name: Clippy (net+blk+gpu+snd)
run: cargo clippy --locked --features net,blk,gpu,snd -- -D warnings
- name: Clippy (net+blk+gpu+snd+input)
run: cargo clippy --locked --features net,blk,gpu,snd,input -- -D warnings

code-quality-macos:
name: libkrun (macOS aarch64)
Expand Down
15 changes: 15 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = ["src/libkrun"]
members = ["src/libkrun", "src/krun_input"]
exclude = ["examples/gtk_display"]
resolver = "2"

Expand Down
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
LIBRARY_HEADER = include/libkrun.h
LIBRARY_HEADER_DISPLAY = include/libkrun_display.h
LIBRARY_HEADER_INPUT = include/libkrun_input.h

ABI_VERSION=1
FULL_VERSION=1.15.1
Expand Down Expand Up @@ -58,6 +59,9 @@ endif
ifeq ($(SND),1)
FEATURE_FLAGS += --features snd
endif
ifeq ($(INPUT),1)
FEATURE_FLAGS += --features input
endif
ifeq ($(NITRO),1)
VARIANT = -nitro
FEATURE_FLAGS := --features nitro
Expand Down Expand Up @@ -147,6 +151,7 @@ install: libkrun.pc
install -d $(DESTDIR)$(PREFIX)/include
install -m 644 $(LIBRARY_HEADER) $(DESTDIR)$(PREFIX)/include
install -m 644 $(LIBRARY_HEADER_DISPLAY) $(DESTDIR)$(PREFIX)/include
install -m 644 $(LIBRARY_HEADER_INPUT) $(DESTDIR)$(PREFIX)/include
install -m 644 libkrun.pc $(DESTDIR)$(PREFIX)/$(LIBDIR_$(OS))/pkgconfig
install -m 755 $(LIBRARY_RELEASE_$(OS)) $(DESTDIR)$(PREFIX)/$(LIBDIR_$(OS))/
cd $(DESTDIR)$(PREFIX)/$(LIBDIR_$(OS))/ ; ln -sf $(KRUN_BINARY_$(OS)) $(KRUN_SONAME_$(OS)) ; ln -sf $(KRUN_SONAME_$(OS)) $(KRUN_BASE_$(OS))
Expand Down
14 changes: 14 additions & 0 deletions examples/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file added examples/external_kernel
Binary file not shown.
1 change: 1 addition & 0 deletions examples/gui_vm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ edition = "2024"
[dependencies]
gtk_display = { path = "../krun_gtk_display" }
krun-sys = { path = "../../krun-sys" }
krun_input = { path = "../../src/krun_input" }
anyhow = "1.0.98"
clap = "4.5.39"
clap_derive = "4.5.32"
Expand Down
164 changes: 135 additions & 29 deletions examples/gui_vm/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
use clap::Parser;
use clap_derive::Parser;
use gtk_display::DisplayBackendHandle;
use gtk_display::{
Axis, DisplayBackendHandle, DisplayInputOptions, InputBackendHandle, TouchArea,
TouchScreenOptions,
};

use krun_sys::{
KRUN_LOG_LEVEL_TRACE, KRUN_LOG_LEVEL_WARN, KRUN_LOG_STYLE_ALWAYS, KRUN_LOG_TARGET_DEFAULT,
VIRGLRENDERER_RENDER_SERVER, VIRGLRENDERER_THREAD_SYNC, VIRGLRENDERER_USE_ASYNC_FENCE_CB,
VIRGLRENDERER_USE_EGL, VIRGLRENDERER_VENUS, krun_add_display, krun_create_ctx,
krun_display_set_dpi, krun_display_set_physical_size, krun_display_set_refresh_rate,
krun_set_display_backend, krun_set_exec, krun_set_gpu_options, krun_set_log_level,
krun_set_root, krun_start_enter,
VIRGLRENDERER_USE_EGL, VIRGLRENDERER_VENUS, krun_add_display, krun_add_input_device,
krun_add_input_device_fd, krun_create_ctx, krun_display_set_dpi,
krun_display_set_physical_size, krun_display_set_refresh_rate, krun_init_log,
krun_set_display_backend, krun_set_exec, krun_set_gpu_options2, krun_set_root,
krun_set_vm_config, krun_start_enter,
};
use log::LevelFilter;
use regex::{Captures, Regex};
use std::ffi::{CString, c_void};
use std::fmt::Display;
use std::fs::{File, OpenOptions};
use std::mem::size_of_val;

use anyhow::Context;
use std::os::fd::IntoRawFd;
use std::path::PathBuf;
use std::process::exit;
use std::ptr::null;
use std::str::FromStr;
Expand All @@ -32,19 +44,20 @@ struct DisplayArg {
height: u32,
refresh_rate: Option<u32>,
physical_size: Option<PhysicalSize>,
touch: bool,
}

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

let captures = RE.captures(display_string).ok_or_else(|| {
format!("Invalid display string '{display_string}' format. Examples of valid values:\n '1920x1080', '1920x1080@60', '1920x1080:162x91mm', '1920x1080:300dpi', '1920x1080@90:300dpi'")
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'")
})?;

fn parse_group<T: FromStr>(captures: &Captures, name: &str) -> Result<Option<T>, String>
Expand Down Expand Up @@ -78,48 +91,86 @@ fn parse_display(display_string: &str) -> Result<DisplayArg, String> {
(None, None, None) => None,
_ => unreachable!("regex bug"),
},
touch: captures.name("touch").is_some(),
})
}

#[derive(Parser, Debug)]
struct Args {
#[arg(long)]
root_dir: Option<CString>,
root_dir: CString,

executable: Option<CString>,
argv: Vec<CString>,

// Display specifications in the format WIDTHxHEIGHT[@FPS][:DPIdpi|:PHYSICAL_WIDTHxPHYSICAL_HEIGHTmm]
#[clap(long, value_parser = parse_display)]
display: Vec<DisplayArg>,

/// Attach a virtual keyboard input device
#[arg(long)]
keyboard_input: bool,

/// Pipe (or file) where to write log (with terminal color formatting)
#[arg(long)]
color_log: Option<PathBuf>,

/// Passthrough an input device (e.g. /dev/input/event0)
#[arg(long)]
input: Vec<PathBuf>,
}

fn krun_thread(args: &Args, display_backend_handle: DisplayBackendHandle) -> anyhow::Result<()> {
fn krun_thread(
args: &Args,
display_backend_handle: DisplayBackendHandle,
input_device_handles: Vec<InputBackendHandle>,
) -> anyhow::Result<()> {
unsafe {
krun_call!(krun_set_log_level(3))?;
if let Some(path) = &args.color_log {
krun_call!(krun_init_log(
OpenOptions::new()
.write(true)
.open(path)
.context("Failed to open log output")?
.into_raw_fd(),
KRUN_LOG_LEVEL_TRACE,
KRUN_LOG_STYLE_ALWAYS,
0
))?;
} else {
krun_call!(krun_init_log(
KRUN_LOG_TARGET_DEFAULT,
KRUN_LOG_LEVEL_WARN,
0,
0,
))?;
}

let ctx = krun_call_u32!(krun_create_ctx())?;

krun_call!(krun_set_gpu_options(
krun_call!(krun_set_vm_config(ctx, 4, 4096))?;

krun_call!(krun_set_gpu_options2(
ctx,
VIRGLRENDERER_USE_EGL
| VIRGLRENDERER_VENUS
| VIRGLRENDERER_RENDER_SERVER
| VIRGLRENDERER_THREAD_SYNC
| VIRGLRENDERER_USE_ASYNC_FENCE_CB
| VIRGLRENDERER_USE_ASYNC_FENCE_CB,
4096
))?;

if let Some(root_dir) = &args.root_dir {
krun_call!(krun_set_root(ctx, root_dir.as_ptr()))?;
// Executable variable should be set if we have root_dir, this is verified by clap
let executable = args.executable.as_ref().unwrap().as_ptr();
let argv: Vec<_> = args.argv.iter().map(|a| a.as_ptr()).collect();
let argv_ptr = if argv.is_empty() {
null()
} else {
argv.as_ptr()
};
let envp = [null()];
krun_call!(krun_set_exec(ctx, executable, argv_ptr, envp.as_ptr()))?;
}
krun_call!(krun_set_root(ctx, args.root_dir.as_ptr()))?;

let executable = args.executable.as_ref().unwrap().as_ptr();
let argv: Vec<_> = args.argv.iter().map(|a| a.as_ptr()).collect();
let argv_ptr = if argv.is_empty() {
null()
} else {
argv.as_ptr()
};
let envp = [null()];
krun_call!(krun_set_exec(ctx, executable, argv_ptr, envp.as_ptr()))?;

for display in &args.display {
let display_id = krun_call_u32!(krun_add_display(ctx, display.width, display.height))?;
Expand All @@ -144,21 +195,76 @@ fn krun_thread(args: &Args, display_backend_handle: DisplayBackendHandle) -> any
&raw const display_backend as *const c_void,
size_of_val(&display_backend),
))?;

for input in &args.input {
let fd = File::open(input)
.with_context(|| format!("Failed to open input device {input:?}"))?
.into_raw_fd();
krun_call!(krun_add_input_device_fd(ctx, fd))
.context("Failed to attach input device")?;
}

// Configure all input devices
for handle in &input_device_handles {
let config_backend = handle.get_config();
let event_provider_backend = handle.get_events();

krun_call!(krun_add_input_device(
ctx,
&raw const config_backend as *const c_void,
size_of_val(&config_backend),
&raw const event_provider_backend as *const c_void,
size_of_val(&event_provider_backend),
))?;
}

krun_call!(krun_start_enter(ctx))?;
};
Ok(())
}

fn main() -> anyhow::Result<()> {
env_logger::builder().filter_level(LevelFilter::Info).init();
env_logger::builder()
.filter_level(LevelFilter::Debug)
.init();
let args = Args::parse();

let (display_backend, display_worker) =
gtk_display::crate_display("libkrun examples/gui_vm".to_string());
let mut per_display_inputs = vec![vec![]; args.display.len()];
for (idx, display) in args.display.iter().enumerate() {
if display.touch {
per_display_inputs[idx].push(DisplayInputOptions::TouchScreen(TouchScreenOptions {
// There is no specific reason for these axis sizes, just picked what my
// physical hardware had
area: TouchArea {
x: Axis {
max: 13764,
res: 40,
fuzz: 40,
..Default::default()
},
y: Axis {
max: 7740,
res: 40,
fuzz: 40,
..Default::default()
},
},
emit_mt: true,
emit_non_mt: false,
triggered_by_mouse: true,
}));
}
}

let (display_backend, input_backends, display_worker) = gtk_display::init(
"libkrun examples/gui_vm".to_string(),
args.keyboard_input,
per_display_inputs,
)?;

thread::scope(|s| {
s.spawn(|| {
if let Err(e) = krun_thread(&args, display_backend) {
if let Err(e) = krun_thread(&args, display_backend, input_backends) {
eprintln!("{e}");
exit(1);
}
Expand Down
3 changes: 2 additions & 1 deletion examples/krun_gtk_display/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ edition = "2024"

[dependencies]
utils = { path = "../../src/utils" } # Our version of rust vmm-sys-util
crossbeam-channel = "0.5.15"
gtk = { version = "0.10", package = "gtk4", features = ["v4_16"] }
krun_display = { path = "../../src/krun_display" }
krun_input = { path = "../../src/krun_input" }
anyhow = "1.0.98"
log = "0.4.27"
libc = "0.2.174"
crossbeam-channel = "0.5.15"
Loading
Loading