@@ -4,31 +4,81 @@ use gtk_display::DisplayBackendHandle;
4
4
use krun_sys:: {
5
5
VIRGLRENDERER_RENDER_SERVER , VIRGLRENDERER_THREAD_SYNC , VIRGLRENDERER_USE_ASYNC_FENCE_CB ,
6
6
VIRGLRENDERER_USE_EGL , VIRGLRENDERER_VENUS , krun_add_display, krun_create_ctx,
7
+ krun_display_set_dpi, krun_display_set_physical_size, krun_display_set_refresh_rate,
7
8
krun_set_display_backend, krun_set_exec, krun_set_gpu_options, krun_set_log_level,
8
9
krun_set_root, krun_start_enter,
9
10
} ;
10
11
use log:: LevelFilter ;
12
+ use regex:: { Captures , Regex } ;
11
13
use std:: ffi:: { CString , c_void} ;
14
+ use std:: fmt:: Display ;
12
15
use std:: process:: exit;
13
16
use std:: ptr:: null;
17
+ use std:: str:: FromStr ;
18
+ use std:: sync:: LazyLock ;
14
19
use std:: thread;
15
20
16
21
mod krun_utils;
17
22
23
+ #[ derive( Debug , Copy , Clone ) ]
24
+ pub enum PhysicalSize {
25
+ Dpi ( u32 ) ,
26
+ DimensionsMillimeters ( u16 , u16 ) ,
27
+ }
28
+
18
29
#[ derive( Debug , Clone , Copy ) ]
19
30
struct DisplayArg {
20
31
width : u32 ,
21
32
height : u32 ,
33
+ refresh_rate : Option < u32 > ,
34
+ physical_size : Option < PhysicalSize > ,
22
35
}
23
36
24
- fn parse_display ( s : & str ) -> Result < DisplayArg , String > {
25
- let parts: Vec < & str > = s. split ( 'x' ) . collect ( ) ;
26
- if parts. len ( ) != 2 {
27
- return Err ( "Expected format: [width]x[height]" . to_string ( ) ) ;
37
+ /// Parses a display settings string.
38
+ /// The expected format is "WIDTHxHEIGHT[@FPS][:DPIdpi|:PHYSICAL_WIDTHxPHYSICAL_HEIGHTmm]".
39
+ fn parse_display ( display_string : & str ) -> Result < DisplayArg , String > {
40
+ static RE : LazyLock < Regex > = LazyLock :: new ( || {
41
+ 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)?$" ,
43
+ ) . unwrap ( )
44
+ } ) ;
45
+
46
+ let captures = RE . captures ( display_string) . ok_or_else ( || {
47
+ format ! ( "Invalid display string '{s}' format. Examples of valid values:\n '1920x1080', '1920x1080@60', '1920x1080:162x91mm', '1920x1080:300dpi', '1920x1080@90:300dpi'" )
48
+ } ) ?;
49
+
50
+ fn parse_group < T : FromStr > ( captures : & Captures , name : & str ) -> Result < Option < T > , String >
51
+ where
52
+ T :: Err : Display ,
53
+ {
54
+ captures
55
+ . name ( name)
56
+ . map ( |match_| {
57
+ match_
58
+ . as_str ( )
59
+ . parse :: < T > ( )
60
+ . map_err ( |e| format ! ( "Failed to parse {name}: {e}" ) )
61
+ } )
62
+ . transpose ( )
28
63
}
29
- let width = parts[ 0 ] . parse ( ) . map_err ( |_| "Invalid width" ) ?;
30
- let height = parts[ 1 ] . parse ( ) . map_err ( |_| "Invalid height" ) ?;
31
- Ok ( DisplayArg { width, height } )
64
+
65
+ Ok ( DisplayArg {
66
+ width : parse_group ( & captures, "width" ) ?. expect ( "regex bug" ) ,
67
+ height : parse_group ( & captures, "height" ) ?. expect ( "regex bug" ) ,
68
+ refresh_rate : parse_group ( & captures, "refresh_rate" ) ?,
69
+ physical_size : match (
70
+ parse_group ( & captures, "dpi" ) ?,
71
+ parse_group ( & captures, "width_mm" ) ?,
72
+ parse_group ( & captures, "height_mm" ) ?,
73
+ ) {
74
+ ( Some ( dpi) , None , None ) => Some ( PhysicalSize :: Dpi ( dpi) ) ,
75
+ ( None , Some ( width_mm) , Some ( height_mm) ) => {
76
+ Some ( PhysicalSize :: DimensionsMillimeters ( width_mm, height_mm) )
77
+ }
78
+ ( None , None , None ) => None ,
79
+ _ => unreachable ! ( "regex bug" ) ,
80
+ } ,
81
+ } )
32
82
}
33
83
34
84
#[ derive( Parser , Debug ) ]
@@ -38,6 +88,7 @@ struct Args {
38
88
39
89
executable : Option < CString > ,
40
90
argv : Vec < CString > ,
91
+ // Display specifications in the format WIDTHxHEIGHT[@FPS][:DPIdpi|:PHYSICAL_WIDTHxPHYSICAL_HEIGHTmm]
41
92
#[ clap( long, value_parser = parse_display) ]
42
93
display : Vec < DisplayArg > ,
43
94
}
@@ -71,7 +122,21 @@ fn krun_thread(args: &Args, display_backend_handle: DisplayBackendHandle) -> any
71
122
}
72
123
73
124
for display in & args. display {
74
- krun_call ! ( krun_add_display( ctx, display. width, display. height) ) ?;
125
+ let display_id = krun_call_u32 ! ( krun_add_display( ctx, display. width, display. height) ) ?;
126
+ if let Some ( refresh_rate) = display. refresh_rate {
127
+ krun_call ! ( krun_display_set_refresh_rate( ctx, display_id, refresh_rate) ) ?;
128
+ }
129
+ match display. physical_size {
130
+ None => ( ) ,
131
+ Some ( PhysicalSize :: Dpi ( dpi) ) => {
132
+ krun_call ! ( krun_display_set_dpi( ctx, display_id, dpi) ) ?;
133
+ }
134
+ Some ( PhysicalSize :: DimensionsMillimeters ( width_mm, height_mm) ) => {
135
+ krun_call ! ( krun_display_set_physical_size(
136
+ ctx, display_id, width_mm, height_mm
137
+ ) ) ?;
138
+ }
139
+ } ;
75
140
}
76
141
let display_backend = display_backend_handle. get ( ) ;
77
142
krun_call ! ( krun_set_display_backend(
0 commit comments