///|
/// Application-lifetime capabilities supplied to startup hooks.
pub struct ApplicationContext {
tasks : @async.TaskGroup[Unit]
windows : WindowManager
locale_preferences : @locale.LocalePreferences
request_quit : () -> Unit
application_identifier : String
application_executable : String
application_arguments : Array[String]
url_schemes : Array[String]
}
///|
/// Window-lifetime capabilities supplied to window startup hooks.
pub struct WindowContext {
id : String
handle : WindowHandle
windows : WindowManager
tasks : @async.TaskGroup[Unit]
events : WindowEventEmitter
locale_preferences : @locale.LocalePreferences
}
///|
/// A typed event destination bound to one explicit window.
pub struct WindowEventEmitter {
emit_event : async (@proton_contract.ContractRoute, String, Json) -> Unit noraise
}
///|
priv struct ApplicationLifecycleActivation {
shutdown : async () -> Unit
}
///|
priv struct ApplicationLifecycleHook {
start : async (ApplicationContext) -> ApplicationLifecycleActivation
}
///|
priv struct WindowLifecycleActivation {
close : async () -> Unit
}
///|
priv struct WindowLifecycleHook {
start : async (WindowContext) -> WindowLifecycleActivation
}
///|
fn ApplicationContext::ApplicationContext(
tasks : @async.TaskGroup[Unit],
windows : WindowManager,
locale_preferences : @locale.LocalePreferences,
request_quit? : () -> Unit = fn() { },
application_identifier? : String = "",
application_executable? : String = "",
application_arguments? : Array[String] = [],
url_schemes? : Array[String] = [],
) -> ApplicationContext {
ApplicationContext::{
tasks,
windows,
locale_preferences,
request_quit,
application_identifier,
application_executable,
application_arguments,
url_schemes,
}
}
///|
fn WindowContext::WindowContext(
id : String,
handle : WindowHandle,
windows : WindowManager,
tasks : @async.TaskGroup[Unit],
events : WindowEventEmitter,
locale_preferences : @locale.LocalePreferences,
) -> WindowContext {
WindowContext::{ id, handle, windows, tasks, events, locale_preferences, }
}
///|
/// Returns the immutable application locale selected at startup.
pub fn ApplicationContext::locale(self : ApplicationContext) -> @locale.Locale {
self.locale_preferences.locale()
}
///|
/// Returns the application's ordered language preferences.
pub fn ApplicationContext::preferred_languages(
self : ApplicationContext,
) -> Array[@locale.Locale] {
self.locale_preferences.preferred_languages()
}
///|
/// Returns the immutable application locale selected at startup.
pub fn WindowContext::locale(self : WindowContext) -> @locale.Locale {
self.locale_preferences.locale()
}
///|
/// Returns the application's ordered language preferences.
pub fn WindowContext::preferred_languages(
self : WindowContext,
) -> Array[@locale.Locale] {
self.locale_preferences.preferred_languages()
}
///|
/// Returns the declarative id of this window. The primary window uses
/// `"main"`.
pub fn WindowContext::id(self : WindowContext) -> String {
self.id
}
///|
fn WindowEventEmitter::WindowEventEmitter(
emit_event : async (@proton_contract.ContractRoute, String, Json) -> Unit noraise,
) -> WindowEventEmitter {
WindowEventEmitter::{ emit_event, }
}
///|
/// Returns the structured task group owned by this application.
pub fn ApplicationContext::task_group(
self : ApplicationContext,
) -> @async.TaskGroup[Unit] {
self.tasks
}
///|
/// Returns the window manager owned by this running application.
pub fn ApplicationContext::windows(self : ApplicationContext) -> WindowManager {
self.windows
}
///|
/// Requests an orderly application shutdown.
///
/// Proton closes every open window, runs configured close interception, and
/// destroys the native runtime after all windows have closed. A denied window
/// close cancels this quit request.
pub fn ApplicationContext::quit(self : ApplicationContext) -> Unit {
(self.request_quit)()
}
///|
fn ApplicationContext::protocol_client_values(
self : ApplicationContext,
scheme : String,
executable : String?,
) -> (String, String) raise AppControlError {
let scheme = scheme.trim().to_owned().to_lower()
guard app_url_scheme_is_valid(scheme) else {
raise InvalidProtocolScheme(scheme~)
}
guard self.url_schemes.contains(scheme) else {
raise UndeclaredProtocolScheme(scheme~)
}
let executable = executable.unwrap_or(self.application_executable)
guard executable.trim().to_owned() != "" else {
raise InvalidExecutable(path=executable)
}
(scheme, executable)
}
///|
fn app_control_native_error(
action : String,
error : @native.NativeError,
) -> AppControlError {
NativeControlFailure(action~, status=error.status(), detail=error.message())
}
///|
/// Sets this application as the default handler for a declared URL scheme.
///
/// `executable` and `arguments` match Electron's Windows-only command
/// override. Other platforms use the packaged application identity.
pub fn ApplicationContext::set_as_default_protocol_client(
self : ApplicationContext,
scheme : String,
executable? : String,
arguments? : Array[String] = [],
) -> Bool raise AppControlError {
let (scheme, executable) = self.protocol_client_values(scheme, executable)
@native.protocol_client_set(
scheme,
self.application_identifier,
executable,
arguments,
) catch {
error =>
raise app_control_native_error("set default protocol client", error)
}
}
///|
/// Removes this application as the default handler for a declared URL scheme.
///
/// Electron exposes removal on macOS and Windows. Linux returns `false`.
pub fn ApplicationContext::remove_default_protocol_client(
self : ApplicationContext,
scheme : String,
executable? : String,
arguments? : Array[String] = [],
) -> Bool raise AppControlError {
let (scheme, executable) = self.protocol_client_values(scheme, executable)
@native.protocol_client_remove(
scheme,
self.application_identifier,
executable,
arguments,
) catch {
error =>
raise app_control_native_error("remove default protocol client", error)
}
}
///|
/// Reports whether this application handles a declared URL scheme by default.
pub fn ApplicationContext::is_default_protocol_client(
self : ApplicationContext,
scheme : String,
executable? : String,
arguments? : Array[String] = [],
) -> Bool raise AppControlError {
let (scheme, executable) = self.protocol_client_values(scheme, executable)
@native.protocol_client_is_default(
scheme,
self.application_identifier,
executable,
arguments,
) catch {
error =>
raise app_control_native_error("query default protocol client", error)
}
}
///|
/// Schedules a new application instance after the current instance exits.
///
/// Calling this method multiple times schedules multiple instances. It does
/// not quit the current application; call `quit` or `exit` separately.
pub fn ApplicationContext::relaunch(
self : ApplicationContext,
options? : RelaunchOptions,
) -> Unit raise AppControlError {
let (executable, arguments) = self.relaunch_command(options)
@native.process_schedule_relaunch(executable, arguments) catch {
error =>
raise app_control_native_error("schedule application relaunch", error)
}
}
///|
fn ApplicationContext::relaunch_command(
self : ApplicationContext,
options : RelaunchOptions?,
) -> (String, Array[String]) raise AppControlError {
let (executable, arguments) = match options {
None => (self.application_executable, self.application_arguments.copy())
Some({ executable: None, arguments: None, }) =>
(self.application_executable, self.application_arguments.copy())
Some(options) =>
(
options.executable.unwrap_or(self.application_executable),
options.arguments.unwrap_or([]),
)
}
guard executable.trim().to_owned() != "" else {
raise InvalidExecutable(path=executable)
}
(executable, arguments)
}
///|
/// Immediately terminates the process with `exit_code`.
///
/// This skips window close interception and lifecycle shutdown hooks. Any
/// relaunches already scheduled through `relaunch` are started first.
pub fn ApplicationContext::exit(
self : ApplicationContext,
exit_code? : Int = 0,
) -> Unit {
ignore(self)
@native.process_exit(exit_code)
}
///|
/// Returns the session-controlled handle for this concrete window instance.
pub fn WindowContext::handle(self : WindowContext) -> WindowHandle {
self.handle
}
///|
/// Returns the application window manager.
pub fn WindowContext::windows(self : WindowContext) -> WindowManager {
self.windows
}
///|
/// Returns the structured task group owned by this window.
pub fn WindowContext::task_group(
self : WindowContext,
) -> @async.TaskGroup[Unit] {
self.tasks
}
///|
/// Returns an event emitter bound to this window's active page.
pub fn WindowContext::events(self : WindowContext) -> WindowEventEmitter {
self.events
}
///|
/// Emits a typed event to this emitter's window.
pub async fn[Payload : ToJson] WindowEventEmitter::emit(
self : WindowEventEmitter,
event : @proton_contract.Event[Payload],
payload : Payload,
) -> Unit {
event.validate()
(self.emit_event)(
event.contract_route(),
event.name(),
ToJson::to_json(payload),
)
}
///|
async fn start_application_lifecycle_hooks(
hooks : Array[ApplicationLifecycleHook],
context : ApplicationContext,
group : @async.TaskGroup[Unit],
cleanup_failures : Array[AppCleanupError],
) -> Unit raise AppRunError {
for index, hook in hooks {
let activation = (hook.start)(context) catch {
error =>
raise LifecycleHookError(
ApplicationStart(index~, detail=@debug.render(Repr(error))),
)
}
group.add_defer(async fn() {
@async.protect_from_cancel(activation.shutdown) catch {
error =>
cleanup_failures.push(
LifecycleHook(
ApplicationShutdown(index~, detail=@debug.render(Repr(error))),
),
)
}
})
}
}
///|
async fn start_window_lifecycle_hooks(
hooks : Array[WindowLifecycleHook],
context : WindowContext,
group : @async.TaskGroup[Unit],
cleanup_failures : Array[AppCleanupError],
) -> Unit raise AppRunError {
for index, hook in hooks {
let activation = (hook.start)(context) catch {
error =>
raise LifecycleHookError(
WindowReady(index~, detail=@debug.render(Repr(error))),
)
}
group.add_defer(async fn() {
@async.protect_from_cancel(activation.close) catch {
error =>
cleanup_failures.push(
LifecycleHook(
WindowClose(index~, detail=@debug.render(Repr(error))),
),
)
}
})
}
}