feat(autostart): start minimized to the tray on login (#3)
Build Lotus Chat Desktop / prepare (push) Successful in 7s
Build Lotus Chat Desktop / build-windows (push) Successful in 34m32s
Build Lotus Chat Desktop / build-linux (push) Successful in 36m5s
Build Lotus Chat Desktop / build-arch (push) Successful in 19s
Build Lotus Chat Desktop / update-manifest (push) Successful in 4s

- Launch-on-login now starts the app with `--autostart`. With "Start
  minimized" on (the default), that launch begins "settled", so neither
  the page-load reveal nor the 8 s failsafe shows the window: it stays in
  the tray with sync and notifications running. The tray, a second launch
  or a deep link reveals it.
- The choice is a file in the app config dir (`start-minimized`), read
  before the web client loads; `get_start_minimized` / `set_start_minimized`
  back the new Settings switch.
- Existing registrations made without args are rewritten at startup
  (re-enable), so current launch-on-login users get `--autostart` too.
- window-state no longer restores VISIBLE: it show()ed the window at
  creation whenever it was last closed visible (or on first run), before
  the page painted and even on a login start. Visibility is owned by the
  page-load reveal + failsafe.

Verified on Linux under Xvfb with the debug build:
  normal launch → shown; --autostart → hidden (past the failsafe) and still
  running; --autostart with the setting off → shown; --autostart after
  quitting visible → hidden; normal launch after quitting visible → shown;
  a second launch reveals the hidden instance; an existing autostart entry
  gets `--autostart` added.

Bump cinny (the Settings switch and the SW protocol guard).

Closes #3

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
Lotus CI
2026-09-23 22:46:24 -04:00
co-authored by Claude Opus 5.5
parent 9e320fa326
commit 67253d02f8
2 changed files with 69 additions and 5 deletions
+68 -4
View File
@@ -582,6 +582,42 @@ fn get_tray_dnd(app: tauri::AppHandle) -> bool {
.unwrap_or(false)
}
/// cinny-desktop #3: launch-on-login starts the app with this arg, so a login
/// start can stay in the tray instead of opening the window.
const AUTOSTART_ARG: &str = "--autostart";
/// Where the "start minimized when launched at login" choice lives. A plain file
/// in the app config dir, because it is needed natively before the web client
/// (and its settings) has loaded.
fn start_minimized_file(app: &tauri::AppHandle) -> Option<std::path::PathBuf> {
app.path()
.app_config_dir()
.ok()
.map(|dir| dir.join("start-minimized"))
}
/// The saved choice; defaults to on (a login start lands in the tray).
fn read_start_minimized(app: &tauri::AppHandle) -> bool {
start_minimized_file(app)
.and_then(|path| std::fs::read_to_string(path).ok())
.map(|value| value.trim() != "0")
.unwrap_or(true)
}
#[tauri::command]
fn get_start_minimized(app: tauri::AppHandle) -> bool {
read_start_minimized(&app)
}
#[tauri::command]
fn set_start_minimized(app: tauri::AppHandle, value: bool) -> Result<(), String> {
let path = start_minimized_file(&app).ok_or("no app config dir")?;
if let Some(dir) = path.parent() {
std::fs::create_dir_all(dir).map_err(|e| e.to_string())?;
}
std::fs::write(path, if value { "1" } else { "0" }).map_err(|e| e.to_string())
}
/// Paint a small white-ringed dot of `color` into the bottom-right (or, with
/// `left`, bottom-left) corner of an RGBA buffer, in place. Cross-platform
/// (operates on raw pixels).
@@ -779,6 +815,8 @@ pub fn run() {
set_tray_update_ready,
set_tray_call_state,
set_taskbar_progress,
get_start_minimized,
set_start_minimized,
flash_window,
focus_main_window,
send_notification,
@@ -805,10 +843,16 @@ pub fn run() {
// decorated=false at startup would re-create the frameless window
// BEFORE lib.rs applies Mica (a broken combination) and before the web
// side has pushed the user's current setting.
// VISIBLE is excluded too: the plugin `show()`s the window as soon as
// it is created whenever the saved state was visible (or nothing was
// saved yet) — before the page paints, and even on a launch-on-login
// start that should stay in the tray (cinny-desktop #3). Visibility is
// owned by the page-load reveal + failsafe in `setup`.
tauri_plugin_window_state::Builder::default()
.with_state_flags(
tauri_plugin_window_state::StateFlags::all()
& !tauri_plugin_window_state::StateFlags::DECORATIONS,
& !tauri_plugin_window_state::StateFlags::DECORATIONS
& !tauri_plugin_window_state::StateFlags::VISIBLE,
)
.build(),
)
@@ -822,10 +866,11 @@ pub fn run() {
// P6-1 launch-on-login. The web side drives it via the plugin's own
// `plugin:autostart|enable`/`disable`/`is-enabled` commands (no wrapper
// command). The MacosLauncher arg is mandatory by the plugin API even
// though macOS is out of scope; `None` = no extra launch args.
// though macOS is out of scope. `--autostart` marks a login start
// (cinny-desktop #3).
builder = builder.plugin(tauri_plugin_autostart::init(
tauri_plugin_autostart::MacosLauncher::LaunchAgent,
None,
Some(vec![AUTOSTART_ARG]),
));
}
@@ -938,7 +983,15 @@ pub fn run() {
// set true by the on_page_load reveal (window shown) and by the
// close-to-tray handler (window intentionally hidden). The 8s failsafe
// below only reveals the window if neither of those has happened.
let window_settled = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
//
// cinny-desktop #3: a launch-on-login start with "start minimized" on
// begins settled, so neither the reveal nor the failsafe shows the
// window — it stays in the tray (sync + notifications running) until
// the tray icon, a second launch or a deep link reveals it.
let hide_on_launch = std::env::args().any(|arg| arg == AUTOSTART_ARG)
&& read_start_minimized(app.handle());
let window_settled =
std::sync::Arc::new(std::sync::atomic::AtomicBool::new(hide_on_launch));
let settled_page_load = window_settled.clone();
let window = WebviewWindowBuilder::new(app, "main".to_string(), window_url)
.title("Lotus Chat")
@@ -1013,6 +1066,17 @@ pub fn run() {
}
});
// Registrations made before the `--autostart` arg existed have no
// args; re-enabling rewrites the OS entry with the current ones.
#[cfg(desktop)]
{
use tauri_plugin_autostart::ManagerExt;
let autolaunch = app.autolaunch();
if autolaunch.is_enabled().unwrap_or(false) {
let _ = autolaunch.enable();
}
}
// Deep links (matrix:): route both the cold-start case and the
// already-running case (forwarded via single-instance argv) into the
// web client.