///|
let default_config_file = "proton.project.json"
///|
fn App::new(
title : String,
width : Int,
height : Int,
entry : @manifest.AppEntry,
debug : Int,
size_hint : @manifest.WindowSizeHint,
) -> App {
App::{
file_path: None,
auto_config: false,
window: @manifest.WindowManifest::new(title, width, height, size_hint~),
entry,
windows: [],
debug_level_value: debug,
headless_value: false,
single_instance_identifier: None,
title_overridden: false,
size_overridden: false,
titlebar_style_overridden: false,
entry_overridden: false,
debug_overridden: false,
bridge_startup_timeout_value_ms: default_bridge_startup_timeout_ms,
menu: None,
extension_settings: Map([]),
permission_grants: [],
command_extensions: Map([]),
command_registrars: [],
application_lifecycle_hooks: [],
planned_views: [],
launch_input_handlers: [],
window_event_handlers: [],
view_event_handlers: [],
window_close_handler: None,
navigation_handler: None,
popup_handler: None,
download_handler: None,
certificate_handler: None,
media_handler: None,
download_event_handlers: [],
update_handlers: [],
window_lifecycle_hooks: [],
validation_errors: [],
}
}
///|
fn App::set_entry(self : App, entry : @manifest.AppEntry) -> App {
self.entry_overridden = true
self.entry = entry
self
}
///|
fn App::validation_error(self : App) -> AppConfigurationError? {
if self.bridge_startup_timeout_value_ms <= 0 {
Some(
InvalidSetting(
name="bridge_startup_timeout_ms",
message="must be positive",
),
)
} else {
self.validation_errors.get(0)
}
}
///|
fn App::resolved_app_config(
self : App,
) -> ResolvedAppConfig raise AppConfigurationError {
match self.file_path {
Some(path) => self.resolved_config_manifest(path)
None =>
if self.auto_config {
match implicit_config_path() {
Some(path) => self.resolved_config_manifest(path)
None => self.inline_app_config()
}
} else {
self.inline_app_config()
}
}
}
///|
fn App::resolved_config_manifest(
self : App,
path : String,
) -> ResolvedAppConfig raise AppConfigurationError {
let project = @bootstrap.load_proton_project_config_from_file(path) catch {
error => raise Bootstrap(error)
}
let dev_mode = proton_dev_mode_enabled()
let loaded = if dev_mode {
resolved_dev_manifest(project)
} else {
project.manifest()
}
self.merge_config(
loaded,
project=Some(project),
update_channel=if dev_mode {
None
} else {
resolved_update_channel(project)
},
)
}
///|
fn resolved_dev_manifest(
config : @bootstrap.ProtonProjectConfig,
) -> @bootstrap.LoadedAppManifest {
match proton_dev_url_override() {
Some(url) => {
let loaded = config.manifest()
@bootstrap.LoadedAppManifest::new(
manifest_with_entry(loaded.manifest(), @manifest.AppEntry::Url(url)),
base_dir=loaded.base_dir(),
)
}
None => config.dev_manifest()
}
}
///|
fn manifest_with_entry(
manifest : @manifest.AppManifest,
entry : @manifest.AppEntry,
) -> @manifest.AppManifest {
@manifest.AppManifest::new(
manifest.window,
entry,
debug=manifest.debug,
windows=manifest.windows,
extensions=manifest.extensions,
permissions=manifest.permission_grants(),
)
}
///|
fn implicit_config_path() -> String? {
let environment = match @env.get_env_var("PROTON_CONFIG_PATH") {
Some(value) => {
let trimmed = value.trim().to_owned()
if trimmed != "" {
Some(trimmed)
} else {
None
}
}
None => None
}
choose_implicit_config_path(
bundled=bundled_config_path(),
environment~,
cwd_has_default=@mbfs.path_exists(default_config_file),
)
}
///|
fn choose_implicit_config_path(
bundled~ : String?,
environment~ : String?,
cwd_has_default~ : Bool,
) -> String? {
match bundled {
Some(path) => Some(path)
None =>
match environment {
Some(path) => Some(path)
None => if cwd_has_default { Some(default_config_file) } else { None }
}
}
}
///|
fn bundled_config_path() -> String? {
let argv = @env.args()
guard argv.length() > 0 else { return None }
let executable : @mbpath.Path = argv[0]
let executable = executable.resolve()
let executable_dir = facade_path_parent(executable.to_string())
let adjacent = @mbpath.Path(executable_dir)
.join(default_config_file)
.normalize()
.to_string()
if @mbfs.path_exists(adjacent) {
return Some(adjacent)
}
let resources = @mbpath.Path(executable_dir)
.join("../Resources/proton.project.json")
.normalize()
.to_string()
if @mbfs.path_exists(resources) {
Some(resources)
} else {
None
}
}
///|
fn facade_path_parent(path : String) -> String {
let native_path : @mbpath.Path = path
native_path.dirname().to_string()
}
///|
fn proton_dev_mode_enabled() -> Bool {
match @env.get_env_var("PROTON_DEV") {
Some(value) if env_flag_is_enabled(value) => true
_ =>
match @env.get_env_var("PROTON_MODE") {
Some(value) => value.trim().to_owned().to_lower() == "dev"
None => false
}
}
}
///|
fn proton_dev_url_override() -> String? {
match @env.get_env_var("PROTON_FRONTEND_URL") {
Some(value) => {
let trimmed = value.trim().to_owned()
if trimmed == "" {
None
} else {
Some(trimmed)
}
}
None => None
}
}
///|
fn env_flag_is_enabled(value : String) -> Bool {
match value.trim().to_owned().to_lower() {
"" | "0" | "false" | "no" | "off" => false
_ => true
}
}
///|
fn proton_headless_mode_enabled() -> Bool {
match @env.get_env_var("PROTON_HEADLESS") {
Some(value) => env_flag_is_enabled(value)
None => false
}
}
///|
fn App::resolved_headless(self : App) -> Bool {
self.headless_value || proton_headless_mode_enabled()
}
///|
fn App::inline_app_config(self : App) -> ResolvedAppConfig {
ResolvedAppConfig::{
manifest: self.inline_manifest(),
permission_base_path: current_permission_base_path(),
application_identifier: self.single_instance_identifier,
instance_identifier: self.single_instance_identifier,
url_schemes: [],
document_extensions: [],
update_channel: None,
}
}
///|
/// Resolves the update channel from a loaded project configuration.
fn resolved_update_channel(
project : @bootstrap.ProtonProjectConfig,
) -> ResolvedUpdateChannel? {
guard project.updater() is Some(config) else { return None }
Some(ResolvedUpdateChannel::{ config, })
}
///|
fn App::inline_manifest(self : App) -> @manifest.AppManifest {
@manifest.AppManifest::new(
self.window,
self.entry,
debug=self.debug_level_value,
windows=self.windows,
extensions=self.extension_settings,
permissions=self.permission_grants,
)
}
///|
fn App::merge_config(
self : App,
loaded : @bootstrap.LoadedAppManifest,
project? : @bootstrap.ProtonProjectConfig? = None,
update_channel? : ResolvedUpdateChannel? = None,
) -> ResolvedAppConfig {
self.warn_config_overrides()
let manifest = loaded.manifest()
let window = self.merged_window(manifest.window)
let entry = if self.entry_overridden { self.entry } else { manifest.entry }
let debug = if self.debug_overridden {
self.debug_level_value
} else {
manifest.debug
}
let extensions = merge_extension_settings(
manifest.extensions,
self.extension_settings,
)
let permissions = manifest.permission_grants()
for grant in self.permission_grants {
permissions.push(grant)
}
let windows = manifest.windows.copy()
for window in self.windows {
windows.push(window)
}
ResolvedAppConfig::{
manifest: @manifest.AppManifest::new(
window,
entry,
debug~,
windows~,
extensions~,
permissions~,
),
permission_base_path: match loaded.base_dir() {
Some(base_dir) => {
let base : @mbpath.Path = base_dir
base.resolve().to_string()
}
None => current_permission_base_path()
},
application_identifier: match self.single_instance_identifier {
Some(identifier) => Some(identifier)
None =>
match project {
Some(project) => project.metadata().identifier()
None => None
}
},
instance_identifier: match self.single_instance_identifier {
Some(identifier) => Some(identifier)
None =>
match project {
Some(project) if project.single_instance() =>
project.metadata().identifier()
_ => None
}
},
url_schemes: resolved_url_schemes(project),
document_extensions: resolved_document_extensions(project),
update_channel,
}
}
///|
fn resolved_url_schemes(
project : @bootstrap.ProtonProjectConfig?,
) -> Array[String] {
match project {
Some(project) =>
match project.bundle() {
Some(bundle) => bundle.url_schemes()
None => []
}
None => []
}
}
///|
fn resolved_document_extensions(
project : @bootstrap.ProtonProjectConfig?,
) -> Array[String] {
let extensions : Array[String] = []
match project {
Some(project) =>
match project.bundle() {
Some(bundle) =>
for extension in bundle.document_extensions() {
extensions.push(extension.to_lower())
}
None => ()
}
None => ()
}
extensions
}
///|
fn current_permission_base_path() -> String {
let current : @mbpath.Path = "."
current.resolve().to_string()
}
///|
fn App::warn_config_overrides(self : App) -> Unit {
if self.title_overridden {
println(
"warning: Proton API App::title overrides proton.project.json window.title",
)
}
if self.size_overridden {
println(
"warning: Proton API App::size overrides proton.project.json window.width/window.height",
)
}
if self.titlebar_style_overridden {
println(
"warning: Proton API App::titlebar_style overrides proton.project.json window.titlebar_style",
)
}
if self.entry_overridden {
println(
"warning: Proton API App::entry_* overrides proton.project.json entry",
)
}
if self.debug_overridden {
println(
"warning: Proton API App::debug/debug_level overrides proton.project.json debug",
)
}
}
///|
fn merge_extension_settings(
base : Map[String, @manifest.ExtensionSetting],
overrides : Map[String, @manifest.ExtensionSetting],
) -> Map[String, @manifest.ExtensionSetting] {
let merged : Map[String, @manifest.ExtensionSetting] = Map([])
for name, setting in base {
merged[name] = setting
}
for name, setting in overrides {
merged[name] = setting
}
merged
}
///|
fn App::merged_window(
self : App,
window : @manifest.WindowManifest,
) -> @manifest.WindowManifest {
let title = if self.title_overridden {
self.window.title
} else {
window.title
}
let width = if self.size_overridden {
self.window.width
} else {
window.width
}
let height = if self.size_overridden {
self.window.height
} else {
window.height
}
let titlebar_style = if self.titlebar_style_overridden {
self.window.titlebar_style
} else {
window.titlebar_style
}
@manifest.WindowManifest::new(
title,
width,
height,
size_hint=window.size_hint,
titlebar_style~,
)
}