@@ -18,13 +18,21 @@ struct CreateWindowParams {
1818 html : Option < String > ,
1919 width : Option < u32 > ,
2020 height : Option < u32 > ,
21+ x : Option < i32 > ,
22+ y : Option < i32 > ,
23+ show : Option < bool > ,
24+ min_width : Option < u32 > ,
25+ min_height : Option < u32 > ,
26+ max_width : Option < u32 > ,
27+ max_height : Option < u32 > ,
2128 icon_path : Option < String > ,
2229 resizable : Option < bool > ,
2330 always_on_top : Option < bool > ,
2431 fullscreen : Option < bool > ,
2532 decorations : Option < bool > ,
2633 center : Option < bool > ,
2734 preload : Option < String > ,
35+ content_size : Option < bool > ,
2836}
2937
3038pub fn create_window_with_target (
@@ -34,26 +42,54 @@ pub fn create_window_with_target(
3442) -> Result < Value > {
3543 let p: CreateWindowParams = serde_json:: from_value ( params) ?;
3644
45+ // Pre-generate window id so we can capture it in callbacks
46+ let id = Uuid :: new_v4 ( ) . to_string ( ) ;
47+
3748 // Build tao window
3849 let mut wb = WindowBuilder :: new ( ) ;
3950 if let Some ( title) = p. title { wb = wb. with_title ( title) ; }
4051 if let ( Some ( w) , Some ( h) ) = ( p. width , p. height ) { wb = wb. with_inner_size ( tao:: dpi:: LogicalSize :: new ( w as f64 , h as f64 ) ) ; }
52+ if let ( Some ( x) , Some ( y) ) = ( p. x , p. y ) { wb = wb. with_position ( tao:: dpi:: LogicalPosition :: new ( x as f64 , y as f64 ) ) ; }
53+ // Combine min/max sizes if provided
54+ if p. min_width . is_some ( ) || p. min_height . is_some ( ) {
55+ let mw = p. min_width . unwrap_or ( 0 ) as f64 ;
56+ let mh = p. min_height . unwrap_or ( 0 ) as f64 ;
57+ wb = wb. with_min_inner_size ( tao:: dpi:: LogicalSize :: new ( mw, mh) ) ;
58+ }
59+ if p. max_width . is_some ( ) || p. max_height . is_some ( ) {
60+ let mw = p. max_width . unwrap_or ( u32:: MAX ) as f64 ;
61+ let mh = p. max_height . unwrap_or ( u32:: MAX ) as f64 ;
62+ wb = wb. with_max_inner_size ( tao:: dpi:: LogicalSize :: new ( mw, mh) ) ;
63+ }
4164 if let Some ( icon_path) = p. icon_path . as_deref ( ) { if let Ok ( icon) = load_icon ( icon_path) { wb = wb. with_window_icon ( Some ( icon) ) ; } }
4265 if let Some ( v) = p. resizable { wb = wb. with_resizable ( v) ; }
4366 if let Some ( v) = p. always_on_top { wb = wb. with_always_on_top ( v) ; }
4467 if let Some ( v) = p. fullscreen { if v { wb = wb. with_fullscreen ( Some ( tao:: window:: Fullscreen :: Borderless ( None ) ) ) ; } }
4568 if let Some ( v) = p. decorations { wb = wb. with_decorations ( v) ; }
4669
4770 let window = wb. build ( target) ?;
71+ if p. content_size . unwrap_or ( false ) {
72+ // noop placeholder: tao/wry works with inner size already
73+ }
4874
4975 // Build webview
5076 let mut wvb = WebViewBuilder :: new ( ) ;
5177 if let Some ( script) = p. preload . as_deref ( ) { wvb = wvb. with_initialization_script ( script) ; }
5278 if let Some ( url) = p. url { wvb = wvb. with_url ( & url) ; }
5379 if let Some ( html) = p. html { wvb = wvb. with_html ( & html) ; }
54- let webview = wvb. build ( & window) ?;
80+ let win_id_for_ipc = id. clone ( ) ;
81+ let webview = wvb. with_ipc_handler ( {
82+ let tx = app. tx_out . clone ( ) ;
83+ move |request : wry:: http:: Request < String > | {
84+ let body = request. body ( ) ;
85+ let payload = serde_json:: from_str :: < serde_json:: Value > ( body) . unwrap_or ( json ! ( { "raw" : body } ) ) ;
86+ let _ = tx. send ( RpcResponse :: notify ( "webview.ipc" , json ! ( { "windowId" : win_id_for_ipc, "payload" : payload } ) ) ) ;
87+ }
88+ } ) . build ( & window) ?;
89+
90+ // Show window depending on flag (default true) BEFORE moving window
91+ if p. show . unwrap_or ( true ) { window. set_visible ( true ) ; } else { window. set_visible ( false ) ; }
5592
56- let id = Uuid :: new_v4 ( ) . to_string ( ) ;
5793 app. windows . insert ( id. clone ( ) , window) ;
5894 app. webviews . insert ( id. clone ( ) , webview) ;
5995
@@ -62,6 +98,8 @@ pub fn create_window_with_target(
6298 let _ = op_center ( app, json ! ( { "windowId" : id. clone( ) } ) , RpcId :: Null ) ;
6399 }
64100
101+ // Visibility already set above
102+
65103 Ok ( json ! ( { "windowId" : id } ) )
66104}
67105
@@ -232,6 +270,43 @@ pub fn op_get_position(app: &mut App, params: Value, id: RpcId) {
232270 }
233271}
234272
273+ #[ derive( Debug , Deserialize ) ]
274+ #[ serde( rename_all = "camelCase" ) ]
275+ struct BoundsParams { window_id : String , x : Option < i32 > , y : Option < i32 > , width : Option < u32 > , height : Option < u32 > }
276+
277+ pub fn op_set_bounds ( app : & mut App , params : Value , id : RpcId ) {
278+ match serde_json:: from_value :: < BoundsParams > ( params) {
279+ Ok ( p) => {
280+ if let Some ( win) = app. windows . get ( & p. window_id ) {
281+ use tao:: dpi:: { PhysicalPosition , PhysicalSize } ;
282+ if let ( Some ( x) , Some ( y) ) = ( p. x , p. y ) { win. set_outer_position ( PhysicalPosition :: new ( x, y) ) ; }
283+ if let ( Some ( w) , Some ( h) ) = ( p. width , p. height ) { win. set_inner_size ( PhysicalSize :: new ( w, h) ) ; }
284+ let _ = app. tx_out . send ( RpcResponse :: result ( id, json ! ( true ) ) ) ;
285+ } else { let _ = app. tx_out . send ( RpcResponse :: error ( id, -32001 , "Window not found" . into ( ) ) ) ; }
286+ }
287+ Err ( e) => { let _ = app. tx_out . send ( RpcResponse :: error ( id, -32602 , e. to_string ( ) ) ) ; }
288+ }
289+ }
290+
291+ pub fn op_get_bounds ( app : & mut App , params : Value , id : RpcId ) {
292+ match serde_json:: from_value :: < WithWindowIdOnly > ( params) {
293+ Ok ( p) => {
294+ if let Some ( win) = app. windows . get ( & p. window_id ) {
295+ let pos = win. outer_position ( ) . ok ( ) ;
296+ let size = win. inner_size ( ) ;
297+ let res = json ! ( {
298+ "x" : pos. as_ref( ) . map( |p| p. x) ,
299+ "y" : pos. as_ref( ) . map( |p| p. y) ,
300+ "width" : size. width,
301+ "height" : size. height,
302+ } ) ;
303+ let _ = app. tx_out . send ( RpcResponse :: result ( id, res) ) ;
304+ } else { let _ = app. tx_out . send ( RpcResponse :: error ( id, -32001 , "Window not found" . into ( ) ) ) ; }
305+ }
306+ Err ( e) => { let _ = app. tx_out . send ( RpcResponse :: error ( id, -32602 , e. to_string ( ) ) ) ; }
307+ }
308+ }
309+
235310#[ derive( Debug , Deserialize ) ]
236311#[ serde( rename_all = "camelCase" ) ]
237312struct PostMessageParams { window_id : String , payload : serde_json:: Value }
@@ -311,6 +386,9 @@ pub fn op_get_size(app: &mut App, params: Value, id: RpcId) {
311386pub fn op_maximize ( app : & mut App , params : Value , id : RpcId ) { with_window ( app, params, id, |w| { w. set_maximized ( true ) ; Ok ( json ! ( true ) ) } ) ; }
312387pub fn op_minimize ( app : & mut App , params : Value , id : RpcId ) { with_window ( app, params, id, |w| { w. set_minimized ( true ) ; Ok ( json ! ( true ) ) } ) ; }
313388pub fn op_unminimize ( app : & mut App , params : Value , id : RpcId ) { with_window ( app, params, id, |w| { w. set_minimized ( false ) ; Ok ( json ! ( true ) ) } ) ; }
389+ pub fn op_unmaximize ( app : & mut App , params : Value , id : RpcId ) { with_window ( app, params, id, |w| { w. set_maximized ( false ) ; Ok ( json ! ( true ) ) } ) ; }
390+ pub fn op_is_maximized ( app : & mut App , params : Value , id : RpcId ) { with_window ( app, params, id, |w| { Ok ( json ! ( w. is_maximized( ) ) ) } ) ; }
391+ pub fn op_restore ( app : & mut App , params : Value , id : RpcId ) { with_window ( app, params, id, |w| { w. set_minimized ( false ) ; w. set_maximized ( false ) ; Ok ( json ! ( true ) ) } ) ; }
314392pub fn op_focus ( app : & mut App , params : Value , id : RpcId ) { with_window ( app, params, id, |w| { w. set_focus ( ) ; Ok ( json ! ( true ) ) } ) ; }
315393pub fn op_center ( app : & mut App , params : Value , id : RpcId ) { with_window ( app, params, id, |w| {
316394 use tao:: dpi:: { PhysicalPosition } ;
@@ -328,6 +406,87 @@ pub fn op_set_always_on_top(app: &mut App, params: Value, id: RpcId) { with_wind
328406pub fn op_set_resizable ( app : & mut App , params : Value , id : RpcId ) { with_window_bool ( app, params, id, |w, v| { w. set_resizable ( v) ; Ok ( json ! ( true ) ) } ) ; }
329407pub fn op_is_visible ( app : & mut App , params : Value , id : RpcId ) { with_window ( app, params, id, |w| { Ok ( json ! ( w. is_visible( ) ) ) } ) ; }
330408
409+ #[ derive( Debug , Deserialize ) ]
410+ #[ serde( rename_all = "camelCase" ) ]
411+ struct SizeOnlyParams { window_id : String , width : u32 , height : u32 }
412+
413+ pub fn op_set_min_size ( app : & mut App , params : Value , id : RpcId ) {
414+ match serde_json:: from_value :: < SizeOnlyParams > ( params) {
415+ Ok ( p) => {
416+ if let Some ( win) = app. windows . get ( & p. window_id ) {
417+ use tao:: dpi:: PhysicalSize ;
418+ win. set_min_inner_size ( Some ( PhysicalSize :: new ( p. width , p. height ) ) ) ;
419+ let _ = app. tx_out . send ( RpcResponse :: result ( id, json ! ( true ) ) ) ;
420+ } else { let _ = app. tx_out . send ( RpcResponse :: error ( id, -32001 , "Window not found" . into ( ) ) ) ; }
421+ }
422+ Err ( e) => { let _ = app. tx_out . send ( RpcResponse :: error ( id, -32602 , e. to_string ( ) ) ) ; }
423+ }
424+ }
425+
426+ pub fn op_set_max_size ( app : & mut App , params : Value , id : RpcId ) {
427+ match serde_json:: from_value :: < SizeOnlyParams > ( params) {
428+ Ok ( p) => {
429+ if let Some ( win) = app. windows . get ( & p. window_id ) {
430+ use tao:: dpi:: PhysicalSize ;
431+ win. set_max_inner_size ( Some ( PhysicalSize :: new ( p. width , p. height ) ) ) ;
432+ let _ = app. tx_out . send ( RpcResponse :: result ( id, json ! ( true ) ) ) ;
433+ } else { let _ = app. tx_out . send ( RpcResponse :: error ( id, -32001 , "Window not found" . into ( ) ) ) ; }
434+ }
435+ Err ( e) => { let _ = app. tx_out . send ( RpcResponse :: error ( id, -32602 , e. to_string ( ) ) ) ; }
436+ }
437+ }
438+
439+ #[ derive( Debug , Deserialize ) ]
440+ #[ serde( rename_all = "camelCase" ) ]
441+ struct AttentionParams { window_id : String , critical : Option < bool > }
442+
443+ pub fn op_request_user_attention ( app : & mut App , params : Value , id : RpcId ) {
444+ match serde_json:: from_value :: < AttentionParams > ( params) {
445+ Ok ( p) => {
446+ if let Some ( win) = app. windows . get ( & p. window_id ) {
447+ let demand = if p. critical . unwrap_or ( false ) { tao:: window:: UserAttentionType :: Critical } else { tao:: window:: UserAttentionType :: Informational } ;
448+ win. request_user_attention ( Some ( demand) ) ;
449+ let _ = app. tx_out . send ( RpcResponse :: result ( id, json ! ( true ) ) ) ;
450+ } else { let _ = app. tx_out . send ( RpcResponse :: error ( id, -32001 , "Window not found" . into ( ) ) ) ; }
451+ }
452+ Err ( e) => { let _ = app. tx_out . send ( RpcResponse :: error ( id, -32602 , e. to_string ( ) ) ) ; }
453+ }
454+ }
455+
456+ #[ derive( Debug , Deserialize ) ]
457+ #[ serde( rename_all = "camelCase" ) ]
458+ struct ScreenshotParams { window_id : String }
459+
460+ pub fn op_screenshot ( app : & mut App , params : Value , id : RpcId ) {
461+ match serde_json:: from_value :: < ScreenshotParams > ( params) {
462+ Ok ( p) => {
463+ let r = ( || -> anyhow:: Result < String > {
464+ // Best-effort: capture primary screen for now
465+ let screens: Vec < screenshots:: Screen > = screenshots:: Screen :: all ( ) ?;
466+ let mut iter = screens. into_iter ( ) ;
467+ let screen: screenshots:: Screen = iter. next ( ) . ok_or ( anyhow ! ( "No screen" ) ) ?;
468+ let img = screen. capture ( ) ?; // ImageBuffer RGBA
469+ let ( w, h) = ( img. width ( ) , img. height ( ) ) ;
470+ let mut buf = Vec :: new ( ) ;
471+ {
472+ use image:: codecs:: png:: PngEncoder ;
473+ use image:: ExtendedColorType ;
474+ use image:: ImageEncoder ;
475+ let enc = PngEncoder :: new ( & mut buf) ;
476+ enc. write_image ( img. as_raw ( ) , w, h, ExtendedColorType :: Rgba8 ) ?;
477+ }
478+ use base64:: Engine as _;
479+ Ok ( base64:: engine:: general_purpose:: STANDARD . encode ( & buf) )
480+ } ) ( ) ;
481+ match r {
482+ Ok ( b64) => { let _ = app. tx_out . send ( RpcResponse :: result ( id, json ! ( { "base64Png" : b64} ) ) ) ; }
483+ Err ( e) => { let _ = app. tx_out . send ( RpcResponse :: error ( id, -33010 , e. to_string ( ) ) ) ; }
484+ }
485+ }
486+ Err ( e) => { let _ = app. tx_out . send ( RpcResponse :: error ( id, -32602 , e. to_string ( ) ) ) ; }
487+ }
488+ }
489+
331490#[ derive( Debug , Deserialize ) ]
332491#[ serde( rename_all = "camelCase" ) ]
333492struct WithWindowIdBool { window_id : String , value : bool }
0 commit comments