sciter/
host.rs

1//! Sciter host application helpers.
2
3use ::{_API};
4use capi::scdef::SCITER_RT_OPTIONS;
5use capi::sctypes::*;
6use capi::screquest::HREQUEST;
7use capi::schandler::NativeHandler;
8use dom::{self, event::EventHandler};
9use eventhandler::*;
10use value::{Value};
11
12pub use capi::scdef::{LOAD_RESULT, OUTPUT_SUBSYTEMS, OUTPUT_SEVERITY};
13pub use capi::scdef::{SCN_LOAD_DATA, SCN_DATA_LOADED, SCN_ATTACH_BEHAVIOR, SCN_INVALIDATE_RECT};
14
15
16/// A specialized `Result` type for Sciter host operations.
17pub type Result<T> = ::std::result::Result<T, ()>;
18
19macro_rules! ok_or {
20	($ok:ident) => {
21		if $ok != 0 {
22			Ok(())
23		} else {
24			Err(())
25		}
26	};
27
28	($ok:ident, $rv:expr) => {
29		if $ok != 0 {
30			Ok($rv)
31		} else {
32			Err(())
33		}
34	};
35
36	($ok:ident, $rv:expr, $err:expr) => {
37		if $ok != 0 {
38			Ok($rv)
39		} else {
40			Err($err)
41		}
42	};
43}
44
45
46/** Sciter notification handler for [`Window.sciter_handler()`](../window/struct.Window.html#method.sciter_handler).
47
48## Resource handling and custom resource loader
49
50HTML loaded into Sciter may contain external resources: CSS (Cascading Style Sheets),
51images, fonts, cursors and scripts.
52To get any of such resources Sciter will first send a `on_data_load(SCN_LOAD_DATA)` notification
53to your application using the callback handler registered with `sciter::Window.sciter_handler()` function.
54
55Your application can provide your own data for such resources
56(for example, from the resource section, database or other storage of your choice)
57or delegate the resource loading to the built-in HTTP client or file loader, or discard the loading at all.
58
59Note: This handler should be registered before any
60[`load_html()`](struct.Host.html#method.load_html) or
61[`load_file()`](struct.Host.html#method.load_file) calls
62in order to send notifications while loading.
63
64*/
65#[allow(unused_variables)]
66pub trait HostHandler {
67
68	/// Notifies that Sciter is about to download the referred resource.
69	///
70	/// You can load or overload data immediately by calling `self.data_ready()` with parameters provided by `SCN_LOAD_DATA`,
71	/// or save them (including `request_id`) for later usage and answer here with `LOAD_RESULT::LOAD_DELAYED` code.
72	///
73	/// Also you can discard the request (data will not be loaded at the document)
74	/// or take care about this request completely by yourself (via the [request API](../request/index.html)).
75	fn on_data_load(&mut self, pnm: &mut SCN_LOAD_DATA) -> Option<LOAD_RESULT> { return None; }
76
77	/// This notification indicates that external data (for example, image) download process completed.
78	fn on_data_loaded(&mut self, pnm: &SCN_DATA_LOADED) { }
79
80	/// This notification is sent on parsing the document and while processing elements
81	/// having non empty `behavior: ` style attribute value.
82	fn on_attach_behavior(&mut self, pnm: &mut SCN_ATTACH_BEHAVIOR) -> bool { return false; }
83
84	/// This notification is sent when instance of the engine is destroyed.
85	fn on_engine_destroyed(&mut self) { }
86
87	/// This notification is sent when the engine encounters critical rendering error: e.g. DirectX gfx driver error.
88  /// Most probably bad gfx drivers.
89	fn on_graphics_critical_failure(&mut self) { }
90
91	/// This notification is sent when the engine needs some area to be redrawn.
92	fn on_invalidate(&mut self, pnm: &SCN_INVALIDATE_RECT) {}
93
94	/// This output function will be used for reporting problems found while loading html and css documents.
95	fn on_debug_output(&mut self, subsystem: OUTPUT_SUBSYTEMS, severity: OUTPUT_SEVERITY, message: &str) {
96		if !message.is_empty() {
97			if severity == OUTPUT_SEVERITY::INFO {
98				// e.g. `stdout.println` in TIScript
99				println!("{:?}:{:?}: {}", severity, subsystem, message);
100			} else {
101				// e.g. `stderr.println` or CSS/script errors and warnings.
102				eprintln!("{:?}:{:?}: {}", severity, subsystem, message);
103			}
104		}
105	}
106
107	/// This function is used as response to [`on_data_load`](#method.on_data_load) request.
108	///
109	/// Parameters here must be taken from [`SCN_LOAD_DATA`](struct.SCN_LOAD_DATA.html) structure. You can store them for later usage,
110	/// but you must answer as [`LOAD_DELAYED`](enum.LOAD_RESULT.html#variant.LOAD_DELAYED) code and provide an `request_id` here.
111	fn data_ready(&self, hwnd: HWINDOW, uri: &str, data: &[u8], request_id: Option<HREQUEST>) {
112		let s = s2w!(uri);
113		match request_id {
114			Some(req) => {
115				(_API.SciterDataReadyAsync)(hwnd, s.as_ptr(), data.as_ptr(), data.len() as UINT, req)
116			},
117			None => {
118				(_API.SciterDataReady)(hwnd, s.as_ptr(), data.as_ptr(), data.len() as UINT)
119			},
120		};
121	}
122
123  /// This function is used as a response to the [`on_attach_behavior`](#method.on_attach_behavior) request
124  /// to attach a newly created behavior `handler` to the requested element.
125	fn attach_behavior<Handler: EventHandler>(&self, pnm: &mut SCN_ATTACH_BEHAVIOR, handler: Handler) {
126		// make native handler
127		let boxed = Box::new(handler);
128		let ptr = Box::into_raw(boxed);	// dropped in `_event_handler_proc`
129		pnm.elementProc = ::eventhandler::_event_handler_proc::<Handler>;
130		pnm.elementTag = ptr as LPVOID;
131	}
132}
133
134
135/// Default `HostHandler` implementation
136#[derive(Default)]
137struct DefaultHandler;
138
139/// Default `HostHandler` implementation
140impl HostHandler for DefaultHandler {
141
142}
143
144use std::rc::Rc;
145use std::cell::RefCell;
146
147type BehaviorList = Vec<(String, Box<dyn Fn() -> Box<dyn EventHandler>>)>;
148type SharedBehaviorList = Rc<RefCell<BehaviorList>>;
149type SharedArchive = Rc<RefCell<Option<Archive>>>;
150
151#[repr(C)]
152struct HostCallback<Callback> {
153	sig: u32,
154	behaviors: SharedBehaviorList,
155	handler: Callback,
156  archive: SharedArchive,
157}
158
159/// Sciter host runtime support.
160pub struct Host {
161	hwnd: HWINDOW,
162	behaviors: SharedBehaviorList,
163	handler: RefCell<NativeHandler>,
164  archive: SharedArchive,
165}
166
167impl Host {
168
169	/// Attach Sciter host to an existing window.
170	///
171	/// Usually Sciter window is created by [`sciter::Window::create()`](../window/struct.Window.html#method.create),
172	/// but you can attach Sciter to an existing native window.
173	/// In this case you need [to mix-in](https://sciter.com/developers/embedding-principles/)
174	/// window events processing with `SciterProcND` (Windows only).
175	/// Sciter engine will be initialized either on `WM_CREATE` or `WM_INITDIALOG` response
176	/// or by calling `SciterCreateOnDirectXWindow` (again, Windows only).
177	pub fn attach(hwnd: HWINDOW) -> Host {
178		// Host with default debug handler installed
179		let host = Host {
180      hwnd,
181      behaviors: Default::default(),
182      handler: Default::default(),
183      archive: Default::default(),
184    };
185		host.setup_callback(DefaultHandler::default());
186		return host;
187	}
188
189	/// Attach Sciter host to an existing window with the given `Host` handler.
190	pub fn attach_with<Handler: HostHandler>(hwnd: HWINDOW, handler: Handler) -> Host {
191	  let host = Host {
192      hwnd,
193      behaviors: Default::default(),
194      handler: Default::default(),
195      archive: Default::default(),
196    };
197	  host.setup_callback(handler);
198	  return host;
199	}
200
201	/// Attach [`dom::EventHandler`](../dom/event/trait.EventHandler.html) to the Sciter window.
202	pub fn event_handler<Handler: EventHandler>(&self, handler: Handler) {
203		self.attach_handler(handler)
204	}
205
206	/// Attach [`dom::EventHandler`](../dom/event/trait.EventHandler.html) to the Sciter window.
207	#[doc(hidden)]
208	pub fn attach_handler<Handler: EventHandler>(&self, handler: Handler) {
209		let hwnd = self.get_hwnd();
210		let boxed = Box::new( WindowHandler { hwnd, handler } );
211		let ptr = Box::into_raw(boxed);	// dropped in `_event_handler_window_proc`
212		// eprintln!("{}: {:?}", std::any::type_name::<Handler>(), ptr);
213
214		let func = _event_handler_window_proc::<Handler>;
215		let flags = dom::event::default_events();
216		(_API.SciterWindowAttachEventHandler)(hwnd, func, ptr as LPVOID, flags as UINT);
217	}
218
219	/// Set callback for Sciter engine events.
220	pub(crate) fn setup_callback<Callback: HostHandler>(&self, handler: Callback) {
221
222		let payload: HostCallback<Callback> = HostCallback {
223			sig: 17,
224			behaviors: Rc::clone(&self.behaviors),
225      archive: Rc::clone(&self.archive),
226			handler: handler,
227		};
228
229		*self.handler.borrow_mut() = NativeHandler::from(payload);
230		let ptr = self.handler.borrow().as_mut_ptr();
231
232		(_API.SciterSetCallback)(self.get_hwnd(), _on_handle_notification::<Callback>, ptr);
233		(_API.SciterSetupDebugOutput)(0 as HWINDOW, ptr, _on_debug_notification::<Callback>);
234	}
235
236	/// Register a native event handler for the specified behavior name.
237	///
238	/// See the [`Window::register_behavior`](../window/struct.Window.html#method.register_behavior) for an example.
239	pub fn register_behavior<Factory>(&self, name: &str, factory: Factory)
240	where
241		Factory: Fn() -> Box<dyn EventHandler> + 'static
242	{
243		let make: Box<dyn Fn() -> Box<dyn EventHandler>> = Box::new(factory);
244		let pair = (name.to_owned(), make);
245		self.behaviors.borrow_mut().push(pair);
246	}
247
248  /// Register an archive produced by `packfolder`.
249  ///
250  /// See documentation of the [`Archive`](struct.Archive.html).
251  pub fn register_archive(&self, resource: &[u8]) -> Result<()> {
252    *self.archive.borrow_mut() = Some(Archive::open(resource)?);
253    Ok(())
254  }
255
256	/// Set debug mode for this window.
257	pub fn enable_debug(&self, enable: bool) {
258		(_API.SciterSetOption)(self.hwnd, SCITER_RT_OPTIONS::SCITER_SET_DEBUG_MODE, enable as UINT_PTR);
259	}
260
261	/// Get native window handle.
262	pub fn get_hwnd(&self) -> HWINDOW {
263		self.hwnd
264	}
265
266	/// Get window root DOM element.
267	pub fn get_root(&self) -> Option<dom::Element> {
268		dom::Element::from_window(self.hwnd).ok()
269	}
270
271	/// Load an HTML document from file.
272	pub fn load_file(&self, uri: &str) -> bool {
273		// TODO: it should be `Result<()>` instead `bool`
274		let s = s2w!(uri);
275		(_API.SciterLoadFile)(self.hwnd, s.as_ptr()) != 0
276	}
277
278	/// Load an HTML document from memory.
279	pub fn load_html(&self, html: &[u8], uri: Option<&str>) -> bool {
280		match uri {
281			Some(uri) => {
282				let s = s2w!(uri);
283				(_API.SciterLoadHtml)(self.hwnd, html.as_ptr(), html.len() as UINT, s.as_ptr()) != 0
284			},
285			None => {
286				(_API.SciterLoadHtml)(self.hwnd, html.as_ptr(), html.len() as UINT, 0 as LPCWSTR) != 0
287			}
288		}
289	}
290
291	/// This function is used as response to [`HostHandler::on_data_load`](trait.HostHandler.html#method.on_data_load) request.
292	pub fn data_ready(&self, uri: &str, data: &[u8]) {
293		let s = s2w!(uri);
294		(_API.SciterDataReady)(self.hwnd, s.as_ptr(), data.as_ptr(), data.len() as UINT);
295	}
296
297	/// Use this function outside of [`HostHandler::on_data_load`](trait.HostHandler.html#method.on_data_load) request.
298	///
299	/// It can be used for two purposes:
300	///
301	/// 1. Asynchronious resource loading in respect of [`on_data_load`](trait.HostHandler.html#method.on_data_load)
302	/// requests (you must use `request_id` in this case).
303	/// 2. Refresh of an already loaded resource (for example, dynamic image updates).
304	pub fn data_ready_async(&self, uri: &str, data: &[u8], request_id: Option<HREQUEST>) {
305		let s = s2w!(uri);
306		let req = request_id.unwrap_or(::std::ptr::null_mut());
307		(_API.SciterDataReadyAsync)(self.hwnd, s.as_ptr(), data.as_ptr(), data.len() as UINT, req);
308	}
309
310	/// Evaluate the given script in context of the current document.
311	///
312	/// This function returns `Result<Value,Value>` with script function result value or with Sciter script error.
313	pub fn eval_script(&self, script: &str) -> ::std::result::Result<Value, Value> {
314		let (s,n) = s2wn!(script);
315		let mut rv = Value::new();
316		let ok = (_API.SciterEval)(self.hwnd, s.as_ptr(), n, rv.as_ptr());
317		ok_or!(ok, rv, rv)
318	}
319
320	/// Call a script function defined in the global namespace.
321	///
322	/// This function returns `Result<Value,Value>` with script function result value or with Sciter script error.
323	///
324	/// You can use the [`&make_args!(args...)`](../macro.make_args.html) macro which helps you
325	/// to construct script arguments from Rust types.
326	pub fn call_function(&self, name: &str, args: &[Value]) -> ::std::result::Result<Value, Value> {
327		let mut rv = Value::new();
328		let s = s2u!(name);
329		let argv = Value::pack_args(args);
330		let ok = (_API.SciterCall)(self.hwnd, s.as_ptr(), argv.len() as UINT, argv.as_ptr(), rv.as_ptr());
331		ok_or!(ok, rv, rv)
332	}
333
334	/// Set home url for Sciter resources.
335	///
336	/// If you set it like `set_home_url("https://sciter.com/modules/")` then
337	///
338	///  `<script src="sciter:lib/root-extender.tis">` will load
339	///  root-extender.tis from
340	///
341	/// `https://sciter.com/modules/lib/root-extender.tis`.
342	pub fn set_home_url(&self, url: &str) -> Result<()> {
343		let s = s2w!(url);
344		let ok = (_API.SciterSetHomeURL)(self.hwnd, s.as_ptr());
345		ok_or!(ok)
346	}
347
348	/// Set media type of this Sciter instance.
349	///
350	/// For example, media type can be "handheld", "projection", "screen", "screen-hires", etc.
351	/// By default, Sciter window has the `"screen"` media type.
352	///
353	/// Media type name is used while loading and parsing style sheets in the engine,
354	/// so you should call this function **before** loading document in it.
355	///
356	pub fn set_media_type(&self, media_type: &str) -> Result<()> {
357		let s = s2w!(media_type);
358		let ok = (_API.SciterSetMediaType)(self.hwnd, s.as_ptr());
359		ok_or!(ok)
360	}
361
362	/// Set media variables (dictionary) for this Sciter instance.
363	///
364	/// By default Sciter window has `"screen:true"` and `"desktop:true"/"handheld:true"` media variables.
365	///
366	/// Media variables can be changed in runtime. This will cause styles of the document to be reset.
367	///
368	/// ## Example
369	///
370	/// ```rust,no_run
371	/// # use sciter::vmap;
372	/// # let mut host = sciter::Host::attach(0 as sciter::types::HWINDOW);
373	/// host.set_media_vars( &vmap! {
374	///   "screen" => true,
375	///   "handheld" => true,
376	/// }).unwrap();
377	/// ```
378	pub fn set_media_vars(&self, media: &Value) -> Result<()> {
379		let ok = (_API.SciterSetMediaVars)(self.hwnd, media.as_cptr());
380		ok_or!(ok)
381	}
382
383	/// Set or append the [master](https://sciter.com/css-extensions-in-h-smile-engine-part-i-style-sets/)
384	/// style sheet styles (**globally**, for all windows).
385	pub fn set_master_css(&self, css: &str, append: bool) -> Result<()> {
386		let s = s2u!(css);
387		let b = s.as_bytes();
388		let n = b.len() as UINT;
389		let ok = if append {
390			(_API.SciterAppendMasterCSS)(b.as_ptr(), n)
391		} else {
392			(_API.SciterSetMasterCSS)(b.as_ptr(), n)
393		};
394		ok_or!(ok)
395	}
396
397	/// Set (reset) style sheet of the **current** document.
398	///
399	/// Will reset styles for all elements according to given CSS.
400	pub fn set_window_css(&self, css: &str, base_url: &str, media_type: &str) -> Result<()> {
401		let s = s2u!(css);
402		let url = s2w!(base_url);
403		let media = s2w!(media_type);
404		let b = s.as_bytes();
405		let n = b.len() as UINT;
406		let ok = (_API.SciterSetCSS)(self.hwnd, b.as_ptr(), n, url.as_ptr(), media.as_ptr());
407		ok_or!(ok)
408	}
409
410}
411
412
413// Sciter notification handler.
414// This comes as free function due to https://github.com/rust-lang/rust/issues/32364
415extern "system" fn _on_handle_notification<T: HostHandler>(pnm: *mut ::capi::scdef::SCITER_CALLBACK_NOTIFICATION, param: LPVOID) -> UINT
416{
417	use capi::scdef::{SCITER_NOTIFICATION, SCITER_CALLBACK_NOTIFICATION};
418
419	// reconstruct pointer to Handler
420	let callback = NativeHandler::get_data::<HostCallback<T>>(&param);
421	let me: &mut T = &mut callback.handler;
422
423	// process notification
424	let nm: &mut SCITER_CALLBACK_NOTIFICATION = unsafe { &mut *pnm };
425	let code: SCITER_NOTIFICATION = unsafe { ::std::mem::transmute(nm.code) };
426
427
428	let result: UINT = match code {
429		SCITER_NOTIFICATION::SC_LOAD_DATA => {
430			let scnm = pnm as *mut SCN_LOAD_DATA;
431      let scnm = unsafe { &mut *scnm };
432			let mut re = me.on_data_load(scnm);
433      if re.is_none() {
434        if let Some(archive) = callback.archive.borrow().as_ref() {
435          let uri = w2s!(scnm.uri);
436          if uri.starts_with("this://app/") {
437            if let Some(data) = archive.get(&uri) {
438              me.data_ready(scnm.hwnd, &uri, data, None);
439            } else {
440              eprintln!("[sciter] error: can't load {:?}", uri);
441            }
442          }
443          re = Some(LOAD_RESULT::LOAD_DEFAULT);
444        }
445      }
446			re.unwrap_or(LOAD_RESULT::LOAD_DEFAULT) as UINT
447		},
448
449		SCITER_NOTIFICATION::SC_DATA_LOADED => {
450			let scnm = pnm as *mut SCN_DATA_LOADED;
451			me.on_data_loaded(unsafe { &mut *scnm } );
452			0
453		},
454
455		SCITER_NOTIFICATION::SC_ATTACH_BEHAVIOR => {
456			let scnm = pnm as *mut SCN_ATTACH_BEHAVIOR;
457			let scnm = unsafe { &mut *scnm };
458			let mut re = me.on_attach_behavior(scnm);
459			if !re {
460				let name = u2s!(scnm.name);
461				let behavior = callback.behaviors
462					.borrow()
463					.iter()
464					.find(|x| x.0 == name)
465					.map(|x| x.1());
466
467				if let Some(behavior) = behavior {
468					let boxed = Box::new( BoxedHandler { handler: behavior } );
469					let ptr = Box::into_raw(boxed);	// dropped in `_event_handler_behavior_proc`
470
471					scnm.elementProc = ::eventhandler::_event_handler_behavior_proc;
472					scnm.elementTag = ptr as LPVOID;
473					re = true;
474				}
475			}
476			re as UINT
477		},
478
479		SCITER_NOTIFICATION::SC_ENGINE_DESTROYED => {
480			me.on_engine_destroyed();
481			0
482		},
483
484		SCITER_NOTIFICATION::SC_GRAPHICS_CRITICAL_FAILURE => {
485			me.on_graphics_critical_failure();
486			0
487		},
488
489		SCITER_NOTIFICATION::SC_INVALIDATE_RECT => {
490			let scnm = pnm as *const SCN_INVALIDATE_RECT;
491			me.on_invalidate(unsafe { &*scnm });
492			0
493		}
494
495		_ => 0,
496	};
497
498	return result;
499}
500
501// Sciter debug output handler.
502extern "system" fn _on_debug_notification<T: HostHandler>(param: LPVOID, subsystem: OUTPUT_SUBSYTEMS, severity: OUTPUT_SEVERITY,
503	text: LPCWSTR, _text_length: UINT)
504{
505	// reconstruct pointer to Handler
506	// let me = unsafe { &mut *(param as *mut HostCallback<T>) };
507	let me = NativeHandler::get_data::<HostCallback<T>>(&param);
508	let message = ::utf::w2s(text).replace("\r", "\n");
509	me.handler.on_debug_output(subsystem, severity, message.trim_end());
510}
511
512
513/// Sciter compressed archive.
514///
515/// An archive is produced by `packfolder` tool (from SDK) that creates a single blob with compressed resources.
516/// It allows to use the same resource pack uniformly across different platforms.
517///
518/// For example, app resource files (HTML/CSS/scripts) can be stored in an `assets` folder
519/// that can be packed into a single archive by calling `packfolder.exe assets target/assets.rc -binary`.
520/// And later it can be accessed via the Archive API explicitly:
521///
522/// ```rust,ignore
523/// let archived = include_bytes!("target/assets.rc");
524/// let assets = sciter::host::Archive::open(archived).expect("Unable to load archive.");
525///
526/// // access `assets/index.htm`
527/// let html_data = assets.get("index.htm").unwrap();
528/// ```
529///
530/// or implicitly via the `this://app/` URL after registering the archive via
531/// [`Window::archive_handler`](../window/struct.Window.html#method.archive_handler):
532///
533/// ```rust,ignore
534///
535/// let archived = include_bytes!("target/assets.rc");
536/// let mut frame = sciter::Window::new();
537/// frame.archive_handler(archived).expect("Unable to load archive");
538/// frame.load("this://app/index.htm");
539/// ```
540pub struct Archive(HSARCHIVE);
541
542/// Close the archive.
543impl Drop for Archive {
544  fn drop(&mut self) {
545    (_API.SciterCloseArchive)(self.0);
546  }
547}
548
549impl Archive {
550  /// Open an archive blob.
551  pub fn open(archived: &[u8]) -> Result<Self> {
552    let p = (_API.SciterOpenArchive)(archived.as_ptr(), archived.len() as u32);
553    if !p.is_null() {
554      Ok(Archive(p))
555    } else {
556      Err(())
557    }
558  }
559
560  /// Get an archive item.
561  ///
562  /// Given a path, returns a reference to the contents of an archived item.
563  pub fn get(&self, path: &str) -> Option<&[u8]> {
564    // skip initial part of the path
565    let skip = if path.starts_with("this://app/") {
566      "this://app/".len()
567    } else if path.starts_with("//") {
568      "//".len()
569    } else {
570      0
571    };
572
573    let wname = s2w!(path);
574    let name = &wname[skip..];
575
576    let mut pb = ::std::ptr::null();
577    let mut cb = 0;
578    let ok = (_API.SciterGetArchiveItem)(self.0, name.as_ptr(), &mut pb, &mut cb);
579    if ok != 0 && !pb.is_null() {
580      let data = unsafe { ::std::slice::from_raw_parts(pb, cb as usize) };
581      Some(data)
582    } else {
583      None
584    }
585  }
586}