///|
pub fn abi_version() -> Int {
proton_abi_version_ffi()
}
///|
pub fn runtime_info_json() -> String raise NativeError {
require_native_text("runtime info JSON", PayloadBytesExcludingTerminator, (
buffer,
buffer_len,
required,
) => proton_runtime_info_json_ffi(buffer, buffer_len, required))
}
///|
fn decode_runtime_info(text : String) -> RuntimeInfo raise NativeError {
let json = @json.parse(text) catch {
error =>
raise InvalidPayload(
context="runtime info JSON",
message=error.to_string(),
)
}
let info : RuntimeInfo = @json.from_json(json) catch {
error =>
raise InvalidPayload(context="runtime info", message=error.to_string())
}
expect_abi_version(info.abi_version, "runtime info")
info
}
///|
pub fn runtime_info() -> RuntimeInfo raise NativeError {
decode_runtime_info(runtime_info_json())
}
///|
pub fn AppActivation::new(
urls? : Array[String] = [],
files? : Array[String] = [],
reopen? : Bool = false,
) -> AppActivation {
AppActivation::{
abi_version: 1,
urls: urls.copy(),
files: files.copy(),
reopen,
}
}
///|
/// Claims one operating-system application identity. A secondary process
/// forwards its activation before returning `Forwarded`.
pub fn AppInstance::acquire(
identifier : String,
activation : AppActivation,
) -> AppInstanceAcquire raise NativeError {
if identifier.trim().to_owned() == "" {
raise invalid_argument("app instance identifier must not be empty")
}
let out_instance = Ref(proton_invalid_handle)
let out_primary = Ref(0)
let status = proton_app_instance_acquire_ffi(
@ffi.to_cstr(identifier),
@ffi.to_cstr(activation.to_json().stringify()),
out_instance,
out_primary,
)
if status < 0 {
raise native_error(status)
} else if out_primary.val == 0 {
AppInstanceAcquire::Forwarded
} else if out_instance.val == proton_invalid_handle {
raise InvalidPayload(
context="app instance",
message="primary acquisition returned an invalid handle",
)
} else {
AppInstanceAcquire::Primary(AppInstance::{
handle: out_instance.val,
destroyed: false,
})
}
}
///|
/// Attaches the primary instance listener to a runtime's existing wake source.
pub fn AppInstance::attach_runtime(
self : AppInstance,
runtime : Runtime,
) -> Unit raise NativeError {
if self.destroyed {
raise invalid_argument("app instance is destroyed")
}
check_status(
proton_app_instance_attach_runtime_ffi(self.handle, runtime.active_handle()),
)
}
///|
pub fn AppInstance::destroy(self : AppInstance) -> Unit raise NativeError {
if self.destroyed {
return
}
check_status(proton_app_instance_destroy_ffi(self.handle))
self.handle = proton_invalid_handle
self.destroyed = true
}
///|
pub fn last_error_message() -> String {
let required = proton_last_error_message_ffi(FixedArray::make(1, b'\x00'), 0)
if required <= 0 {
return ""
}
let buffer = FixedArray::make(required + 1, b'\x00')
let _ = proton_last_error_message_ffi(buffer, buffer.length())
@ffi.from_cstr(Bytes::from_array(buffer))
}
///|
fn native_error(status : Int) -> NativeError {
Status(status~, message=last_error_message())
}
///|
fn check_status(status : Int) -> Unit raise NativeError {
if status < 0 {
raise native_error(status)
}
}
///|
fn invalid_argument(message : String) -> NativeError {
InvalidArgument(message~)
}
///|
fn validate_window_config(config : WindowConfig) -> Unit raise NativeError {
match config.raw_json {
Some(_) => ()
None =>
if config.width <= 0 || config.height <= 0 {
raise invalid_argument("width and height must be positive")
}
}
}
///|
fn decode_native_json(text : String, label : String) -> Json raise NativeError {
@json.parse(text) catch {
error =>
raise InvalidPayload(
context=label + " JSON",
message="invalid JSON: " + error.to_string(),
)
}
}
///|
fn decode_string_json(json : Json, label : String) -> String raise NativeError {
@json.from_json(json) catch {
error =>
raise InvalidPayload(
context=label,
message="expected string: " + error.to_string(),
)
}
}
///|
fn decode_int_json(json : Json, label : String) -> Int raise NativeError {
@json.from_json(json) catch {
error =>
raise InvalidPayload(
context=label,
message="expected integer: " + error.to_string(),
)
}
}
///|
fn decode_bool_json(json : Json, label : String) -> Bool raise NativeError {
@json.from_json(json) catch {
error =>
raise InvalidPayload(
context=label,
message="expected boolean: " + error.to_string(),
)
}
}
///|
/// Rejects payloads whose `abi_version` does not match the schema version
/// this binding implements.
fn expect_abi_version(found : Int, context : String) -> Unit raise NativeError {
if found != 1 {
raise InvalidPayload(
context~,
message="unsupported abi_version " + found.to_string() + ", expected 1",
)
}
}
///|
/// Validates an optional `abi_version` field: absent is accepted (older
/// runtimes do not emit one), but a present field must be the number 1.
fn decode_optional_abi_version(
object : Map[String, Json],
context : String,
) -> Unit raise NativeError {
match object {
{ "abi_version"? : None, .. } => ()
{ "abi_version": Number(version, ..), .. } =>
expect_abi_version(version.to_int(), context)
_ => raise InvalidPayload(context~, message="expected number abi_version")
}
}
///|
fn decode_runtime_event_json(text : String) -> RuntimeEvent raise NativeError {
let json = decode_native_json(text, "runtime event")
let object = match json {
Object(object) => object
_ =>
raise InvalidPayload(context="runtime event", message="expected object")
}
decode_optional_abi_version(object, "runtime event")
guard object
is {
"type": event_type_json,
"window"? : window_json,
"view"? : view_json,
"title"? : title_json,
"is_loading"? : is_loading_json,
"state"? : state_json,
"request_id"? : request_id_json,
"url"? : url_json,
"method"? : method_json,
"user_gesture"? : user_gesture_json,
"redirect"? : redirect_json,
"disposition"? : disposition_json,
"download_id"? : download_id_json,
"suggested_name"? : suggested_name_json,
"download_state"? : download_state_json,
"received_bytes"? : received_bytes_json,
"total_bytes"? : total_bytes_json,
"percent"? : percent_json,
"error"? : error_json,
"permissions"? : permissions_json,
"command_id"? : command_id_json,
"revision"? : revision_json,
"items"? : items_json,
"ok"? : ok_json,
"message"? : message_json,
..
} else {
raise InvalidPayload(context="runtime event", message="missing type")
}
let event_type = decode_string_json(event_type_json, "runtime event type")
let window = window_json.map(value => {
decode_int64_json(value, "runtime event window")
})
let view = view_json.map(value => {
decode_int64_json(value, "runtime event view")
})
let title = title_json.map(value => {
decode_string_json(value, "runtime event title")
})
let is_loading = is_loading_json.map(value => {
decode_bool_json(value, "runtime event is_loading")
})
let state = match state_json {
None => None
Some(value) =>
Some(
@json.from_json(value) catch {
error =>
raise InvalidPayload(
context="runtime event window state",
message=error.to_string(),
)
},
)
}
let request_id = request_id_json.map(value => {
decode_int64_json(value, "runtime event request id")
})
let url = url_json.map(value => decode_string_json(value, "runtime event URL"))
let http_method = method_json.map(value => {
decode_string_json(value, "runtime event method")
})
let user_gesture = user_gesture_json.map(value => {
decode_bool_json(value, "runtime event user gesture")
})
let redirect = redirect_json.map(value => {
decode_bool_json(value, "runtime event redirect")
})
let disposition = disposition_json.map(value => {
decode_int_json(value, "runtime event disposition")
})
let download_id = download_id_json.map(value => {
decode_int_json(value, "runtime event download id")
})
let suggested_name = suggested_name_json.map(value => {
decode_string_json(value, "runtime event suggested name")
})
let download_state = download_state_json.map(value => {
decode_string_json(value, "runtime event download state")
})
let received_bytes = received_bytes_json.map(value => {
decode_int64_json(value, "runtime event received bytes")
})
let total_bytes = total_bytes_json.map(value => {
decode_int64_json(value, "runtime event total bytes")
})
let percent = percent_json.map(value => {
decode_int_json(value, "runtime event percent")
})
let error_code = error_json.map(value => {
decode_int_json(value, "runtime event error code")
})
let permissions = permissions_json.map(value => {
decode_int_json(value, "runtime event permissions")
})
let menu_command_id = command_id_json.map(value => {
decode_string_json(value, "runtime event command id")
})
let revision = revision_json.map(value => {
decode_int64_json(value, "runtime event revision")
})
let items = match items_json {
None => []
Some(Array(values)) => {
let items : Array[String] = []
for value in values {
items.push(decode_string_json(value, "runtime event item"))
}
items
}
Some(_) =>
raise InvalidPayload(
context="runtime event items",
message="expected string array",
)
}
let ok = match ok_json {
None => None
Some(True) => Some(true)
Some(False) => Some(false)
Some(_) =>
raise InvalidPayload(
context="runtime event ok",
message="expected boolean",
)
}
let message = message_json.map(value => {
decode_string_json(value, "runtime event message")
})
RuntimeEvent::{
event_type,
window,
view,
title,
is_loading,
state,
request_id,
url,
http_method,
user_gesture,
redirect,
disposition,
download_id,
suggested_name,
download_state,
received_bytes,
total_bytes,
percent,
error_code,
permissions,
menu_command_id,
revision,
items,
ok,
message,
}
}
///|
fn decode_int64_json(json : Json, label : String) -> Int64 raise NativeError {
@json.from_json(json) catch {
error =>
raise InvalidPayload(
context=label,
message="expected int64: " + error.to_string(),
)
}
}
///|
fn decode_bridge_request_json(text : String) -> BridgeRequest raise NativeError {
let json = decode_native_json(text, "bridge request")
let object = match json {
Object(object) => object
_ =>
raise InvalidPayload(context="bridge request", message="expected object")
}
decode_optional_abi_version(object, "bridge request")
guard object is { "request_id": request_id_json, .. } else {
raise InvalidPayload(context="bridge request", message="missing request_id")
}
guard object is { "window": window_json, .. } else {
raise InvalidPayload(context="bridge request", message="missing window")
}
guard object is { "op": op_json, .. } else {
raise InvalidPayload(context="bridge request", message="missing op")
}
guard object
is {
"payload"? : payload_json,
"page_instance"? : page_instance_json,
"source_origin"? : source_origin_json,
..
}
let payload = payload_json.unwrap_or(Json::empty_object())
let page_instance = match page_instance_json {
Some(String(value)) => Some(value)
_ => None
}
let source_origin = match source_origin_json {
Some(value) => decode_string_json(value, "bridge request source_origin")
None =>
raise InvalidPayload(
context="bridge request",
message="missing source_origin",
)
}
let request_id = decode_int64_json(request_id_json, "bridge request_id")
let window = decode_int64_json(window_json, "bridge request window")
let op = decode_string_json(op_json, "bridge request op")
BridgeRequest::{
request_id,
window,
op,
payload,
page_instance,
source_origin,
}
}
///|
pub fn execute_process(
config? : RuntimeConfig = RuntimeConfig::new(),
) -> ProcessResult raise NativeError {
let out_exit_code = Ref(0)
let status = proton_execute_process_ffi(
@ffi.to_cstr(config.to_json_string()),
out_exit_code,
)
if status < 0 {
raise native_error(status)
} else if status == proton_process_handled {
ProcessResult::SubprocessHandled(out_exit_code.val)
} else {
ProcessResult::MainProcess
}
}
///|
pub fn probe_runtime(
config? : RuntimeConfig = RuntimeConfig::new(),
) -> Unit raise NativeError {
check_status(
proton_runtime_probe_json_ffi(@ffi.to_cstr(config.to_json_string())),
)
}
///|
pub fn RuntimeConfig::probe(self : RuntimeConfig) -> Unit raise NativeError {
probe_runtime(config=self)
}
///|
pub fn Runtime::new(
config? : RuntimeConfig = RuntimeConfig::new(),
) -> Runtime raise NativeError {
let out_runtime = Ref(proton_invalid_handle)
let status = proton_runtime_create_json_ffi(
@ffi.to_cstr(config.to_json_string()),
out_runtime,
)
if status < 0 {
raise native_error(status)
} else {
Runtime::{ handle: out_runtime.val, lifecycle: RuntimeActive }
}
}
///|
pub fn Runtime::destroy(self : Runtime) -> Unit raise NativeError {
match self.lifecycle {
RuntimeDestroyed => return
RuntimeActive | RuntimeDestroying => self.lifecycle = RuntimeDestroying
}
let status = proton_runtime_destroy_ffi(self.handle)
if status < 0 {
raise native_error(status)
} else {
self.handle = proton_invalid_handle
self.lifecycle = RuntimeDestroyed
}
}
///|
fn Runtime::active_handle(self : Runtime) -> Int64 raise NativeError {
match self.lifecycle {
RuntimeActive => self.handle
RuntimeDestroying | RuntimeDestroyed =>
raise Status(
status=proton_err_invalid_state,
message="runtime is destroying or destroyed",
)
}
}
///|
/// Advances one external-message-pump iteration for a low-level host.
pub fn Runtime::do_message_loop_work(self : Runtime) -> Unit raise NativeError {
check_status(proton_runtime_do_message_loop_work_ffi(self.active_handle()))
}
///|
/// Waits for low-level external-pump work.
pub fn Runtime::wait(
self : Runtime,
interest_mask~ : Int,
timeout_ms? : Int,
) -> RuntimeWaitReady raise NativeError {
if interest_mask == runtime_wait_none {
raise invalid_argument("interest_mask is required")
}
if interest_mask.land(runtime_wait_all.lnot()) != 0 {
raise invalid_argument("interest_mask contains unsupported bits")
}
let ready_mask = Ref(runtime_wait_none)
let status = proton_runtime_wait_ffi(
self.active_handle(),
interest_mask,
encode_wait_timeout(timeout_ms),
ready_mask,
)
if status < 0 {
raise native_error(status)
} else {
RuntimeWaitReady::{ mask: ready_mask.val }
}
}
///|
/// Encodes an optional wait timeout for the ABI. The sentinel lives here and
/// nowhere else: callers say `None` to wait indefinitely. A deadline that has
/// already passed is a zero wait rather than an error, because arriving late is
/// normal and is not a reason to refuse to poll.
fn encode_wait_timeout(timeout_ms : Int?) -> Int {
match timeout_ms {
None => runtime_wait_timeout_infinite
Some(timeout_ms) => if timeout_ms < 0 { 0 } else { timeout_ms }
}
}
///|
/// Takes over the calling thread's event loop. The loop belongs to the thread
/// rather than to a runtime: it starts before the first runtime exists and
/// outlives the last one, so a host can run async work while it is still
/// deciding what runtime to build.
///
/// Must be called on the process's main thread.
pub fn host_loop_begin() -> Unit raise NativeError {
check_status(proton_host_loop_begin_ffi())
}
///|
/// Runs one iteration of the host loop: block until work arrives or the
/// timeout expires, then advance the platform toolkit. Pass no `timeout_ms` to
/// wait indefinitely.
///
/// This is the only thing that drives the platform while the host loop owns the
/// thread, so a host must keep calling it. The returned mask says which kinds
/// of work became ready; it is empty when nothing happened.
pub fn host_loop_poll(timeout_ms? : Int) -> RuntimeWaitReady raise NativeError {
let ready_mask = Ref(runtime_wait_none)
let status = proton_host_loop_poll_ffi(
encode_wait_timeout(timeout_ms),
ready_mask,
)
if status < 0 {
raise native_error(status)
} else {
RuntimeWaitReady::{ mask: ready_mask.val }
}
}
///|
/// Releases the host loop. Safe to call when no loop is running.
pub fn host_loop_end() -> Unit {
proton_host_loop_end_ffi()
}
///|
/// Wakes a blocked `Runtime::wait` or `host_loop_poll`, or makes the next one
/// return immediately when none is blocked yet. A lost wakeup deadlocks the
/// host, so the two cases behave the same.
///
/// Takes no runtime and is safe from any thread, touching only atomics and the
/// platform run loop. That is what lets a thread outside the runtime call it:
/// handles validate thread ownership and such a thread owns none.
pub fn signal_wakeup() -> Unit {
proton_runtime_signal_wakeup_ffi()
}
///|
/// Installs the write end of a non-blocking pipe used to wake the host async
/// runtime. The native runtime duplicates the descriptor and does not borrow
/// the caller's ownership.
pub fn Runtime::set_wakeup_fd(
self : Runtime,
wakeup_fd : Int,
) -> Unit raise NativeError {
if wakeup_fd < -1 {
raise invalid_argument("wakeup_fd must be -1 or a valid descriptor")
}
check_status(
proton_runtime_set_wakeup_fd_ffi(self.active_handle(), wakeup_fd),
)
}
///|
/// Prepares a platform-owned wakeup source and returns its locator.
///
/// Call `activate_wakeup_source` after opening the locator for reading.
pub fn Runtime::prepare_wakeup_source(
self : Runtime,
) -> String raise NativeError {
let handle = self.active_handle()
require_native_text(
"runtime wakeup source",
PayloadBytesExcludingTerminator,
(buffer, buffer_len, required) => {
proton_runtime_prepare_wakeup_source_ffi(
handle, buffer, buffer_len, required,
)
},
)
}
///|
/// Activates a prepared wakeup source after its reader is connected.
pub fn Runtime::activate_wakeup_source(
self : Runtime,
) -> Unit raise NativeError {
check_status(proton_runtime_activate_wakeup_source_ffi(self.active_handle()))
}
///|
/// Returns the delay until CEF next needs its external message pump serviced,
/// or `None` when no delayed pump is scheduled.
pub fn Runtime::next_wakeup_delay_ms(self : Runtime) -> Int? raise NativeError {
let delay = Ref(-1L)
let status = proton_runtime_next_wakeup_delay_ms_ffi(
self.active_handle(),
delay,
)
if status < 0 {
raise native_error(status)
} else if delay.val < 0L {
None
} else if delay.val > 2147483647L {
Some(2147483647)
} else {
Some(delay.val.to_int())
}
}
///|
/// Replaces the application-level native menu bar for this runtime.
pub fn Runtime::set_menu(
self : Runtime,
menu : MenuBar,
) -> Unit raise NativeError {
check_status(
proton_runtime_set_menu_json_ffi(
self.active_handle(),
@ffi.to_cstr(menu.to_json_string()),
),
)
}
///|
pub fn Runtime::poll_event_json(self : Runtime) -> String? raise NativeError {
let handle = self.active_handle()
read_native_text("runtime event JSON", PayloadBytesExcludingTerminator, (
buffer,
buffer_len,
required,
) => proton_runtime_poll_event_json_ffi(handle, buffer, buffer_len, required))
}
///|
pub fn Runtime::poll_event(self : Runtime) -> RuntimeEvent? raise NativeError {
match self.poll_event_json() {
Some(text) => Some(decode_runtime_event_json(text))
None => None
}
}
///|
pub fn Runtime::poll_bridge_request_json(
self : Runtime,
) -> String? raise NativeError {
let handle = self.active_handle()
read_native_text("bridge request JSON", PayloadBytesExcludingTerminator, (
buffer,
buffer_len,
required,
) => {
proton_runtime_poll_bridge_request_json_ffi(
handle, buffer, buffer_len, required,
)
})
}
///|
pub fn Runtime::poll_bridge_request(
self : Runtime,
) -> BridgeRequest? raise NativeError {
match self.poll_bridge_request_json() {
Some(text) => Some(decode_bridge_request_json(text))
None => None
}
}
///|
pub fn Runtime::respond_bridge_request(
self : Runtime,
response : BridgeResponse,
) -> Unit raise NativeError {
check_status(
proton_runtime_respond_bridge_request_json_ffi(
self.active_handle(),
@ffi.to_cstr(response.to_json_string()),
),
)
}
///|
pub fn Window::new(
runtime : Runtime,
config? : WindowConfig = WindowConfig::new(),
) -> Window raise NativeError {
validate_window_config(config)
let out_window = Ref(proton_invalid_handle)
let status = proton_window_create_json_ffi(
runtime.active_handle(),
@ffi.to_cstr(config.to_json_string()),
out_window,
)
if status < 0 {
raise native_error(status)
} else {
Window::{ handle: out_window.val, lifecycle: WindowLive }
}
}
///|
pub fn Window::destroy(self : Window) -> Unit raise NativeError {
match self.lifecycle {
WindowDestroyed => return
WindowLive | WindowCloseRequested | WindowDestroying =>
self.lifecycle = WindowDestroying
}
let status = proton_window_destroy_ffi(self.handle)
if status < 0 {
raise native_error(status)
} else {
self.handle = proton_invalid_handle
self.lifecycle = WindowDestroyed
}
}
///|
fn Window::live_handle(self : Window) -> Int64 raise NativeError {
match self.lifecycle {
WindowLive => self.handle
WindowCloseRequested | WindowDestroying | WindowDestroyed =>
raise Status(
status=proton_err_invalid_state,
message="window is closing or destroyed",
)
}
}
///|
pub fn Window::show(self : Window) -> Unit raise NativeError {
check_status(proton_window_show_ffi(self.live_handle()))
}
///|
/// Borrows this owning window handle for APIs that must not destroy it.
pub fn Window::as_ref(self : Window) -> WindowRef {
WindowRef::unsafe_from_handle(
match self.lifecycle {
WindowLive => self.handle
WindowCloseRequested | WindowDestroying | WindowDestroyed =>
proton_invalid_handle
},
)
}
///|
/// Returns the stable native handle used to correlate runtime events.
pub fn Window::id(self : Window) -> Int64 {
match self.lifecycle {
WindowLive | WindowCloseRequested => self.handle
WindowDestroying | WindowDestroyed => proton_invalid_handle
}
}
///|
pub fn Window::hide(self : Window) -> Unit raise NativeError {
check_status(proton_window_hide_ffi(self.live_handle()))
}
///|
pub fn Window::close(self : Window) -> Unit raise NativeError {
match self.lifecycle {
WindowCloseRequested | WindowDestroying | WindowDestroyed => return
WindowLive => ()
}
let status = proton_window_close_ffi(self.handle)
if status < 0 {
raise native_error(status)
} else {
self.lifecycle = WindowCloseRequested
}
}
///|
pub fn Window::focus(self : Window) -> Unit raise NativeError {
check_status(proton_window_focus_ffi(self.live_handle()))
}
///|
pub fn Window::set_title(
self : Window,
title : String,
) -> Unit raise NativeError {
check_status(
proton_window_set_title_ffi(self.live_handle(), @ffi.to_cstr(title)),
)
}
///|
pub fn Window::set_size(
self : Window,
width : Int,
height : Int,
) -> Unit raise NativeError {
if width <= 0 || height <= 0 {
raise invalid_argument("width and height must be positive")
}
check_status(proton_window_set_size_ffi(self.live_handle(), width, height))
}
///|
pub fn Window::minimize(self : Window) -> Unit raise NativeError {
check_status(proton_window_minimize_ffi(self.live_handle()))
}
///|
pub fn Window::maximize(self : Window) -> Unit raise NativeError {
check_status(proton_window_maximize_ffi(self.live_handle()))
}
///|
pub fn Window::restore(self : Window) -> Unit raise NativeError {
check_status(proton_window_restore_ffi(self.live_handle()))
}
///|
pub fn Window::set_fullscreen(
self : Window,
fullscreen : Bool,
) -> Unit raise NativeError {
check_status(
proton_window_set_fullscreen_ffi(
self.live_handle(),
if fullscreen {
1
} else {
0
},
),
)
}
///|
pub fn Window::set_position(
self : Window,
x : Int,
y : Int,
) -> Unit raise NativeError {
check_status(proton_window_set_position_ffi(self.live_handle(), x, y))
}
///|
pub fn Window::set_always_on_top(
self : Window,
always_on_top : Bool,
) -> Unit raise NativeError {
check_status(
proton_window_set_always_on_top_ffi(
self.live_handle(),
if always_on_top {
1
} else {
0
},
),
)
}
///|
pub fn Window::set_zoom_percent(
self : Window,
zoom_percent : Int,
) -> Unit raise NativeError {
if zoom_percent < 25 || zoom_percent > 500 {
raise invalid_argument("zoom_percent must be between 25 and 500")
}
check_status(
proton_window_set_zoom_percent_ffi(self.live_handle(), zoom_percent),
)
}
///|
pub fn Window::state(self : Window) -> WindowState raise NativeError {
let handle = self.live_handle()
let text = match
read_native_text("window state", PayloadBytesExcludingTerminator, (
buffer,
buffer_len,
required,
) => proton_window_state_json_ffi(handle, buffer, buffer_len, required)) {
Some(text) => text
None =>
raise InvalidPayload(context="window state", message="missing payload")
}
@json.from_json(decode_native_json(text, "window state")) catch {
error =>
raise InvalidPayload(context="window state", message=error.to_string())
}
}
///|
pub fn Window::set_close_interception(
self : Window,
enabled : Bool,
) -> Unit raise NativeError {
check_status(
proton_window_set_close_interception_ffi(
self.live_handle(),
if enabled {
1
} else {
0
},
),
)
}
///|
pub fn Window::respond_close_request(
self : Window,
request_id : Int64,
allow : Bool,
) -> Unit raise NativeError {
check_status(
proton_window_respond_close_request_ffi(
self.live_handle(),
request_id,
if allow {
1
} else {
0
},
),
)
}
///|
pub fn Window::load_url(self : Window, url : String) -> Unit raise NativeError {
check_status(
proton_window_load_url_ffi(self.live_handle(), @ffi.to_cstr(url)),
)
}
///|
pub fn Window::load_html(
self : Window,
html : String,
base_url : String,
) -> Unit raise NativeError {
check_status(
proton_window_load_html_ffi(
self.live_handle(),
@ffi.to_cstr(html),
@ffi.to_cstr(base_url),
),
)
}
///|
/// Load an HTML document whose relative URLs resolve inside `asset_root`.
pub fn Window::load_asset(
self : Window,
html : String,
document_url : String,
asset_root : String,
) -> Unit raise NativeError {
check_status(
proton_window_load_asset_ffi(
self.live_handle(),
@ffi.to_cstr(html),
@ffi.to_cstr(document_url),
@ffi.to_cstr(asset_root),
),
)
}
///|
pub fn Window::eval(self : Window, script : String) -> Unit raise NativeError {
check_status(proton_window_eval_ffi(self.live_handle(), @ffi.to_cstr(script)))
}
///|
pub fn Window::browser_command(
self : Window,
command : String,
download_id? : Int,
) -> Unit raise NativeError {
let fields : Map[String, Json] = { "command": command }
match download_id {
Some(download_id) =>
fields["download_id"] = Json::number(
download_id.to_double(),
repr=download_id.to_string(),
)
None => ()
}
check_status(
proton_window_browser_command_json_ffi(
self.live_handle(),
@ffi.to_cstr(Json::object(fields).stringify()),
),
)
}
///|
pub fn Window::respond_browser_request(
self : Window,
request_id : Int64,
action : String,
path? : String,
) -> Unit raise NativeError {
let fields : Map[String, Json] = {
"request_id": request_id.to_string(),
"action": action,
}
match path {
Some(path) => fields["path"] = Json(path)
None => ()
}
check_status(
proton_window_respond_browser_request_json_ffi(
self.live_handle(),
@ffi.to_cstr(Json::object(fields).stringify()),
),
)
}
///|
pub fn Window::emit_bridge_event_json(
self : Window,
event_json : String,
) -> Unit raise NativeError {
check_status(
proton_window_emit_bridge_event_json_ffi(
self.live_handle(),
@ffi.to_cstr(event_json),
),
)
}
///|
fn window_bridge_json(
window : Window,
context : String,
query : (Int64, FixedArray[Byte], Int, Ref[Int]) -> Int,
) -> String? raise NativeError {
let handle = window.live_handle()
read_native_text(context, PayloadBytesExcludingTerminator, (
buffer,
buffer_len,
required,
) => query(handle, buffer, buffer_len, required))
}
///|
fn decode_bridge_lifecycle_state(
text : String,
) -> BridgeLifecycleState raise NativeError {
let json = decode_native_json(text, "bridge lifecycle state")
let state : BridgeLifecycleState = @json.from_json(json) catch {
error =>
raise InvalidPayload(
context="bridge lifecycle state",
message=error.to_string(),
)
}
expect_abi_version(state.abi_version, "bridge lifecycle state")
state
}
///|
pub fn Window::bridge_lifecycle_state(
self : Window,
) -> BridgeLifecycleState raise NativeError {
let text = match
window_bridge_json(
self, "bridge lifecycle state", proton_window_bridge_state_json_ffi,
) {
Some(text) => text
None =>
raise InvalidPayload(
context="bridge lifecycle state",
message="missing payload",
)
}
decode_bridge_lifecycle_state(text)
}
///|
fn decode_bridge_diagnostic(
text : String,
) -> BridgeDiagnostic raise NativeError {
let json = decode_native_json(text, "bridge diagnostic")
let diagnostic : BridgeDiagnostic = @json.from_json(json) catch {
error =>
raise InvalidPayload(
context="bridge diagnostic",
message=error.to_string(),
)
}
expect_abi_version(diagnostic.abi_version, "bridge diagnostic")
diagnostic
}
///|
pub fn Window::take_bridge_failure(
self : Window,
) -> BridgeDiagnostic? raise NativeError {
match
window_bridge_json(
self, "bridge diagnostic", proton_window_take_bridge_failure_json_ffi,
) {
None => None
Some(text) => Some(decode_bridge_diagnostic(text))
}
}
///|
fn optional_dialog_text_buffer(value : String?) -> Bytes {
match value {
Some(value) => @ffi.to_cstr(value)
None => @ffi.to_cstr("")
}
}
///|
fn dialog_text_payload_len(buffer : Bytes) -> Int {
let len = buffer.length()
if len >= 1 {
len - 1
} else {
len
}
}
///|
pub fn WindowRef::begin_message_dialog(
self : WindowRef,
title : String?,
message : String,
level : DialogLevel,
) -> Int64 raise NativeError {
let title_buffer = optional_dialog_text_buffer(title)
let message_buffer = @ffi.to_cstr(message)
let dialog = Ref(proton_invalid_handle)
let status = proton_window_begin_message_dialog_ffi(
self.handle,
title_buffer,
dialog_text_payload_len(title_buffer),
message_buffer,
dialog_text_payload_len(message_buffer),
level,
dialog,
)
if status < 0 {
raise native_error(status)
} else {
dialog.val
}
}
///|
pub fn Runtime::begin_message_dialog(
self : Runtime,
title : String?,
message : String,
level : DialogLevel,
) -> Int64 raise NativeError {
let title_buffer = optional_dialog_text_buffer(title)
let message_buffer = @ffi.to_cstr(message)
let dialog = Ref(proton_invalid_handle)
let status = proton_runtime_begin_message_dialog_ffi(
self.active_handle(),
title_buffer,
dialog_text_payload_len(title_buffer),
message_buffer,
dialog_text_payload_len(message_buffer),
level,
dialog,
)
if status < 0 {
raise native_error(status)
} else {
dialog.val
}
}
///|
pub fn Runtime::poll_message_dialog(
self : Runtime,
dialog : Int64,
) -> Bool raise NativeError {
let handle = self.active_handle()
match
read_native_text("message dialog result", BufferBytesIncludingTerminator, (
buffer,
buffer_len,
required,
) => {
proton_runtime_poll_dialog_result_ffi(
handle, dialog, buffer, buffer_len, required,
)
}) {
None => false
Some(_) => true
}
}
///|
pub fn WindowRef::begin_confirm_dialog(
self : WindowRef,
title : String?,
message : String,
level : DialogLevel,
) -> Int64 raise NativeError {
let title_buffer = optional_dialog_text_buffer(title)
let message_buffer = @ffi.to_cstr(message)
let dialog = Ref(proton_invalid_handle)
let status = proton_window_begin_confirm_dialog_ffi(
self.handle,
title_buffer,
dialog_text_payload_len(title_buffer),
message_buffer,
dialog_text_payload_len(message_buffer),
level,
dialog,
)
if status < 0 {
raise native_error(status)
} else {
dialog.val
}
}
///|
fn WindowRef::begin_file_dialog(
window : WindowRef,
title : String?,
path : String?,
callback : (Int64, Bytes, Int, Bytes, Int, Ref[Int64]) -> Int,
) -> Int64 raise NativeError {
let title_buffer = optional_dialog_text_buffer(title)
let path_buffer = optional_dialog_text_buffer(path)
let dialog = Ref(proton_invalid_handle)
let status = callback(
window.handle,
title_buffer,
dialog_text_payload_len(title_buffer),
path_buffer,
dialog_text_payload_len(path_buffer),
dialog,
)
if status < 0 {
raise native_error(status)
} else {
dialog.val
}
}
///|
pub fn WindowRef::begin_open_file_dialog(
self : WindowRef,
title : String?,
path : String?,
) -> Int64 raise NativeError {
self.begin_file_dialog(title, path, proton_window_begin_open_file_dialog_ffi)
}
///|
pub fn WindowRef::begin_save_file_dialog(
self : WindowRef,
title : String?,
path : String?,
) -> Int64 raise NativeError {
self.begin_file_dialog(title, path, proton_window_begin_save_file_dialog_ffi)
}
///|
pub fn WindowRef::begin_choose_directory_dialog(
self : WindowRef,
title : String?,
path : String?,
) -> Int64 raise NativeError {
self.begin_file_dialog(
title, path, proton_window_begin_choose_directory_dialog_ffi,
)
}
///|
pub fn WindowRef::poll_dialog_result(
self : WindowRef,
dialog : Int64,
) -> DialogPollResult raise NativeError {
let handle = self.handle
match
read_native_text("window dialog result", BufferBytesIncludingTerminator, (
buffer,
buffer_len,
required,
) => {
proton_window_poll_dialog_result_ffi(
handle, dialog, buffer, buffer_len, required,
)
}) {
None => Pending
Some(result) => Ready(result)
}
}
///|
fn validate_view_config(config : ViewConfig) -> Unit raise NativeError {
match config.raw_json {
Some(_) => ()
None =>
if config.width <= 0 || config.height <= 0 {
raise invalid_argument("view width and height must be positive")
}
}
}
///|
/// Creates a web contents view inside `window`. The view renders above the
/// window's main browser content; use `set_z_order` to stack multiple views.
/// Requires the `web_contents_view` native runtime feature.
pub fn View::new(
window : Window,
config : ViewConfig,
) -> View raise NativeError {
validate_view_config(config)
let out_view = Ref(proton_invalid_handle)
let status = proton_view_create_json_ffi(
window.live_handle(),
@ffi.to_cstr(config.to_json_string()),
out_view,
)
if status < 0 {
raise native_error(status)
} else {
View::{ handle: out_view.val, lifecycle: ViewLive }
}
}
///|
pub fn View::destroy(self : View) -> Unit raise NativeError {
match self.lifecycle {
ViewDestroyed => return
ViewLive | ViewDestroying => self.lifecycle = ViewDestroying
}
let status = proton_view_destroy_ffi(self.handle)
if status < 0 {
raise native_error(status)
} else {
self.handle = proton_invalid_handle
self.lifecycle = ViewDestroyed
}
}
///|
fn View::live_handle(self : View) -> Int64 raise NativeError {
match self.lifecycle {
ViewLive => self.handle
ViewDestroying | ViewDestroyed =>
raise Status(status=proton_err_invalid_state, message="view is destroyed")
}
}
///|
/// Borrows this view handle for APIs that must not destroy it.
pub fn View::as_ref(self : View) -> ViewRef {
ViewRef::unsafe_from_handle(
match self.lifecycle {
ViewLive => self.handle
ViewDestroying | ViewDestroyed => proton_invalid_handle
},
)
}
///|
/// Returns the stable native handle used to correlate native calls.
pub fn View::id(self : View) -> Int64 {
match self.lifecycle {
ViewLive => self.handle
ViewDestroying | ViewDestroyed => proton_invalid_handle
}
}
///|
/// Moves and resizes the view. `x`/`y` use a top-left origin in the owning
/// window's content coordinate space, matching Electron's `setBounds`.
pub fn View::set_bounds(
self : View,
x~ : Int,
y~ : Int,
width~ : Int,
height~ : Int,
) -> Unit raise NativeError {
if width <= 0 || height <= 0 {
raise invalid_argument("view width and height must be positive")
}
check_status(
proton_view_set_bounds_ffi(self.live_handle(), x, y, width, height),
)
}
///|
pub fn View::set_visible(self : View, visible : Bool) -> Unit raise NativeError {
check_status(
proton_view_set_visible_ffi(self.live_handle(), if visible { 1 } else { 0 }),
)
}
///|
/// Stacks the view relative to the window's other views; higher `z_order`
/// renders above lower values, and views always render above the window's
/// main browser content.
pub fn View::set_z_order(self : View, z_order : Int) -> Unit raise NativeError {
check_status(proton_view_set_z_order_ffi(self.live_handle(), z_order))
}
///|
pub fn View::load_url(self : View, url : String) -> Unit raise NativeError {
if url.is_empty() {
raise invalid_argument("url must not be empty")
}
check_status(proton_view_load_url_ffi(self.live_handle(), @ffi.to_cstr(url)))
}
///|
/// Reads the current view state from the native runtime.
pub fn View::state(self : View) -> ViewState raise NativeError {
let handle = self.live_handle()
let text = match
read_native_text("view state", PayloadBytesExcludingTerminator, (
buffer,
buffer_len,
required,
) => proton_view_state_json_ffi(handle, buffer, buffer_len, required)) {
Some(text) => text
None =>
raise InvalidPayload(context="view state", message="missing payload")
}
@json.from_json(decode_native_json(text, "view state")) catch {
error =>
raise InvalidPayload(context="view state", message=error.to_string())
}
}
///|
/// Loads inline HTML into the view, served from `base_url` on the `proton://`
/// scheme, mirroring `Window::load_html`.
pub fn View::load_html(
self : View,
html : String,
base_url : String,
) -> Unit raise NativeError {
check_status(
proton_view_load_html_ffi(
self.live_handle(),
@ffi.to_cstr(html),
@ffi.to_cstr(base_url),
),
)
}
///|
/// Executes JavaScript in the view's main frame without awaiting a result.
pub fn View::eval(self : View, script : String) -> Unit raise NativeError {
check_status(proton_view_eval_ffi(self.live_handle(), @ffi.to_cstr(script)))
}
///|
/// Sends a browser control command to the view: `back`, `forward`, `reload`,
/// `reload_ignore_cache`, `stop`, `open_devtools`, or `close_devtools`.
pub fn View::browser_command(
self : View,
command : String,
download_id? : Int,
) -> Unit raise NativeError {
let fields : Map[String, Json] = { "command": command }
match download_id {
Some(download_id) =>
fields["download_id"] = Json::number(
download_id.to_double(),
repr=download_id.to_string(),
)
None => ()
}
check_status(
proton_view_browser_command_json_ffi(
self.live_handle(),
@ffi.to_cstr(Json::object(fields).stringify()),
),
)
}