feat: console only in debug, update config parsing
This commit is contained in:
@@ -0,0 +1,40 @@
|
|||||||
|
# bropicker config
|
||||||
|
# Расположение: %APPDATA%\bropicker\config.toml
|
||||||
|
# Открывается из пикера кнопкой шестерёнки (subl/notepad)
|
||||||
|
|
||||||
|
# [[browsers]] — один блок на запись пикера.
|
||||||
|
# path — полный путь к exe (или имя из PATH)
|
||||||
|
# flags — аргументы запуска; пробелы внутри значения — в кавычках
|
||||||
|
# icon — необязательно: путь к png/svg, иначе подбирается по имени
|
||||||
|
|
||||||
|
[[browsers]]
|
||||||
|
name = "Firefox"
|
||||||
|
path = 'C:\Program Files\Mozilla Firefox\firefox.exe'
|
||||||
|
flags = ""
|
||||||
|
|
||||||
|
# Профили Chrome: имена каталогов профилей смотри на chrome://version
|
||||||
|
# (Profile Path: ...\User Data\Default → "Default",
|
||||||
|
# ...\User Data\Profile 1 → "Profile 1")
|
||||||
|
|
||||||
|
[[browsers]]
|
||||||
|
name = "Chrome — Work"
|
||||||
|
path = 'C:\Program Files (x86)\Google\Chrome\Application\chrome.exe'
|
||||||
|
flags = '--profile-directory="Default"'
|
||||||
|
|
||||||
|
[[browsers]]
|
||||||
|
name = "Chrome — Personal"
|
||||||
|
path = 'C:\Program Files (x86)\Google\Chrome\Application\chrome.exe'
|
||||||
|
flags = '--profile-directory="Profile 1"'
|
||||||
|
|
||||||
|
[[browsers]]
|
||||||
|
name = "Firefox — Work"
|
||||||
|
path = 'C:\Program Files\Mozilla Firefox\firefox.exe'
|
||||||
|
flags = '-P "Work"'
|
||||||
|
|
||||||
|
# Запомненные домены: домен = имя записи из [[browsers]]
|
||||||
|
[remembered]
|
||||||
|
"github.com" = "Chrome — Work"
|
||||||
|
|
||||||
|
[settings]
|
||||||
|
remember_choice = true
|
||||||
|
always_ask = true
|
||||||
@@ -149,6 +149,13 @@ HTML-макеты → PNG: **headless Edge** (`--headless --screenshot`), он
|
|||||||
cwd = system32 и иконки бы отвалились); проверено запуском из
|
cwd = system32 и иконки бы отвалились); проверено запуском из
|
||||||
LOCALAPPDATA с cwd=System32
|
LOCALAPPDATA с cwd=System32
|
||||||
- [x] Release-профиль: lto + codegen-units=1 + strip
|
- [x] Release-профиль: lto + codegen-units=1 + strip
|
||||||
|
- [x] Release без консоли: `windows_subsystem="windows"` только для не-debug
|
||||||
|
(в debug консоль остаётся); весь вывод → `%APPDATA%\bropicker\bropicker.log`
|
||||||
|
(config::log с unix-timestamp)
|
||||||
|
- [x] Парсер флагов понимает кавычки (`split_flags`) — профили браузеров:
|
||||||
|
`flags = '--profile-directory="Profile 1"'` (Chrome), `-P "Work"` (Firefox);
|
||||||
|
пример: docs/config.example.toml
|
||||||
|
- [x] Cold start release: 131 ms (после добавления файлового лога)
|
||||||
|
|
||||||
## Этап F — релиз
|
## Этап F — релиз
|
||||||
|
|
||||||
|
|||||||
+190
-171
@@ -1,171 +1,190 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
pub fn log(msg: &str) {
|
||||||
pub struct BrowserEntry {
|
use std::io::Write;
|
||||||
pub name: String,
|
let Ok(appdata) = std::env::var("APPDATA") else {
|
||||||
pub path: String,
|
return;
|
||||||
#[serde(default)]
|
};
|
||||||
pub flags: String,
|
let dir = Path::new(&appdata).join("bropicker");
|
||||||
#[serde(default)]
|
if std::fs::create_dir_all(&dir).is_err() {
|
||||||
pub icon: String,
|
return;
|
||||||
}
|
}
|
||||||
|
let path = dir.join("bropicker.log");
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
let ts = std::time::SystemTime::now()
|
||||||
pub struct Settings {
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
#[serde(default = "default_true")]
|
.map(|d| d.as_secs())
|
||||||
pub remember_choice: bool,
|
.unwrap_or(0);
|
||||||
#[serde(default = "default_true")]
|
if let Ok(mut f) = std::fs::OpenOptions::new().create(true).append(true).open(&path) {
|
||||||
pub always_ask: bool,
|
let _ = writeln!(f, "[{ts}] {msg}");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
fn default_true() -> bool {
|
|
||||||
true
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
}
|
pub struct BrowserEntry {
|
||||||
|
pub name: String,
|
||||||
impl Default for Settings {
|
pub path: String,
|
||||||
fn default() -> Self {
|
#[serde(default)]
|
||||||
Self {
|
pub flags: String,
|
||||||
remember_choice: true,
|
#[serde(default)]
|
||||||
always_ask: true,
|
pub icon: String,
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct Settings {
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
#[serde(default = "default_true")]
|
||||||
pub struct Config {
|
pub remember_choice: bool,
|
||||||
#[serde(default)]
|
#[serde(default = "default_true")]
|
||||||
pub browsers: Vec<BrowserEntry>,
|
pub always_ask: bool,
|
||||||
#[serde(default)]
|
}
|
||||||
pub remembered: HashMap<String, String>,
|
|
||||||
#[serde(default)]
|
fn default_true() -> bool {
|
||||||
pub settings: Settings,
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn config_path() -> PathBuf {
|
impl Default for Settings {
|
||||||
if let Ok(p) = std::env::var("BP_CONFIG") {
|
fn default() -> Self {
|
||||||
return PathBuf::from(p);
|
Self {
|
||||||
}
|
remember_choice: true,
|
||||||
std::env::var("APPDATA")
|
always_ask: true,
|
||||||
.map(|d| PathBuf::from(d).join("bropicker").join("config.toml"))
|
}
|
||||||
.unwrap_or_else(|_| PathBuf::from("config.toml"))
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load() -> Config {
|
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
||||||
let path = config_path();
|
pub struct Config {
|
||||||
match std::fs::read_to_string(&path) {
|
#[serde(default)]
|
||||||
Ok(s) => toml::from_str(&s).unwrap_or_else(|e| {
|
pub browsers: Vec<BrowserEntry>,
|
||||||
eprintln!("[bp] config parse error ({path:?}): {e}");
|
#[serde(default)]
|
||||||
Config::default()
|
pub remembered: HashMap<String, String>,
|
||||||
}),
|
#[serde(default)]
|
||||||
Err(_) => Config::default(),
|
pub settings: Settings,
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
pub fn config_path() -> PathBuf {
|
||||||
pub fn save(cfg: &Config) {
|
if let Ok(p) = std::env::var("BP_CONFIG") {
|
||||||
let path = config_path();
|
return PathBuf::from(p);
|
||||||
if let Some(dir) = path.parent() {
|
}
|
||||||
let _ = std::fs::create_dir_all(dir);
|
std::env::var("APPDATA")
|
||||||
}
|
.map(|d| PathBuf::from(d).join("bropicker").join("config.toml"))
|
||||||
match toml::to_string_pretty(cfg) {
|
.unwrap_or_else(|_| PathBuf::from("config.toml"))
|
||||||
Ok(s) => {
|
}
|
||||||
if let Err(e) = std::fs::write(&path, s) {
|
|
||||||
eprintln!("[bp] config save failed ({path:?}): {e}");
|
pub fn load() -> Config {
|
||||||
}
|
let path = config_path();
|
||||||
}
|
match std::fs::read_to_string(&path) {
|
||||||
Err(e) => eprintln!("[bp] config serialize failed: {e}"),
|
Ok(s) => toml::from_str(&s).unwrap_or_else(|e| {
|
||||||
}
|
crate::config::log(&format!("[bp] config parse error ({path:?}): {e}"));
|
||||||
}
|
Config::default()
|
||||||
|
}),
|
||||||
pub fn ensure_browsers(cfg: &mut Config) {
|
Err(_) => Config::default(),
|
||||||
if cfg.browsers.is_empty() {
|
}
|
||||||
cfg.browsers = detect_browsers();
|
}
|
||||||
eprintln!("[bp] autodetected {} browsers", cfg.browsers.len());
|
|
||||||
if !cfg.browsers.is_empty() {
|
pub fn save(cfg: &Config) {
|
||||||
save(cfg);
|
let path = config_path();
|
||||||
}
|
if let Some(dir) = path.parent() {
|
||||||
}
|
let _ = std::fs::create_dir_all(dir);
|
||||||
}
|
}
|
||||||
|
match toml::to_string_pretty(cfg) {
|
||||||
pub fn domain_of(url: &str) -> String {
|
Ok(s) => {
|
||||||
let rest = url.split("://").nth(1).unwrap_or(url);
|
if let Err(e) = std::fs::write(&path, s) {
|
||||||
let host = rest.split(['/', '?', '#']).next().unwrap_or("");
|
log(&format!("[bp] config save failed ({path:?}): {e}"));
|
||||||
let host = host.split('@').next().unwrap_or(host);
|
}
|
||||||
let host = host.split(':').next().unwrap_or(host);
|
}
|
||||||
host.trim_start_matches("www.").to_lowercase()
|
Err(e) => log(&format!("[bp] config serialize failed: {e}")),
|
||||||
}
|
}
|
||||||
|
}
|
||||||
fn extract_exe(cmd: &str) -> Option<String> {
|
|
||||||
let t = cmd.trim();
|
pub fn ensure_browsers(cfg: &mut Config) {
|
||||||
if t.starts_with('"') {
|
if cfg.browsers.is_empty() {
|
||||||
t.split('"').nth(1).map(String::from)
|
cfg.browsers = detect_browsers();
|
||||||
} else {
|
log(&format!("[bp] autodetected {} browsers", cfg.browsers.len()));
|
||||||
t.split_whitespace().next().map(String::from)
|
if !cfg.browsers.is_empty() {
|
||||||
}
|
save(cfg);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
pub fn detect_browsers() -> Vec<BrowserEntry> {
|
}
|
||||||
use winreg::enums::{HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE};
|
|
||||||
use winreg::RegKey;
|
pub fn domain_of(url: &str) -> String {
|
||||||
|
let rest = url.split("://").nth(1).unwrap_or(url);
|
||||||
let mut out: Vec<BrowserEntry> = Vec::new();
|
let host = rest.split(['/', '?', '#']).next().unwrap_or("");
|
||||||
|
let host = host.split('@').next().unwrap_or(host);
|
||||||
for hive in [HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER] {
|
let host = host.split(':').next().unwrap_or(host);
|
||||||
let Ok(clients) =
|
host.trim_start_matches("www.").to_lowercase()
|
||||||
RegKey::predef(hive).open_subkey("SOFTWARE\\Clients\\StartMenuInternet")
|
}
|
||||||
else {
|
|
||||||
continue;
|
fn extract_exe(cmd: &str) -> Option<String> {
|
||||||
};
|
let t = cmd.trim();
|
||||||
for key_name in clients.enum_keys().flatten() {
|
if t.starts_with('"') {
|
||||||
let Ok(client) = clients.open_subkey(&key_name) else {
|
t.split('"').nth(1).map(String::from)
|
||||||
continue;
|
} else {
|
||||||
};
|
t.split_whitespace().next().map(String::from)
|
||||||
let friendly: String = client
|
}
|
||||||
.get_value("")
|
}
|
||||||
.unwrap_or_else(|_| key_name.clone());
|
|
||||||
let command: String = client
|
pub fn detect_browsers() -> Vec<BrowserEntry> {
|
||||||
.open_subkey("shell\\open\\command")
|
use winreg::enums::{HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE};
|
||||||
.and_then(|k| k.get_value(""))
|
use winreg::RegKey;
|
||||||
.unwrap_or_default();
|
|
||||||
let Some(path) = extract_exe(&command) else {
|
let mut out: Vec<BrowserEntry> = Vec::new();
|
||||||
continue;
|
|
||||||
};
|
for hive in [HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER] {
|
||||||
if !Path::new(&path).exists() {
|
let Ok(clients) =
|
||||||
continue;
|
RegKey::predef(hive).open_subkey("SOFTWARE\\Clients\\StartMenuInternet")
|
||||||
}
|
else {
|
||||||
if out
|
continue;
|
||||||
.iter()
|
};
|
||||||
.any(|b: &BrowserEntry| b.path.eq_ignore_ascii_case(&path))
|
for key_name in clients.enum_keys().flatten() {
|
||||||
{
|
let Ok(client) = clients.open_subkey(&key_name) else {
|
||||||
continue;
|
continue;
|
||||||
}
|
};
|
||||||
out.push(BrowserEntry {
|
let friendly: String = client
|
||||||
name: friendly,
|
.get_value("")
|
||||||
path,
|
.unwrap_or_else(|_| key_name.clone());
|
||||||
flags: String::new(),
|
let command: String = client
|
||||||
icon: String::new(),
|
.open_subkey("shell\\open\\command")
|
||||||
});
|
.and_then(|k| k.get_value(""))
|
||||||
}
|
.unwrap_or_default();
|
||||||
}
|
let Some(path) = extract_exe(&command) else {
|
||||||
|
continue;
|
||||||
out
|
};
|
||||||
}
|
if !Path::new(&path).exists() {
|
||||||
|
continue;
|
||||||
pub fn icon_for(name: &str) -> &'static str {
|
}
|
||||||
let n = name.to_lowercase();
|
if out
|
||||||
if n.contains("firefox") {
|
.iter()
|
||||||
"logos/firefox_48x48.png"
|
.any(|b: &BrowserEntry| b.path.eq_ignore_ascii_case(&path))
|
||||||
} else if n.contains("chrome") {
|
{
|
||||||
"logos/chrome_48x48.png"
|
continue;
|
||||||
} else if n.contains("opera") {
|
}
|
||||||
"logos/opera_48x48.png"
|
out.push(BrowserEntry {
|
||||||
} else if n.contains("brave") {
|
name: friendly,
|
||||||
"logos/brave_48x48.png"
|
path,
|
||||||
} else if n.contains("яндекс") || n.contains("yandex") {
|
flags: String::new(),
|
||||||
"logos/yandex_48x48.png"
|
icon: String::new(),
|
||||||
} else {
|
});
|
||||||
"icons/globe.svg"
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn icon_for(name: &str) -> &'static str {
|
||||||
|
let n = name.to_lowercase();
|
||||||
|
if n.contains("firefox") {
|
||||||
|
"logos/firefox_48x48.png"
|
||||||
|
} else if n.contains("chrome") {
|
||||||
|
"logos/chrome_48x48.png"
|
||||||
|
} else if n.contains("opera") {
|
||||||
|
"logos/opera_48x48.png"
|
||||||
|
} else if n.contains("brave") {
|
||||||
|
"logos/brave_48x48.png"
|
||||||
|
} else if n.contains("яндекс") || n.contains("yandex") {
|
||||||
|
"logos/yandex_48x48.png"
|
||||||
|
} else {
|
||||||
|
"icons/globe.svg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+42
-15
@@ -1,3 +1,5 @@
|
|||||||
|
#![cfg_attr(all(not(debug_assertions), target_os = "windows"), windows_subsystem = "windows")]
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
@@ -11,6 +13,27 @@ use config::{BrowserEntry, Config};
|
|||||||
|
|
||||||
slint::include_modules!();
|
slint::include_modules!();
|
||||||
|
|
||||||
|
fn split_flags(flags: &str) -> Vec<String> {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
let mut cur = String::new();
|
||||||
|
let mut in_quotes = false;
|
||||||
|
for c in flags.chars() {
|
||||||
|
match c {
|
||||||
|
'"' => in_quotes = !in_quotes,
|
||||||
|
' ' if !in_quotes => {
|
||||||
|
if !cur.is_empty() {
|
||||||
|
out.push(std::mem::take(&mut cur));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => cur.push(c),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !cur.is_empty() {
|
||||||
|
out.push(cur);
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
fn load_icon(rel: &str) -> slint::Image {
|
fn load_icon(rel: &str) -> slint::Image {
|
||||||
if let Ok(exe) = std::env::current_exe() {
|
if let Ok(exe) = std::env::current_exe() {
|
||||||
if let Some(dir) = exe.parent() {
|
if let Some(dir) = exe.parent() {
|
||||||
@@ -41,7 +64,7 @@ fn to_ui_config(entry: &BrowserEntry) -> BrowserConfig {
|
|||||||
fn launch(entry: &BrowserEntry, url: &str) -> std::io::Result<()> {
|
fn launch(entry: &BrowserEntry, url: &str) -> std::io::Result<()> {
|
||||||
let mut cmd = std::process::Command::new(&entry.path);
|
let mut cmd = std::process::Command::new(&entry.path);
|
||||||
if !entry.flags.is_empty() {
|
if !entry.flags.is_empty() {
|
||||||
cmd.args(entry.flags.split_whitespace());
|
cmd.args(split_flags(&entry.flags));
|
||||||
}
|
}
|
||||||
cmd.arg(url).spawn().map(|_| ())
|
cmd.arg(url).spawn().map(|_| ())
|
||||||
}
|
}
|
||||||
@@ -114,17 +137,18 @@ fn init(cfg: Config, url: Option<String>) -> State {
|
|||||||
let entry = browser_model.row_data(index).expect("row exists");
|
let entry = browser_model.row_data(index).expect("row exists");
|
||||||
let url = main_window.get_current_url().to_string();
|
let url = main_window.get_current_url().to_string();
|
||||||
|
|
||||||
println!(
|
|
||||||
"Launching [{}]: {:?} {:?} {}",
|
|
||||||
index, entry.path, entry.flags, url
|
|
||||||
);
|
|
||||||
|
|
||||||
let entry_ref = BrowserEntry {
|
let entry_ref = BrowserEntry {
|
||||||
name: entry.name.to_string(),
|
name: entry.name.to_string(),
|
||||||
path: entry.path.to_string(),
|
path: entry.path.to_string(),
|
||||||
flags: entry.flags.to_string(),
|
flags: entry.flags.to_string(),
|
||||||
icon: String::new(),
|
icon: String::new(),
|
||||||
};
|
};
|
||||||
|
let url = main_window.get_current_url().to_string();
|
||||||
|
|
||||||
|
config::log(&format!(
|
||||||
|
"[bp] launching [{}]: {:?} {:?} {}",
|
||||||
|
index, entry_ref.path, entry_ref.flags, url
|
||||||
|
));
|
||||||
|
|
||||||
match launch(&entry_ref, &url) {
|
match launch(&entry_ref, &url) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
@@ -137,7 +161,7 @@ fn init(cfg: Config, url: Option<String>) -> State {
|
|||||||
}
|
}
|
||||||
let _ = main_window.window().hide();
|
let _ = main_window.window().hide();
|
||||||
}
|
}
|
||||||
Err(e) => eprintln!("launch failed: {e}"),
|
Err(e) => config::log(&format!("[bp] launch failed: {e}")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -159,10 +183,10 @@ fn init(cfg: Config, url: Option<String>) -> State {
|
|||||||
};
|
};
|
||||||
let url = main_window.get_current_url().to_string();
|
let url = main_window.get_current_url().to_string();
|
||||||
|
|
||||||
println!(
|
config::log(&format!(
|
||||||
"Launching [{}]: {:?} {:?} {}",
|
"[bp] launching [{}]: {:?} {:?} {}",
|
||||||
index, entry.path, entry.flags, url
|
index, entry.path, entry.flags, url
|
||||||
);
|
));
|
||||||
|
|
||||||
match launch(&entry, &url) {
|
match launch(&entry, &url) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
@@ -175,7 +199,7 @@ fn init(cfg: Config, url: Option<String>) -> State {
|
|||||||
}
|
}
|
||||||
let _ = main_window.window().hide();
|
let _ = main_window.window().hide();
|
||||||
}
|
}
|
||||||
Err(e) => eprintln!("launch failed: {e}"),
|
Err(e) => config::log(&format!("[bp] launch failed: {e}")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -221,7 +245,7 @@ fn init(cfg: Config, url: Option<String>) -> State {
|
|||||||
.spawn()
|
.spawn()
|
||||||
.or_else(|_| std::process::Command::new("notepad").arg(&path).spawn());
|
.or_else(|_| std::process::Command::new("notepad").arg(&path).spawn());
|
||||||
if let Err(e) = editor {
|
if let Err(e) = editor {
|
||||||
eprintln!("[bp] cannot open editor: {e}");
|
config::log(&format!("[bp] cannot open editor: {e}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -329,9 +353,9 @@ pub fn main() {
|
|||||||
let domain = config::domain_of(url);
|
let domain = config::domain_of(url);
|
||||||
if let Some(name) = cfg.remembered.get(&domain).cloned() {
|
if let Some(name) = cfg.remembered.get(&domain).cloned() {
|
||||||
if cfg.settings.always_ask {
|
if cfg.settings.always_ask {
|
||||||
eprintln!("[bp] '{domain}' remembered -> {name}, but always_ask is on");
|
config::log(&format!("[bp] '{domain}' remembered -> {name}, but always_ask is on"));
|
||||||
} else if let Some(entry) = cfg.browsers.iter().find(|b| b.name == name) {
|
} else if let Some(entry) = cfg.browsers.iter().find(|b| b.name == name) {
|
||||||
eprintln!("[bp] '{domain}' remembered -> launching {name}");
|
config::log(&format!("[bp] '{domain}' remembered -> launching {name}"));
|
||||||
let _ = launch(entry, url);
|
let _ = launch(entry, url);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -360,7 +384,10 @@ pub fn main() {
|
|||||||
winit::center_window(mw.window());
|
winit::center_window(mw.window());
|
||||||
mw.invoke_restore_focus();
|
mw.invoke_restore_focus();
|
||||||
}
|
}
|
||||||
eprintln!("[bp] cold start -> event loop: {:?}", start.elapsed());
|
config::log(&format!(
|
||||||
|
"[bp] cold start -> event loop: {:?}",
|
||||||
|
start.elapsed()
|
||||||
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
state.main_window.run().unwrap();
|
state.main_window.run().unwrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user