windows.rs 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. use crate::{
  2. constants::{UPDATER_ENABLED, window_state_flags},
  3. server::get_wsl_config,
  4. };
  5. use std::{ops::Deref, time::Duration};
  6. use tauri::{AppHandle, Manager, Runtime, WebviewUrl, WebviewWindow, WebviewWindowBuilder};
  7. use tauri_plugin_window_state::AppHandleExt;
  8. use tokio::sync::mpsc;
  9. #[cfg(target_os = "linux")]
  10. use std::sync::OnceLock;
  11. #[cfg(target_os = "linux")]
  12. fn use_decorations() -> bool {
  13. static DECORATIONS: OnceLock<bool> = OnceLock::new();
  14. *DECORATIONS.get_or_init(|| {
  15. crate::linux_windowing::use_decorations(&crate::linux_windowing::SessionEnv::capture())
  16. })
  17. }
  18. #[cfg(not(target_os = "linux"))]
  19. fn use_decorations() -> bool {
  20. true
  21. }
  22. pub struct MainWindow(WebviewWindow);
  23. impl Deref for MainWindow {
  24. type Target = WebviewWindow;
  25. fn deref(&self) -> &Self::Target {
  26. &self.0
  27. }
  28. }
  29. impl MainWindow {
  30. pub const LABEL: &str = "main";
  31. pub fn create(app: &AppHandle) -> Result<Self, tauri::Error> {
  32. if let Some(window) = app.get_webview_window(Self::LABEL) {
  33. let _ = window.set_focus();
  34. let _ = window.unminimize();
  35. return Ok(Self(window));
  36. }
  37. let wsl_enabled = get_wsl_config(app.clone())
  38. .ok()
  39. .map(|v| v.enabled)
  40. .unwrap_or(false);
  41. let decorations = use_decorations();
  42. let window_builder = base_window_config(
  43. WebviewWindowBuilder::new(app, Self::LABEL, WebviewUrl::App("/".into())),
  44. app,
  45. decorations,
  46. )
  47. .title("OpenCode")
  48. .disable_drag_drop_handler()
  49. .zoom_hotkeys_enabled(false)
  50. .visible(true)
  51. .maximized(true)
  52. .initialization_script(format!(
  53. r#"
  54. window.__OPENCODE__ ??= {{}};
  55. window.__OPENCODE__.updaterEnabled = {UPDATER_ENABLED};
  56. window.__OPENCODE__.wsl = {wsl_enabled};
  57. "#
  58. ));
  59. let window = window_builder.build()?;
  60. // Ensure window is focused after creation (e.g., after update/relaunch)
  61. let _ = window.set_focus();
  62. setup_window_state_listener(app, &window);
  63. #[cfg(windows)]
  64. {
  65. use tauri_plugin_decorum::WebviewWindowExt;
  66. let _ = window.create_overlay_titlebar();
  67. }
  68. Ok(Self(window))
  69. }
  70. }
  71. fn setup_window_state_listener(app: &AppHandle, window: &WebviewWindow) {
  72. let (tx, mut rx) = mpsc::channel::<()>(1);
  73. window.on_window_event(move |event| {
  74. use tauri::WindowEvent;
  75. if !matches!(event, WindowEvent::Moved(_) | WindowEvent::Resized(_)) {
  76. return;
  77. }
  78. let _ = tx.try_send(());
  79. });
  80. tokio::spawn({
  81. let app = app.clone();
  82. async move {
  83. let save = || {
  84. let handle = app.clone();
  85. let app = app.clone();
  86. let _ = handle.run_on_main_thread(move || {
  87. let _ = app.save_window_state(window_state_flags());
  88. });
  89. };
  90. while rx.recv().await.is_some() {
  91. tokio::time::sleep(Duration::from_millis(200)).await;
  92. save();
  93. }
  94. }
  95. });
  96. }
  97. pub struct LoadingWindow(WebviewWindow);
  98. impl Deref for LoadingWindow {
  99. type Target = WebviewWindow;
  100. fn deref(&self) -> &Self::Target {
  101. &self.0
  102. }
  103. }
  104. impl LoadingWindow {
  105. pub const LABEL: &str = "loading";
  106. pub fn create(app: &AppHandle) -> Result<Self, tauri::Error> {
  107. let decorations = use_decorations();
  108. let window_builder = base_window_config(
  109. WebviewWindowBuilder::new(app, Self::LABEL, tauri::WebviewUrl::App("/loading".into())),
  110. app,
  111. decorations,
  112. )
  113. .center()
  114. .resizable(false)
  115. .inner_size(640.0, 480.0)
  116. .visible(true);
  117. Ok(Self(window_builder.build()?))
  118. }
  119. }
  120. fn base_window_config<'a, R: Runtime, M: Manager<R>>(
  121. window_builder: WebviewWindowBuilder<'a, R, M>,
  122. _app: &AppHandle,
  123. decorations: bool,
  124. ) -> WebviewWindowBuilder<'a, R, M> {
  125. let window_builder = window_builder.decorations(decorations);
  126. #[cfg(windows)]
  127. let window_builder = window_builder
  128. // Some VPNs set a global/system proxy that WebView2 applies even for loopback
  129. // connections, which breaks the app's localhost sidecar server.
  130. // Note: when setting additional args, we must re-apply wry's default
  131. // `--disable-features=...` flags.
  132. .additional_browser_args(
  133. "--proxy-bypass-list=<-loopback> --disable-features=msWebOOUI,msPdfOOUI,msSmartScreenProtection",
  134. )
  135. .data_directory(_app.path().config_dir().expect("Failed to get config dir").join(_app.config().product_name.clone().unwrap()))
  136. .decorations(false);
  137. #[cfg(target_os = "macos")]
  138. let window_builder = window_builder
  139. .title_bar_style(tauri::TitleBarStyle::Overlay)
  140. .hidden_title(true)
  141. .traffic_light_position(tauri::LogicalPosition::new(12.0, 18.0));
  142. window_builder
  143. }