sciter-sdk / rust-sciter

Rust bindings for Sciter

Home Page:https://sciter.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

How to embed into GameMaker window?

GirkovArpa opened this issue · comments

I'm on Windows 10 64bit.

I used sciter::Window::attach() to "attach" it to the GameMaker window.

Then called frame.load_html() and frame.run_app().

But the HTML isn't displayed. The GameMaker window freezes until I click the close button, then the game proceeds without the window being closed. It's as if the close button closes the attached (invisible) Sciter window the first time it's clicked.

If I use sciter::Window::create() instead, it works as expected, simply running the sciter application alongside the GameMaker game as 2 distinct programs.

But I want to embed the Sciter one as a transparent overlay.

What's GameMaker?

I believe the issue is with the difference in pumping messages. Do you attach from a different thread?

I'm referring to GameMaker Studio 2.

I will add that replacing frame.run_app() with frame.run_loop() makes no difference at all.

Also, using sciter::Window::new() instead of sciter::Window::attach() before the program's own window is displayed, causes the Sciter window to be displayed, and the GameMaker window doesn't appear until after the Sciter window is closed.

Using sciter::Window::new() a half-second after the GameMaker window appears causes the Sciter window to be displayed normally, as a separate program, but the separate GameMaker window display is frozen.

Clicking the close button on the GameMaker window causes it to unfreeze, but not close. Clicking close again closes both the GameMaker window and the Sciter window.

Closing the Sciter window will also close the GameMaker window.

I believe the issue is with the difference in pumping messages. Do you attach from a different thread?

GameMaker games are single-threaded, so it must be attaching from the main thread.

I can post a video if you want. Also if it matters, GameMaker uses DirectX11.

I will add that replacing frame.run_app() with frame.run_loop() makes no difference at all.

First, you don't need run_app() in your case - it runs its own message loop and the host application (GameMaker) will be frozen at that time. Or will behave like this:

Also, using sciter::Window::new() instead of sciter::Window::attach() before the program's own window is displayed, causes the Sciter window to be displayed, and the GameMaker window doesn't appear until after the Sciter window is closed.

Next, Window.attach is about attaching Sciter (as an HTML engine) to an existing native window.

Window.create creates a new native window and then attaches Sciter to it. So, the difference is about who creates a native window.

In any case, you have to route some WinAPI messages to Sciter in order to get UI working. And it can be done via SciterProcND - apparently, I haven't exposed it properly in Rust bindings, need to do.

So, you have to intercept messages in an existing window (for example, via SetWindowLongPtrW + GWLP_WNDPROC) and route messages to Sciter. Check out this article about some details.

Feel free to ask questions. Also, you can share your code or tell how you are trying to use that GameMaker, perhaps there is a better way.

I want to write a GameMaker extension that allows embedding a transparent Sciter UI into the game window. I'm just experimenting trying to get the Sciter window embedded.

I don't think GameMaker exposes the internal Windows event loop. Only the window handle.

I guess you mention GWLP_WNDPROC because according to the documentation it:

Sets a new address for the window procedure.

It's not immediately clear to me how this allows me to route window events to Sciter, but is it at least possible?

#[no_mangle]
pub extern "cdecl" fn foo(handle: *const c_char) -> f64 {
	let c_str: &CStr = unsafe { CStr::from_ptr(handle) };
	let hex_string: &str = c_str.to_str().unwrap();
	let window_handle = i64::from_str_radix(hex_string, 16).unwrap();
	println!("{:?}", window_handle);

	let handler = EventHandler {};
	//let mut frame = sciter::Window::new();
	let mut frame = sciter::Window::attach(window_handle as *mut sciter::types::_HWINDOW);
	frame.event_handler(handler);
	let dir = env::current_dir().unwrap().as_path().display().to_string();
	let filename = format!("{}\\{}", dir, "index.htm");
	println!("Full filename with path of index.htm: {}", filename);
	frame.load_html(include_bytes!("../index.htm"), None);
	frame.run_app();

	window_handle as f64
}

It's not immediately clear to me how this allows me to route window events to Sciter, but is it at least possible?

Something like this:

let hwnd = window_handle as sciter::types:HWINDOW;
hook_messages(hwnd);
let mut frame = sciter::Window::attach(hwnd);
fn hook_messages(hwnd: HWINDOW) {
	use sciter::types::*;

	#[link(name="user32")]
	extern "system"
	{
		fn SetWindowLongPtrW(hwnd: HWINDOW, index: i32, new_data: WndProc) -> WndProc;
		fn CallWindowProcW(prev: WndProc, hwnd: HWINDOW, msg: UINT, wp: WPARAM, lp: LPARAM) -> LRESULT;
	}

	type WndProc = extern "system" fn (hwnd: HWINDOW, msg: UINT, wp: WPARAM, lp: LPARAM) -> LRESULT;
	type PrevProcs = std::collections::HashMap<HWINDOW, WndProc>;

	thread_local! {
		static PREV_PROC: std::cell::RefCell<PrevProcs> = Default::default();
	}

	// https://sciter.com/developers/embedding-principles/
	extern "system" fn wnd_proc(hwnd: HWINDOW, msg: UINT, wp: WPARAM, lp: LPARAM) -> LRESULT {
		// first, pass the message to Sciter.
		let mut handled = false as BOOL;
		let lr = (sciter::SciterAPI().SciterProcND)(hwnd, msg, wp, lp, &mut handled);

		// if it was handled by Sciter, we're done here.
		if handled != 0 {
			return lr;
		}

		// if not, call the original window proc.
		let mut lr: LRESULT = 0;
		PREV_PROC.with(|procs| {
			let prev_proc = *procs.borrow().get(&hwnd).expect("An unregistered WindowProc is called somehow.");
			lr = unsafe { CallWindowProcW(prev_proc, hwnd, msg, wp, lp) }
		});

		// and return its result
		lr
	}

	// Subclass the window in order to receive its messages.
	const GWLP_WNDPROC: i32 = -4;
	let prev_proc = unsafe { SetWindowLongPtrW(hwnd, GWLP_WNDPROC, wnd_proc) };
	PREV_PROC.with(|procs| {
		procs.borrow_mut().insert(hwnd, prev_proc);
	});



}

I corrected a few typos but there's one error I'm not sure how to solve:

let lr = (_API.SciterProcND)(hwnd, msg, wp, lp, &mut handled);
          ^^^^ not found in this scope

Per the example here I tried writing this near the top of my file:

use ::{_API};

But I get this error:

use ::{_API};
        ^^^^ no `_API` external crate

This is the entire contents of lib.rs:

// $ cargo +stable-i686-pc-windows-gnu build --release

#![crate_type = "cdylib"]

extern crate libc;
use libc::c_char;
use std::ffi::CStr;
use std::i64;

extern crate sciter;
use std::env;

#[no_mangle]
pub extern "cdecl" fn foo(handle: *const c_char) -> f64 {
	let c_str: &CStr = unsafe { CStr::from_ptr(handle) };
	let hex_string: &str = c_str.to_str().unwrap();
	let window_handle = i64::from_str_radix(hex_string, 16).unwrap();
        println!("{:?}", window_handle);
  
        let hwnd = window_handle as sciter::types::HWINDOW;
        hook_messages(hwnd);
        let mut frame = sciter::Window::attach(hwnd);

	let dir = env::current_dir().unwrap().as_path().display().to_string();
	let filename = format!("{}\\{}", dir, "index.htm");
	println!("Full filename with path of index.htm: {}", filename);
	frame.load_html(include_bytes!("../index.htm"), None);
	frame.run_app();

	window_handle as f64
}

fn hook_messages(hwnd: sciter::types::HWINDOW) {
	use sciter::types::*;

	#[link(name="user32")]
	extern "system"
	{
		fn SetWindowLongPtrW(hwnd: HWINDOW, index: i32, new_data: WndProc) -> WndProc;
		fn CallWindowProcW(prev: WndProc, hwnd: HWINDOW, msg: UINT, wp: WPARAM, lp: LPARAM) -> LRESULT;
	}

	type WndProc = extern "system" fn (hwnd: HWINDOW, msg: UINT, wp: WPARAM, lp: LPARAM) -> LRESULT;
	type PrevProcs = std::collections::HashMap<HWINDOW, WndProc>;

	thread_local! {
		static PREV_PROC: std::cell::RefCell<PrevProcs> = Default::default();
	}

	// https://sciter.com/developers/embedding-principles/
	extern "system" fn wnd_proc(hwnd: HWINDOW, msg: UINT, wp: WPARAM, lp: LPARAM) -> LRESULT {
		// first, pass the message to Sciter.
		let mut handled = false as BOOL;
		let lr = (_API.SciterProcND)(hwnd, msg, wp, lp, &mut handled);

		// if it was handled by Sciter, we're done here.
		if handled != 0 {
			return lr;
		}

		// if not, call the original window proc.
		let mut lr: LRESULT = 0;
		PREV_PROC.with(|procs| {
			let prev_proc = *procs.borrow().get(&hwnd).expect("An unregistered WindowProc is called somehow.");
			lr = unsafe { CallWindowProcW(prev_proc, hwnd, msg, wp, lp) }
		});

		// and return its result
		lr
	}

	// Subclass the window in order to receive its messages.
	const GWLP_WNDPROC: i32 = -4;
	let prev_proc = unsafe { SetWindowLongPtrW(hwnd, GWLP_WNDPROC, wnd_proc) };
	PREV_PROC.with(|procs| {
		procs.borrow_mut().insert(hwnd, prev_proc);
	});
}

And cargo install _API says there's no crate with that name.

Sorry, it was a copy-paste from the implementation.

It should be sciter::SciterAPI().SciterProcND in your case.

It compiles with cargo build on 64bit Windows but not for 32bit Windows with cargo +stable-i686-pc-windows-gnu build because:

undefined reference to `SetWindowLongPtrW@12'

According to this issue in an unrelated repository, it's because:

I've just checked the winapi rs docs for the definition of Get/SetWindowLongPtrW and it is only exported for "x86_64" aka 64 bits Windows.

Unfortunately my version of GameMaker produces only 32bit executables*, so a 64bit .dll won't be compatible.

*Beta support for 64bit dropped earlier this month.

So this is really a problem with the winapi crate and GameMaker. If there's not an easy fix may as well close this.

You can use SetWindowLongW on 32-bit Windows. By the way, why do you use GCC instead of MSVC?

SetWindowLongW compiles. But (using frame.run_loop) the game window never even displays. It shows up in Task Manager as a process, but it's invisible.

Loading a .dll is blocking in GameMaker, so maybe that's why. But I put a println! statement inside wnd_proc and I can see it's constantly getting called, so not sure what's happening.

Loading the .dll after the game window is displayed just freezes it until the close button is clicked, upon which the game resumes.

There's some TIScript in index.htm with stdout.println calls, and they don't work either.


I used GCC because I prefer VSC over MSVC which on my computer takes forever to start and constantly freezes and lags.

You don't need calling run_loop. Hook the window, attach Sciter to it, load html and that's it. Ah yes, you need to show it: call window.expand(false).

In order to use MSVC you don't need VisualStudio, only build tools (compiler and sdk) : https://rust-lang.github.io/rustup/installation/windows.html

If you tell me how to use GameMaker and load a plugin in it, step by step, I'll try to check what's wrong with Sciter.

I tried adding frame.expand(false) with no success. I published a repository of what I'm doing here if you want to look at it.

You can see the entirety of the GameMaker script in the README:

var foo = external_define(
	"sciter_gamemaker.dll", 
	"foo", 
	dll_cdecl,
	ty_real, 
	1, 
	ty_string
);
var handle = window_handle();
var handle_as_hex_string = string(handle);
external_call(foo, handle_as_hex_string);

Is there an advantage to using MSVC over GCC?

In order to use MSVC you don't need VisualStudio, only build tools (compiler and sdk) : https://rust-lang.github.io/rustup/installation/windows.html

I actually replied before clicking your link. I thought you were referring to my other repositories where I used GCC. I didn't realize that Rust was using GCC to compile; I thought it had its own compiler.

Rust has its own compiler, but uses an external linker and libraries (either GCC or MSVC). Anyway, it's not relevant to Game Maker. I'll try to check.

Any working examples of embedding into any window using sciter-rs?

I can't get this to work even with a Notepad window.