361 lines
15 KiB
Rust
361 lines
15 KiB
Rust
mod commands;
|
|
mod community;
|
|
mod config;
|
|
mod cover_logo;
|
|
mod diagnostics;
|
|
mod freenet;
|
|
mod friends;
|
|
mod persona;
|
|
mod hero_crop;
|
|
mod import;
|
|
mod inbox;
|
|
mod ledger;
|
|
mod hero_detector;
|
|
mod loudness;
|
|
mod lyric_align;
|
|
mod lyric_prep;
|
|
mod lyrics;
|
|
mod paths;
|
|
mod pipeline;
|
|
mod playback;
|
|
mod profile;
|
|
mod playlist_formats;
|
|
mod playlists;
|
|
mod provider_playlist;
|
|
mod preview;
|
|
mod setlist_scan;
|
|
mod shared_playlists;
|
|
mod shazam;
|
|
mod slsk_link;
|
|
mod smtc;
|
|
mod spaces;
|
|
mod proposals;
|
|
mod overlap;
|
|
mod state;
|
|
mod thumbbar;
|
|
mod vocal_sep;
|
|
mod youtube_playlist;
|
|
mod youtube_setlist;
|
|
mod ytdlp;
|
|
|
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
|
pub fn run() {
|
|
tauri::Builder::default()
|
|
.manage(state::AppState::default())
|
|
.setup(|app| {
|
|
app.handle().plugin(
|
|
tauri_plugin_log::Builder::default()
|
|
.level(log::LevelFilter::Info)
|
|
.clear_targets()
|
|
.target(tauri_plugin_log::Target::new(
|
|
tauri_plugin_log::TargetKind::Stdout,
|
|
))
|
|
.build(),
|
|
)?;
|
|
app.handle().plugin(tauri_plugin_dialog::init())?;
|
|
// Must run before anything touches the app-data dir.
|
|
config::migrate_app_data(app.handle());
|
|
// Prepare the small local vision model while the user is still on
|
|
// the app shell, so a first artist visit pays only warm inference.
|
|
hero_detector::warm_up(app.handle().clone());
|
|
// Before playback::init: the event pump it spawns starts pushing
|
|
// transport state at the card immediately, and the card has to be
|
|
// registered by then or the first track's status is dropped.
|
|
smtc::init(app.handle());
|
|
// Only subclasses the window here; the buttons themselves go up
|
|
// when the shell says the taskbar button exists.
|
|
thumbbar::init(app.handle());
|
|
playback::init(app.handle());
|
|
playback::prune_cache(app.handle());
|
|
preview::init(app.handle());
|
|
loudness::init(app.handle());
|
|
diagnostics::init(app.handle());
|
|
// The experimental Freenet layer: applies the saved opt-in (off
|
|
// by default) and forwards adapter status to the webview. Fully
|
|
// isolated — its absence or failure touches nothing else.
|
|
freenet::init(app.handle());
|
|
if let Err(e) = pipeline::init(app.handle()) {
|
|
log::error!("library init failed: {e}");
|
|
}
|
|
Ok(())
|
|
})
|
|
.invoke_handler(tauri::generate_handler![
|
|
commands::app_status,
|
|
commands::excluded_search_phrases,
|
|
commands::get_credentials,
|
|
commands::connect_and_login,
|
|
commands::connect_stored,
|
|
commands::disconnect,
|
|
commands::start_search,
|
|
commands::stop_search,
|
|
commands::download_file,
|
|
commands::copy_stream_to_folder,
|
|
commands::save_to_library,
|
|
commands::stream_cache_len,
|
|
commands::cancel_download,
|
|
commands::show_in_folder,
|
|
commands::refresh_artist_art,
|
|
commands::play_stream,
|
|
commands::play_local,
|
|
commands::preload_local,
|
|
commands::player_control,
|
|
commands::player_state,
|
|
commands::waveform,
|
|
commands::library_albums,
|
|
commands::library_album_stats,
|
|
commands::album_tracks,
|
|
commands::album_art,
|
|
commands::library_art_prewarm,
|
|
commands::stream_embedded_art,
|
|
commands::refresh_album_art,
|
|
commands::refresh_all_album_art,
|
|
commands::get_settings,
|
|
commands::set_settings,
|
|
commands::forget_credentials,
|
|
commands::pick_folder,
|
|
commands::open_external,
|
|
commands::record_play,
|
|
commands::add_play_seconds,
|
|
commands::artist_play_stats,
|
|
commands::recent_albums,
|
|
commands::clear_play_history,
|
|
commands::play_row_count,
|
|
commands::followed_artists,
|
|
commands::follow_artist,
|
|
commands::unfollow_artist,
|
|
commands::slsk_join_room,
|
|
commands::slsk_leave_room,
|
|
commands::slsk_say_room,
|
|
commands::slsk_message_user,
|
|
commands::slsk_room_list,
|
|
commands::slsk_browse_user,
|
|
commands::slsk_user_info,
|
|
commands::slsk_user_interests,
|
|
commands::slsk_similar_users,
|
|
slsk_link::slsk_link_status,
|
|
slsk_link::slsk_link_self,
|
|
slsk_link::slsk_link_unlink,
|
|
slsk_link::slsk_link_check,
|
|
slsk_link::slsk_link_forget,
|
|
friends::friends_list,
|
|
friends::friend_request,
|
|
friends::friend_request_by_key,
|
|
friends::friend_accept,
|
|
friends::friend_decline,
|
|
friends::friend_remove,
|
|
friends::friend_forget,
|
|
shared_playlists::shared_playlists_mine,
|
|
shared_playlists::shared_playlist_publish,
|
|
shared_playlists::shared_playlist_withdraw,
|
|
shared_playlists::shared_playlist_fetch,
|
|
shared_playlists::shared_playlist_import,
|
|
shared_playlists::share_send,
|
|
shared_playlists::shares_inbox,
|
|
shared_playlists::share_dismiss,
|
|
inbox::inbox_status,
|
|
inbox::inbox_publish,
|
|
inbox::inbox_check,
|
|
spaces::spaces_list,
|
|
spaces::space_create,
|
|
spaces::space_open,
|
|
spaces::space_post,
|
|
spaces::space_invite,
|
|
spaces::space_remove_member,
|
|
spaces::space_set_pinned,
|
|
spaces::space_leave,
|
|
proposals::space_board,
|
|
proposals::space_propose,
|
|
proposals::space_decide,
|
|
overlap::overlap_list,
|
|
overlap::overlap_compute,
|
|
overlap::overlap_forget,
|
|
commands::buddy_list,
|
|
commands::buddy_add,
|
|
commands::buddy_remove,
|
|
commands::home_artist_news,
|
|
commands::home_warm,
|
|
commands::mini_open,
|
|
commands::mini_close,
|
|
commands::window_glass,
|
|
playlists::list_playlists,
|
|
playlists::create_playlist,
|
|
playlists::rename_playlist,
|
|
playlists::set_playlist_description,
|
|
playlists::set_playlist_pinned,
|
|
playlists::duplicate_playlist,
|
|
playlists::delete_playlist,
|
|
playlists::playlist_items,
|
|
playlists::playlist_add_items,
|
|
playlists::playlist_remove_items,
|
|
playlists::playlist_set_order,
|
|
playlists::playlist_relink_item,
|
|
playlists::playlist_cover,
|
|
playlists::playlist_cover_pick,
|
|
playlists::playlist_cover_clear,
|
|
playlists::playlist_import_probe,
|
|
playlists::playlist_import_folder,
|
|
playlists::playlist_import_text,
|
|
playlists::playlist_import_youtube,
|
|
playlists::playlist_import_youtube_playlist,
|
|
playlists::playlist_import_link,
|
|
playlists::playlist_import_track_links,
|
|
playlists::playlist_import_commit,
|
|
setlist_scan::playlist_scan_youtube,
|
|
setlist_scan::playlist_pick_set_file,
|
|
setlist_scan::playlist_scan_file,
|
|
setlist_scan::playlist_scan_cancel,
|
|
ytdlp::ytdlp_status,
|
|
ytdlp::ytdlp_download,
|
|
ytdlp::deno_download,
|
|
playlists::playlist_export,
|
|
playlists::library_track_search,
|
|
lyrics::lyrics_get,
|
|
lyrics::lyrics_search,
|
|
lyrics::lyrics_select,
|
|
lyrics::lyrics_set_offset,
|
|
lyrics::lyrics_set_auto_sync,
|
|
lyrics::lyrics_prepare_instrumental,
|
|
lyrics::lyrics_vocal_activity,
|
|
lyrics::lyrics_save_lines,
|
|
lyrics::lyrics_save_text,
|
|
lyrics::lyrics_clear,
|
|
commands::catalog_search,
|
|
commands::catalog_search_tracks,
|
|
commands::catalog_artist_track_candidates,
|
|
commands::catalog_track_by_isrc,
|
|
import::import_scan,
|
|
import::import_portable_root,
|
|
import::import_resolve_root,
|
|
import::import_session_get,
|
|
import::import_session_set,
|
|
import::import_session_clear,
|
|
import::import_get_dest,
|
|
import::import_set_dest,
|
|
import::import_apply,
|
|
import::import_identify,
|
|
import::import_cover_report,
|
|
import::import_local_art,
|
|
import::import_audition,
|
|
import::import_audition_missing,
|
|
import::import_audition_clear,
|
|
import::import_probe_file,
|
|
pipeline::rescan_library,
|
|
commands::catalog_artist,
|
|
commands::catalog_artist_art,
|
|
commands::catalog_artist_logos,
|
|
commands::catalog_artist_identity,
|
|
commands::catalog_artist_socials,
|
|
commands::remember_fused_artist_portrait,
|
|
commands::catalog_artist_bio,
|
|
commands::catalog_cached_cover_logo,
|
|
commands::catalog_cover_logo,
|
|
commands::catalog_cover_logo_debug,
|
|
commands::catalog_ocr_experiment,
|
|
commands::catalog_artist_hero_seed,
|
|
commands::catalog_artist_overview,
|
|
commands::catalog_artist_popular,
|
|
commands::pick_artist_hero,
|
|
commands::clear_artist_hero,
|
|
commands::catalog_related,
|
|
commands::catalog_track_listeners,
|
|
commands::catalog_artist_profile,
|
|
commands::catalog_album,
|
|
commands::catalog_album_info,
|
|
commands::catalog_art,
|
|
commands::catalog_cover,
|
|
commands::invalidate_catalog_cover,
|
|
commands::catalog_hero_preview,
|
|
commands::catalog_hero_gpu_source,
|
|
commands::catalog_cached_hero_art,
|
|
commands::catalog_hero_art,
|
|
commands::preload_stream,
|
|
commands::preload_cached_stream,
|
|
commands::stream_waveform,
|
|
commands::stream_cache_ready,
|
|
freenet::freenet_status,
|
|
freenet::freenet_set_enabled,
|
|
freenet::freenet_detect,
|
|
freenet::freenet_set_load_remote_images,
|
|
freenet::freenet_node_info,
|
|
freenet::freenet_node_start,
|
|
freenet::freenet_node_stop,
|
|
community::contacts_meta_list,
|
|
community::contact_meta_set,
|
|
community::community_pick_file,
|
|
persona::persona_status,
|
|
persona::persona_create,
|
|
persona::persona_export,
|
|
persona::persona_import,
|
|
persona::persona_rotate,
|
|
persona::persona_remove,
|
|
profile::profile_get,
|
|
profile::profile_publish,
|
|
profile::profile_set_avatar,
|
|
profile::profile_clear_avatar,
|
|
preview::preview_resolve,
|
|
preview::preview_prewarm,
|
|
preview::preview_load,
|
|
preview::preview_control,
|
|
preview::preview_dispose,
|
|
preview::player_pause_acked,
|
|
preview::player_fade_pause_acked,
|
|
preview::player_fade_resume,
|
|
diagnostics::debug_stats,
|
|
smtc::smtc_metadata,
|
|
lyric_prep::lyric_prep_request,
|
|
lyric_align::lyric_model_status,
|
|
lyric_align::lyric_model_download,
|
|
vocal_sep::vocal_sep_status,
|
|
vocal_sep::vocal_sep_download,
|
|
])
|
|
.run({
|
|
let mut context = tauri::generate_context!();
|
|
// Browser args ride the WINDOW CONFIG, nothing else: wry always sets
|
|
// AdditionalBrowserArguments programmatically, and at least WebView2
|
|
// 151.x then drops the WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS env var
|
|
// entirely — flags "delivered" that way vanish silently on some
|
|
// runtimes (they held on the home machine, gone on the work one).
|
|
// Verify in the browser process command line (Get-CimInstance
|
|
// Win32_Process), never by assumption. Explicit args replace wry's
|
|
// defaults, so those are restated. Two WebView2 presentation fixes
|
|
// live here (2026-08-26, the dark-glass work):
|
|
//
|
|
// 1. NO delegated compositing: with it, DWM composes the webview's
|
|
// layers at rest and blends semi-transparent darks in the wrong
|
|
// colorspace — the app rested washed-out LIGHT and looked correct
|
|
// only while an animation forced Chromium to compose in-process.
|
|
// 2. Force the sRGB color profile: on an HDR display the MAXIMIZED
|
|
// window's rest path takes a different SDR-white tone mapping
|
|
// than the animated path — covers blew out near-white at rest
|
|
// and snapped correct on hover. One profile, one mapping, both
|
|
// paths agree.
|
|
// 3. Force the wheel scroll animator ON: this WebView2 shipped
|
|
// wheel detents as INSTANT 120px jumps (measured: a dispatched
|
|
// detent moved scrollTop in exactly one frame) while the
|
|
// present pipeline ran a flawless 144Hz — scroll position
|
|
// updating at detent cadence reads as "choppy but the FPS
|
|
// says 144". The system animation setting is on and
|
|
// prefers-reduced-motion is off, so the snap is WebView2's
|
|
// embedded default, not the Chromium system heuristic; the
|
|
// switch pins enable_scroll_animator=true in WebPreferences.
|
|
//
|
|
// Debug builds add CDP on 9222 for instrumentation (see HANDOFF.md).
|
|
let base = "--disable-features=msWebOOUI,msPdfOOUI,msSmartScreenProtection,DelegatedCompositing \
|
|
--autoplay-policy=no-user-gesture-required \
|
|
--enable-smooth-scrolling \
|
|
--force-color-profile=srgb";
|
|
#[cfg(debug_assertions)]
|
|
// A second instance needs its own CDP port, or it silently fails
|
|
// to bind and cannot be driven at all.
|
|
let cdp = std::env::var("SOULSTREAM_CDP_PORT").unwrap_or_else(|_| "9222".into());
|
|
let args = format!("{base} --remote-debugging-port={cdp}");
|
|
#[cfg(not(debug_assertions))]
|
|
let args = base.to_string();
|
|
for window in &mut context.config_mut().app.windows {
|
|
window.additional_browser_args = Some(args.clone());
|
|
}
|
|
context
|
|
})
|
|
.expect("error while running tauri application");
|
|
}
|