diff --git a/src/assets/localization/fallback.strings b/src/assets/localization/fallback.strings index 156989cb1..0b4cabe2b 100644 --- a/src/assets/localization/fallback.strings +++ b/src/assets/localization/fallback.strings @@ -1245,8 +1245,8 @@ "AtDateAgo" = "on {date}"; "AudioPause" = "Pause audio"; "AudioPlay" = "Play audio"; -"ToggleUserNotifications" = "Toggle user notifications"; -"ToggleChatNotifications" = "Toggle chat notifications"; +"AriaToggleUserNotifications" = "Toggle user notifications"; +"AriaToggleChatNotifications" = "Toggle chat notifications"; "ChannelInaccessible" = "channel is inaccessible"; "GroupInaccessible" = "group is inaccessible"; "AriaPasswordToggle" = "Toggle password visibility"; @@ -2280,4 +2280,4 @@ "ContextMenuHintTouch" = "To edit or reply, close this menu. Then long tap on a message."; "GiftValueForSaleOnFragment" = "for sale on Fragment"; "GiftValueForSaleOnTelegram" = "for sale on Telegram"; -"EmbeddedMessageNoCaption" = "Caption removed"; \ No newline at end of file +"EmbeddedMessageNoCaption" = "Caption removed"; diff --git a/src/components/common/profile/ChatExtra.tsx b/src/components/common/profile/ChatExtra.tsx index 82b656935..66a7e4cd4 100644 --- a/src/components/common/profile/ChatExtra.tsx +++ b/src/components/common/profile/ChatExtra.tsx @@ -423,11 +423,11 @@ const ChatExtra: FC = ({ )} {!isInSettings && ( - - {oldLang('Notifications')} + + {lang('Notifications')} diff --git a/src/components/left/LeftColumn.scss b/src/components/left/LeftColumn.scss index 9e73abfe3..c469e4c7b 100644 --- a/src/components/left/LeftColumn.scss +++ b/src/components/left/LeftColumn.scss @@ -47,7 +47,7 @@ body.is-tauri.is-macos #Main:not(.is-fullscreen) &:not(#TopicListHeader) { justify-content: space-between; - padding: 0.5rem 0.5rem 0.5rem 4.5rem; + padding: 0.5rem 0.5rem 0.5rem var(--window-controls-width); .SearchInput { max-width: calc(100% - 2.75rem); diff --git a/src/global/actions/ui/initial.ts b/src/global/actions/ui/initial.ts index befe3d5a9..e43958b12 100644 --- a/src/global/actions/ui/initial.ts +++ b/src/global/actions/ui/initial.ts @@ -174,8 +174,8 @@ addCallback((global: GlobalState) => { if (IS_TAURI) { document.body.classList.add('is-tauri'); } - if (IS_ELECTRON) { // Legacy - document.body.classList.add('is-electron'); + if (IS_ELECTRON) { // Legacy, pretend to be Tauri + document.body.classList.add('is-tauri'); } }); diff --git a/src/styles/index.scss b/src/styles/index.scss index 368d692e6..a6cc412fc 100644 --- a/src/styles/index.scss +++ b/src/styles/index.scss @@ -92,7 +92,7 @@ body.is-ios { --border-radius-messages-small: 0.5rem; } -body.is-tauri, body.is-electron { +body.is-tauri { --custom-cursor: default; --window-controls-width: 5rem; } diff --git a/src/types/language.d.ts b/src/types/language.d.ts index 5140bc7ca..ea3276479 100644 --- a/src/types/language.d.ts +++ b/src/types/language.d.ts @@ -1051,8 +1051,8 @@ export interface LangPair { 'JustNowAgo': undefined; 'AudioPause': undefined; 'AudioPlay': undefined; - 'ToggleUserNotifications': undefined; - 'ToggleChatNotifications': undefined; + 'AriaToggleUserNotifications': undefined; + 'AriaToggleChatNotifications': undefined; 'ChannelInaccessible': undefined; 'GroupInaccessible': undefined; 'AriaPasswordToggle': undefined; diff --git a/tauri/Cargo.lock b/tauri/Cargo.lock index dd075fde4..194aebd0a 100644 --- a/tauri/Cargo.lock +++ b/tauri/Cargo.lock @@ -1605,6 +1605,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "gethostname" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc257fdb4038301ce4b9cd1b3b51704509692bb3ff716a410cbd07925d9dae55" +dependencies = [ + "rustix", + "windows-targets 0.52.6", +] + [[package]] name = "getrandom" version = "0.1.16" @@ -3221,6 +3231,18 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "os_info" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0e1ac5fde8d43c34139135df8ea9ee9465394b2d8d20f032d38998f64afffc3" +dependencies = [ + "log", + "plist", + "serde", + "windows-sys 0.52.0", +] + [[package]] name = "os_pipe" version = "1.2.2" @@ -4838,6 +4860,15 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "sys-locale" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" +dependencies = [ + "libc", +] + [[package]] name = "system-deps" version = "6.2.2" @@ -5142,6 +5173,24 @@ dependencies = [ "url", ] +[[package]] +name = "tauri-plugin-os" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a1c77ebf6f20417ab2a74e8c310820ba52151406d0c80fbcea7df232e3f6ba" +dependencies = [ + "gethostname", + "log", + "os_info", + "serde", + "serde_json", + "serialize-to-javascript", + "sys-locale", + "tauri", + "tauri-plugin", + "thiserror 2.0.16", +] + [[package]] name = "tauri-plugin-process" version = "2.3.0" @@ -5351,7 +5400,7 @@ dependencies = [ [[package]] name = "telegram_air" -version = "2.8.8" +version = "2.8.9" dependencies = [ "ab_glyph", "cocoa", @@ -5368,6 +5417,7 @@ dependencies = [ "tauri-plugin-fs", "tauri-plugin-log", "tauri-plugin-notification", + "tauri-plugin-os", "tauri-plugin-process", "tauri-plugin-shell", "tauri-plugin-single-instance", diff --git a/tauri/Cargo.toml b/tauri/Cargo.toml index a36ba1467..1505244ea 100644 --- a/tauri/Cargo.toml +++ b/tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "telegram_air" -version = "2.8.8" +version = "2.8.9" description = "Telegram Air" authors = ["Alexander Zinchuk"] license = "GPLv3" @@ -31,6 +31,7 @@ url = "2.5.7" image = "0.25.6" imageproc = "0.25.0" ab_glyph = "0.2.31" +tauri-plugin-os = "2" [target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\"))".dependencies] tauri-plugin-single-instance = { version = "2.3.3", features = ["deep-link"] } diff --git a/tauri/build.rs b/tauri/build.rs index d860e1e6a..795b9b7c8 100644 --- a/tauri/build.rs +++ b/tauri/build.rs @@ -1,3 +1,3 @@ fn main() { - tauri_build::build() + tauri_build::build() } diff --git a/tauri/src/deeplink.rs b/tauri/src/deeplink.rs index e10bde433..0a03ea5f8 100644 --- a/tauri/src/deeplink.rs +++ b/tauri/src/deeplink.rs @@ -5,46 +5,46 @@ use tauri_plugin_deep_link::DeepLinkExt; pub struct Deeplink; impl Deeplink { - pub fn init() -> Self { - Self {} - } + pub fn init() -> Self { + Self {} + } - pub fn setup(&self, app: &tauri::AppHandle) -> Result<(), Box> { - // Clone the app handle for use in the closure - let app_handle = app.clone(); + pub fn setup(&self, app: &tauri::AppHandle) -> Result<(), Box> { + // Clone the app handle for use in the closure + let app_handle = app.clone(); - // Set up the deep link event handler - app.deep_link().on_open_url(move |event| { - // Store URLs to avoid calling event.urls() multiple times (it consumes the event) - let urls = event.urls(); - info!("Deep link received: {:?}", urls); + // Set up the deep link event handler + app.deep_link().on_open_url(move |event| { + // Store URLs to avoid calling event.urls() multiple times (it consumes the event) + let urls = event.urls(); + info!("Deep link received: {:?}", urls); - // Get the main window - if let Some(window) = app_handle.get_webview_window("main") { - // Emit the deep link event to the frontend - if let Err(err) = window.emit("deeplink", &urls) { - info!("Error emitting deeplink event: {:?}", err); - } + // Get the main window + if let Some(window) = app_handle.get_webview_window("main") { + // Emit the deep link event to the frontend + if let Err(err) = window.emit("deeplink", &urls) { + info!("Error emitting deeplink event: {:?}", err); + } - // Request user attention and focus the window - if let Err(err) = window.request_user_attention(Some(UserAttentionType::Informational)) { - info!("Error requesting user attention: {:?}", err); - } + // Request user attention and focus the window + if let Err(err) = window.request_user_attention(Some(UserAttentionType::Informational)) { + info!("Error requesting user attention: {:?}", err); + } - if let Err(err) = window.show() { - info!("Error showing window: {:?}", err); - } + if let Err(err) = window.show() { + info!("Error showing window: {:?}", err); + } - if let Err(err) = window.unminimize() { - info!("Error unminimizing window: {:?}", err); - } + if let Err(err) = window.unminimize() { + info!("Error unminimizing window: {:?}", err); + } - if let Err(err) = window.set_focus() { - info!("Error setting focus: {:?}", err); - } - } - }); + if let Err(err) = window.set_focus() { + info!("Error setting focus: {:?}", err); + } + } + }); - Ok(()) - } + Ok(()) + } } diff --git a/tauri/src/lib.rs b/tauri/src/lib.rs index abc04d323..00b4fd30a 100644 --- a/tauri/src/lib.rs +++ b/tauri/src/lib.rs @@ -33,9 +33,19 @@ impl Default for AppStateStruct { pub type AppState = Mutex; -pub const TRAFFIC_LIGHT_POSITION_OVERLAY: LogicalPosition = LogicalPosition::new(12.0, 26.0); +pub const TRAFFIC_LIGHT_POSITION_OVERLAY_LEGACY: LogicalPosition = LogicalPosition::new(12.0, 26.0); +pub const TRAFFIC_LIGHT_POSITION_OVERLAY_26: LogicalPosition = LogicalPosition::new(12.0, 30.0); pub const TRAFFIC_LIGHT_POSITION_DEFAULT: LogicalPosition = LogicalPosition::new(12.0, 12.0); +pub static TRAFFIC_LIGHT_POSITION_OVERLAY: LazyLock> = LazyLock::new(|| { + if let tauri_plugin_os::Version::Semantic(major, _, _) = tauri_plugin_os::version() { + if major >= 26 { + return TRAFFIC_LIGHT_POSITION_OVERLAY_26; + } + } + TRAFFIC_LIGHT_POSITION_OVERLAY_LEGACY +}); + pub const WINDOW_WIDTH: f64 = 1088.0; pub const WINDOW_HEIGHT: f64 = 700.0; pub const WINDOW_MIN_WIDTH: f64 = 360.0; @@ -90,6 +100,7 @@ pub fn run() { open_new_window(app.clone(), BASE_URL.to_string()).unwrap(); } })) + .plugin(tauri_plugin_os::init()) .plugin(tauri_plugin_fs::init()) .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_notification::init()) @@ -124,7 +135,7 @@ pub fn run() { state.title.clone() }; let traffic_position = if state.is_overlay { - TRAFFIC_LIGHT_POSITION_OVERLAY + *TRAFFIC_LIGHT_POSITION_OVERLAY } else { TRAFFIC_LIGHT_POSITION_DEFAULT }; @@ -199,7 +210,7 @@ fn mark_title_bar_overlay(window: tauri::WebviewWindow, is_overlay: bool) { mac::update_window_title( base_window.clone(), "".to_string(), - TRAFFIC_LIGHT_POSITION_OVERLAY, + *TRAFFIC_LIGHT_POSITION_OVERLAY, ); } } else { @@ -297,7 +308,10 @@ pub(crate) fn open_new_window( .inner_size(WINDOW_WIDTH, WINDOW_HEIGHT) .min_inner_size(WINDOW_MIN_WIDTH, WINDOW_MIN_HEIGHT) .disable_drag_drop_handler() // Required for Drag & Drop on Windows - .initialization_script(&format!("window.tauri = {{ version: '{}' }};", env!("CARGO_PKG_VERSION"))) + .initialization_script(&format!( + "window.tauri = {{ version: '{}' }};", + env!("CARGO_PKG_VERSION") + )) .on_download(|window, event| { match event { #[allow(unused_variables)] @@ -345,7 +359,7 @@ pub(crate) fn open_new_window( #[cfg(target_os = "macos")] if let Some(base_window) = app.get_window(&window_label) { - mac::setup_traffic_light_positioner(&base_window, TRAFFIC_LIGHT_POSITION_OVERLAY); + mac::setup_traffic_light_positioner(&base_window, *TRAFFIC_LIGHT_POSITION_OVERLAY); } // Apply stored notification count to the new window diff --git a/tauri/src/main.rs b/tauri/src/main.rs index 5ec0af2a1..f52f6fab7 100644 --- a/tauri/src/main.rs +++ b/tauri/src/main.rs @@ -2,5 +2,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] fn main() { - app_lib::run(); + app_lib::run(); } diff --git a/tauri/src/tray/badge.rs b/tauri/src/tray/badge.rs index 434154d01..c081c9381 100644 --- a/tauri/src/tray/badge.rs +++ b/tauri/src/tray/badge.rs @@ -1,8 +1,8 @@ -use image::{Rgba, RgbaImage, imageops}; -use imageproc::drawing::{draw_filled_rect_mut, draw_filled_circle_mut, draw_text_mut, text_size}; -use imageproc::rect::Rect; -use imageproc::filter::gaussian_blur_f32; use ab_glyph::{FontRef, PxScale}; +use image::{Rgba, RgbaImage, imageops}; +use imageproc::drawing::{draw_filled_circle_mut, draw_filled_rect_mut, draw_text_mut, text_size}; +use imageproc::filter::gaussian_blur_f32; +use imageproc::rect::Rect; use std::io::Cursor; use tauri::image::Image; @@ -47,21 +47,25 @@ pub fn set_badge_count_icon(window: &tauri::WebviewWindow, amount: i32, is_muted } pub fn generate_counter_png(size: u32, count: i32, is_muted: bool) -> Vec { - let background_color = if is_muted { BADGE_BACKGROUND_COLOR_MUTED } else { BADGE_BACKGROUND_COLOR }; + let background_color = if is_muted { + BADGE_BACKGROUND_COLOR_MUTED + } else { + BADGE_BACKGROUND_COLOR + }; // Prepare text properties let (text, font, scale, text_width, text_height) = if count >= 0 { let text = if count < 100 { - count.to_string() + count.to_string() } else { - format!("..{:02}", count % 100) + format!("..{:02}", count % 100) }; let font = FontRef::try_from_slice(FONT).expect("Invalid font"); let scale = { - let base = if text.len() < 3 { 0.9 } else { 0.75 }; - let calculated_scale = (base * size as f32).ceil(); - PxScale::from(calculated_scale) + let base = if text.len() < 3 { 0.9 } else { 0.75 }; + let calculated_scale = (base * size as f32).ceil(); + PxScale::from(calculated_scale) }; let (text_width, text_height) = text_size(scale, &font, &text); @@ -102,7 +106,15 @@ pub fn generate_counter_png(size: u32, count: i32, is_muted: bool) -> Vec { badge_height / 2 }; - draw_rounded_rect(&mut img, edge_space, edge_space, badge_width, badge_height, corner_radius, background_color); + draw_rounded_rect( + &mut img, + edge_space, + edge_space, + badge_width, + badge_height, + corner_radius, + background_color, + ); // Apply gaussian blur for antialiasing effect img = gaussian_blur_f32(&img, 0.75); @@ -111,15 +123,25 @@ pub fn generate_counter_png(size: u32, count: i32, is_muted: bool) -> Vec { let x = edge_space as f32 + ((badge_width as f32 - text_width as f32) / 2.0).ceil(); let baseline_offset = scale.y * 0.15; - let y = edge_space as f32 + ((badge_height as f32 - text_height as f32) / 2.0 - baseline_offset).ceil(); + let y = edge_space as f32 + + ((badge_height as f32 - text_height as f32) / 2.0 - baseline_offset).ceil(); - draw_text_mut(&mut img, BADGE_TEXT_COLOR, x as i32, y as i32, scale, &font, &text); + draw_text_mut( + &mut img, + BADGE_TEXT_COLOR, + x as i32, + y as i32, + scale, + &font, + &text, + ); } let mut buffer = Vec::new(); let mut cursor = Cursor::new(&mut buffer); - img.write_to(&mut cursor, image::ImageFormat::Png) - .expect("PNG encode failed"); + img + .write_to(&mut cursor, image::ImageFormat::Png) + .expect("PNG encode failed"); buffer } @@ -130,8 +152,9 @@ pub fn overlay_tray_icon(icon: &Image, counter: &Image) -> Image<'static> { let icon_img = image::RgbaImage::from_raw(icon.width(), icon.height(), icon_rgba.to_vec()) .expect("Failed to create RgbaImage from icon data"); - let counter_img = image::RgbaImage::from_raw(counter.width(), counter.height(), counter_rgba.to_vec()) - .expect("Failed to create RgbaImage from counter data"); + let counter_img = + image::RgbaImage::from_raw(counter.width(), counter.height(), counter_rgba.to_vec()) + .expect("Failed to create RgbaImage from counter data"); let mut result = icon_img.clone(); @@ -150,7 +173,8 @@ pub fn overlay_tray_icon(icon: &Image, counter: &Image) -> Image<'static> { // Convert back to tauri Image let mut buffer = Vec::new(); let mut cursor = Cursor::new(&mut buffer); - result.write_to(&mut cursor, image::ImageFormat::Png) + result + .write_to(&mut cursor, image::ImageFormat::Png) .expect("PNG encode failed"); Image::from_bytes(&buffer).expect("Failed to create Image from bytes") @@ -163,33 +187,80 @@ fn draw_rounded_rect( width: u32, height: u32, radius: u32, - color: Rgba + color: Rgba, ) { let radius = radius.min(width / 2).min(height / 2); if radius == 0 { - draw_filled_rect_mut(img, Rect::at(x as i32, y as i32).of_size(width, height), color); + draw_filled_rect_mut( + img, + Rect::at(x as i32, y as i32).of_size(width, height), + color, + ); return; } if width > 2 * radius && height > 2 * radius { - draw_filled_rect_mut(img, Rect::at((x + radius) as i32, (y + radius) as i32).of_size(width - 2 * radius, height - 2 * radius), color); + draw_filled_rect_mut( + img, + Rect::at((x + radius) as i32, (y + radius) as i32) + .of_size(width - 2 * radius, height - 2 * radius), + color, + ); } if width > 2 * radius { - draw_filled_rect_mut(img, Rect::at((x + radius) as i32, y as i32).of_size(width - 2 * radius, radius), color); - draw_filled_rect_mut(img, Rect::at((x + radius) as i32, (y + height - radius) as i32).of_size(width - 2 * radius, radius), color); + draw_filled_rect_mut( + img, + Rect::at((x + radius) as i32, y as i32).of_size(width - 2 * radius, radius), + color, + ); + draw_filled_rect_mut( + img, + Rect::at((x + radius) as i32, (y + height - radius) as i32) + .of_size(width - 2 * radius, radius), + color, + ); } if height > 2 * radius { - draw_filled_rect_mut(img, Rect::at(x as i32, (y + radius) as i32).of_size(radius, height - 2 * radius), color); - draw_filled_rect_mut(img, Rect::at((x + width - radius) as i32, (y + radius) as i32).of_size(radius, height - 2 * radius), color); + draw_filled_rect_mut( + img, + Rect::at(x as i32, (y + radius) as i32).of_size(radius, height - 2 * radius), + color, + ); + draw_filled_rect_mut( + img, + Rect::at((x + width - radius) as i32, (y + radius) as i32) + .of_size(radius, height - 2 * radius), + color, + ); } let radius_i32 = radius as i32; - draw_filled_circle_mut(img, ((x + radius) as i32, (y + radius) as i32), radius_i32, color); - draw_filled_circle_mut(img, ((x + width - radius) as i32, (y + radius) as i32), radius_i32, color); - draw_filled_circle_mut(img, ((x + radius) as i32, (y + height - radius) as i32), radius_i32, color); - draw_filled_circle_mut(img, ((x + width - radius) as i32, (y + height - radius) as i32), radius_i32, color); + draw_filled_circle_mut( + img, + ((x + radius) as i32, (y + radius) as i32), + radius_i32, + color, + ); + draw_filled_circle_mut( + img, + ((x + width - radius) as i32, (y + radius) as i32), + radius_i32, + color, + ); + draw_filled_circle_mut( + img, + ((x + radius) as i32, (y + height - radius) as i32), + radius_i32, + color, + ); + draw_filled_circle_mut( + img, + ((x + width - radius) as i32, (y + height - radius) as i32), + radius_i32, + color, + ); }