2022-02-21 21:01:45 -08:00
#![ cfg_attr(
2022-03-28 18:41:35 -07:00
all ( not ( debug_assertions ) , target_os = " windows " ) ,
windows_subsystem = " windows "
2022-02-21 21:01:45 -08:00
) ]
2025-11-21 22:45:34 +00:00
#![ recursion_limit = " 256 " ]
2022-02-21 21:01:45 -08:00
2025-05-02 04:51:17 -05:00
use native_dialog ::{ DialogBuilder , MessageLevel } ;
2025-01-09 23:37:17 +02:00
use std ::env ;
2024-08-27 21:49:36 +02:00
use tauri ::{ Listener , Manager } ;
2026-04-12 15:38:08 -06:00
use tauri_plugin_fs ::FsExt ;
2023-06-30 08:11:32 -07:00
use theseus ::prelude ::* ;
2023-05-08 12:14:08 -07:00
2023-03-31 11:00:43 -07:00
mod api ;
2023-05-08 12:14:08 -07:00
mod error ;
2023-03-31 11:00:43 -07:00
2023-06-30 08:11:32 -07:00
#[ cfg(target_os = " macos " ) ]
mod macos ;
2025-09-29 09:28:31 -06:00
#[ cfg(feature = " updater " ) ]
mod updater_impl ;
#[ cfg(not(feature = " updater " )) ]
mod updater_impl_noop ;
2023-03-31 11:00:43 -07:00
// Should be called in launcher initialization
2023-09-12 09:27:03 -07:00
#[ tracing::instrument(skip_all) ]
2023-03-31 11:00:43 -07:00
#[ tauri::command ]
2023-04-16 10:12:37 -07:00
async fn initialize_state ( app : tauri ::AppHandle ) -> api ::Result < ( ) > {
2024-12-22 20:03:58 -07:00
tracing ::info! ( " Initializing app event state... " ) ;
2024-08-16 23:20:11 -07:00
theseus ::EventState ::init ( app . clone ( ) ) . await ? ;
2024-08-30 10:42:58 -07:00
2025-09-29 09:28:31 -06:00
tracing ::info! ( " Initializing app state... " ) ;
2026-02-02 08:10:23 -05:00
State ::init ( app . config ( ) . identifier . clone ( ) ) . await ? ;
2024-08-30 10:42:58 -07:00
2024-08-16 23:20:11 -07:00
let state = State ::get ( ) . await ? ;
app . asset_protocol_scope ( )
. allow_directory ( state . directories . caches_dir ( ) , true ) ? ;
2024-08-28 21:44:08 -07:00
app . asset_protocol_scope ( )
. allow_directory ( state . directories . caches_dir ( ) . join ( " icons " ) , true ) ? ;
2026-04-12 15:38:08 -06:00
app . fs_scope ( )
. allow_directory ( state . directories . profiles_dir ( ) , true ) ? ;
2024-08-16 23:20:11 -07:00
2023-03-31 11:00:43 -07:00
Ok ( ( ) )
}
2024-08-16 23:20:11 -07:00
// Should be call once Vue has mounted the app
#[ tracing::instrument(skip_all) ]
#[ tauri::command ]
fn show_window ( app : tauri ::AppHandle ) {
2024-08-28 21:44:08 -07:00
let win = app . get_window ( " main " ) . unwrap ( ) ;
2024-08-23 13:50:02 -05:00
if let Err ( e ) = win . show ( ) {
2025-05-02 04:51:17 -05:00
DialogBuilder ::message ( )
. set_level ( MessageLevel ::Error )
2024-08-23 13:50:02 -05:00
. set_title ( " Initialization error " )
2025-05-02 04:51:17 -05:00
. set_text ( format! (
2025-05-01 16:13:13 -07:00
" Cannot display application window due to an error: \n {e} "
2024-08-23 13:50:02 -05:00
) )
2025-05-02 04:51:17 -05:00
. alert ( )
. show ( )
2024-08-23 13:50:02 -05:00
. unwrap ( ) ;
panic! ( " cannot display application window " )
} else {
let _ = win . set_focus ( ) ;
2024-08-20 17:53:14 -07:00
}
2024-08-16 23:20:11 -07:00
}
2023-06-19 14:59:06 -07:00
#[ tauri::command ]
fn is_dev ( ) -> bool {
2023-06-26 14:29:53 -07:00
cfg! ( debug_assertions )
2023-06-19 14:59:06 -07:00
}
2025-09-29 09:28:31 -06:00
#[ tauri::command ]
fn are_updates_enabled ( ) -> bool {
cfg! ( feature = " updater " )
2025-09-29 12:17:23 -06:00
& & env ::var ( " MODRINTH_EXTERNAL_UPDATE_PROVIDER " ) . is_err ( )
2025-09-29 09:28:31 -06:00
}
#[ cfg(feature = " updater " ) ]
pub use updater_impl ::* ;
#[ cfg(not(feature = " updater " )) ]
pub use updater_impl_noop ::* ;
2023-07-28 19:56:49 -07:00
// Toggles decorations
#[ tauri::command ]
async fn toggle_decorations ( b : bool , window : tauri ::Window ) -> api ::Result < ( ) > {
window . set_decorations ( b ) . map_err ( | e | {
theseus ::Error ::from ( theseus ::ErrorKind ::OtherError ( format! (
2025-05-01 16:13:13 -07:00
" Failed to toggle decorations: {e} "
2023-07-28 19:56:49 -07:00
) ) )
} ) ? ;
Ok ( ( ) )
}
2024-08-30 10:42:58 -07:00
#[ tauri::command ]
fn restart_app ( app : tauri ::AppHandle ) {
app . restart ( ) ;
}
2023-06-30 08:11:32 -07:00
// if Tauri app is called with arguments, then those arguments will be treated as commands
// ie: deep links or filepaths for .mrpacks
2022-02-21 21:01:45 -08:00
fn main ( ) {
2023-05-08 12:14:08 -07:00
/*
2023-06-30 08:11:32 -07:00
tracing is set basd on the environment variable RUST_LOG = xxx , depending on the amount of logs to show
ERROR > WARN > INFO > DEBUG > TRACE
eg . RUST_LOG = info will show info , warn , and error logs
RUST_LOG = " theseus=trace " will show * all * messages but from theseus only ( and not dependencies using similar crates )
RUST_LOG = " theseus=trace " will show * all * messages but from theseus only ( and not dependencies using similar crates )
2023-05-08 12:14:08 -07:00
2023-06-30 08:11:32 -07:00
Error messages returned to Tauri will display as traced error logs if they return an error .
This will also include an attached span trace if the error is from a tracing error , and the level is set to info , debug , or trace
2023-05-08 12:14:08 -07:00
2023-06-30 08:11:32 -07:00
on unix :
RUST_LOG = " theseus=trace " { run command }
2023-05-08 12:14:08 -07:00
* /
2026-02-02 08:10:23 -05:00
let tauri_context = tauri ::generate_context! ( ) ;
let _log_guard = theseus ::start_logger ( & tauri_context . config ( ) . identifier ) ;
2023-05-08 12:14:08 -07:00
2023-06-30 08:11:32 -07:00
tracing ::info! ( " Initialized tracing subscriber. Loading Modrinth App! " ) ;
2023-05-08 12:14:08 -07:00
2023-06-30 08:11:32 -07:00
let mut builder = tauri ::Builder ::default ( ) ;
2024-08-27 21:49:36 +02:00
#[ cfg(feature = " updater " ) ]
{
2025-09-29 09:28:31 -06:00
use tauri_plugin_http ::reqwest ::header ::{ HeaderValue , USER_AGENT } ;
2026-01-07 21:38:02 -05:00
use theseus ::launcher_user_agent ;
2025-09-29 09:28:31 -06:00
builder = builder . plugin (
tauri_plugin_updater ::Builder ::new ( )
. header (
USER_AGENT ,
2026-01-07 21:38:02 -05:00
HeaderValue ::from_str ( & launcher_user_agent ( ) ) . unwrap ( ) ,
2025-09-29 09:28:31 -06:00
)
. unwrap ( )
. build ( ) ,
) ;
2024-08-27 21:49:36 +02:00
}
2023-06-30 08:11:32 -07:00
builder = builder
2024-12-22 20:03:58 -07:00
. plugin ( tauri_plugin_single_instance ::init ( | app , args , _cwd | {
if let Some ( payload ) = args . get ( 1 ) {
tracing ::info! ( " Handling deep link from arg {payload} " ) ;
let payload = payload . clone ( ) ;
tauri ::async_runtime ::spawn ( api ::utils ::handle_command (
payload ,
) ) ;
}
if let Some ( win ) = app . get_window ( " main " ) {
let _ = win . set_focus ( ) ;
}
} ) )
2025-07-02 02:03:58 +01:00
. plugin ( tauri_plugin_http ::init ( ) )
2024-08-27 21:49:36 +02:00
. plugin ( tauri_plugin_os ::init ( ) )
. plugin ( tauri_plugin_dialog ::init ( ) )
. plugin ( tauri_plugin_deep_link ::init ( ) )
2026-03-26 18:55:15 +00:00
. plugin ( tauri_plugin_fs ::init ( ) )
2024-12-14 19:23:47 -07:00
. plugin ( tauri_plugin_opener ::init ( ) )
2024-08-27 21:49:36 +02:00
. plugin (
tauri_plugin_window_state ::Builder ::default ( )
. with_filename ( " app-window-state.json " )
2025-08-26 13:43:38 +03:00
// Use *only* POSITION and SIZE state flags, because saving VISIBLE causes the `visible: false` to not take effect
. with_state_flags (
tauri_plugin_window_state ::StateFlags ::POSITION
2025-10-19 21:00:11 -04:00
| tauri_plugin_window_state ::StateFlags ::SIZE
| tauri_plugin_window_state ::StateFlags ::MAXIMIZED ,
2025-08-26 13:43:38 +03:00
)
2024-08-27 21:49:36 +02:00
. build ( ) ,
)
2023-06-30 08:11:32 -07:00
. setup ( | app | {
2024-08-16 23:20:11 -07:00
#[ cfg(target_os = " macos " ) ]
2024-08-27 21:49:36 +02:00
{
2024-08-27 17:50:10 -07:00
let payload = macos ::deep_link ::get_or_init_payload ( app ) ;
2024-08-16 23:20:11 -07:00
2025-06-13 18:59:28 -07:00
let mtx_copy = payload . payload ;
2024-08-27 21:49:36 +02:00
app . listen ( " deep-link://new-url " , move | url | {
let mtx_copy_copy = mtx_copy . clone ( ) ;
let request = url . payload ( ) . to_owned ( ) ;
2024-08-16 23:20:11 -07:00
2024-08-27 17:50:10 -07:00
let actual_request =
serde_json ::from_str ::< Vec < String > > ( & request )
. ok ( )
. map ( | mut x | x . remove ( 0 ) )
. unwrap_or ( request ) ;
2024-08-16 23:20:11 -07:00
tauri ::async_runtime ::spawn ( async move {
2024-08-27 17:50:10 -07:00
tracing ::info! ( " Handling deep link {actual_request} " ) ;
2024-08-16 23:20:11 -07:00
2024-08-27 21:49:36 +02:00
let mut payload = mtx_copy_copy . lock ( ) . await ;
2024-08-16 23:20:11 -07:00
if payload . is_none ( ) {
2024-08-27 17:50:10 -07:00
* payload = Some ( actual_request . clone ( ) ) ;
2024-08-16 23:20:11 -07:00
}
2024-08-27 17:50:10 -07:00
let _ =
api ::utils ::handle_command ( actual_request ) . await ;
2024-08-16 23:20:11 -07:00
} ) ;
2024-08-27 21:49:36 +02:00
} ) ;
2024-08-16 23:20:11 -07:00
} ;
#[ cfg(not(target_os = " macos " )) ]
2024-08-27 21:49:36 +02:00
app . listen ( " deep-link://new-url " , | url | {
let payload = url . payload ( ) . to_owned ( ) ;
tracing ::info! ( " Handling deep link {payload} " ) ;
tauri ::async_runtime ::spawn ( api ::utils ::handle_command (
payload ,
) ) ;
} ) ;
2023-07-28 19:56:49 -07:00
2024-12-23 17:13:13 -07:00
#[ cfg(not(target_os = " linux " )) ]
2025-08-08 15:50:44 -07:00
if let Some ( window ) = app . get_window ( " main " )
& & let Err ( e ) = window . set_shadow ( true )
{
tracing ::warn! ( " Failed to set window shadow: {e} " ) ;
2023-06-30 08:11:32 -07:00
}
2023-06-20 22:04:17 -07:00
Ok ( ( ) )
} ) ;
2023-06-20 22:03:59 -04:00
2024-08-27 21:49:36 +02:00
builder = builder
2023-06-28 09:58:58 -07:00
. plugin ( api ::auth ::init ( ) )
2023-08-04 23:38:34 -07:00
. plugin ( api ::mr_auth ::init ( ) )
2023-07-22 12:37:19 -07:00
. plugin ( api ::import ::init ( ) )
2023-06-28 09:58:58 -07:00
. plugin ( api ::logs ::init ( ) )
. plugin ( api ::jre ::init ( ) )
. plugin ( api ::metadata ::init ( ) )
2025-07-02 21:32:15 +01:00
. plugin ( api ::minecraft_skins ::init ( ) )
2023-06-28 09:58:58 -07:00
. plugin ( api ::pack ::init ( ) )
. plugin ( api ::process ::init ( ) )
. plugin ( api ::profile ::init ( ) )
. plugin ( api ::profile_create ::init ( ) )
. plugin ( api ::settings ::init ( ) )
. plugin ( api ::tags ::init ( ) )
. plugin ( api ::utils ::init ( ) )
2024-07-24 11:03:19 -07:00
. plugin ( api ::cache ::init ( ) )
2026-05-03 01:43:34 +02:00
. plugin ( api ::connected_library ::init ( ) )
2026-03-26 18:55:15 +00:00
. plugin ( api ::files ::init ( ) )
2024-08-28 21:44:08 -07:00
. plugin ( api ::ads ::init ( ) )
App redesign (#2946)
* Start of app redesign
* format
* continue progress
* Content page nearly done
* Fix recursion issues with content page
* Fix update all alignment
* Discover page progress
* Settings progress
* Removed unlocked-size hack that breaks web
* Revamp project page, refactor web project page to share code with app, fixed loading bar, misc UI/UX enhancements, update ko-fi logo, update arrow icons, fix web issues caused by floating-vue migration, fix tooltip issues, update web tooltips, clean up web hydration issues
* Ads + run prettier
* Begin auth refactor, move common messages to ui lib, add i18n extraction to all apps, begin Library refactor
* fix ads not hiding when plus log in
* rev lockfile changes/conflicts
* Fix sign in page
* Add generated
* (mostly) Data driven search
* Fix search mobile issue
* profile fixes
* Project versions page, fix typescript on UI lib and misc fixes
* Remove unused gallery component
* Fix linkfunction err
* Search filter controls at top, localization for locked filters
* Fix provided filter names
* Fix navigating from instance browse to main browse
* Friends frontend (#2995)
* Friends system frontend
* (almost) finish frontend
* finish friends, fix lint
* Fix lint
---------
Signed-off-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
* Refresh macOS app icon
* Update web search UI more
* Fix link opens
* Fix frontend build
---------
Signed-off-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
Co-authored-by: Jai A <jaiagr+gpg@pm.me>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2024-12-11 19:54:18 -08:00
. plugin ( api ::friends ::init ( ) )
2025-04-26 18:09:58 -07:00
. plugin ( api ::worlds ::init ( ) )
2025-09-29 09:28:31 -06:00
. manage ( PendingUpdateData ::default ( ) )
2023-07-28 19:56:49 -07:00
. invoke_handler ( tauri ::generate_handler! [
initialize_state ,
is_dev ,
2025-09-29 09:28:31 -06:00
are_updates_enabled ,
get_update_size ,
enqueue_update_for_installation ,
remove_enqueued_update ,
2023-08-05 17:44:02 -04:00
toggle_decorations ,
2024-08-16 23:20:11 -07:00
show_window ,
2024-08-30 10:42:58 -07:00
restart_app ,
2023-07-28 19:56:49 -07:00
] ) ;
2023-06-14 20:16:16 -04:00
2024-12-22 20:03:58 -07:00
tracing ::info! ( " Initializing app... " ) ;
2026-02-02 08:10:23 -05:00
let app = builder . build ( tauri_context ) ;
2024-08-27 21:49:36 +02:00
match app {
Ok ( app ) = > {
app . run ( | app , event | {
2025-09-29 09:28:31 -06:00
#[ cfg(not(any(feature = " updater " , target_os = " macos " ))) ]
2025-06-14 02:10:12 +02:00
drop ( ( app , event ) ) ;
2025-09-29 09:28:31 -06:00
#[ cfg(feature = " updater " ) ]
if matches! ( event , tauri ::RunEvent ::Exit ) {
let update_data = app . state ::< PendingUpdateData > ( ) . inner ( ) ;
if let Some ( ( update , data ) ) = & * update_data . 0. lock ( ) . unwrap ( ) {
fn set_changelog_toast ( version : Option < String > ) {
let toast_result : theseus ::Result < ( ) > = tauri ::async_runtime ::block_on ( async move {
let mut settings = settings ::get ( ) . await ? ;
settings . pending_update_toast_for_version = version ;
settings ::set ( settings ) . await ? ;
Ok ( ( ) )
} ) ;
if let Err ( e ) = toast_result {
tracing ::warn! ( " Failed to set pending_update_toast: {e} " )
}
}
set_changelog_toast ( Some ( update . version . clone ( ) ) ) ;
if let Err ( e ) = update . install ( data ) {
tracing ::error! ( " Error while updating: {e} " ) ;
set_changelog_toast ( None ) ;
DialogBuilder ::message ( )
. set_level ( MessageLevel ::Error )
. set_title ( " Update error " )
. set_text ( format! ( " Failed to install update due to an error: \n {e} " ) )
. alert ( )
. show ( )
. unwrap ( ) ;
}
2025-10-06 16:20:09 -06:00
app . restart ( ) ;
2025-09-29 09:28:31 -06:00
}
}
2024-08-27 21:49:36 +02:00
#[ cfg(target_os = " macos " ) ]
if let tauri ::RunEvent ::Opened { urls } = event {
tracing ::info! ( " Handling webview open {urls:?} " ) ;
let file = urls
. into_iter ( )
2025-06-13 18:59:28 -07:00
. find_map ( | url | url . to_file_path ( ) . ok ( ) ) ;
2024-08-27 21:49:36 +02:00
if let Some ( file ) = file {
2024-08-27 17:50:10 -07:00
let payload =
macos ::deep_link ::get_or_init_payload ( app ) ;
2024-08-27 21:49:36 +02:00
2025-06-13 18:59:28 -07:00
let mtx_copy = payload . payload ;
2024-08-27 17:50:10 -07:00
let request = file . to_string_lossy ( ) . to_string ( ) ;
2024-08-27 21:49:36 +02:00
tauri ::async_runtime ::spawn ( async move {
let mut payload = mtx_copy . lock ( ) . await ;
if payload . is_none ( ) {
* payload = Some ( request . clone ( ) ) ;
}
let _ = api ::utils ::handle_command ( request ) . await ;
} ) ;
}
}
} ) ;
}
Err ( e ) = > {
2025-09-29 09:28:31 -06:00
tracing ::error! ( " Error while running tauri application: {:?} " , e ) ;
2024-08-27 21:49:36 +02:00
#[ cfg(target_os = " windows " ) ]
2024-08-23 13:50:02 -05:00
{
2024-08-27 21:49:36 +02:00
// tauri doesn't expose runtime errors, so matching a string representation seems like the only solution
2025-05-01 16:13:13 -07:00
if format! ( " {e:?} " ) . contains (
2024-08-27 21:49:36 +02:00
" Runtime(CreateWebview(WebView2Error(WindowsError " ,
) {
2025-05-02 04:51:17 -05:00
DialogBuilder ::message ( )
. set_level ( MessageLevel ::Error )
2024-08-27 21:49:36 +02:00
. set_title ( " Initialization error " )
2024-10-21 21:08:06 +02:00
. set_text ( " Your Microsoft Edge WebView2 installation is corrupt. \n \n Microsoft Edge WebView2 is required to run Modrinth App. \n \n Learn how to repair it at https://support.modrinth.com/en/articles/8797765-corrupted-microsoft-edge-webview2-installation " )
2025-05-02 04:51:17 -05:00
. alert ( )
. show ( )
2024-08-27 21:49:36 +02:00
. unwrap ( ) ;
panic! ( " webview2 initialization failed " )
}
2024-08-23 13:50:02 -05:00
}
2025-05-02 04:51:17 -05:00
DialogBuilder ::message ( )
. set_level ( MessageLevel ::Error )
2024-08-27 21:49:36 +02:00
. set_title ( " Initialization error " )
2025-05-02 04:51:17 -05:00
. set_text ( format! (
2025-05-01 16:13:13 -07:00
" Cannot initialize application due to an error: \n {e:?} "
2024-08-27 21:49:36 +02:00
) )
2025-05-02 04:51:17 -05:00
. alert ( )
. show ( )
2024-08-27 21:49:36 +02:00
. unwrap ( ) ;
2024-08-23 13:50:02 -05:00
2024-08-27 21:49:36 +02:00
panic! ( " {1} : {:?} " , e , " error while running tauri application " )
}
2024-08-23 13:50:02 -05:00
}
2022-03-28 18:41:35 -07:00
}