Merge remote-tracking branch 'upstream/main'
This commit is contained in:
BIN
apps/app/dmg/dmg-background.png
Normal file
BIN
apps/app/dmg/dmg-background.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 284 KiB |
@@ -1,4 +1,6 @@
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::time::{Duration, Instant};
|
||||
use tauri::plugin::TauriPlugin;
|
||||
use tauri::{Manager, PhysicalPosition, PhysicalSize, Runtime};
|
||||
@@ -14,6 +16,156 @@ pub struct AdsState {
|
||||
}
|
||||
|
||||
const AD_LINK: &str = "https://modrinth.com/wrapper/app-ads-cookie";
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
const ADS_USER_AGENT: &str = concat!(
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ",
|
||||
"(KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ",
|
||||
"ModrinthApp/",
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
" (Modrinth App)",
|
||||
);
|
||||
|
||||
#[cfg(windows)]
|
||||
fn ads_user_agent_override_params() -> String {
|
||||
serde_json::json!({
|
||||
"userAgent": ADS_USER_AGENT,
|
||||
"platform": "Win32",
|
||||
"userAgentMetadata": {
|
||||
"brands": [
|
||||
{ "brand": "Chromium", "version": "128" },
|
||||
{ "brand": "Google Chrome", "version": "128" },
|
||||
{ "brand": "Modrinth App", "version": env!("CARGO_PKG_VERSION") },
|
||||
{ "brand": "Not=A?Brand", "version": "99" },
|
||||
],
|
||||
"fullVersion": "128.0.0.0",
|
||||
"fullVersionList": [
|
||||
{ "brand": "Chromium", "version": "128.0.0.0" },
|
||||
{ "brand": "Google Chrome", "version": "128.0.0.0" },
|
||||
{ "brand": "Modrinth App", "version": env!("CARGO_PKG_VERSION") },
|
||||
{ "brand": "Not=A?Brand", "version": "99.0.0.0" },
|
||||
],
|
||||
"platform": "Windows",
|
||||
"platformVersion": "10.0.0",
|
||||
"architecture": "x86",
|
||||
"bitness": "64",
|
||||
"model": "",
|
||||
"mobile": false,
|
||||
},
|
||||
})
|
||||
.to_string()
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn configure_ads_cookie_settings(
|
||||
core_webview2: &webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2,
|
||||
) {
|
||||
use webview2_com::Microsoft::Web::WebView2::Win32::{
|
||||
COREWEBVIEW2_TRACKING_PREVENTION_LEVEL_NONE, ICoreWebView2,
|
||||
ICoreWebView2_13, ICoreWebView2Profile3,
|
||||
};
|
||||
use windows_core::Interface;
|
||||
|
||||
match core_webview2
|
||||
.cast::<ICoreWebView2_13>()
|
||||
.and_then(|core_webview2| unsafe { core_webview2.Profile() })
|
||||
.and_then(|profile| profile.cast::<ICoreWebView2Profile3>())
|
||||
{
|
||||
Ok(profile) => {
|
||||
if let Err(error) = unsafe {
|
||||
profile.SetPreferredTrackingPreventionLevel(
|
||||
COREWEBVIEW2_TRACKING_PREVENTION_LEVEL_NONE,
|
||||
)
|
||||
} {
|
||||
tracing::warn!(
|
||||
?error,
|
||||
"Failed to disable ads WebView2 tracking prevention"
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::warn!(
|
||||
?error,
|
||||
"Failed to access ads WebView2 profile tracking prevention settings"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_webview_visible<R: Runtime>(
|
||||
webview: &tauri::Webview<R>,
|
||||
_visible: bool,
|
||||
) {
|
||||
webview
|
||||
.with_webview(
|
||||
#[allow(unused_variables)]
|
||||
move |wv| {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let controller = wv.controller();
|
||||
unsafe { controller.SetIsVisible(_visible) }.ok();
|
||||
}
|
||||
},
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
|
||||
fn set_webview_visible_for_window<R: Runtime>(
|
||||
app: &tauri::AppHandle<R>,
|
||||
webview: &tauri::Webview<R>,
|
||||
visible: bool,
|
||||
) {
|
||||
let is_minimized = app
|
||||
.get_window("main")
|
||||
.and_then(|window| window.is_minimized().ok())
|
||||
.unwrap_or(false);
|
||||
|
||||
set_webview_visible(webview, visible && !is_minimized);
|
||||
}
|
||||
|
||||
fn sync_webview_visibility_for_main_window<R: Runtime>(
|
||||
app: &tauri::AppHandle<R>,
|
||||
main_window: &tauri::Window<R>,
|
||||
was_minimized: &AtomicBool,
|
||||
) {
|
||||
let is_minimized = main_window.is_minimized().unwrap_or(false);
|
||||
let was = was_minimized.load(Ordering::SeqCst);
|
||||
|
||||
if is_minimized == was {
|
||||
return;
|
||||
}
|
||||
|
||||
was_minimized.store(is_minimized, Ordering::SeqCst);
|
||||
|
||||
let ads_visible = if is_minimized {
|
||||
false
|
||||
} else {
|
||||
match app.state::<RwLock<AdsState>>().try_read() {
|
||||
Ok(state) => state.shown && !state.modal_shown,
|
||||
Err(_) => false,
|
||||
}
|
||||
};
|
||||
|
||||
let mut webviews = Vec::new();
|
||||
let mut seen_webviews = HashSet::new();
|
||||
|
||||
for webview in main_window.webviews() {
|
||||
seen_webviews.insert(webview.label().to_string());
|
||||
webviews.push(webview);
|
||||
}
|
||||
|
||||
for webview in app.webviews().into_values() {
|
||||
if seen_webviews.insert(webview.label().to_string()) {
|
||||
webviews.push(webview);
|
||||
}
|
||||
}
|
||||
|
||||
for webview in webviews {
|
||||
let visible =
|
||||
!is_minimized && (webview.label() != "ads-window" || ads_visible);
|
||||
|
||||
set_webview_visible(&webview, visible);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
tauri::plugin::Builder::<R>::new("ads")
|
||||
@@ -30,10 +182,11 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
// visible when we refresh, the Aditude wrapper will not make any ad requests
|
||||
// unless Chromium reports the page as visible. The refresh does not reset the
|
||||
// visibility state.
|
||||
let app = app.clone();
|
||||
let refresh_app = app.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
loop {
|
||||
if let Some(webview) = app.webviews().get_mut("ads-window")
|
||||
if let Some(webview) =
|
||||
refresh_app.webviews().get_mut("ads-window")
|
||||
{
|
||||
let _ = webview.navigate(AD_LINK.parse().unwrap());
|
||||
}
|
||||
@@ -43,6 +196,34 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(main_window) = app.get_window("main") {
|
||||
let app_handle = app.clone();
|
||||
let event_window = main_window.clone();
|
||||
let was_minimized = Arc::new(AtomicBool::new(false));
|
||||
|
||||
main_window.on_window_event(move |_| {
|
||||
sync_webview_visibility_for_main_window(
|
||||
&app_handle,
|
||||
&event_window,
|
||||
&was_minimized,
|
||||
);
|
||||
|
||||
let delayed_app_handle = app_handle.clone();
|
||||
let delayed_event_window = event_window.clone();
|
||||
let delayed_was_minimized = was_minimized.clone();
|
||||
|
||||
tauri::async_runtime::spawn(async move {
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
|
||||
sync_webview_visibility_for_main_window(
|
||||
&delayed_app_handle,
|
||||
&delayed_event_window,
|
||||
&delayed_was_minimized,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
@@ -103,31 +284,38 @@ pub async fn init_ads_window<R: Runtime>(
|
||||
webview.show().ok();
|
||||
webview.set_position(position).ok();
|
||||
webview.set_size(size).ok();
|
||||
set_webview_visible_for_window(&app, webview, true);
|
||||
} else {
|
||||
webview.hide().ok();
|
||||
webview
|
||||
.set_position(PhysicalPosition::new(-1000, -1000))
|
||||
.ok();
|
||||
set_webview_visible(webview, false);
|
||||
}
|
||||
|
||||
Some(webview.clone())
|
||||
} else if let Some(window) = app.get_window("main") {
|
||||
#[cfg(windows)]
|
||||
let webview_url =
|
||||
WebviewUrl::External("about:blank".parse().unwrap());
|
||||
#[cfg(not(windows))]
|
||||
let webview_url = WebviewUrl::External(AD_LINK.parse().unwrap());
|
||||
|
||||
let webview = window.add_child(
|
||||
tauri::webview::WebviewBuilder::new(
|
||||
"ads-window",
|
||||
WebviewUrl::External(
|
||||
AD_LINK.parse().unwrap(),
|
||||
),
|
||||
)
|
||||
.initialization_script_for_all_frames(include_str!("ads-init.js"))
|
||||
// We use a standard Chrome user agent for compatibility with our ad provider,
|
||||
// since Tauri is not recognized by ad providers by default.
|
||||
// Aditude has separately informed SSPs and IVT vendors that this traffic
|
||||
// originates from a desktop app.
|
||||
.user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36")
|
||||
.zoom_hotkeys_enabled(false)
|
||||
.transparent(true)
|
||||
.on_new_window(|_, _| tauri::webview::NewWindowResponse::Deny),
|
||||
tauri::webview::WebviewBuilder::new("ads-window", webview_url)
|
||||
.initialization_script_for_all_frames(include_str!(
|
||||
"ads-init.js"
|
||||
))
|
||||
// We use a standard Chrome user agent for compatibility with our ad provider,
|
||||
// since Tauri is not recognized by ad providers by default.
|
||||
// Aditude has separately informed SSPs and IVT vendors that this traffic
|
||||
// originates from a desktop app.
|
||||
.user_agent(ADS_USER_AGENT)
|
||||
.zoom_hotkeys_enabled(false)
|
||||
.transparent(true)
|
||||
.on_new_window(|_, _| {
|
||||
tauri::webview::NewWindowResponse::Deny
|
||||
}),
|
||||
// set both the `hide`/`show` state and `position`,
|
||||
// to ensure that the webview is actually shown/hidden
|
||||
if state.shown {
|
||||
@@ -140,15 +328,68 @@ pub async fn init_ads_window<R: Runtime>(
|
||||
|
||||
if state.shown {
|
||||
webview.show().ok();
|
||||
set_webview_visible_for_window(&app, &webview, true);
|
||||
} else {
|
||||
webview.hide().ok();
|
||||
set_webview_visible(&webview, false);
|
||||
}
|
||||
|
||||
webview.with_webview(#[allow(unused_variables)] |webview2| {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use webview2_com::CallDevToolsProtocolMethodCompletedHandler;
|
||||
use webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2_8;
|
||||
use windows_core::Interface;
|
||||
use windows_core::HSTRING;
|
||||
|
||||
let core_webview2 =
|
||||
unsafe { webview2.controller().CoreWebView2() };
|
||||
|
||||
if let Ok(core_webview2) = core_webview2 {
|
||||
configure_ads_cookie_settings(&core_webview2);
|
||||
|
||||
let navigate_webview = core_webview2.clone();
|
||||
let handler =
|
||||
CallDevToolsProtocolMethodCompletedHandler::create(
|
||||
Box::new(move |result: windows_core::Result<()>, _| {
|
||||
if let Err(error) = result {
|
||||
tracing::error!(
|
||||
?error,
|
||||
"Failed to override ads user-agent client hints"
|
||||
);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
navigate_webview
|
||||
.Navigate(&HSTRING::from(AD_LINK))
|
||||
.ok();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}) as Box<_>,
|
||||
);
|
||||
|
||||
unsafe {
|
||||
if let Err(error) = core_webview2
|
||||
.CallDevToolsProtocolMethod(
|
||||
&HSTRING::from(
|
||||
"Emulation.setUserAgentOverride",
|
||||
),
|
||||
&HSTRING::from(
|
||||
ads_user_agent_override_params(),
|
||||
),
|
||||
&handler,
|
||||
)
|
||||
{
|
||||
tracing::error!(
|
||||
?error,
|
||||
"Failed to install ads user-agent client hints override"
|
||||
);
|
||||
|
||||
core_webview2.Navigate(&HSTRING::from(AD_LINK)).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let webview2_controller = webview2.controller();
|
||||
let Ok(webview2_8) = unsafe { webview2_controller.CoreWebView2() }
|
||||
@@ -166,9 +407,9 @@ pub async fn init_ads_window<R: Runtime>(
|
||||
None
|
||||
};
|
||||
|
||||
let Some(webview) = webview.clone() else {
|
||||
if webview.is_none() {
|
||||
return Ok(());
|
||||
};
|
||||
}
|
||||
|
||||
// tauri::async_runtime::spawn(async move {
|
||||
// loop {
|
||||
@@ -249,6 +490,7 @@ pub async fn show_ads_window<R: Runtime>(
|
||||
webview.set_size(size).ok();
|
||||
webview.set_position(position).ok();
|
||||
webview.show().ok();
|
||||
set_webview_visible_for_window(&app, webview, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -6,6 +6,7 @@
|
||||
|
||||
use native_dialog::{DialogBuilder, MessageLevel};
|
||||
use std::env;
|
||||
use std::sync::atomic::Ordering;
|
||||
use tauri::{Listener, Manager};
|
||||
use tauri_plugin_fs::FsExt;
|
||||
use theseus::prelude::*;
|
||||
@@ -96,6 +97,17 @@ fn restart_app(app: tauri::AppHandle) {
|
||||
app.restart();
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn set_restart_after_pending_update(
|
||||
should_restart: bool,
|
||||
) -> api::Result<()> {
|
||||
let state = State::get().await?;
|
||||
state
|
||||
.restart_after_pending_update
|
||||
.store(should_restart, Ordering::Relaxed);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// if Tauri app is called with arguments, then those arguments will be treated as commands
|
||||
// ie: deep links or filepaths for .mrpacks
|
||||
fn main() {
|
||||
@@ -246,6 +258,7 @@ fn main() {
|
||||
get_update_size,
|
||||
enqueue_update_for_installation,
|
||||
remove_enqueued_update,
|
||||
set_restart_after_pending_update,
|
||||
toggle_decorations,
|
||||
show_window,
|
||||
restart_app,
|
||||
@@ -263,7 +276,13 @@ fn main() {
|
||||
#[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() {
|
||||
let should_restart = State::get_if_initialized()
|
||||
.map(|s| {
|
||||
s.restart_after_pending_update.load(Ordering::Relaxed)
|
||||
})
|
||||
.unwrap_or(false);
|
||||
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?;
|
||||
@@ -272,27 +291,46 @@ fn main() {
|
||||
Ok(())
|
||||
});
|
||||
if let Err(e) = toast_result {
|
||||
tracing::warn!("Failed to set pending_update_toast: {e}")
|
||||
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);
|
||||
match update.install(data) {
|
||||
Ok(()) => {
|
||||
if should_restart {
|
||||
tracing::info!(
|
||||
"Pending update installed successfully (version {}); restarting because user requested reload",
|
||||
update.version
|
||||
);
|
||||
app.restart();
|
||||
} else {
|
||||
tracing::info!(
|
||||
"Pending update installed successfully (version {}); exiting without relaunch (user did not request reload)",
|
||||
update.version
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
"Pending update install failed (version {}): {e}",
|
||||
update.version
|
||||
);
|
||||
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();
|
||||
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();
|
||||
}
|
||||
}
|
||||
app.restart();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
if let tauri::RunEvent::Opened { urls } = event {
|
||||
tracing::info!("Handling webview open {urls:?}");
|
||||
|
||||
@@ -30,7 +30,22 @@
|
||||
"exceptionDomain": "",
|
||||
"frameworks": [],
|
||||
"providerShortName": null,
|
||||
"signingIdentity": null
|
||||
"signingIdentity": null,
|
||||
"dmg": {
|
||||
"background": "./dmg/dmg-background.png",
|
||||
"windowSize": {
|
||||
"width": 661,
|
||||
"height": 432
|
||||
},
|
||||
"appPosition": {
|
||||
"x": 188,
|
||||
"y": 212
|
||||
},
|
||||
"applicationFolderPosition": {
|
||||
"x": 475,
|
||||
"y": 212
|
||||
}
|
||||
}
|
||||
},
|
||||
"shortDescription": "",
|
||||
"linux": {
|
||||
|
||||
Reference in New Issue
Block a user