From ead6d8d3a19c99bcb5008b8ad4e02e3328006125 Mon Sep 17 00:00:00 2001 From: leroyloren <57643470+leroyloren@users.noreply.github.com> Date: Sun, 8 Oct 2023 13:46:04 +0200 Subject: [PATCH 01/50] Update cs.rs --- src/lang/cs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lang/cs.rs b/src/lang/cs.rs index cc6d48e59..5e60b5f40 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -555,7 +555,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("auto_disconnect_option_tip", "Automatické ukončení příchozích relací při nečinnosti uživatele"), ("Connection failed due to inactivity", "Připojení se nezdařilo z důvodu nečinnosti"), ("Check for software update on startup", "Kontrola aktualizace softwaru při spuštění"), - ("upgrade_rustdesk_server_pro_to_{}_tip", "Upgradujte prosím RustDesk Server Pro na verzi {} nebo novější!"), + ("upgrade_rustdesk_server_pro_to_{}_tip", "Aktualizujte prosím RustDesk Server Pro na verzi {} nebo novější!"), ("pull_group_failed_tip", "Nepodařilo se obnovit skupinu"), ].iter().cloned().collect(); } From ab195ea5203cd956756fbe72eb4bc62a40aa02e8 Mon Sep 17 00:00:00 2001 From: AnonymousWP <50231698+AnonymousWP@users.noreply.github.com> Date: Sun, 8 Oct 2023 23:34:41 +0200 Subject: [PATCH 02/50] feat(translation): add and fix Dutch strings --- src/lang/nl.rs | 54 +++++++++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/lang/nl.rs b/src/lang/nl.rs index b879a896c..147417ced 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -99,9 +99,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Properties", "Eigenschappen"), ("Multi Select", "Meervoudig selecteren"), ("Select All", "Selecteer Alle"), - ("Unselect All", "Deselecteer alles"), + ("Unselect All", "De-selecteer alles"), ("Empty Directory", "Lege Map"), - ("Not an empty directory", "Geen Lege Map"), + ("Not an empty directory", "Geen lege map"), ("Are you sure you want to delete this file?", "Weet je zeker dat je dit bestand wilt verwijderen?"), ("Are you sure you want to delete this empty directory?", "Weet je zeker dat je deze lege map wilt verwijderen?"), ("Are you sure you want to delete the file of this directory?", "Weet je zeker dat je het bestand uit deze map wilt verwijderen?"), @@ -145,7 +145,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Failed to make direct connection to remote desktop", "Onmogelijk direct verbinding te maken met extern bureaublad"), ("Set Password", "Wachtwoord Instellen"), ("OS Password", "OS Wachtwoord"), - ("install_tip", "Je gebruikt een niet geinstalleerde versie. Als gevolg van UAC-beperkingen is het in sommige gevallen niet mogelijk om als controleterminal de muis en het toetsenbord te bedienen of het scherm over te nemen. Klik op de knop hieronder om RustDesk op het systeem te installeren om het bovenstaande probleem te voorkomen."), + ("install_tip", "Je gebruikt een niet geïnstalleerde versie. Als gevolg van UAC-beperkingen is het in sommige gevallen niet mogelijk om als controleterminal de muis en het toetsenbord te bedienen of het scherm over te nemen. Klik op de knop hieronder om RustDesk op het systeem te installeren om het bovenstaande probleem te voorkomen."), ("Click to upgrade", "Klik voor upgrade"), ("Click to download", "Klik om te downloaden"), ("Click to update", "Klik om bij te werken"), @@ -182,7 +182,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Allow using keyboard and mouse", "Gebruik toetsenbord en muis toestaan"), ("Allow using clipboard", "Gebruik klembord toestaan"), ("Allow hearing sound", "Geluidsweergave toestaan"), - ("Allow file copy and paste", "Kopieren en plakken van bestanden toestaan"), + ("Allow file copy and paste", "Kopiëren en plakken van bestanden toestaan"), ("Connected", "Verbonden"), ("Direct and encrypted connection", "Directe en versleutelde verbinding"), ("Relayed and encrypted connection", "Doorgeschakelde en versleutelde verbinding"), @@ -284,7 +284,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Open System Setting", "Systeeminstelling Openen"), ("How to get Android input permission?", "Hoe krijg ik Android invoer toestemming?"), ("android_input_permission_tip1", "Om ervoor te zorgen dat een extern apparaat uw Android-apparaat kan besturen via muis of aanraking, moet u RustDesk toestaan om de \"Toegankelijkheid\" service te gebruiken."), - ("android_input_permission_tip2", "Ga naar de volgende pagina met systeeminstellingen, zoek en ga naar [Geinstalleerde Services], schakel de service [RustDesk Input] in."), + ("android_input_permission_tip2", "Ga naar de volgende pagina met systeeminstellingen, zoek en ga naar [Geïnstalleerde Services], schakel de service [RustDesk Input] in."), ("android_new_connection_tip", "Er is een nieuw controleverzoek binnengekomen, dat uw huidige apparaat wil controleren."), ("android_service_will_start_tip", "Als u \"Schermopname\" inschakelt, wordt de service automatisch gestart, zodat andere apparaten een verbinding met uw apparaat kunnen aanvragen."), ("android_stop_service_tip", "Het sluiten van de service zal automatisch alle gemaakte verbindingen sluiten."), @@ -405,7 +405,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Please wait for the remote side to accept your session request...", "Wacht tot de andere kant uw sessieverzoek accepteert..."), ("One-time Password", "Eenmalig Wachtwoord"), ("Use one-time password", "Gebruik een eenmalig Wachtwoord"), - ("One-time password length", "Eenmalig Wachtwoord lengre"), + ("One-time password length", "Eenmalig Wachtwoordlengte"), ("Request access to your device", "Toegang tot uw toestel aanvragen"), ("Hide connection management window", "Verberg het venster voor verbindingsbeheer"), ("hide_cm_tip", "Dit kan alleen als de toegang via een permanent wachtwoord verloopt."), @@ -438,7 +438,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("special character", "speciaal teken"), ("length>=8", "lengte>=8"), ("Weak", "Zwak"), - ("Medium", "Midelmatig"), + ("Medium", "Middelmatig"), ("Strong", "Sterk"), ("Switch Sides", "Wissel van kant"), ("Please confirm if you want to share your desktop?", "bevestig als je je bureaublad wilt delen?"), @@ -455,7 +455,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Text chat", "Tekst chat"), ("Stop voice call", "Stop spraakoproep"), ("relay_hint_tip", "Indien een directe verbinding niet mogelijk is, kunt u proberen verbinding te maken via een Relay Server. \nAls u bij de eerste poging een relaisverbinding tot stand wilt brengen, kunt u het achtervoegsel \"/r\" toevoegen aan het ID of de optie \"Altijd verbinden via relaisserver\" selecteren op de externe terminal."), - ("Reconnect", "Herverbinden"), + ("Reconnect", "Opnieuw verbinden"), ("Codec", "Codec"), ("Resolution", "Resolutie"), ("No transfers in progress", "Geen overdrachten in uitvoering"), @@ -473,7 +473,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("empty_favorite_tip", "Nog geen favoriete Station op afstand? Laat ons iemand vinden om mee te verbinden en voeg hem toe aan je favorieten!"), ("empty_lan_tip", "Oh nee, het lijkt erop dat we nog geen extern station hebben ontdekt."), ("empty_address_book_tip", "Oh jee, het lijkt erop dat er momenteel geen externe stations in je adresboek staan."), - ("eg: admin", "bijv: admin"), + ("eg: admin", "bijvoorbeeld: admin"), ("Empty Username", "Gebruikersnaam Leeg"), ("Empty Password", "Wachtwoord Leeg"), ("Me", "Ik"), @@ -515,7 +515,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("clipboard_wait_response_timeout_tip", "Time-out in afwachting van kopieer-antwoord."), ("Incoming connection", "Inkomende verbinding"), ("Outgoing connection", "Uitgaande verbinding"), - ("Exit", "Verlaten"), + ("Exit", "Afsluiten"), ("Open", "Open"), ("logout_tip", "Weet je zeker dat je je wilt afmelden?"), ("Service", "Service"), @@ -527,7 +527,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Open connection in new tab", "Verbinding openen in een nieuw tabblad"), ("Move tab to new window", "Tabblad verplaatsen naar nieuw venster"), ("Can not be empty", "Mag niet leeg zijn"), - ("Already exists", "Bestaat reeds"), + ("Already exists", "Bestaat al"), ("Change Password", "Wijzig Wachtwoord"), ("Refresh Password", "Wachtwoord Vernieuwen"), ("ID", "ID"), @@ -541,21 +541,21 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Change Color", "Kleur Aanpassen"), ("Primary Color", "Hoofdkleur"), ("HSV Color", "HSV Kleur"), - ("Installation Successful!", ""), - ("Installation failed!", ""), - ("Reverse mouse wheel", ""), - ("{} sessions", ""), - ("scam_title", ""), - ("scam_text1", ""), - ("scam_text2", ""), - ("Don't show again", ""), - ("I Agree", ""), - ("Decline", ""), - ("Timeout in minutes", ""), - ("auto_disconnect_option_tip", ""), - ("Connection failed due to inactivity", ""), - ("Check for software update on startup", ""), - ("upgrade_rustdesk_server_pro_to_{}_tip", ""), - ("pull_group_failed_tip", ""), + ("Installation Successful!", "Installatie geslaagd!"), + ("Installation failed!", "Installatie mislukt!"), + ("Reverse mouse wheel", "Muiswiel omkeren"), + ("{} sessions", "{} sessies"), + ("scam_title", "U wordt misschien opgelicht!"), + ("scam_text1", "Als u aan de telefoon bent met iemand die u NIET kent EN VERTROUWT en die u heeft gevraagd om RustDesk te gebruiken en de service te starten, ga dan niet verder en hang onmiddellijk op."), + ("scam_text2", "Het is waarschijnlijk een oplichter die probeert uw geld of andere privégegevens te stelen."), + ("Don't show again", "Niet opnieuw tonen"), + ("I Agree", "Ik ga akkoord"), + ("Decline", "Afwijzen"), + ("Timeout in minutes", "Time-out in minuten"), + ("auto_disconnect_option_tip", "Inkomende sessies automatisch sluiten bij inactiviteit van de gebruiker"), + ("Connection failed due to inactivity", "Automatisch verbinding verbroken wegens inactiviteit"), + ("Check for software update on startup", "Checken voor updates bij opstarten"), + ("upgrade_rustdesk_server_pro_to_{}_tip", "Upgrade RustDesk Server Pro naar versie {} of nieuwer!"), + ("pull_group_failed_tip", "Vernieuwen van groep mislukt"), ].iter().cloned().collect(); } From f176832851aa2e0e6349911f1c06a3dcd40bae99 Mon Sep 17 00:00:00 2001 From: dignow Date: Mon, 9 Oct 2023 14:06:26 +0800 Subject: [PATCH 03/50] fix, uniform capitalization style, capitalize the first letter of a phrase Signed-off-by: dignow --- src/lang/en.rs | 176 ++++++++++++++++++++++++++++++++++++++++++------- src/lang/lv.rs | 1 - 2 files changed, 153 insertions(+), 24 deletions(-) diff --git a/src/lang/en.rs b/src/lang/en.rs index 7bedc3182..ea5c18399 100644 --- a/src/lang/en.rs +++ b/src/lang/en.rs @@ -3,21 +3,101 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = [ ("desk_tip", "Your desktop can be accessed with this ID and password."), ("connecting_status", "Connecting to the RustDesk network..."), + ("Enable Service", "Enable service"), + ("Start Service", "Enable service"), ("not_ready_status", "Not ready. Please check your connection"), + ("Transfer File", "Transfer file"), + ("Recent Sessions", "Recent sessions"), + ("Address Book", "Address book"), + ("TCP Tunneling", "TCP tunneling"), + ("Enable Keyboard/Mouse", "Enable keyboard/mouse"), + ("Enable Clipboard", "Enable clipboard"), + ("Enable File Transfer", "Enable file transfer"), + ("Enable TCP Tunneling", "Enable TCP tunneling"), + ("IP Whitelisting", "IP whitelisting"), + ("ID/Relay Server", "ID/Relay server"), + ("Import Server Config", "Import server config"), + ("Export Server Config", "Export server config"), ("id_change_tip", "Only a-z, A-Z, 0-9 and _ (underscore) characters allowed. The first letter must be a-z, A-Z. Length between 6 and 16."), + ("Slogan_tip", "Made with heart in this chaotic world!"), + ("Build Date", "Build date"), + ("Audio Input", "Audio input"), + ("Hardware Codec", "Hardware codec"), + ("ID Server", "ID server"), + ("Relay Server", "Relay server"), + ("API Server", "API server"), + ("invalid_http", "must start with http:// or https://"), + ("server_not_support", "Not yet supported by the server"), + ("Password Required", "Password required"), + ("Wrong Password", "Wrong password"), + ("Connection Error", "Connection error"), + ("Login Error", "Login error"), + ("Show Hidden Files", "Show hidden files"), + ("Refresh File", "Refresh file"), + ("Remote Computer", "Remote computer"), + ("Local Computer", "Local computer"), + ("Confirm Delete", "Confirm delete"), + ("Multi Select", "Multi select"), + ("Select All", "Select all"), + ("Unselect All", "Unselect all"), + ("Empty Directory", "Empty directory"), + ("Custom Image Quality", "Custom image quality"), + ("Adjust Window", "Adjust window"), + ("Insert Lock", "Insert lock"), + ("Set Password", "Set password"), + ("OS Password", "OS password"), ("install_tip", "Due to UAC, RustDesk can not work properly as the remote side in some cases. To avoid UAC, please click the button below to install RustDesk to the system."), ("config_acc", "In order to control your Desktop remotely, you need to grant RustDesk \"Accessibility\" permissions."), ("config_screen", "In order to access your Desktop remotely, you need to grant RustDesk \"Screen Recording\" permissions."), + ("Installation Path", "Installation path"), ("agreement_tip", "By starting the installation, you accept the license agreement."), + ("Accept and Install", "Accept and install"), ("not_close_tcp_tip", "Don't close this window while you are using the tunnel"), + ("Remote Host", "Remote host"), + ("Remote Port", "Remote port"), + ("Local Port", "Local port"), + ("Local Address", "Local address"), + ("Change Local Port", "Change local port"), ("setup_server_tip", "For faster connection, please set up your own server"), + ("Enter Remote ID", "Enter remote ID"), ("Auto Login", "Auto Login (Only valid if you set \"Lock after session end\")"), - ("Always connect via relay", "Always Connect via Relay"), + ("Enable Direct IP Access", "Enable direct IP access"), + ("Create Desktop Shortcut", "Create desktop shortcut"), + ("Change Path", "Change path"), + ("Create Folder", "Create folder"), ("whitelist_tip", "Only whitelisted IP can access me"), + ("verification_tip", "A verification code has been sent to the registered email address, enter the verification code to continue logging in."), ("whitelist_sep", "Separated by comma, semicolon, spaces or new line"), + ("Add Tag", "Add tag"), ("Wrong credentials", "Wrong username or password"), - ("invalid_http", "must start with http:// or https://"), + ("Edit Tag", "Edit tag"), + ("Forget Password", "Forget password"), + ("Add to Favorites", "Add to favorites"), + ("Remove from Favorites", "Remove from favorites"), + ("Socks5 Proxy", "Socks5 proxy"), ("install_daemon_tip", "For starting on boot, you need to install system service."), + ("Are you sure to close the connection?", "Are you sure you want to close the connection?"), + ("One-Finger Tap", "One-finger tap"), + ("Left Mouse", "Left mouse"), + ("One-Long Tap", "One-long tap"), + ("Two-Finger Tap", "Two-finger tap"), + ("Right Mouse", "Right mouse"), + ("One-Finger Move", "One-finger move"), + ("Double Tap & Move", "Double tap & move"), + ("Mouse Drag", "Mouse drag"), + ("Three-Finger vertically", "Three-finger vertically"), + ("Mouse Wheel", "Mouse wheel"), + ("Two-Finger Move", "Two-finger move"), + ("Canvas Move", "Canvas move"), + ("Pinch to Zoom", "Pinch to zoom"), + ("Canvas Zoom", "Canvas zoom"), + ("Share Screen", "Share screen"), + ("Screen Capture", "Screen capture"), + ("Input Control", "Input control"), + ("Audio Capture", "Audio capture"), + ("File Connection", "File connection"), + ("Screen Connection", "Screen connection"), + ("Open System Setting", "Open system setting"), ("android_input_permission_tip1", "In order for a remote device to control your Android device via mouse or touch, you need to allow RustDesk to use the \"Accessibility\" service."), ("android_input_permission_tip2", "Please go to the next system settings page, find and enter [Installed Services], turn on [RustDesk Input] service."), ("android_new_connection_tip", "New control request has been received, which wants to control your current device."), @@ -27,70 +107,120 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("android_start_service_tip", "Tap [Start Service] or enable [Screen Capture] permission to start the screen sharing service."), ("android_permission_may_not_change_tip", "Permissions for established connections may not be changed instantly until reconnected."), ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), - ("doc_fix_wayland", "https://rustdesk.com/docs/en/manual/linux/#x11-required"), - ("server_not_support", "Not yet supported by the server"), + ("Ignore Battery Optimizations", "Ignore battery optimizations"), ("android_open_battery_optimizations_tip", "If you want to disable this feature, please go to the next RustDesk application settings page, find and enter [Battery], Uncheck [Unrestricted]"), + ("Start on Boot", "Start on boot"), + ("Enable Remote Restart", "Enable remote restart"), + ("Restart Remote Device", "Restart remote device"), + ("Restarting Remote Device", "Restarting remote device"), ("remote_restarting_tip", "Remote device is restarting, please close this message box and reconnect with permanent password after a while"), - ("Are you sure to close the connection?", "Are you sure you want to close the connection?"), + ("Exit Fullscreen", "Exit fullscreen"), + ("Mobile Actions", "Mobile actions"), + ("Select Monitor", "Select monitor"), + ("Control Actions", "Control actions"), + ("Display Settings", "Display settings"), + ("Image Quality", "Image quality"), + ("Scroll Style", "Scroll style"), + ("Show Toolbar", "Show toolbar"), + ("Hide Toolbar", "Hide toolbar"), + ("Direct Connection", "Direct connection"), + ("Relay Connection", "Relay connection"), + ("Secure Connection", "Secure connection"), + ("Insecure Connection", "Insecure connection"), + ("Dark Theme", "Dark theme"), + ("Light Theme", "Light theme"), + ("Follow System", "Follow system"), + ("Unlock Security Settings", "Unlock security settings"), + ("Enable Audio", "Enable audio"), + ("Unlock Network Settings", "Unlock network settings"), + ("Direct IP Access", "Direct IP access"), + ("Audio Input Device", "Audio input device"), + ("Use IP Whitelisting", "Use IP whitelisting"), + ("Pin Toolbar", "Pin toolbar"), + ("Unpin Toolbar", "Unpin toolbar"), + ("Enable Recording Session", "Enable recording session"), + ("Enable LAN Discovery", "Enable LAN discovery"), + ("Deny LAN Discovery", "Deny LAN discovery"), ("elevated_foreground_window_tip", "The current window of the remote desktop requires higher privilege to operate, so it's unable to use the mouse and keyboard temporarily. You can request the remote user to minimize the current window, or click elevation button on the connection management window. To avoid this problem, it is recommended to install the software on the remote device."), + ("Keyboard Settings", "Keyboard settings"), + ("Full Access", "Full access"), + ("Screen Share", "Screen share"), ("JumpLink", "View"), - ("Stop service", "Stop Service"), + ("Please Select the screen to be shared(Operate on the peer side).", "Please select the screen to be shared(Operate on the peer side)."), + ("One-time Password", "One-time password"), ("hide_cm_tip", "Allow hiding only if accepting sessions via password and using permanent password"), ("wayland_experiment_tip", "Wayland support is in experimental stage, please use X11 if you require unattended access."), - ("Slogan_tip", "Made with heart in this chaotic world!"), - ("verification_tip", "A verification code has been sent to the registered email address, enter the verification code to continue logging in."), + ("Add to Address Book", "Add to address book"), ("software_render_tip", "If you're using Nvidia graphics card under Linux and the remote window closes immediately after connecting, switching to the open-source Nouveau driver and choosing to use software rendering may help. A software restart is required."), ("config_input", "In order to control remote desktop with keyboard, you need to grant RustDesk \"Input Monitoring\" permissions."), - ("request_elevation_tip", "You can also request elevation if there is someone on the remote side."), - ("wait_accept_uac_tip", "Please wait for the remote user to accept the UAC dialog."), - ("still_click_uac_tip", "Still requires the remote user to click OK on the UAC window of running RustDesk."), ("config_microphone", "In order to speak remotely, you need to grant RustDesk \"Record Audio\" permissions."), + ("request_elevation_tip", "You can also request elevation if there is someone on the remote side."), + ("Elevation Error", "Elevation error"), + ("still_click_uac_tip", "Still requires the remote user to click OK on the UAC window of running RustDesk."), + ("Request Elevation", "Request elevation"), + ("wait_accept_uac_tip", "Please wait for the remote user to accept the UAC dialog."), + ("Switch Sides", "Switch sides"), + ("Default View Style", "Default view style"), + ("Default Scroll Style", "Default scroll style"), + ("Default Image Quality", "Default image quality"), + ("Default Codec", "Default codec"), + ("Other Default Options", "Other default options"), ("relay_hint_tip", "It may not be possible to connect directly; you can try connecting via relay. Additionally, if you want to use a relay on your first attempt, you can add the \"/r\" suffix to the ID or select the option \"Always connect via relay\" in the card of recent sessions if it exists."), - ("No transfers in progress", ""), ("install_cert_tip", "Install RustDesk certificate"), ("confirm_install_cert_tip", "This is a RustDesk testing certificate, which can be trusted. The certificate will be used to trust and install RustDesk drivers when required."), + ("RDP Settings", "RDP settings"), + ("New Connection", "New connection"), + ("Your Device", "Your device"), ("empty_recent_tip", "Oops, no recent sessions!\nTime to plan a new one."), ("empty_favorite_tip", "No favorite peers yet?\nLet's find someone to connect with and add it to your favorites!"), ("empty_lan_tip", "Oh no, it looks like we haven't discovered any peers yet."), ("empty_address_book_tip", "Oh dear, it appears that there are currently no peers listed in your address book."), + ("Empty Username", "Empty username"), + ("Empty Password", "Empty password"), ("identical_file_tip", "This file is identical with the peer's one."), ("show_monitors_tip", "Show monitors in toolbar"), - ("enter_rustdesk_passwd_tip", "Enter RustDesk password"), - ("remember_rustdesk_passwd_tip", "Remember RustDesk password"), + ("View Mode", "View mode"), ("login_linux_tip", "You need to login to remote Linux account to enable a X desktop session"), ("verify_rustdesk_password_tip", "Verify RustDesk password"), ("remember_account_tip", "Remember this account"), ("os_account_desk_tip", "This account is used to login the remote OS and enable the desktop session in headless"), + ("OS Account", "Os account"), ("another_user_login_title_tip", "Another user already logged in"), ("another_user_login_text_tip", "Disconnect"), ("xorg_not_found_title_tip", "Xorg not found"), ("xorg_not_found_text_tip", "Please install Xorg"), ("no_desktop_title_tip", "No desktop is available"), ("no_desktop_text_tip", "Please install GNOME desktop"), + ("System Sound", "System sound"), + ("Copy Fingerprint", "Copy fingerprint"), + ("no fingerprints", "No fingerprints"), ("resolution_original_tip", "Original resolution"), ("resolution_fit_local_tip", "Fit local resolution"), ("resolution_custom_tip", "Custom resolution"), + ("Accept and Elevate", "Accept and elevate"), ("accept_and_elevate_btn_tooltip", "Accept the connection and elevate UAC permissions."), ("clipboard_wait_response_timeout_tip", "Timed out waiting for copy response."), ("logout_tip", "Are you sure you want to log out?"), ("exceed_max_devices", "You have reached the maximum number of managed devices."), + ("Change Password", "Change password"), + ("Refresh Password", "Refresh password"), + ("Grid View", "Grid view"), + ("List View", "List view"), + ("Toggle Tags", "Toggle tags"), ("pull_ab_failed_tip", "Failed to refresh address book"), ("push_ab_failed_tip", "Failed to sync address book to server"), ("synced_peer_readded_tip", "The devices that were present in the recent sessions will be synchronized back to the address book."), - ("View Mode", "View mode"), - ("Block user input", "Block User Input"), - ("Start session recording", "Start Session Recording"), - ("Stop session recording", "Stop Session Recording"), - ("Enable remote configuration modification", "Enable Remote Configuration Modification"), + ("Change Color", "Change color"), + ("Primary Color", "Primary color"), + ("HSV Color", "HSV color"), + ("Installation Successful!", "Installation successful!"), ("scam_title", "You May Be Being SCAMMED!"), ("scam_text1", "If you are on the phone with someone you DON'T know AND TRUST who has asked you to use RustDesk and start the service, do not proceed and hang up immediately."), ("scam_text2", "They are likely a scammer trying to steal your money or other private information."), - ("Don't show again", "Don't show again"), - ("I Agree", "I Agree"), - ("Decline", "Decline"), ("auto_disconnect_option_tip", "Automatically close incoming sessions on user inactivity"), ("Connection failed due to inactivity", "Automatically disconnected due to inactivity"), ("upgrade_rustdesk_server_pro_to_{}_tip", "Please upgrade RustDesk Server Pro to version {} or newer!"), ("pull_group_failed_tip", "Failed to refresh group"), - ].iter().cloned().collect(); + ("doc_fix_wayland", "https://rustdesk.com/docs/en/manual/linux/#x11-required"), + ].iter().cloned().collect(); } diff --git a/src/lang/lv.rs b/src/lang/lv.rs index 03480f54e..eab3cb40a 100644 --- a/src/lang/lv.rs +++ b/src/lang/lv.rs @@ -481,7 +481,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Me", "Es"), ("identical_file_tip", "Šis fails ir identisks sesijas failam."), ("show_monitors_tip", "Rādīt monitorus rīkjoslā"), - ("enter_rustdesk_passwd_tip", "Ievadiet RustDesk paroli"), ("View Mode", "Skatīšanas režīms"), ("login_linux_tip", "Jums ir jāpiesakās attālajā Linux kontā, lai iespējotu X darbvirsmas sesiju"), ("verify_rustdesk_password_tip", "Pārbaudīt RustDesk paroli"), From 3f4ac84cfb11c5d6a90118c8fe199a2d166e4783 Mon Sep 17 00:00:00 2001 From: Mateusz Prais Date: Mon, 9 Oct 2023 18:34:32 +0200 Subject: [PATCH 04/50] Improve wording, add missing tip and documentation link --- src/lang/pl.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lang/pl.rs b/src/lang/pl.rs index 71813688c..46bdb6e19 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -3,7 +3,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = [ ("Status", "Status"), ("Your Desktop", "Twój pulpit"), - ("desk_tip", "W celu połączenia się z tym urządzeniem należy użyć poniższego ID i hasła"), + ("desk_tip", "Aby połączyć się z tym urządzeniem, użyj poniższego ID i hasła"), ("Password", "Hasło"), ("Ready", "Gotowe"), ("Established", "Nawiązano"), @@ -244,10 +244,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Socks5 Proxy", "Proxy Socks5"), ("Hostname", "Nazwa hosta"), ("Discovered", "Wykryte"), - ("install_daemon_tip", "Podpowiedź instalacji daemona"), + ("install_daemon_tip", "By uruchomić RustDesk przy starcie systemu, musisz zainstalować usługę systemową."), ("Remote ID", "Zdalne ID"), ("Paste", "Wklej"), - ("Paste here?", "Wkleić tu?"), + ("Paste here?", "Wkleić tutaj?"), ("Are you sure to close the connection?", "Czy na pewno chcesz zakończyć połączenie?"), ("Download new version", "Pobierz nową wersję"), ("Touch mode", "Tryb dotykowy"), @@ -295,7 +295,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Overwrite", "Nadpisz"), ("This file exists, skip or overwrite this file?", "Ten plik istnieje, pominąć czy nadpisać ten plik?"), ("Quit", "Zrezygnuj"), - ("doc_mac_permission", "doc_mac_permission"), + ("doc_mac_permission", "https://rustdesk.com/docs/en/client/mac/#enable-permissions"), ("Help", "Pomoc"), ("Failed", "Niepowodzenie"), ("Succeeded", "Udało się"), From 8892c8c8830c6b0c4b0f55e1a9eb5a4604a64fe4 Mon Sep 17 00:00:00 2001 From: rustdesk Date: Tue, 10 Oct 2023 11:53:27 +0800 Subject: [PATCH 05/50] remove doc_mac_permission from tempate --- src/lang/ar.rs | 1 - src/lang/ca.rs | 1 - src/lang/cn.rs | 1 - src/lang/cs.rs | 1 - src/lang/da.rs | 1 - src/lang/de.rs | 1 - src/lang/el.rs | 1 - src/lang/eo.rs | 1 - src/lang/es.rs | 1 - src/lang/fa.rs | 1 - src/lang/fr.rs | 1 - src/lang/hu.rs | 1 - src/lang/id.rs | 1 - src/lang/it.rs | 1 - src/lang/ja.rs | 1 - src/lang/ko.rs | 1 - src/lang/kz.rs | 1 - src/lang/lt.rs | 1 - src/lang/lv.rs | 3 --- src/lang/nl.rs | 1 - src/lang/pl.rs | 1 - src/lang/pt_PT.rs | 1 - src/lang/ptbr.rs | 1 - src/lang/ro.rs | 1 - src/lang/ru.rs | 1 - src/lang/sk.rs | 1 - src/lang/sl.rs | 1 - src/lang/sq.rs | 1 - src/lang/sr.rs | 1 - src/lang/sv.rs | 1 - src/lang/template.rs | 1 - src/lang/th.rs | 1 - src/lang/tr.rs | 1 - src/lang/tw.rs | 1 - src/lang/ua.rs | 1 - src/lang/vn.rs | 1 - 36 files changed, 38 deletions(-) diff --git a/src/lang/ar.rs b/src/lang/ar.rs index 3500e2916..0533591bc 100644 --- a/src/lang/ar.rs +++ b/src/lang/ar.rs @@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Overwrite", "استبدال"), ("This file exists, skip or overwrite this file?", "الملف موجود, هل تريد التجاوز او الاستبدال؟"), ("Quit", "خروج"), - ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), ("Help", "مساعدة"), ("Failed", "فشل"), ("Succeeded", "نجاح"), diff --git a/src/lang/ca.rs b/src/lang/ca.rs index 1fd7dff11..30eb7babf 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Overwrite", "Sobreescriure"), ("This file exists, skip or overwrite this file?", "Aquest arxiu ja existeix, ometre o sobreescriure l'arxiu?"), ("Quit", "Sortir"), - ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), ("Help", "Ajuda"), ("Failed", "Ha fallat"), ("Succeeded", "Aconseguit"), diff --git a/src/lang/cn.rs b/src/lang/cn.rs index 00cdc9390..9b7ef42f4 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Overwrite", "覆盖"), ("This file exists, skip or overwrite this file?", "这个文件/文件夹已存在,跳过/覆盖?"), ("Quit", "退出"), - ("doc_mac_permission", "https://rustdesk.com/docs/zh-cn/manual/mac#%E5%90%AF%E7%94%A8%E6%9D%83%E9%99%90"), ("Help", "帮助"), ("Failed", "失败"), ("Succeeded", "成功"), diff --git a/src/lang/cs.rs b/src/lang/cs.rs index 5e60b5f40..3b9359df0 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Overwrite", "Přepsat"), ("This file exists, skip or overwrite this file?", "Tento soubor existuje, přeskočit, nebo přepsat tento soubor?"), ("Quit", "Ukončit"), - ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), ("Help", "Nápověda"), ("Failed", "Nepodařilo se"), ("Succeeded", "Úspěšný"), diff --git a/src/lang/da.rs b/src/lang/da.rs index 7d1321890..57769979e 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Overwrite", "Overskriv"), ("This file exists, skip or overwrite this file?", "Denne fil findes allerede, vil du springe over eller overskrive denne fil?"), ("Quit", "Afslut"), - ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), ("Help", "Hjælp"), ("Failed", "Mislykkedet"), ("Succeeded", "Vellykket"), diff --git a/src/lang/de.rs b/src/lang/de.rs index 4d2d8b466..2f3f725f0 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Overwrite", "Überschreiben"), ("This file exists, skip or overwrite this file?", "Diese Datei existiert; überspringen oder überschreiben?"), ("Quit", "Beenden"), - ("doc_mac_permission", "https://rustdesk.com/docs/de/manual/mac/#berechtigungen-aktivieren"), ("Help", "Hilfe"), ("Failed", "Fehlgeschlagen"), ("Succeeded", "Erfolgreich"), diff --git a/src/lang/el.rs b/src/lang/el.rs index 76327d0ea..b1818d354 100644 --- a/src/lang/el.rs +++ b/src/lang/el.rs @@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Overwrite", "Αντικατάσταση"), ("This file exists, skip or overwrite this file?", "Αυτό το αρχείο υπάρχει, παράβλεψη ή αντικατάσταση αυτού του αρχείου;"), ("Quit", "Έξοδος"), - ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), ("Help", "Βοήθεια"), ("Failed", "Απέτυχε"), ("Succeeded", "Επιτυχής"), diff --git a/src/lang/eo.rs b/src/lang/eo.rs index 9b73932f2..44104f1ae 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Overwrite", ""), ("This file exists, skip or overwrite this file?", ""), ("Quit", ""), - ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), ("Help", ""), ("Failed", ""), ("Succeeded", ""), diff --git a/src/lang/es.rs b/src/lang/es.rs index 4de8a142e..518ab3ce2 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Overwrite", "Sobrescribir"), ("This file exists, skip or overwrite this file?", "Este archivo existe, ¿omitir o sobrescribir este archivo?"), ("Quit", "Salir"), - ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), ("Help", "Ayuda"), ("Failed", "Fallido"), ("Succeeded", "Logrado"), diff --git a/src/lang/fa.rs b/src/lang/fa.rs index 25a526377..1aadddf67 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Overwrite", "بازنویسی"), ("This file exists, skip or overwrite this file?", "این فایل وجود دارد، از فایل رد شود یا آن را بازنویسی کند؟"), ("Quit", "خروج"), - ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), ("Help", "راهنما"), ("Failed", "ناموفق"), ("Succeeded", "موفقیت آمیز"), diff --git a/src/lang/fr.rs b/src/lang/fr.rs index 16022b6f5..f61cd3f61 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Overwrite", "Écraser"), ("This file exists, skip or overwrite this file?", "Ce fichier existe, ignorer ou écraser ce fichier ?"), ("Quit", "Quitter"), - ("doc_mac_permission", "https://rustdesk.com/docs/fr/manual/mac/#enable-permissions"), ("Help", "Aider"), ("Failed", "échouer"), ("Succeeded", "Succès"), diff --git a/src/lang/hu.rs b/src/lang/hu.rs index bb3388341..3a91e6058 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Overwrite", "Felülírás"), ("This file exists, skip or overwrite this file?", "Ez a fájl már létezik, kihagyja vagy felülírja ezt a fájlt?"), ("Quit", "Kilépés"), - ("doc_mac_permission", "https://rustdesk.com/docs/hu/manual/mac/#enable-permissions"), ("Help", "Segítség"), ("Failed", "Sikertelen"), ("Succeeded", "Sikeres"), diff --git a/src/lang/id.rs b/src/lang/id.rs index 967ff2bcd..57a6d1f64 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Overwrite", "Ganti"), ("This file exists, skip or overwrite this file?", "File ini sudah ada, lewati atau ganti file ini?"), ("Quit", "Keluar"), - ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), ("Help", "Bantuan"), ("Failed", "Gagal"), ("Succeeded", "Berhasil"), diff --git a/src/lang/it.rs b/src/lang/it.rs index e0a944036..f1abaf231 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Overwrite", "Sovrascrivi"), ("This file exists, skip or overwrite this file?", "Questo file esiste, vuoi ignorarlo o sovrascrivere questo file?"), ("Quit", "Esci"), - ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), ("Help", "Aiuto"), ("Failed", "Fallito"), ("Succeeded", "Completato"), diff --git a/src/lang/ja.rs b/src/lang/ja.rs index 04b00dbfe..dc62f16a4 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Overwrite", "上書き"), ("This file exists, skip or overwrite this file?", "このファイルは存在しています。スキップするか上書きしますか?"), ("Quit", "終了"), - ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), // @TODO: Update url when someone translates the docum"), ("Help", "ヘルプ"), ("Failed", "失敗"), ("Succeeded", "成功"), diff --git a/src/lang/ko.rs b/src/lang/ko.rs index 4d8677c81..e25f0129f 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Overwrite", "덮어쓰기"), ("This file exists, skip or overwrite this file?", "해당 파일이 이미 존재합니다, 넘어가거나 덮어쓰시겠습니까?"), ("Quit", "종료"), - ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), ("Help", "지원"), ("Failed", "실패"), ("Succeeded", "성공"), diff --git a/src/lang/kz.rs b/src/lang/kz.rs index 2eab96f76..23f9955c3 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Overwrite", "Үстінен қайта жазу"), ("This file exists, skip or overwrite this file?", "Бұл файыл бар, өткізіп жіберу әлде үстінен қайта жазу керек пе?"), ("Quit", "Шығу"), - ("doc_mac_permission", ""), ("Help", "Көмек"), ("Failed", "Сәтсіз"), ("Succeeded", "Сәтті"), diff --git a/src/lang/lt.rs b/src/lang/lt.rs index c2dece955..b12491237 100644 --- a/src/lang/lt.rs +++ b/src/lang/lt.rs @@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Overwrite", "Perrašyti"), ("This file exists, skip or overwrite this file?", "Šis failas egzistuoja, praleisti arba perrašyti šį failą?"), ("Quit", "Išeiti"), - ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/"), ("Help", "Pagalba"), ("Failed", "Nepavyko"), ("Succeeded", "Pavyko"), diff --git a/src/lang/lv.rs b/src/lang/lv.rs index eab3cb40a..9f49ea4a8 100644 --- a/src/lang/lv.rs +++ b/src/lang/lv.rs @@ -57,7 +57,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("ID Server", "ID serveris"), ("Relay Server", "Releja serveris"), ("API Server", "API serveris"), - ("Key", "Atslēga"), ("invalid_http", "jāsākas ar http:// vai https://"), ("Invalid IP", "Nederīga IP"), ("Invalid format", "Nederīgs formāts"), @@ -296,8 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Overwrite", "Pārrakstīt"), ("This file exists, skip or overwrite this file?", "Šis fails pastāv, izlaist vai pārrakstīt šo failu?"), ("Quit", "Iziet"), - ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), - ("doc_fix_wayland", "https://rustdesk.com/docs/en/manual/linux/#x11-required"), ("Help", "Palīdzība"), ("Failed", "Neizdevās"), ("Succeeded", "Izdevās"), diff --git a/src/lang/nl.rs b/src/lang/nl.rs index 147417ced..bba933000 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Overwrite", "Overschrijven"), ("This file exists, skip or overwrite this file?", "Dit bestand bestaat reeds, overslaan of overschrijven?"), ("Quit", "Afsluiten"), - ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), ("Help", "https://rustdesk.com/docs/en/manual/linux/#x11-required"), ("Failed", "Mislukt"), ("Succeeded", "Geslaagd"), diff --git a/src/lang/pl.rs b/src/lang/pl.rs index 46bdb6e19..be4f442d9 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Overwrite", "Nadpisz"), ("This file exists, skip or overwrite this file?", "Ten plik istnieje, pominąć czy nadpisać ten plik?"), ("Quit", "Zrezygnuj"), - ("doc_mac_permission", "https://rustdesk.com/docs/en/client/mac/#enable-permissions"), ("Help", "Pomoc"), ("Failed", "Niepowodzenie"), ("Succeeded", "Udało się"), diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index f540bc76d..c27536851 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Overwrite", "Substituir"), ("This file exists, skip or overwrite this file?", "Este ficheiro já existe, ignorar ou substituir este ficheiro?"), ("Quit", "Saída"), - ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), ("Help", "Ajuda"), ("Failed", "Falhou"), ("Succeeded", "Conseguiu"), diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index 719886a18..662257511 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Overwrite", "Substituir"), ("This file exists, skip or overwrite this file?", "Este arquivo existe, pular ou substituir este arquivo?"), ("Quit", "Sair"), - ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), ("Help", "Ajuda"), ("Failed", "Falhou"), ("Succeeded", "Sucesso"), diff --git a/src/lang/ro.rs b/src/lang/ro.rs index b347278fc..14ca50dcf 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Overwrite", "Suprascrie"), ("This file exists, skip or overwrite this file?", "Fișier deja existent. Omite sau suprascrie?"), ("Quit", "Ieși"), - ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), ("Help", "Ajutor"), ("Failed", "Nereușit"), ("Succeeded", "Reușit"), diff --git a/src/lang/ru.rs b/src/lang/ru.rs index 3556bdbd0..fd6687ed1 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Overwrite", "Перезаписать"), ("This file exists, skip or overwrite this file?", "Файл существует, пропустить или перезаписать его?"), ("Quit", "Выйти"), - ("doc_mac_permission", "https://rustdesk.com/docs/ru/manual/mac/#включение-разрешений"), ("Help", "Помощь"), ("Failed", "Не выполнено"), ("Succeeded", "Выполнено"), diff --git a/src/lang/sk.rs b/src/lang/sk.rs index 05c3160fd..f64350e20 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Overwrite", "Prepísať"), ("This file exists, skip or overwrite this file?", "Preskočiť alebo prepísať existujúci súbor?"), ("Quit", "Ukončiť"), - ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), ("Help", "Nápoveda"), ("Failed", "Nepodarilo sa"), ("Succeeded", "Podarilo sa"), diff --git a/src/lang/sl.rs b/src/lang/sl.rs index c22b2ecd1..71cfa4446 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Overwrite", "Prepiši"), ("This file exists, skip or overwrite this file?", "Datoteka obstaja, izpusti ali prepiši?"), ("Quit", "Izhod"), - ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), ("Help", "Pomoč"), ("Failed", "Ni uspelo"), ("Succeeded", "Uspelo"), diff --git a/src/lang/sq.rs b/src/lang/sq.rs index d0875114d..324e19e89 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Overwrite", "Përshkruaj"), ("This file exists, skip or overwrite this file?", "Ky skedar ekziston , tejkalo ose përshkruaj këtë skedarë"), ("Quit", "Hiq"), - ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), ("Help", "Ndihmë"), ("Failed", "Deshtoi"), ("Succeeded", "Sukses"), diff --git a/src/lang/sr.rs b/src/lang/sr.rs index 13819f37e..104399803 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Overwrite", "Prepiši preko"), ("This file exists, skip or overwrite this file?", "Ova datoteka postoji, preskoči ili prepiši preko?"), ("Quit", "Izlaz"), - ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), ("Help", "Pomoć"), ("Failed", "Greška"), ("Succeeded", "Uspešno"), diff --git a/src/lang/sv.rs b/src/lang/sv.rs index 2440462dd..14bc6f750 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Overwrite", "Skriv över"), ("This file exists, skip or overwrite this file?", "Filen finns redan, hoppa över eller skriv över filen?"), ("Quit", "Avsluta"), - ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), ("Help", "Hjälp"), ("Failed", "Misslyckades"), ("Succeeded", "Lyckades"), diff --git a/src/lang/template.rs b/src/lang/template.rs index e442c826e..1817ea1af 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Overwrite", ""), ("This file exists, skip or overwrite this file?", ""), ("Quit", ""), - ("doc_mac_permission", ""), ("Help", ""), ("Failed", ""), ("Succeeded", ""), diff --git a/src/lang/th.rs b/src/lang/th.rs index c3726a5bb..5d4f2d11c 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Overwrite", "เขียนทับ"), ("This file exists, skip or overwrite this file?", "พบไฟล์ที่มีอยู่แล้ว ต้องการเขียนทับหรือไม่?"), ("Quit", "ออก"), - ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), ("Help", "ช่วยเหลือ"), ("Failed", "ล้มเหลว"), ("Succeeded", "สำเร็จ"), diff --git a/src/lang/tr.rs b/src/lang/tr.rs index 20a1ce942..733be0066 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Overwrite", "üzerine yaz"), ("This file exists, skip or overwrite this file?", "Bu dosya var, bu dosya atlansın veya üzerine yazılsın mı?"), ("Quit", "Çıkış"), - ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), ("Help", "Yardım"), ("Failed", "Arızalı"), ("Succeeded", "başarılı"), diff --git a/src/lang/tw.rs b/src/lang/tw.rs index dbe2c20e1..277a356f3 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Overwrite", "取代"), ("This file exists, skip or overwrite this file?", "此檔案/資料夾已存在,要略過或是取代此檔案嗎?"), ("Quit", "退出"), - ("doc_mac_permission", "https://rustdesk.com/docs/zh-tw/manual/mac/#啟用權限"), ("Help", "說明"), ("Failed", "失敗"), ("Succeeded", "成功"), diff --git a/src/lang/ua.rs b/src/lang/ua.rs index facada5f1..1f4053a32 100644 --- a/src/lang/ua.rs +++ b/src/lang/ua.rs @@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Overwrite", "Перезаписати"), ("This file exists, skip or overwrite this file?", "Цей файл існує, пропустити чи перезаписати файл?"), ("Quit", "Вийти"), - ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), ("Help", "Допомога"), ("Failed", "Не вдалося"), ("Succeeded", "Успішно"), diff --git a/src/lang/vn.rs b/src/lang/vn.rs index 9eec26b0c..5e8570822 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -295,7 +295,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Overwrite", "Ghi đè"), ("This file exists, skip or overwrite this file?", "Tệp tin này đã tồn tại, bạn có muốn bỏ qua hay ghi đè lên tệp tin này?"), ("Quit", "Thoát"), - ("doc_mac_permission", "https://rustdesk.com/docs/en/manual/mac/#enable-permissions"), ("Help", "Trợ giúp"), ("Failed", "Thất bại"), ("Succeeded", "Thành công"), From 3d9ec91b355324b6cc6f402ea29e5f2dd6880274 Mon Sep 17 00:00:00 2001 From: bovirus <1262554+bovirus@users.noreply.github.com> Date: Tue, 10 Oct 2023 15:52:55 +0200 Subject: [PATCH 06/50] Update Italian language --- src/lang/it.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/lang/it.rs b/src/lang/it.rs index f1abaf231..d8ce18686 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -179,8 +179,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Accept", "Accetta"), ("Dismiss", "Rifiuta"), ("Disconnect", "Disconnetti"), - ("Allow using keyboard and mouse", "Consenti l'uso di tastiera e mouse"), - ("Allow using clipboard", "Consenti l'uso degli appunti"), + ("Allow using keyboard and mouse", "Consenti uso tastiera e mouse"), + ("Allow using clipboard", "Consenti uso degli appunti"), ("Allow hearing sound", "Consenti la riproduzione dell'audio"), ("Allow file copy and paste", "Consenti copia e incolla di file"), ("Connected", "Connesso"), @@ -188,12 +188,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Relayed and encrypted connection", "Connessione tramite relay e cifrata"), ("Direct and unencrypted connection", "Connessione diretta e non cifrata"), ("Relayed and unencrypted connection", "Connessione tramite relay e non cifrata"), - ("Enter Remote ID", "Inserisci l'ID remoto"), + ("Enter Remote ID", "Inserisci ID remoto"), ("Enter your password", "Inserisci la password"), ("Logging in...", "Autenticazione..."), ("Enable RDP session sharing", "Abilita condivisione sessione RDP"), ("Auto Login", "Accesso automatico"), - ("Enable Direct IP Access", "Abilita l'accesso diretto tramite IP"), + ("Enable Direct IP Access", "Abilita accesso diretto tramite IP"), ("Rename", "Rinomina"), ("Space", "Spazio"), ("Create Desktop Shortcut", "Crea collegamento sul desktop"), @@ -405,7 +405,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("One-time Password", "Password monouso"), ("Use one-time password", "Usa password monouso"), ("One-time password length", "Lunghezza password monouso"), - ("Request access to your device", "Richiedi l'accesso al dispositivo"), + ("Request access to your device", "Richiedi accesso al dispositivo"), ("Hide connection management window", "Nascondi la finestra di gestione delle connessioni"), ("hide_cm_tip", "Permetti di nascondere solo se si accettano sessioni con password permanente"), ("wayland_experiment_tip", "Il supporto Wayland è in fase sperimentale, se vuoi un accesso stabile usa X11."), @@ -420,14 +420,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("software_render_tip", "Se nel computer con Linux è presente una scheda grafica Nvidia e la finestra remota si chiude immediatamente dopo la connessione, installa il nuovo driver open source e usa il rendering software.\nPotrebbe essere necessario un riavvio del programma."), ("Always use software rendering", "Usa sempre rendering software"), ("config_input", "Per controllare il desktop remoto con la tastiera, è necessario concedere le autorizzazioni a RustDesk 'Monitoraggio input'."), - ("config_microphone", "Per poter chiamare, è necessario concedere l'autorizzazione a RustDesk 'Registra audio'."), + ("config_microphone", "Per poter chiamare, è necessario concedere l'autorizzazione 'Registra audio' a RustDesk."), ("request_elevation_tip", "Se c'è qualcuno nel lato remoto è possibile richiedere l'elevazione."), ("Wait", "Attendi"), - ("Elevation Error", "Errore durante l'elevazione dei diritti"), - ("Ask the remote user for authentication", "Chiedi l'autenticazione all'utente remoto"), + ("Elevation Error", "Errore durante elevazione dei diritti"), + ("Ask the remote user for authentication", "Chiedi autenticazione all'utente remoto"), ("Choose this if the remote account is administrator", "Scegli questa opzione se l'account remoto è amministratore"), ("Transmit the username and password of administrator", "Trasmetti il nome utente e la password dell'amministratore"), - ("still_click_uac_tip", "Richiedi ancora che l'utente remoto faccia clic su OK nella finestra UAC dell'esecuzione di RustDesk."), + ("still_click_uac_tip", "Richiedi ancora che l'utente remoto selezioni 'OK' nella finestra UAC dell'esecuzione di RustDesk."), ("Request Elevation", "Richiedi elevazione dei diritti"), ("wait_accept_uac_tip", "Attendi che l'utente remoto accetti la finestra di dialogo UAC."), ("Elevate successfully", "Elevazione dei diritti effettuata correttamente"), @@ -551,7 +551,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("I Agree", "Accetto"), ("Decline", "Non accetto"), ("Timeout in minutes", "Timeout in minuti"), - ("auto_disconnect_option_tip", ""), + ("auto_disconnect_option_tip", "Chiudi automaticamente sessioni in entrata in caso di inattività utente"), ("Connection failed due to inactivity", "Connessione non riuscita a causa di inattività"), ("Check for software update on startup", "All'avvio verifica presenza aggiornamenti programma"), ("upgrade_rustdesk_server_pro_to_{}_tip", "Aggiorna RustDesk Server Pro alla versione {} o successiva!"), From 387d712b6722942a4ed8356842f4c0a61f3a085e Mon Sep 17 00:00:00 2001 From: Sahil Yeole Date: Tue, 10 Oct 2023 20:58:58 +0530 Subject: [PATCH 07/50] make scam alert text scrollable Signed-off-by: Sahil Yeole --- flutter/lib/mobile/pages/server_page.dart | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/flutter/lib/mobile/pages/server_page.dart b/flutter/lib/mobile/pages/server_page.dart index e31312905..972386667 100644 --- a/flutter/lib/mobile/pages/server_page.dart +++ b/flutter/lib/mobile/pages/server_page.dart @@ -328,13 +328,20 @@ class _ScamWarningDialogState extends State { ), ), SizedBox(height: 18), - Text( - translate("scam_text1")+"\n\n" - +translate("scam_text2")+"\n", - style: TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - fontSize: 16.0, + SizedBox( + height: 250, + child: Scrollbar( + child: SingleChildScrollView( + child: Text( + translate("scam_text1")+"\n\n" + +translate("scam_text2")+"\n", + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 16.0, + ), + ), + ), ), ), Row( From 20a4cd49debce3e939373489b49316dd6125b1af Mon Sep 17 00:00:00 2001 From: Sahil Yeole Date: Tue, 10 Oct 2023 21:32:05 +0530 Subject: [PATCH 08/50] update scam text height Signed-off-by: Sahil Yeole --- flutter/lib/mobile/pages/server_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter/lib/mobile/pages/server_page.dart b/flutter/lib/mobile/pages/server_page.dart index 972386667..deaddbea6 100644 --- a/flutter/lib/mobile/pages/server_page.dart +++ b/flutter/lib/mobile/pages/server_page.dart @@ -329,7 +329,7 @@ class _ScamWarningDialogState extends State { ), SizedBox(height: 18), SizedBox( - height: 250, + height: 220, child: Scrollbar( child: SingleChildScrollView( child: Text( From ab982e86c391dc31aba67e9439c0bd93143b84af Mon Sep 17 00:00:00 2001 From: Sahil Yeole Date: Tue, 10 Oct 2023 22:48:23 +0530 Subject: [PATCH 09/50] fix scam alert buttons overflow Signed-off-by: Sahil Yeole --- flutter/lib/mobile/pages/server_page.dart | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/flutter/lib/mobile/pages/server_page.dart b/flutter/lib/mobile/pages/server_page.dart index deaddbea6..5dc6f3526 100644 --- a/flutter/lib/mobile/pages/server_page.dart +++ b/flutter/lib/mobile/pages/server_page.dart @@ -368,7 +368,9 @@ class _ScamWarningDialogState extends State { Row( mainAxisAlignment: MainAxisAlignment.end, children: [ - ElevatedButton( + Container( + constraints: BoxConstraints(maxWidth: 150), + child: ElevatedButton( onPressed: isButtonLocked ? null : () { @@ -387,10 +389,15 @@ class _ScamWarningDialogState extends State { fontWeight: FontWeight.bold, fontSize: 13.0, ), + maxLines: 2, + overflow: TextOverflow.ellipsis, ), ), + ), SizedBox(width: 15), - ElevatedButton( + Container( + constraints: BoxConstraints(maxWidth: 150), + child: ElevatedButton( onPressed: () { Navigator.of(context).pop(); }, @@ -403,8 +410,11 @@ class _ScamWarningDialogState extends State { fontWeight: FontWeight.bold, fontSize: 13.0, ), + maxLines: 2, + overflow: TextOverflow.ellipsis, ), ), + ), ], )])), contentPadding: EdgeInsets.all(0.0), From 1416197b621f8a5c47a235c0f7d3631a59d99e23 Mon Sep 17 00:00:00 2001 From: 21pages Date: Wed, 11 Oct 2023 16:50:48 +0800 Subject: [PATCH 10/50] add option filter ab by intersection Signed-off-by: 21pages --- flutter/lib/common/widgets/address_book.dart | 17 +++++++++++++++++ flutter/lib/common/widgets/peers_view.dart | 17 +++++++++++++---- flutter/lib/models/ab_model.dart | 6 ++++++ src/lang/ar.rs | 1 + src/lang/ca.rs | 1 + src/lang/cn.rs | 1 + src/lang/cs.rs | 1 + src/lang/da.rs | 1 + src/lang/de.rs | 1 + src/lang/el.rs | 1 + src/lang/eo.rs | 1 + src/lang/es.rs | 1 + src/lang/fa.rs | 1 + src/lang/fr.rs | 1 + src/lang/hu.rs | 1 + src/lang/id.rs | 1 + src/lang/it.rs | 1 + src/lang/ja.rs | 1 + src/lang/ko.rs | 1 + src/lang/kz.rs | 1 + src/lang/lt.rs | 1 + src/lang/lv.rs | 1 + src/lang/nl.rs | 1 + src/lang/pl.rs | 1 + src/lang/pt_PT.rs | 1 + src/lang/ptbr.rs | 1 + src/lang/ro.rs | 1 + src/lang/ru.rs | 1 + src/lang/sk.rs | 1 + src/lang/sl.rs | 1 + src/lang/sq.rs | 1 + src/lang/sr.rs | 1 + src/lang/sv.rs | 1 + src/lang/template.rs | 1 + src/lang/th.rs | 1 + src/lang/tr.rs | 1 + src/lang/tw.rs | 1 + src/lang/ua.rs | 1 + src/lang/vn.rs | 1 + 39 files changed, 72 insertions(+), 4 deletions(-) diff --git a/flutter/lib/common/widgets/address_book.dart b/flutter/lib/common/widgets/address_book.dart index 5cc06c7a1..983b2219e 100644 --- a/flutter/lib/common/widgets/address_book.dart +++ b/flutter/lib/common/widgets/address_book.dart @@ -229,6 +229,22 @@ class _AddressBookState extends State { ); } + @protected + MenuEntryBase filterMenuItem() { + return MenuEntrySwitch( + switchType: SwitchType.scheckbox, + text: translate('Filter by intersection'), + getter: () async { + return filterAbTagByIntersection(); + }, + setter: (bool v) async { + bind.mainSetLocalOption(key: filterAbTagOption, value: v ? 'Y' : ''); + gFFI.abModel.filterByIntersection.value = v; + }, + dismissOnClicked: true, + ); + } + void _showMenu(RelativeRect pos) { final items = [ getEntry(translate("Add ID"), abAddId), @@ -236,6 +252,7 @@ class _AddressBookState extends State { getEntry(translate("Unselect all tags"), gFFI.abModel.unsetSelectedTags), sortMenuItem(), syncMenuItem(), + filterMenuItem(), ]; mod_menu.showMenu( diff --git a/flutter/lib/common/widgets/peers_view.dart b/flutter/lib/common/widgets/peers_view.dart index 2cf1a45c3..c058dd4dc 100644 --- a/flutter/lib/common/widgets/peers_view.dart +++ b/flutter/lib/common/widgets/peers_view.dart @@ -450,12 +450,21 @@ class AddressBookPeersView extends BasePeersView { if (selectedTags.isEmpty) { return true; } - for (final tag in selectedTags) { - if (idents.contains(tag)) { - return true; + if (gFFI.abModel.filterByIntersection.value) { + for (final tag in selectedTags) { + if (!idents.contains(tag)) { + return false; + } } + return true; + } else { + for (final tag in selectedTags) { + if (idents.contains(tag)) { + return true; + } + } + return false; } - return false; } } diff --git a/flutter/lib/models/ab_model.dart b/flutter/lib/models/ab_model.dart index 8d6bfda2c..4cb76ed6f 100644 --- a/flutter/lib/models/ab_model.dart +++ b/flutter/lib/models/ab_model.dart @@ -23,6 +23,11 @@ bool shouldSortTags() { return bind.mainGetLocalOption(key: sortAbTagsOption).isNotEmpty; } +final filterAbTagOption = 'filter-ab-by-intersection'; +bool filterAbTagByIntersection() { + return bind.mainGetLocalOption(key: filterAbTagOption).isNotEmpty; +} + class AbModel { final abLoading = false.obs; final pullError = "".obs; @@ -31,6 +36,7 @@ class AbModel { final RxMap tagColors = Map.fromEntries([]).obs; final peers = List.empty(growable: true).obs; final sortTags = shouldSortTags().obs; + final filterByIntersection = filterAbTagByIntersection().obs; final retrying = false.obs; bool get emtpy => peers.isEmpty && tags.isEmpty; diff --git a/src/lang/ar.rs b/src/lang/ar.rs index 0533591bc..568513504 100644 --- a/src/lang/ar.rs +++ b/src/lang/ar.rs @@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), + ("Filter by intersection", "") ].iter().cloned().collect(); } diff --git a/src/lang/ca.rs b/src/lang/ca.rs index 30eb7babf..9dab79371 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), + ("Filter by intersection", "") ].iter().cloned().collect(); } diff --git a/src/lang/cn.rs b/src/lang/cn.rs index 9b7ef42f4..d41af5467 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", "启动时检查软件更新"), ("upgrade_rustdesk_server_pro_to_{}_tip", "请升级专业版服务器到{}或更高版本!"), ("pull_group_failed_tip", "获取组信息失败"), + ("Filter by intersection", "按交集过滤") ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index 3b9359df0..728f8bb20 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", "Kontrola aktualizace softwaru při spuštění"), ("upgrade_rustdesk_server_pro_to_{}_tip", "Aktualizujte prosím RustDesk Server Pro na verzi {} nebo novější!"), ("pull_group_failed_tip", "Nepodařilo se obnovit skupinu"), + ("Filter by intersection", "") ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index 57769979e..cc4a3503e 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), + ("Filter by intersection", "") ].iter().cloned().collect(); } diff --git a/src/lang/de.rs b/src/lang/de.rs index 2f3f725f0..37d58d633 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", "Beim Start auf Softwareaktualisierung prüfen"), ("upgrade_rustdesk_server_pro_to_{}_tip", "Bitte aktualisieren Sie RustDesk Server Pro auf die Version {} oder neuer!"), ("pull_group_failed_tip", "Aktualisierung der Gruppe fehlgeschlagen"), + ("Filter by intersection", "") ].iter().cloned().collect(); } diff --git a/src/lang/el.rs b/src/lang/el.rs index b1818d354..183cae8d2 100644 --- a/src/lang/el.rs +++ b/src/lang/el.rs @@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), + ("Filter by intersection", "") ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index 44104f1ae..114009526 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), + ("Filter by intersection", "") ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index 518ab3ce2..9ca8ae68f 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", "Comprobar actualización al iniciar"), ("upgrade_rustdesk_server_pro_to_{}_tip", "¡Por favor, actualiza RustDesk Server Pro a la versión {} o superior"), ("pull_group_failed_tip", "No se ha podido refrescar el grupo"), + ("Filter by intersection", "") ].iter().cloned().collect(); } diff --git a/src/lang/fa.rs b/src/lang/fa.rs index 1aadddf67..9090dfa6b 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), + ("Filter by intersection", "") ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index f61cd3f61..bbc2f6524 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", "Vérifier la disponibilité des mises à jour au démarrage"), ("upgrade_rustdesk_server_pro_to_{}_tip", "Veuillez mettre à jour RustDesk Server Pro avec la version {} ou une version plus récente !"), ("pull_group_failed_tip", "Échec de l'actualisation du groupe"), + ("Filter by intersection", "") ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index 3a91e6058..334bb7435 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), + ("Filter by intersection", "") ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index 57a6d1f64..b455d005d 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", "Periksa pembaruan aplikasi saat sistem dinyalakan."), ("upgrade_rustdesk_server_pro_to_{}_tip", "Silahkan perbarui RustDesk Server Pro ke versi {} atau yang lebih baru!"), ("pull_group_failed_tip", "Gagal memperbarui grup"), + ("Filter by intersection", "") ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index d8ce18686..c14d45b63 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", "All'avvio verifica presenza aggiornamenti programma"), ("upgrade_rustdesk_server_pro_to_{}_tip", "Aggiorna RustDesk Server Pro alla versione {} o successiva!"), ("pull_group_failed_tip", "Impossibile aggiornare il gruppo"), + ("Filter by intersection", "") ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index dc62f16a4..f730a12df 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), + ("Filter by intersection", "") ].iter().cloned().collect(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index e25f0129f..4c8972f75 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), + ("Filter by intersection", "") ].iter().cloned().collect(); } diff --git a/src/lang/kz.rs b/src/lang/kz.rs index 23f9955c3..865f8e455 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), + ("Filter by intersection", "") ].iter().cloned().collect(); } diff --git a/src/lang/lt.rs b/src/lang/lt.rs index b12491237..e0374c70f 100644 --- a/src/lang/lt.rs +++ b/src/lang/lt.rs @@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), + ("Filter by intersection", "") ].iter().cloned().collect(); } diff --git a/src/lang/lv.rs b/src/lang/lv.rs index 9f49ea4a8..2ea614dce 100644 --- a/src/lang/lv.rs +++ b/src/lang/lv.rs @@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", "Startējot pārbaudīt, vai nav programmatūras atjauninājumu"), ("upgrade_rustdesk_server_pro_to_{}_tip", "Lūdzu, jauniniet RustDesk Server Pro uz versiju {} vai jaunāku!"), ("pull_group_failed_tip", "Neizdevās atsvaidzināt grupu"), + ("Filter by intersection", "") ].iter().cloned().collect(); } diff --git a/src/lang/nl.rs b/src/lang/nl.rs index bba933000..79cbb1ec4 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", "Checken voor updates bij opstarten"), ("upgrade_rustdesk_server_pro_to_{}_tip", "Upgrade RustDesk Server Pro naar versie {} of nieuwer!"), ("pull_group_failed_tip", "Vernieuwen van groep mislukt"), + ("Filter by intersection", "") ].iter().cloned().collect(); } diff --git a/src/lang/pl.rs b/src/lang/pl.rs index be4f442d9..6dc57366c 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", "Sprawdź aktualizacje przy starcie programu"), ("upgrade_rustdesk_server_pro_to_{}_tip", "Proszę zaktualizować RustDesk Server Pro do wersji {} lub nowszej!"), ("pull_group_failed_tip", "Błąd odświeżania grup"), + ("Filter by intersection", "") ].iter().cloned().collect(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index c27536851..c8298a749 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), + ("Filter by intersection", "") ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index 662257511..3d935dfa7 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), + ("Filter by intersection", "") ].iter().cloned().collect(); } diff --git a/src/lang/ro.rs b/src/lang/ro.rs index 14ca50dcf..03b66f2e5 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), + ("Filter by intersection", "") ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index fd6687ed1..e5ffec0ab 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", "Проверять обновления программы при запуске"), ("upgrade_rustdesk_server_pro_to_{}_tip", "Обновите RustDesk Server Pro до версии {} или новее!"), ("pull_group_failed_tip", "Невозможно обновить группу"), + ("Filter by intersection", "") ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index f64350e20..11d1cc5e7 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), + ("Filter by intersection", "") ].iter().cloned().collect(); } diff --git a/src/lang/sl.rs b/src/lang/sl.rs index 71cfa4446..c9ff7d82b 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), + ("Filter by intersection", "") ].iter().cloned().collect(); } diff --git a/src/lang/sq.rs b/src/lang/sq.rs index 324e19e89..3ead4dc70 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), + ("Filter by intersection", "") ].iter().cloned().collect(); } diff --git a/src/lang/sr.rs b/src/lang/sr.rs index 104399803..67969158f 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), + ("Filter by intersection", "") ].iter().cloned().collect(); } diff --git a/src/lang/sv.rs b/src/lang/sv.rs index 14bc6f750..a3dc77764 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), + ("Filter by intersection", "") ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index 1817ea1af..a424ba0f9 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), + ("Filter by intersection", "") ].iter().cloned().collect(); } diff --git a/src/lang/th.rs b/src/lang/th.rs index 5d4f2d11c..361da2446 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", "ตรวจสอบการอัปเดตโปรแกรมเมื่อเริ่มต้นใช้งาน"), ("upgrade_rustdesk_server_pro_to_{}_tip", "กรุณาอัปเดต Rustdesk Server Pro ไปยังเวอร์ชัน {} หรือใหม่กว่า!"), ("pull_group_failed_tip", "การเรียกใช้งานกลุ่มล้มเหลว"), + ("Filter by intersection", "") ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index 733be0066..d5d3859b8 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), + ("Filter by intersection", "") ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index 277a356f3..cf502f914 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), + ("Filter by intersection", "") ].iter().cloned().collect(); } diff --git a/src/lang/ua.rs b/src/lang/ua.rs index 1f4053a32..a5756b5f2 100644 --- a/src/lang/ua.rs +++ b/src/lang/ua.rs @@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), + ("Filter by intersection", "") ].iter().cloned().collect(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index 5e8570822..9ed649b32 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -556,5 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), + ("Filter by intersection", "") ].iter().cloned().collect(); } From 578ca6975fd3098acfbec33d5745fec190075542 Mon Sep 17 00:00:00 2001 From: AnonymousWP <50231698+AnonymousWP@users.noreply.github.com> Date: Wed, 11 Oct 2023 12:15:14 +0200 Subject: [PATCH 11/50] fix(translation/Dutch): use formal translation Related: https://github.com/rustdesk/rustdesk/discussions/5948 --- src/lang/nl.rs | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/lang/nl.rs b/src/lang/nl.rs index 79cbb1ec4..f46599194 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -22,7 +22,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("TCP Tunneling", "TCP Tunneling"), ("Remove", "Verwijder"), ("Refresh random password", "Vernieuw willekeurig wachtwoord"), - ("Set your own password", "Stel je eigen wachtwoord in"), + ("Set your own password", "Stel uw eigen wachtwoord in"), ("Enable Keyboard/Mouse", "Toetsenbord/Muis Inschakelen"), ("Enable Clipboard", "Klembord Inschakelen"), ("Enable File Transfer", "Bestandsoverdracht Inschakelen"), @@ -72,7 +72,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Please enter your password", "Geef uw wachtwoord in"), ("Remember password", "Wachtwoord onthouden"), ("Wrong Password", "Verkeerd wachtwoord"), - ("Do you want to enter again?", "Wil je opnieuw ingeven?"), + ("Do you want to enter again?", "Wilt u het opnieuw invoeren?"), ("Connection Error", "Fout bij verbinding"), ("Error", "Fout"), ("Reset by the peer", "Reset door de peer"), @@ -102,9 +102,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Unselect All", "De-selecteer alles"), ("Empty Directory", "Lege Map"), ("Not an empty directory", "Geen lege map"), - ("Are you sure you want to delete this file?", "Weet je zeker dat je dit bestand wilt verwijderen?"), - ("Are you sure you want to delete this empty directory?", "Weet je zeker dat je deze lege map wilt verwijderen?"), - ("Are you sure you want to delete the file of this directory?", "Weet je zeker dat je het bestand uit deze map wilt verwijderen?"), + ("Are you sure you want to delete this file?", "Weet u zeker dat u dit bestand wilt verwijderen?"), + ("Are you sure you want to delete this empty directory?", "Weet u zeker dat u deze lege map wilt verwijderen?"), + ("Are you sure you want to delete the file of this directory?", "Weet u zeker dat u het bestand uit deze map wilt verwijderen?"), ("Do this for all conflicts", "Doe dit voor alle conflicten"), ("This is irreversible!", "Dit is onomkeerbaar!"), ("Deleting", "Verwijderen"), @@ -145,13 +145,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Failed to make direct connection to remote desktop", "Onmogelijk direct verbinding te maken met extern bureaublad"), ("Set Password", "Wachtwoord Instellen"), ("OS Password", "OS Wachtwoord"), - ("install_tip", "Je gebruikt een niet geïnstalleerde versie. Als gevolg van UAC-beperkingen is het in sommige gevallen niet mogelijk om als controleterminal de muis en het toetsenbord te bedienen of het scherm over te nemen. Klik op de knop hieronder om RustDesk op het systeem te installeren om het bovenstaande probleem te voorkomen."), + ("install_tip", "U gebruikt een niet geïnstalleerde versie. Als gevolg van UAC-beperkingen is het in sommige gevallen niet mogelijk om als controleterminal de muis en het toetsenbord te bedienen of het scherm over te nemen. Klik op de knop hieronder om RustDesk op het systeem te installeren om het bovenstaande probleem te voorkomen."), ("Click to upgrade", "Klik voor upgrade"), ("Click to download", "Klik om te downloaden"), ("Click to update", "Klik om bij te werken"), ("Configure", "Configureren"), - ("config_acc", "Om je bureaublad op afstand te kunnen bedienen, moet je RustDesk \"toegankelijkheid\" toestemming geven."), - ("config_screen", "Om toegang te krijgen tot het externe bureaublad, moet je RustDesk de toestemming \"schermregistratie\" geven."), + ("config_acc", "Om uw bureaublad op afstand te kunnen bedienen, moet u RustDesk \"toegankelijkheid\" toestemming geven."), + ("config_screen", "Om toegang te krijgen tot het externe bureaublad, moet u RustDesk de toestemming \"schermregistratie\" geven."), ("Installing ...", "Installeren ..."), ("Install", "Installeer"), ("Installation", "Installatie"), @@ -244,11 +244,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Socks5 Proxy", "Socks5 Proxy"), ("Hostname", "Hostnaam"), ("Discovered", "Ontdekt"), - ("install_daemon_tip", "Om bij het opstarten van de computer te kunnen beginnen, moet je de systeemdienst installeren."), + ("install_daemon_tip", "Om bij het opstarten van de computer te kunnen beginnen, moet u de systeemservice installeren."), ("Remote ID", "Externe ID"), ("Paste", "Plakken"), ("Paste here?", "Hier plakken"), - ("Are you sure to close the connection?", "Weet je zeker dat je de verbinding wilt sluiten?"), + ("Are you sure to close the connection?", "Weet u zeker dat u de verbinding wilt sluiten?"), ("Download new version", "Download nieuwe versie"), ("Touch mode", "Aanraak modus"), ("Mouse mode", "Muismodus"), @@ -280,7 +280,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Capture", "Audio Opnemen"), ("File Connection", "Bestandsverbinding"), ("Screen Connection", "Schermverbinding"), - ("Do you accept?", "Sta je toe?"), + ("Do you accept?", "Geeft u toestemming?"), ("Open System Setting", "Systeeminstelling Openen"), ("How to get Android input permission?", "Hoe krijg ik Android invoer toestemming?"), ("android_input_permission_tip1", "Om ervoor te zorgen dat een extern apparaat uw Android-apparaat kan besturen via muis of aanraking, moet u RustDesk toestaan om de \"Toegankelijkheid\" service te gebruiken."), @@ -323,7 +323,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Enable Remote Restart", "Schakel Herstart op afstand in"), ("Allow remote restart", "Opnieuw Opstarten op afstand toestaan"), ("Restart Remote Device", "Apparaat op afstand herstarten"), - ("Are you sure you want to restart", "Weet je zeker dat je wilt herstarten"), + ("Are you sure you want to restart", "Weet u zeker dat u wilt herstarten"), ("Restarting Remote Device", "Apparaat op afstand herstarten"), ("remote_restarting_tip", "Apparaat op afstand wordt opnieuw opgestart, sluit dit bericht en maak na een ogenblik opnieuw verbinding met het permanente wachtwoord."), ("Copied", "Gekopieerd"), @@ -389,7 +389,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Full Access", "Volledige Toegang"), ("Screen Share", "Scherm Delen"), ("Wayland requires Ubuntu 21.04 or higher version.", "Wayland vereist Ubuntu 21.04 of een hogere versie."), - ("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland vereist een hogere versie van Linux distro. Probeer X11 desktop of verander je OS."), + ("Wayland requires higher version of linux distro. Please try X11 desktop or change your OS.", "Wayland vereist een hogere versie van Linux distro. Probeer X11 desktop of verander van OS."), ("JumpLink", "JumpLink"), ("Please Select the screen to be shared(Operate on the peer side).", "Selecteer het scherm dat moet worden gedeeld (Bediening aan de kant van de peer)."), ("Show RustDesk", "Toon RustDesk"), @@ -408,7 +408,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Request access to your device", "Toegang tot uw toestel aanvragen"), ("Hide connection management window", "Verberg het venster voor verbindingsbeheer"), ("hide_cm_tip", "Dit kan alleen als de toegang via een permanent wachtwoord verloopt."), - ("wayland_experiment_tip", "Wayland ondersteuning is slechts experimenteel. Gebruik alsjeblieft X11 als je onbeheerde toegang nodig hebt."), + ("wayland_experiment_tip", "Wayland ondersteuning is slechts experimenteel. Gebruik alsjeblieft X11 als u onbeheerde toegang nodig hebt."), ("Right click to select tabs", "Rechts klikken om tabbladen te selecteren"), ("Skipped", "Overgeslagen"), ("Add to Address Book", "Toevoegen aan Adresboek"), @@ -440,7 +440,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Medium", "Middelmatig"), ("Strong", "Sterk"), ("Switch Sides", "Wissel van kant"), - ("Please confirm if you want to share your desktop?", "bevestig als je je bureaublad wilt delen?"), + ("Please confirm if you want to share your desktop?", "Bevestig als u uw bureaublad wilt delen?"), ("Display", "Weergave"), ("Default View Style", "Standaard Weergave Stijl"), ("Default Scroll Style", "Standaard Scroll Stijl"), @@ -469,9 +469,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Maximize", "Maximaliseren"), ("Your Device", "Uw Apparaat"), ("empty_recent_tip", "Oeps, geen actuele situatie!\nTijd om een nieuwe te plannen."), - ("empty_favorite_tip", "Nog geen favoriete Station op afstand? Laat ons iemand vinden om mee te verbinden en voeg hem toe aan je favorieten!"), + ("empty_favorite_tip", "Nog geen favoriete Station op afstand? Laat ons iemand vinden om mee te verbinden en voeg hem toe aan uw favorieten!"), ("empty_lan_tip", "Oh nee, het lijkt erop dat we nog geen extern station hebben ontdekt."), - ("empty_address_book_tip", "Oh jee, het lijkt erop dat er momenteel geen externe stations in je adresboek staan."), + ("empty_address_book_tip", "Oh jee, het lijkt erop dat er momenteel geen externe stations in uw adresboek staan."), ("eg: admin", "bijvoorbeeld: admin"), ("Empty Username", "Gebruikersnaam Leeg"), ("Empty Password", "Wachtwoord Leeg"), @@ -516,7 +516,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Outgoing connection", "Uitgaande verbinding"), ("Exit", "Afsluiten"), ("Open", "Open"), - ("logout_tip", "Weet je zeker dat je je wilt afmelden?"), + ("logout_tip", "Weet u zeker dat u zich wilt afmelden?"), ("Service", "Service"), ("Start", "Start"), ("Stop", "Stop"), From f387ccb9e464f34104652453867316cfd6f79401 Mon Sep 17 00:00:00 2001 From: bovirus <1262554+bovirus@users.noreply.github.com> Date: Wed, 11 Oct 2023 19:32:57 +0200 Subject: [PATCH 12/50] Add files via upload --- src/lang/it.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lang/it.rs b/src/lang/it.rs index c14d45b63..854b4a9c0 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -556,6 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", "All'avvio verifica presenza aggiornamenti programma"), ("upgrade_rustdesk_server_pro_to_{}_tip", "Aggiorna RustDesk Server Pro alla versione {} o successiva!"), ("pull_group_failed_tip", "Impossibile aggiornare il gruppo"), - ("Filter by intersection", "") + ("Filter by intersection", "Filtra per incrocio") ].iter().cloned().collect(); } From bd6d863921af991300cc34a504a208549e45634f Mon Sep 17 00:00:00 2001 From: solokot Date: Thu, 12 Oct 2023 10:12:50 +0300 Subject: [PATCH 13/50] Update ru.rs --- src/lang/ru.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lang/ru.rs b/src/lang/ru.rs index e5ffec0ab..d4c9e055c 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -556,6 +556,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", "Проверять обновления программы при запуске"), ("upgrade_rustdesk_server_pro_to_{}_tip", "Обновите RustDesk Server Pro до версии {} или новее!"), ("pull_group_failed_tip", "Невозможно обновить группу"), - ("Filter by intersection", "") + ("Filter by intersection", "Фильтровать по пересечению") ].iter().cloned().collect(); } From a331961ef31ca2f5d497a86dd872742a789cb902 Mon Sep 17 00:00:00 2001 From: Kleofass Date: Thu, 12 Oct 2023 15:13:23 +0300 Subject: [PATCH 14/50] Update lv.rs --- src/lang/lv.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lang/lv.rs b/src/lang/lv.rs index 2ea614dce..9ea0d2d74 100644 --- a/src/lang/lv.rs +++ b/src/lang/lv.rs @@ -57,6 +57,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("ID Server", "ID serveris"), ("Relay Server", "Releja serveris"), ("API Server", "API serveris"), + ("Key", "Atslēga"), ("invalid_http", "jāsākas ar http:// vai https://"), ("Invalid IP", "Nederīga IP"), ("Invalid format", "Nederīgs formāts"), @@ -556,6 +557,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", "Startējot pārbaudīt, vai nav programmatūras atjauninājumu"), ("upgrade_rustdesk_server_pro_to_{}_tip", "Lūdzu, jauniniet RustDesk Server Pro uz versiju {} vai jaunāku!"), ("pull_group_failed_tip", "Neizdevās atsvaidzināt grupu"), - ("Filter by intersection", "") + ("Filter by intersection", "Filtrēt pēc krustpunkta") ].iter().cloned().collect(); } From b89546de373ff69650a3be3401d4c627f62bdb6e Mon Sep 17 00:00:00 2001 From: RustDesk <71636191+rustdesk@users.noreply.github.com> Date: Thu, 12 Oct 2023 20:54:10 +0800 Subject: [PATCH 15/50] Prefer "Key" not translated. --- src/lang/lv.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lang/lv.rs b/src/lang/lv.rs index 9ea0d2d74..66d7f1969 100644 --- a/src/lang/lv.rs +++ b/src/lang/lv.rs @@ -57,7 +57,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("ID Server", "ID serveris"), ("Relay Server", "Releja serveris"), ("API Server", "API serveris"), - ("Key", "Atslēga"), ("invalid_http", "jāsākas ar http:// vai https://"), ("Invalid IP", "Nederīga IP"), ("Invalid format", "Nederīgs formāts"), From a3670b731e40c2b462024cb91f5e5ff82f4a8aa9 Mon Sep 17 00:00:00 2001 From: 21pages Date: Thu, 12 Oct 2023 21:44:04 +0800 Subject: [PATCH 16/50] remove option enable rdp Signed-off-by: 21pages --- .../lib/desktop/pages/desktop_setting_page.dart | 5 ----- src/lang/ar.rs | 1 - src/lang/ca.rs | 1 - src/lang/cn.rs | 1 - src/lang/cs.rs | 1 - src/lang/da.rs | 1 - src/lang/de.rs | 1 - src/lang/el.rs | 1 - src/lang/eo.rs | 1 - src/lang/es.rs | 1 - src/lang/fa.rs | 1 - src/lang/fr.rs | 1 - src/lang/hu.rs | 1 - src/lang/id.rs | 1 - src/lang/it.rs | 1 - src/lang/ja.rs | 1 - src/lang/ko.rs | 1 - src/lang/kz.rs | 1 - src/lang/lt.rs | 1 - src/lang/lv.rs | 1 - src/lang/nl.rs | 1 - src/lang/pl.rs | 1 - src/lang/pt_PT.rs | 1 - src/lang/ptbr.rs | 1 - src/lang/ro.rs | 1 - src/lang/ru.rs | 1 - src/lang/sk.rs | 1 - src/lang/sl.rs | 1 - src/lang/sq.rs | 1 - src/lang/sr.rs | 1 - src/lang/sv.rs | 1 - src/lang/template.rs | 1 - src/lang/th.rs | 1 - src/lang/tr.rs | 1 - src/lang/tw.rs | 1 - src/lang/ua.rs | 1 - src/lang/vn.rs | 1 - src/server/connection.rs | 16 +++++----------- 38 files changed, 5 insertions(+), 52 deletions(-) diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index ca41ff227..7324d3a72 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -729,11 +729,6 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin { Widget more(BuildContext context) { bool enabled = !locked; return _Card(title: 'Security', children: [ - Offstage( - offstage: !Platform.isWindows, - child: _OptionCheckBox(context, 'Enable RDP', 'enable-rdp', - enabled: enabled), - ), shareRdp(context, enabled), _OptionCheckBox(context, 'Deny LAN Discovery', 'enable-lan-discovery', reverse: true, enabled: enabled), diff --git a/src/lang/ar.rs b/src/lang/ar.rs index 568513504..a7f24540e 100644 --- a/src/lang/ar.rs +++ b/src/lang/ar.rs @@ -365,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", "جهاز ادخال الصوت"), ("Use IP Whitelisting", "استخدام قائمة الـ IP البيضاء"), ("Network", "الشبكة"), - ("Enable RDP", "تفعيل RDP"), ("Pin Toolbar", "تثبيت شريط الادوات"), ("Unpin Toolbar", "الغاء تثبيت شريط الادوات"), ("Recording", "التسجيل"), diff --git a/src/lang/ca.rs b/src/lang/ca.rs index 9dab79371..e7180abf3 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -365,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", "Dispositiu d'entrada d'àudio"), ("Use IP Whitelisting", "Utilitza llista de IPs admeses"), ("Network", "Xarxa"), - ("Enable RDP", "Habilitar RDP"), ("Pin Toolbar", ""), ("Unpin Toolbar", ""), ("Recording", "Gravant"), diff --git a/src/lang/cn.rs b/src/lang/cn.rs index d41af5467..d1cff492b 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -365,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", "音频输入设备"), ("Use IP Whitelisting", "只允许白名单上的 IP 访问"), ("Network", "网络"), - ("Enable RDP", "允许 RDP 访问"), ("Pin Toolbar", "固定工具栏"), ("Unpin Toolbar", "取消固定工具栏"), ("Recording", "录屏"), diff --git a/src/lang/cs.rs b/src/lang/cs.rs index 728f8bb20..9267e1f75 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -365,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", "Vstupní zvukové zařízení"), ("Use IP Whitelisting", "Použít bílou listinu IP"), ("Network", "Síť"), - ("Enable RDP", "Povolit protokol RDP"), ("Pin Toolbar", "Připnout panel nástrojů"), ("Unpin Toolbar", "Odepnout panel nástrojů"), ("Recording", "Nahrávání"), diff --git a/src/lang/da.rs b/src/lang/da.rs index cc4a3503e..1ad6f36b5 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -365,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", "Lydindgangsenhed"), ("Use IP Whitelisting", "Brug IP Whitelisting"), ("Network", "Netværk"), - ("Enable RDP", "Aktivér RDP"), ("Pin Toolbar", ""), ("Unpin Toolbar", ""), ("Recording", "Optager"), diff --git a/src/lang/de.rs b/src/lang/de.rs index 37d58d633..bf5da7415 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -365,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", "Audioeingabegerät"), ("Use IP Whitelisting", "IP-Whitelist verwenden"), ("Network", "Netzwerk"), - ("Enable RDP", "RDP aktivieren"), ("Pin Toolbar", "Symbolleiste anpinnen"), ("Unpin Toolbar", "Symbolleiste lösen"), ("Recording", "Aufnahme"), diff --git a/src/lang/el.rs b/src/lang/el.rs index 183cae8d2..a984411a2 100644 --- a/src/lang/el.rs +++ b/src/lang/el.rs @@ -365,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", "Συσκευή εισόδου ήχου"), ("Use IP Whitelisting", "Χρήση λίστας επιτρεπόμενων IP"), ("Network", "Δίκτυο"), - ("Enable RDP", "Ενεργοποίηση RDP"), ("Pin Toolbar", ""), ("Unpin Toolbar", ""), ("Recording", "Εγγραφή"), diff --git a/src/lang/eo.rs b/src/lang/eo.rs index 114009526..51e97c68a 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -365,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", ""), ("Use IP Whitelisting", ""), ("Network", ""), - ("Enable RDP", ""), ("Pin Toolbar", ""), ("Unpin Toolbar", ""), ("Recording", ""), diff --git a/src/lang/es.rs b/src/lang/es.rs index 9ca8ae68f..350505a34 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -365,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", "Dispositivo de entrada de audio"), ("Use IP Whitelisting", "Usar lista de IPs admitidas"), ("Network", "Red"), - ("Enable RDP", "Habilitar RDP"), ("Pin Toolbar", ""), ("Unpin Toolbar", ""), ("Recording", "Grabando"), diff --git a/src/lang/fa.rs b/src/lang/fa.rs index 9090dfa6b..0e0009a6c 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -365,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", "منبع صدا"), ("Use IP Whitelisting", "های مجاز IP استفاده از"), ("Network", "شبکه"), - ("Enable RDP", "RDP فعال شدن"), ("Pin Toolbar", "سجاق کردن نوار ابزار"), ("Unpin Toolbar", "خروج از حالت سجاق نوار ابزار"), ("Recording", "در حال ضبط"), diff --git a/src/lang/fr.rs b/src/lang/fr.rs index bbc2f6524..50b03299a 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -365,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", "Périphérique source audio"), ("Use IP Whitelisting", "Utiliser une liste blanche d'IP"), ("Network", "Réseau"), - ("Enable RDP", "Activer connection RDP"), ("Pin Toolbar", "Épingler la barre d'outil"), ("Unpin Toolbar", "Détacher la barre d'outil"), ("Recording", "Enregistrement"), diff --git a/src/lang/hu.rs b/src/lang/hu.rs index 334bb7435..f9f7051ae 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -365,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", "Audio bemeneti eszköz"), ("Use IP Whitelisting", "Engedélyezési lista használata"), ("Network", "Hálózat"), - ("Enable RDP", "RDP engedélyezése"), ("Pin Toolbar", ""), ("Unpin Toolbar", ""), ("Recording", "Felvétel"), diff --git a/src/lang/id.rs b/src/lang/id.rs index b455d005d..8ed5f1861 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -365,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", "Input Perangkat Audio"), ("Use IP Whitelisting", "Gunakan daftar IP yang diizinkan"), ("Network", "Jaringan"), - ("Enable RDP", "Aktifkan RDP"), ("Pin Toolbar", "Sematkan Toolbar"), ("Unpin Toolbar", "Batal sematkan Toolbar"), ("Recording", "Sedang Merekam"), diff --git a/src/lang/it.rs b/src/lang/it.rs index 854b4a9c0..194f0686c 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -365,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", "Dispositivo ingresso audio"), ("Use IP Whitelisting", "Usa elenco IP autorizzati"), ("Network", "Rete"), - ("Enable RDP", "Abilita RDP"), ("Pin Toolbar", "Blocca barra strumenti"), ("Unpin Toolbar", "Sblocca barra strumenti"), ("Recording", "Registrazione"), diff --git a/src/lang/ja.rs b/src/lang/ja.rs index f730a12df..48b4f28ae 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -365,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", ""), ("Use IP Whitelisting", ""), ("Network", ""), - ("Enable RDP", ""), ("Pin Toolbar", ""), ("Unpin Toolbar", ""), ("Recording", ""), diff --git a/src/lang/ko.rs b/src/lang/ko.rs index 4c8972f75..00a4012e8 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -365,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", ""), ("Use IP Whitelisting", ""), ("Network", ""), - ("Enable RDP", ""), ("Pin Toolbar", ""), ("Unpin Toolbar", ""), ("Recording", ""), diff --git a/src/lang/kz.rs b/src/lang/kz.rs index 865f8e455..d7481eaff 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -365,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", ""), ("Use IP Whitelisting", ""), ("Network", ""), - ("Enable RDP", ""), ("Pin Toolbar", ""), ("Unpin Toolbar", ""), ("Recording", ""), diff --git a/src/lang/lt.rs b/src/lang/lt.rs index e0374c70f..9e63f6160 100644 --- a/src/lang/lt.rs +++ b/src/lang/lt.rs @@ -365,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", "Garso įvestis"), ("Use IP Whitelisting", "Naudoti patikimą IP sąrašą"), ("Network", "Tinklas"), - ("Enable RDP", "Įgalinti RDP"), ("Pin Toolbar", ""), ("Unpin Toolbar", ""), ("Recording", "Įrašymas"), diff --git a/src/lang/lv.rs b/src/lang/lv.rs index 2ea614dce..3bdffa616 100644 --- a/src/lang/lv.rs +++ b/src/lang/lv.rs @@ -365,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", "Audio ievades ierīce"), ("Use IP Whitelisting", "Izmantot balto IP sarakstu"), ("Network", "Tīkls"), - ("Enable RDP", "Iespējot RDP"), ("Pin Toolbar", "Piespraust rīkjoslu"), ("Unpin Toolbar", "Atspraust rīkjoslu"), ("Recording", "Ierakstīšana"), diff --git a/src/lang/nl.rs b/src/lang/nl.rs index f46599194..93967ebad 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -365,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", "Audio-invoerapparaat"), ("Use IP Whitelisting", "Gebruik een witte lijst van IP-adressen"), ("Network", "Netwerk"), - ("Enable RDP", "Zet RDP aan"), ("Pin Toolbar", "Werkbalk Vastzetten"), ("Unpin Toolbar", "Werkbalk Losmaken"), ("Recording", "Opnemen"), diff --git a/src/lang/pl.rs b/src/lang/pl.rs index 6dc57366c..d6c4f5b84 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -365,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", "Urządzenie wejściowe Audio"), ("Use IP Whitelisting", "Użyj białej listy IP"), ("Network", "Sieć"), - ("Enable RDP", "Włącz RDP"), ("Pin Toolbar", "Przypnij pasek narzędzi"), ("Unpin Toolbar", "Odepnij pasek narzędzi"), ("Recording", "Nagrywanie"), diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index c8298a749..8712ac615 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -365,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", ""), ("Use IP Whitelisting", ""), ("Network", ""), - ("Enable RDP", ""), ("Pin Toolbar", ""), ("Unpin Toolbar", ""), ("Recording", ""), diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index 3d935dfa7..f2810f76c 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -365,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", "Dispositivo de entrada de áudio"), ("Use IP Whitelisting", "Utilizar lista de IPs confiáveis"), ("Network", "Rede"), - ("Enable RDP", "Habilitar RDP"), ("Pin Toolbar", ""), ("Unpin Toolbar", ""), ("Recording", "Gravando"), diff --git a/src/lang/ro.rs b/src/lang/ro.rs index 03b66f2e5..73ff3bb22 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -365,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", "Dispozitiv de intrare audio"), ("Use IP Whitelisting", "Folosește lista de IP-uri autorizate"), ("Network", "Rețea"), - ("Enable RDP", "Activează RDP"), ("Pin Toolbar", "Fixează bara de instrumente"), ("Unpin Toolbar", "Detașează bara de instrumente"), ("Recording", "Înregistrare"), diff --git a/src/lang/ru.rs b/src/lang/ru.rs index d4c9e055c..1d7c8cfca 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -365,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", "Источник звука"), ("Use IP Whitelisting", "Использовать белый список IP"), ("Network", "Сеть"), - ("Enable RDP", "Включить RDP"), ("Pin Toolbar", "Закрепить панель инструментов"), ("Unpin Toolbar", "Открепить панель инструментов"), ("Recording", "Запись"), diff --git a/src/lang/sk.rs b/src/lang/sk.rs index 11d1cc5e7..95b808fa7 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -365,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", ""), ("Use IP Whitelisting", ""), ("Network", ""), - ("Enable RDP", ""), ("Pin Toolbar", ""), ("Unpin Toolbar", ""), ("Recording", ""), diff --git a/src/lang/sl.rs b/src/lang/sl.rs index c9ff7d82b..88c4c35e6 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -365,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", "Vhodna naprava za zvok"), ("Use IP Whitelisting", "Omogoči seznam dovoljenih IP naslovov"), ("Network", "Mreža"), - ("Enable RDP", "Omogoči RDP"), ("Pin Toolbar", ""), ("Unpin Toolbar", ""), ("Recording", "Snemanje"), diff --git a/src/lang/sq.rs b/src/lang/sq.rs index 3ead4dc70..e63dadfe0 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -365,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", "Pajisja e hyrjes audio"), ("Use IP Whitelisting", "Përdor listën e bardhë IP"), ("Network", "Rrjeti"), - ("Enable RDP", "Aktivizo RDP"), ("Pin Toolbar", ""), ("Unpin Toolbar", ""), ("Recording", "Regjistrimi"), diff --git a/src/lang/sr.rs b/src/lang/sr.rs index 67969158f..a51e36526 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -365,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", "Uređaj za ulaz zvuka"), ("Use IP Whitelisting", "Koristi listu pouzdanih IP"), ("Network", "Mreža"), - ("Enable RDP", "Dozvoli RDP"), ("Pin Toolbar", ""), ("Unpin Toolbar", ""), ("Recording", "Snimanje"), diff --git a/src/lang/sv.rs b/src/lang/sv.rs index a3dc77764..e0fd1086b 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -365,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", "Inmatningsenhet för ljud"), ("Use IP Whitelisting", "Använd IP-Vitlistning"), ("Network", "Nätverk"), - ("Enable RDP", "Aktivera RDP"), ("Pin Toolbar", ""), ("Unpin Toolbar", ""), ("Recording", "Spelar in"), diff --git a/src/lang/template.rs b/src/lang/template.rs index a424ba0f9..dfd4b9651 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -365,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", ""), ("Use IP Whitelisting", ""), ("Network", ""), - ("Enable RDP", ""), ("Pin Toolbar", ""), ("Unpin Toolbar", ""), ("Recording", ""), diff --git a/src/lang/th.rs b/src/lang/th.rs index 361da2446..4ed4dd76c 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -365,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", "อุปกรณ์รับอินพุทข้อมูลเสียง"), ("Use IP Whitelisting", "ใช้งาน IP ไวท์ลิสต์"), ("Network", "เครือข่าย"), - ("Enable RDP", "เปิดการใช้งาน RDP"), ("Pin Toolbar", "ปักหมุดแถบเครื่องมือ"), ("Unpin Toolbar", "ยกเลิกการปักหมุดแถบเครื่องมือ"), ("Recording", "การบันทึก"), diff --git a/src/lang/tr.rs b/src/lang/tr.rs index d5d3859b8..6ce86e6f3 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -365,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", "Ses Giriş Aygıtı"), ("Use IP Whitelisting", "IP Beyaz Listeyi Kullan"), ("Network", "Ağ"), - ("Enable RDP", "RDP Aktif Et"), ("Pin Toolbar", ""), ("Unpin Toolbar", ""), ("Recording", "Kayıt Ediliyor"), diff --git a/src/lang/tw.rs b/src/lang/tw.rs index cf502f914..a9fc78e99 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -365,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", "音訊輸入裝置"), ("Use IP Whitelisting", "只允許白名單上的 IP 進行連線"), ("Network", "網路"), - ("Enable RDP", "允許 RDP 存取"), ("Pin Toolbar", "釘選工具列"), ("Unpin Toolbar", "取消釘選工具列"), ("Recording", "錄製"), diff --git a/src/lang/ua.rs b/src/lang/ua.rs index a5756b5f2..4e4c29e0c 100644 --- a/src/lang/ua.rs +++ b/src/lang/ua.rs @@ -365,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", "Пристрій введення звуку"), ("Use IP Whitelisting", "Використовувати білий список IP"), ("Network", "Мережа"), - ("Enable RDP", "Увімкнути RDP"), ("Pin Toolbar", "Закріпити панель інструментів"), ("Unpin Toolbar", "Відкріпити панель інструментів"), ("Recording", "Запис"), diff --git a/src/lang/vn.rs b/src/lang/vn.rs index 9ed649b32..88d42cd08 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -365,7 +365,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", "Thiết bị âm thanh đầu vào"), ("Use IP Whitelisting", "Dùng danh sách các IP cho phép"), ("Network", "Mạng"), - ("Enable RDP", "Bật RDP"), ("Pin Toolbar", "Ghim thanh công cụ"), ("Unpin Toolbar", "Bỏ ghim thanh công cụ"), ("Recording", "Đang ghi hình"), diff --git a/src/server/connection.rs b/src/server/connection.rs index 7da5e4086..8e08fa5a8 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -1403,23 +1403,17 @@ impl Connection { self.file_transfer = Some((ft.dir, ft.show_hidden)); } Some(login_request::Union::PortForward(mut pf)) => { + if !Connection::permission("enable-tunnel") { + self.send_login_error("No permission of IP tunneling").await; + sleep(1.).await; + return false; + } let mut is_rdp = false; if pf.host == "RDP" && pf.port == 0 { pf.host = "localhost".to_owned(); pf.port = 3389; is_rdp = true; } - if is_rdp && !Connection::permission("enable-rdp") - || !is_rdp && !Connection::permission("enable-tunnel") - { - if is_rdp { - self.send_login_error("No permission of RDP").await; - } else { - self.send_login_error("No permission of IP tunneling").await; - } - sleep(1.).await; - return false; - } if pf.host.is_empty() { pf.host = "localhost".to_owned(); } From b15d84359ba990abf030549e13297e14e7cf6c3d Mon Sep 17 00:00:00 2001 From: fufesou Date: Thu, 12 Oct 2023 23:11:11 +0800 Subject: [PATCH 17/50] https://github.com/rustdesk/rustdesk/issues/5986 Signed-off-by: fufesou --- flutter/lib/common.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 93c23d59d..a3ffcfd35 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -593,7 +593,7 @@ closeConnection({String? id}) { } } -void windowOnTop(int? id) async { +Future windowOnTop(int? id) async { if (!isDesktop) { return; } @@ -1834,10 +1834,10 @@ enum UriLinkType { // uri link handler bool handleUriLink({List? cmdArgs, Uri? uri, String? uriString}) { List? args; - if (cmdArgs != null) { + if (cmdArgs != null && cmdArgs.isNotEmpty) { args = cmdArgs; // rustdesk - if (args.isNotEmpty && args[0].startsWith(kUniLinksPrefix)) { + if (args[0].startsWith(kUniLinksPrefix)) { final uri = Uri.tryParse(args[0]); if (uri != null) { args = urlLinkToCmdArgs(uri); From c9a923decfd467b3fd203c27a75b843ee2a7df0c Mon Sep 17 00:00:00 2001 From: Ibnul Mutaki Date: Thu, 12 Oct 2023 22:46:33 +0700 Subject: [PATCH 18/50] improve id translate --- src/lang/id.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lang/id.rs b/src/lang/id.rs index b455d005d..9300e03df 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -19,7 +19,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Recent Sessions", "Sesi Terkini"), ("Address Book", "Buku Alamat"), ("Confirmation", "Konfirmasi"), - ("TCP Tunneling", "TCP Tunneling"), + ("TCP Tunneling", "Tunneling TCP"), ("Remove", "Hapus"), ("Refresh random password", "Perbarui kata sandi acak"), ("Set your own password", "Tetapkan kata sandi Anda"), @@ -172,7 +172,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Local Port", "Port Lokal"), ("Local Address", "Alamat lokal"), ("Change Local Port", "Ubah Port Lokal"), - ("setup_server_tip", "Sudah siap, Untuk mendapatkan koneksi yang lebih baik, disarankan untuk menginstal di server anda sendiri"), + ("setup_server_tip", "Untuk mendapatkan koneksi yang lebih baik, disarankan untuk menginstal di server anda sendiri"), ("Too short, at least 6 characters.", "Terlalu pendek, setidaknya 6 karekter."), ("The confirmation is not identical.", "Konfirmasi tidak identik."), ("Permissions", "Izin"), From 6e80f0d93b5b37cfdb2fad228ce5d8d4815f151a Mon Sep 17 00:00:00 2001 From: Ibnul Mutaki Date: Thu, 12 Oct 2023 22:50:53 +0700 Subject: [PATCH 19/50] update readme ID --- docs/README-ID.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/README-ID.md b/docs/README-ID.md index 78aac233a..9999eb72f 100644 --- a/docs/README-ID.md +++ b/docs/README-ID.md @@ -13,12 +13,24 @@ Mari mengobrol bersama kami: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/I2I04VU09) +[![Open Bounties](https://img.shields.io/endpoint?url=https%3A%2F%2Fconsole.algora.io%2Fapi%2Fshields%2Frustdesk%2Fbounties%3Fstatus%3Dopen)](https://console.algora.io/org/rustdesk/bounties?status=open) + Merupakan perangkat lunak Remote Desktop yang baru, dan dibangun dengan Rust. Bahkan kamu bisa langsung menggunakannya tanpa perlu melakukan konfigurasi tambahan. Serta memiliki kontrol penuh terhadap semua data, tanpa perlu merasa was-was tentang isu keamanan, dan yang lebih menarik adalah memiliki opsi untuk menggunakan server rendezvous/relay milik kami, [konfigurasi server sendiri](https://rustdesk.com/server), atau [tulis rendezvous/relay server anda sendiri](https://github.com/rustdesk/rustdesk-server-demo). +![image](https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png) + RustDesk mengajak semua orang untuk ikut berkontribusi. Lihat [`docs/CONTRIBUTING-ID.md`](CONTRIBUTING-ID.md) untuk melihat panduan. +[**FAQ**](https://github.com/rustdesk/rustdesk/wiki/FAQ) + [**UNDUH BINARY**](https://github.com/rustdesk/rustdesk/releases) +[**NIGHTLY BUILD**](https://github.com/rustdesk/rustdesk/releases/tag/nightly) + +[Get it on F-Droid](https://f-droid.org/en/packages/com.carriez.flutter_hbb) + ## Server Publik Gratis Di bawah ini merupakan server gratis yang bisa kamu gunakan, seiring dengan waktu mungkin akan terjadi perubahan spesifikasi pada setiap server yang ada. Jika lokasi kamu berada jauh dengan salah satu server yang tersedia, kemungkinan koneksi akan terasa lambat ketika melakukan proses remote. From d3ce8203bebce4a7e5b35a1d9916e4618c1025a1 Mon Sep 17 00:00:00 2001 From: 21pages Date: Wed, 11 Oct 2023 19:03:34 +0800 Subject: [PATCH 20/50] win,linux remove desktop wallpaper Signed-off-by: 21pages --- Cargo.lock | 23 +++++ Cargo.toml | 5 +- .../desktop/pages/desktop_setting_page.dart | 82 ++++++++++++++++ src/flutter_ffi.rs | 12 +++ src/lang/ar.rs | 4 +- src/lang/ca.rs | 4 +- src/lang/cn.rs | 4 +- src/lang/cs.rs | 4 +- src/lang/da.rs | 4 +- src/lang/de.rs | 4 +- src/lang/el.rs | 4 +- src/lang/eo.rs | 4 +- src/lang/es.rs | 4 +- src/lang/fa.rs | 4 +- src/lang/fr.rs | 4 +- src/lang/hu.rs | 4 +- src/lang/id.rs | 4 +- src/lang/it.rs | 4 +- src/lang/ja.rs | 4 +- src/lang/ko.rs | 4 +- src/lang/kz.rs | 4 +- src/lang/lt.rs | 4 +- src/lang/lv.rs | 4 +- src/lang/nl.rs | 4 +- src/lang/pl.rs | 4 +- src/lang/pt_PT.rs | 4 +- src/lang/ptbr.rs | 4 +- src/lang/ro.rs | 4 +- src/lang/ru.rs | 4 +- src/lang/sk.rs | 4 +- src/lang/sl.rs | 4 +- src/lang/sq.rs | 4 +- src/lang/sr.rs | 4 +- src/lang/sv.rs | 4 +- src/lang/template.rs | 4 +- src/lang/th.rs | 4 +- src/lang/tr.rs | 4 +- src/lang/tw.rs | 4 +- src/lang/ua.rs | 4 +- src/lang/vn.rs | 4 +- src/platform/linux.rs | 43 +++++++- src/platform/windows.rs | 97 +++++++++++++++++++ src/server/connection.rs | 80 ++++++++++++++- src/tray.rs | 6 +- src/ui/index.tis | 6 ++ 45 files changed, 453 insertions(+), 45 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3a8adbda2..24a4a399c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1819,6 +1819,15 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "enquote" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06c36cb11dbde389f4096111698d8b567c0720e3452fd5ac3e6b4e47e1939932" +dependencies = [ + "thiserror", +] + [[package]] name = "enum-iterator" version = "1.4.1" @@ -5214,6 +5223,7 @@ dependencies = [ "users 0.11.0", "uuid", "virtual_display", + "wallpaper", "whoami", "winapi 0.3.9", "windows-service", @@ -6589,6 +6599,19 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wallpaper" +version = "3.2.0" +source = "git+https://github.com/21pages/wallpaper.rs#2bbb70acd93be179c69cb96cb8c3dda487e6f5fd" +dependencies = [ + "dirs 5.0.1", + "enquote", + "rust-ini", + "thiserror", + "winapi 0.3.9", + "winreg 0.11.0", +] + [[package]] name = "want" version = "0.3.0" diff --git a/Cargo.toml b/Cargo.toml index 9e8224fc1..d24e7a061 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,6 +88,7 @@ clipboard = { path = "libs/clipboard" } ctrlc = "3.2" arboard = "3.2" system_shutdown = "4.0" +shutdown_hooks = "0.1" [target.'cfg(target_os = "windows")'.dependencies] winapi = { version = "0.3", features = ["winuser", "wincrypt", "shellscalingapi"] } @@ -96,7 +97,6 @@ windows-service = "0.6" virtual_display = { path = "libs/virtual_display", optional = true } impersonate_system = { git = "https://github.com/21pages/impersonate-system" } shared_memory = "0.12" -shutdown_hooks = "0.1" tauri-winrt-notification = "0.1.2" [target.'cfg(target_os = "macos")'.dependencies] @@ -118,6 +118,9 @@ image = "0.24" [target.'cfg(any(target_os = "macos", target_os = "linux"))'.dependencies] keepawake = { git = "https://github.com/rustdesk-org/keepawake-rs" } +[target.'cfg(any(target_os = "windows", target_os = "linux"))'.dependencies] +wallpaper = { git = "https://github.com/21pages/wallpaper.rs" } + [target.'cfg(target_os = "linux")'.dependencies] psimple = { package = "libpulse-simple-binding", version = "2.27" } pulse = { package = "libpulse-binding", version = "2.27" } diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index 7324d3a72..a105f2135 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:convert'; import 'dart:io'; @@ -322,6 +323,24 @@ class _GeneralState extends State<_General> { 'enable-confirm-closing-tabs', isServer: false), _OptionCheckBox(context, 'Adaptive bitrate', 'enable-abr'), + if (Platform.isWindows || Platform.isLinux) + Row( + children: [ + Flexible( + child: _OptionCheckBox( + context, + 'Remove wallpaper during incoming sessions', + 'allow-remove-wallpaper'), + ), + _CountDownButton( + text: 'Test', + second: 5, + onPressed: () { + bind.mainTestWallpaper(second: 5); + }, + ) + ], + ), _OptionCheckBox( context, 'Open connection in new tab', @@ -1873,6 +1892,69 @@ class _ComboBox extends StatelessWidget { } } +class _CountDownButton extends StatefulWidget { + _CountDownButton({ + Key? key, + required this.text, + required this.second, + required this.onPressed, + }) : super(key: key); + final String text; + final VoidCallback? onPressed; + final int second; + + @override + State<_CountDownButton> createState() => _CountDownButtonState(); +} + +class _CountDownButtonState extends State<_CountDownButton> { + bool _isButtonDisabled = false; + + late int _countdownSeconds = widget.second; + + Timer? _timer; + + @override + void dispose() { + _timer?.cancel(); + super.dispose(); + } + + void _startCountdownTimer() { + _timer = Timer.periodic(Duration(seconds: 1), (timer) { + if (_countdownSeconds <= 0) { + setState(() { + _isButtonDisabled = false; + }); + timer.cancel(); + } else { + setState(() { + _countdownSeconds--; + }); + } + }); + } + + @override + Widget build(BuildContext context) { + return ElevatedButton( + onPressed: _isButtonDisabled + ? null + : () { + widget.onPressed?.call(); + setState(() { + _isButtonDisabled = true; + _countdownSeconds = widget.second; + }); + _startCountdownTimer(); + }, + child: Text( + _isButtonDisabled ? '$_countdownSeconds s' : translate(widget.text), + ), + ); + } +} + //#endregion //#region dialogs diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index c931d8926..b4ae89bc9 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -1613,6 +1613,18 @@ pub fn main_start_ipc_url_server() { std::thread::spawn(move || crate::server::start_ipc_url_server()); } +pub fn main_test_wallpaper(_second: u64) { + #[cfg(any(target_os = "windows", target_os = "linux"))] + std::thread::spawn(move || match crate::platform::WallPaperRemover::new() { + Ok(_remover) => { + std::thread::sleep(std::time::Duration::from_secs(_second)); + } + Err(e) => { + log::info!("create wallpaper remover failed:{:?}", e); + } + }); +} + /// Send a url scheme throught the ipc. /// /// * macOS only diff --git a/src/lang/ar.rs b/src/lang/ar.rs index a7f24540e..7921dbde4 100644 --- a/src/lang/ar.rs +++ b/src/lang/ar.rs @@ -555,6 +555,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), - ("Filter by intersection", "") + ("Filter by intersection", ""), + ("Remove wallpaper during incoming sessions", ""), + ("Test", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ca.rs b/src/lang/ca.rs index e7180abf3..61cbef359 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -555,6 +555,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), - ("Filter by intersection", "") + ("Filter by intersection", ""), + ("Remove wallpaper during incoming sessions", ""), + ("Test", ""), ].iter().cloned().collect(); } diff --git a/src/lang/cn.rs b/src/lang/cn.rs index d1cff492b..ea8f4ee4e 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -555,6 +555,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", "启动时检查软件更新"), ("upgrade_rustdesk_server_pro_to_{}_tip", "请升级专业版服务器到{}或更高版本!"), ("pull_group_failed_tip", "获取组信息失败"), - ("Filter by intersection", "按交集过滤") + ("Filter by intersection", "按交集过滤"), + ("Remove wallpaper during incoming sessions", "接受会话时移除桌面壁纸"), + ("Test", "测试"), ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index 9267e1f75..ff0b87502 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -555,6 +555,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", "Kontrola aktualizace softwaru při spuštění"), ("upgrade_rustdesk_server_pro_to_{}_tip", "Aktualizujte prosím RustDesk Server Pro na verzi {} nebo novější!"), ("pull_group_failed_tip", "Nepodařilo se obnovit skupinu"), - ("Filter by intersection", "") + ("Filter by intersection", ""), + ("Remove wallpaper during incoming sessions", ""), + ("Test", ""), ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index 1ad6f36b5..6b577e5b8 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -555,6 +555,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), - ("Filter by intersection", "") + ("Filter by intersection", ""), + ("Remove wallpaper during incoming sessions", ""), + ("Test", ""), ].iter().cloned().collect(); } diff --git a/src/lang/de.rs b/src/lang/de.rs index bf5da7415..27a6cadab 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -555,6 +555,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", "Beim Start auf Softwareaktualisierung prüfen"), ("upgrade_rustdesk_server_pro_to_{}_tip", "Bitte aktualisieren Sie RustDesk Server Pro auf die Version {} oder neuer!"), ("pull_group_failed_tip", "Aktualisierung der Gruppe fehlgeschlagen"), - ("Filter by intersection", "") + ("Filter by intersection", ""), + ("Remove wallpaper during incoming sessions", ""), + ("Test", ""), ].iter().cloned().collect(); } diff --git a/src/lang/el.rs b/src/lang/el.rs index a984411a2..169ee2359 100644 --- a/src/lang/el.rs +++ b/src/lang/el.rs @@ -555,6 +555,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), - ("Filter by intersection", "") + ("Filter by intersection", ""), + ("Remove wallpaper during incoming sessions", ""), + ("Test", ""), ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index 51e97c68a..59c4db9fe 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -555,6 +555,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), - ("Filter by intersection", "") + ("Filter by intersection", ""), + ("Remove wallpaper during incoming sessions", ""), + ("Test", ""), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index 350505a34..c8abc4e1b 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -555,6 +555,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", "Comprobar actualización al iniciar"), ("upgrade_rustdesk_server_pro_to_{}_tip", "¡Por favor, actualiza RustDesk Server Pro a la versión {} o superior"), ("pull_group_failed_tip", "No se ha podido refrescar el grupo"), - ("Filter by intersection", "") + ("Filter by intersection", ""), + ("Remove wallpaper during incoming sessions", ""), + ("Test", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fa.rs b/src/lang/fa.rs index 0e0009a6c..e5b20d39c 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -555,6 +555,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), - ("Filter by intersection", "") + ("Filter by intersection", ""), + ("Remove wallpaper during incoming sessions", ""), + ("Test", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index 50b03299a..075167af1 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -555,6 +555,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", "Vérifier la disponibilité des mises à jour au démarrage"), ("upgrade_rustdesk_server_pro_to_{}_tip", "Veuillez mettre à jour RustDesk Server Pro avec la version {} ou une version plus récente !"), ("pull_group_failed_tip", "Échec de l'actualisation du groupe"), - ("Filter by intersection", "") + ("Filter by intersection", ""), + ("Remove wallpaper during incoming sessions", ""), + ("Test", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index f9f7051ae..3c3e50205 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -555,6 +555,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), - ("Filter by intersection", "") + ("Filter by intersection", ""), + ("Remove wallpaper during incoming sessions", ""), + ("Test", ""), ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index 8ed5f1861..08a1d30c6 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -555,6 +555,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", "Periksa pembaruan aplikasi saat sistem dinyalakan."), ("upgrade_rustdesk_server_pro_to_{}_tip", "Silahkan perbarui RustDesk Server Pro ke versi {} atau yang lebih baru!"), ("pull_group_failed_tip", "Gagal memperbarui grup"), - ("Filter by intersection", "") + ("Filter by intersection", ""), + ("Remove wallpaper during incoming sessions", ""), + ("Test", ""), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index 194f0686c..a7ce49432 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -555,6 +555,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", "All'avvio verifica presenza aggiornamenti programma"), ("upgrade_rustdesk_server_pro_to_{}_tip", "Aggiorna RustDesk Server Pro alla versione {} o successiva!"), ("pull_group_failed_tip", "Impossibile aggiornare il gruppo"), - ("Filter by intersection", "Filtra per incrocio") + ("Filter by intersection", "Filtra per incrocio"), + ("Remove wallpaper during incoming sessions", ""), + ("Test", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index 48b4f28ae..61dafdb34 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -555,6 +555,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), - ("Filter by intersection", "") + ("Filter by intersection", ""), + ("Remove wallpaper during incoming sessions", ""), + ("Test", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index 00a4012e8..f4c9076ca 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -555,6 +555,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), - ("Filter by intersection", "") + ("Filter by intersection", ""), + ("Remove wallpaper during incoming sessions", ""), + ("Test", ""), ].iter().cloned().collect(); } diff --git a/src/lang/kz.rs b/src/lang/kz.rs index d7481eaff..20efc25c4 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -555,6 +555,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), - ("Filter by intersection", "") + ("Filter by intersection", ""), + ("Remove wallpaper during incoming sessions", ""), + ("Test", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lt.rs b/src/lang/lt.rs index 9e63f6160..c360d3ecb 100644 --- a/src/lang/lt.rs +++ b/src/lang/lt.rs @@ -555,6 +555,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), - ("Filter by intersection", "") + ("Filter by intersection", ""), + ("Remove wallpaper during incoming sessions", ""), + ("Test", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lv.rs b/src/lang/lv.rs index 684f9393a..9d72e407a 100644 --- a/src/lang/lv.rs +++ b/src/lang/lv.rs @@ -555,6 +555,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", "Startējot pārbaudīt, vai nav programmatūras atjauninājumu"), ("upgrade_rustdesk_server_pro_to_{}_tip", "Lūdzu, jauniniet RustDesk Server Pro uz versiju {} vai jaunāku!"), ("pull_group_failed_tip", "Neizdevās atsvaidzināt grupu"), - ("Filter by intersection", "Filtrēt pēc krustpunkta") + ("Filter by intersection", "Filtrēt pēc krustpunkta"), + ("Remove wallpaper during incoming sessions", ""), + ("Test", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nl.rs b/src/lang/nl.rs index 93967ebad..3b30015c3 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -555,6 +555,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", "Checken voor updates bij opstarten"), ("upgrade_rustdesk_server_pro_to_{}_tip", "Upgrade RustDesk Server Pro naar versie {} of nieuwer!"), ("pull_group_failed_tip", "Vernieuwen van groep mislukt"), - ("Filter by intersection", "") + ("Filter by intersection", ""), + ("Remove wallpaper during incoming sessions", ""), + ("Test", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pl.rs b/src/lang/pl.rs index d6c4f5b84..77b3204b8 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -555,6 +555,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", "Sprawdź aktualizacje przy starcie programu"), ("upgrade_rustdesk_server_pro_to_{}_tip", "Proszę zaktualizować RustDesk Server Pro do wersji {} lub nowszej!"), ("pull_group_failed_tip", "Błąd odświeżania grup"), - ("Filter by intersection", "") + ("Filter by intersection", ""), + ("Remove wallpaper during incoming sessions", ""), + ("Test", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index 8712ac615..93d20600b 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -555,6 +555,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), - ("Filter by intersection", "") + ("Filter by intersection", ""), + ("Remove wallpaper during incoming sessions", ""), + ("Test", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index f2810f76c..62e2ef812 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -555,6 +555,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), - ("Filter by intersection", "") + ("Filter by intersection", ""), + ("Remove wallpaper during incoming sessions", ""), + ("Test", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ro.rs b/src/lang/ro.rs index 73ff3bb22..6d755a802 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -555,6 +555,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), - ("Filter by intersection", "") + ("Filter by intersection", ""), + ("Remove wallpaper during incoming sessions", ""), + ("Test", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index 1d7c8cfca..22e920b8e 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -555,6 +555,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", "Проверять обновления программы при запуске"), ("upgrade_rustdesk_server_pro_to_{}_tip", "Обновите RustDesk Server Pro до версии {} или новее!"), ("pull_group_failed_tip", "Невозможно обновить группу"), - ("Filter by intersection", "Фильтровать по пересечению") + ("Filter by intersection", "Фильтровать по пересечению"), + ("Remove wallpaper during incoming sessions", ""), + ("Test", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index 95b808fa7..3f0ad7bb3 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -555,6 +555,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), - ("Filter by intersection", "") + ("Filter by intersection", ""), + ("Remove wallpaper during incoming sessions", ""), + ("Test", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sl.rs b/src/lang/sl.rs index 88c4c35e6..4d05f7e1b 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -555,6 +555,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), - ("Filter by intersection", "") + ("Filter by intersection", ""), + ("Remove wallpaper during incoming sessions", ""), + ("Test", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sq.rs b/src/lang/sq.rs index e63dadfe0..ce69547d3 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -555,6 +555,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), - ("Filter by intersection", "") + ("Filter by intersection", ""), + ("Remove wallpaper during incoming sessions", ""), + ("Test", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sr.rs b/src/lang/sr.rs index a51e36526..d1e5cd206 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -555,6 +555,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), - ("Filter by intersection", "") + ("Filter by intersection", ""), + ("Remove wallpaper during incoming sessions", ""), + ("Test", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sv.rs b/src/lang/sv.rs index e0fd1086b..7deaac905 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -555,6 +555,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), - ("Filter by intersection", "") + ("Filter by intersection", ""), + ("Remove wallpaper during incoming sessions", ""), + ("Test", ""), ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index dfd4b9651..f3187966e 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -555,6 +555,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), - ("Filter by intersection", "") + ("Filter by intersection", ""), + ("Remove wallpaper during incoming sessions", ""), + ("Test", ""), ].iter().cloned().collect(); } diff --git a/src/lang/th.rs b/src/lang/th.rs index 4ed4dd76c..2b2f63d34 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -555,6 +555,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", "ตรวจสอบการอัปเดตโปรแกรมเมื่อเริ่มต้นใช้งาน"), ("upgrade_rustdesk_server_pro_to_{}_tip", "กรุณาอัปเดต Rustdesk Server Pro ไปยังเวอร์ชัน {} หรือใหม่กว่า!"), ("pull_group_failed_tip", "การเรียกใช้งานกลุ่มล้มเหลว"), - ("Filter by intersection", "") + ("Filter by intersection", ""), + ("Remove wallpaper during incoming sessions", ""), + ("Test", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index 6ce86e6f3..69de33892 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -555,6 +555,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), - ("Filter by intersection", "") + ("Filter by intersection", ""), + ("Remove wallpaper during incoming sessions", ""), + ("Test", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index a9fc78e99..15c404937 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -555,6 +555,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), - ("Filter by intersection", "") + ("Filter by intersection", ""), + ("Remove wallpaper during incoming sessions", ""), + ("Test", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ua.rs b/src/lang/ua.rs index 4e4c29e0c..d3b3b9181 100644 --- a/src/lang/ua.rs +++ b/src/lang/ua.rs @@ -555,6 +555,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), - ("Filter by intersection", "") + ("Filter by intersection", ""), + ("Remove wallpaper during incoming sessions", ""), + ("Test", ""), ].iter().cloned().collect(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index 88d42cd08..e29da9ae0 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -555,6 +555,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", ""), ("upgrade_rustdesk_server_pro_to_{}_tip", ""), ("pull_group_failed_tip", ""), - ("Filter by intersection", "") + ("Filter by intersection", ""), + ("Remove wallpaper during incoming sessions", ""), + ("Test", ""), ].iter().cloned().collect(); } diff --git a/src/platform/linux.rs b/src/platform/linux.rs index 0f926c469..a4975d3aa 100644 --- a/src/platform/linux.rs +++ b/src/platform/linux.rs @@ -5,7 +5,9 @@ use desktop::Desktop; use hbb_common::config::CONFIG_OPTION_ALLOW_LINUX_HEADLESS; pub use hbb_common::platform::linux::*; use hbb_common::{ - allow_err, bail, + allow_err, + anyhow::anyhow, + bail, config::Config, libc::{c_char, c_int, c_long, c_void}, log, @@ -26,6 +28,7 @@ use std::{ time::{Duration, Instant}, }; use users::{get_user_by_name, os::unix::UserExt}; +use wallpaper; type Xdo = *const c_void; @@ -1311,3 +1314,41 @@ NoDisplay=false } Ok(()) } + +pub struct WallPaperRemover { + old_path: String, + old_path_dark: Option, // ubuntu 22.04 light/dark theme have different uri +} + +impl WallPaperRemover { + pub fn new() -> ResultType { + let start = std::time::Instant::now(); + let old_path = wallpaper::get().map_err(|e| anyhow!(e.to_string()))?; + let old_path_dark = wallpaper::get_dark().ok(); + if old_path.is_empty() && old_path_dark.clone().unwrap_or_default().is_empty() { + bail!("already solid color"); + } + wallpaper::set_from_path("").map_err(|e| anyhow!(e.to_string()))?; + wallpaper::set_dark_from_path("").ok(); + log::info!( + "created wallpaper remover, old_path:{:?}, old_path_dark:{:?}, elapsed:{:?}", + old_path, + old_path_dark, + start.elapsed(), + ); + Ok(Self { + old_path, + old_path_dark, + }) + } +} + +impl Drop for WallPaperRemover { + fn drop(&mut self) { + allow_err!(wallpaper::set_from_path(&self.old_path).map_err(|e| anyhow!(e.to_string()))); + if let Some(old_path_dark) = &self.old_path_dark { + allow_err!(wallpaper::set_dark_from_path(old_path_dark.as_str()) + .map_err(|e| anyhow!(e.to_string()))); + } + } +} diff --git a/src/platform/windows.rs b/src/platform/windows.rs index a74cbf896..9cc4fd39f 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -14,6 +14,7 @@ use hbb_common::{ message_proto::Resolution, sleep, timeout, tokio, }; +use std::process::{Command, Stdio}; use std::{ collections::HashMap, ffi::OsString, @@ -26,6 +27,7 @@ use std::{ sync::{atomic::Ordering, Arc, Mutex}, time::{Duration, Instant}, }; +use wallpaper; use winapi::{ ctypes::c_void, shared::{minwindef::*, ntdef::NULL, windef::*, winerror::*}, @@ -2335,3 +2337,98 @@ fn get_license() -> Option { } Some(lic) } + +fn get_sid_of_user(username: &str) -> ResultType { + let mut output = Command::new("wmic") + .args(&[ + "useraccount", + "where", + &format!("name='{}'", username), + "get", + "sid", + "/value", + ]) + .creation_flags(CREATE_NO_WINDOW) + .stdout(Stdio::piped()) + .spawn()? + .stdout + .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Failed to open stdout"))?; + let mut result = String::new(); + output.read_to_string(&mut result)?; + let sid_start_index = result + .find('=') + .map(|i| i + 1) + .ok_or(anyhow!("bad output format"))?; + if sid_start_index > 0 && sid_start_index < result.len() + 1 { + Ok(result[sid_start_index..].trim().to_string()) + } else { + bail!("bad output format"); + } +} + +pub struct WallPaperRemover { + old_path: String, +} + +impl WallPaperRemover { + pub fn new() -> ResultType { + let start = std::time::Instant::now(); + if !Self::need_remove() { + bail!("already solid color"); + } + let old_path = match Self::get_recent_wallpaper() { + Ok(old_path) => old_path, + Err(e) => { + log::info!("Failed to get recent wallpaper:{:?}, use fallback", e); + wallpaper::get().map_err(|e| anyhow!(e.to_string()))? + } + }; + Self::set_wallpaper(None)?; + log::info!( + "created wallpaper remover, old_path:{:?}, elapsed:{:?}", + old_path, + start.elapsed(), + ); + Ok(Self { old_path }) + } + + fn get_recent_wallpaper() -> ResultType { + // SystemParametersInfoW may return %appdata%\Microsoft\Windows\Themes\TranscodedWallpaper, not real path and may not real cache + // https://www.makeuseof.com/find-desktop-wallpapers-file-location-windows-11/ + // https://superuser.com/questions/1218413/write-to-current-users-registry-through-a-different-admin-account + let (hkcu, sid) = if is_root() { + let username = get_active_username(); + let sid = get_sid_of_user(&username)?; + log::info!("username:{username}, sid:{sid}"); + (RegKey::predef(HKEY_USERS), format!("{}\\", sid)) + } else { + (RegKey::predef(HKEY_CURRENT_USER), "".to_string()) + }; + let explorer_key = hkcu.open_subkey_with_flags( + &format!( + "{}Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Wallpapers", + sid + ), + KEY_READ, + )?; + Ok(explorer_key.get_value("BackgroundHistoryPath0")?) + } + + fn need_remove() -> bool { + if let Ok(wallpaper) = wallpaper::get() { + return !wallpaper.is_empty(); + } + false + } + + fn set_wallpaper(path: Option) -> ResultType<()> { + wallpaper::set_from_path(&path.unwrap_or_default()).map_err(|e| anyhow!(e.to_string())) + } +} + +impl Drop for WallPaperRemover { + fn drop(&mut self) { + // If the old background is a slideshow, it will be converted into an image. AnyDesk does the same. + allow_err!(Self::set_wallpaper(Some(self.old_path.clone()))); + } +} diff --git a/src/server/connection.rs b/src/server/connection.rs index 8e08fa5a8..fa2b8261b 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -6,6 +6,8 @@ use crate::common::update_clipboard; #[cfg(all(target_os = "linux", feature = "linux_headless"))] #[cfg(not(any(feature = "flatpak", feature = "appimage")))] use crate::platform::linux_desktop_manager; +#[cfg(any(target_os = "windows", target_os = "linux"))] +use crate::platform::WallPaperRemover; #[cfg(windows)] use crate::portable_service::client as portable_client; use crate::{ @@ -60,8 +62,14 @@ lazy_static::lazy_static! { static ref LOGIN_FAILURES: Arc::>> = Default::default(); static ref SESSIONS: Arc::>> = Default::default(); static ref ALIVE_CONNS: Arc::>> = Default::default(); + static ref AUTHED_CONNS: Arc::>> = Default::default(); static ref SWITCH_SIDES_UUID: Arc::>> = Default::default(); } + +#[cfg(any(target_os = "windows", target_os = "linux"))] +lazy_static::lazy_static! { + static ref WALLPAPER_REMOVER: Arc>> = Default::default(); +} pub static CLICK_TIME: AtomicI64 = AtomicI64::new(0); #[cfg(not(any(target_os = "android", target_os = "ios")))] pub static MOUSE_MOVE_TIME: AtomicI64 = AtomicI64::new(0); @@ -143,6 +151,13 @@ struct StartCmIpcPara { tx_cm_stream_ready: mpsc::Sender<()>, } +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum AuthConnType { + Remote, + FileTransfer, + PortForward, +} + pub struct Connection { inner: ConnInner, stream: super::Stream, @@ -205,6 +220,7 @@ pub struct Connection { #[cfg(not(any(target_os = "android", target_os = "ios")))] start_cm_ipc_para: Option, auto_disconnect_timer: Option<(Instant, u64)>, + authed_conn_id: Option, } impl ConnInner { @@ -345,6 +361,7 @@ impl Connection { tx_cm_stream_ready, }), auto_disconnect_timer: None, + authed_conn_id: None, }; let addr = hbb_common::try_into_v4(addr); if !conn.on_open(addr).await { @@ -976,13 +993,17 @@ impl Connection { if self.authorized { return; } - let conn_type = if self.file_transfer.is_some() { - 1 + let (conn_type, auth_conn_type) = if self.file_transfer.is_some() { + (1, AuthConnType::FileTransfer) } else if self.port_forward_socket.is_some() { - 2 + (2, AuthConnType::PortForward) } else { - 0 + (0, AuthConnType::Remote) }; + self.authed_conn_id = Some(self::raii::AuthedConnID::new( + self.inner.id(), + auth_conn_type, + )); self.post_conn_audit( json!({"peer": ((&self.lr.my_id, &self.lr.my_name)), "type": conn_type}), ); @@ -1117,6 +1138,7 @@ impl Connection { *super::video_service::LAST_SYNC_DISPLAYS.write().unwrap() = displays; } } + Self::on_remote_authorized(); } let mut msg_out = Message::new(); msg_out.set_login_response(res); @@ -1155,6 +1177,29 @@ impl Connection { } } + fn on_remote_authorized() { + use std::sync::Once; + static ONCE: Once = Once::new(); + #[cfg(any(target_os = "windows", target_os = "linux"))] + if !Config::get_option("allow-remove-wallpaper").is_empty() { + // multi connections set once + let mut wallpaper = WALLPAPER_REMOVER.lock().unwrap(); + if wallpaper.is_none() { + match crate::platform::WallPaperRemover::new() { + Ok(remover) => { + *wallpaper = Some(remover); + ONCE.call_once(|| { + shutdown_hooks::add_shutdown_hook(shutdown_hook); + }); + } + Err(e) => { + log::info!("create wallpaper remover failed:{:?}", e); + } + } + } + } + } + fn peer_keyboard_enabled(&self) -> bool { self.keyboard && !self.disable_keyboard } @@ -2734,6 +2779,11 @@ impl LinuxHeadlessHandle { } } +#[cfg(any(target_os = "windows", target_os = "linux"))] +extern "C" fn shutdown_hook() { + *WALLPAPER_REMOVER.lock().unwrap() = None; +} + mod raii { use super::*; pub struct ConnectionID(i32); @@ -2767,4 +2817,26 @@ mod raii { .on_connection_close(self.0); } } + + pub struct AuthedConnID(i32); + + impl AuthedConnID { + pub fn new(id: i32, conn_type: AuthConnType) -> Self { + AUTHED_CONNS.lock().unwrap().push((id, conn_type)); + Self(id) + } + } + + impl Drop for AuthedConnID { + fn drop(&mut self) { + let mut lock = AUTHED_CONNS.lock().unwrap(); + lock.retain(|&c| c.0 != self.0); + if lock.iter().filter(|c| c.1 == AuthConnType::Remote).count() == 0 { + #[cfg(any(target_os = "windows", target_os = "linux"))] + { + *WALLPAPER_REMOVER.lock().unwrap() = None; + } + } + } + } } diff --git a/src/tray.rs b/src/tray.rs index fc124e6c4..7b05be101 100644 --- a/src/tray.rs +++ b/src/tray.rs @@ -65,14 +65,14 @@ pub fn make_tray() -> hbb_common::ResultType<()> { ) } }; - let tray_icon = Some( + let _tray_icon = Some( TrayIconBuilder::new() .with_menu(Box::new(tray_menu)) .with_tooltip(tooltip(0)) .with_icon(icon) .build()?, ); - let tray_icon = Arc::new(Mutex::new(tray_icon)); + let _tray_icon = Arc::new(Mutex::new(_tray_icon)); let menu_channel = MenuEvent::receiver(); let tray_channel = TrayEvent::receiver(); @@ -149,7 +149,7 @@ pub fn make_tray() -> hbb_common::ResultType<()> { if let Ok(data) = ipc_receiver.try_recv() { match data { Data::ControlledSessionCount(count) => { - tray_icon + _tray_icon .lock() .unwrap() .as_mut() diff --git a/src/ui/index.tis b/src/ui/index.tis index 9d09dd7a7..67deed7d7 100644 --- a/src/ui/index.tis +++ b/src/ui/index.tis @@ -217,6 +217,7 @@ class Enhancements: Reactor.Component { {has_hwcodec ?
  • {svg_checkmark}{translate("Hardware Codec")} (beta)
  • : ""}
  • {svg_checkmark}{translate("Adaptive bitrate")} (beta)
  • {translate("Recording")}
  • + {is_osx ? "" :
  • {svg_checkmark}{translate("Remove wallpaper during incoming sessions")}
  • } ; } @@ -226,6 +227,9 @@ class Enhancements: Reactor.Component { if (el.id && el.id.indexOf("enable-") == 0) { var enabled = handler.get_option(el.id) != "N"; el.attributes.toggleClass("selected", enabled); + } else if (el.id && el.id.indexOf("allow-") == 0) { + var enabled = handler.get_option(el.id) == "Y"; + el.attributes.toggleClass("selected", enabled); } } @@ -235,6 +239,8 @@ class Enhancements: Reactor.Component { var v = me.id; if (v.indexOf("enable-") == 0) { handler.set_option(v, handler.get_option(v) != 'N' ? 'N' : ''); + } else if (v.indexOf("allow-") == 0) { + handler.set_option(v, handler.get_option(v) == 'Y' ? '' : 'Y'); } else if (v == 'screen-recording') { var dir = handler.get_option("video-save-directory"); if (!dir) dir = handler.default_video_save_directory(); From 5d0384f580d8581ef25273accecc200f37383b5b Mon Sep 17 00:00:00 2001 From: 21pages Date: Fri, 13 Oct 2023 09:37:13 +0800 Subject: [PATCH 21/50] sync option share rdp Signed-off-by: 21pages --- src/hbbs_http/sync.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/hbbs_http/sync.rs b/src/hbbs_http/sync.rs index e997a2794..e020c5a68 100644 --- a/src/hbbs_http/sync.rs +++ b/src/hbbs_http/sync.rs @@ -147,10 +147,22 @@ fn handle_config_options(config_options: HashMap) { config_options .iter() .map(|(k, v)| { - if v.is_empty() { - options.remove(k); + if k == "allow-share-rdp" { + // only changes made after installation take effect. + #[cfg(windows)] + if crate::ui_interface::is_rdp_service_open() { + let current = crate::ui_interface::is_share_rdp(); + let set = v == "Y"; + if current != set { + crate::platform::windows::set_share_rdp(set); + } + } } else { - options.insert(k.to_string(), v.to_string()); + if v.is_empty() { + options.remove(k); + } else { + options.insert(k.to_string(), v.to_string()); + } } }) .count(); From c6bb3d6ae24ac10315419d0f8c2647f9d9e0d078 Mon Sep 17 00:00:00 2001 From: Mr-Update <37781396+Mr-Update@users.noreply.github.com> Date: Fri, 13 Oct 2023 11:56:14 +0200 Subject: [PATCH 22/50] Update de.rs --- src/lang/de.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lang/de.rs b/src/lang/de.rs index bf5da7415..661437085 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -555,6 +555,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", "Beim Start auf Softwareaktualisierung prüfen"), ("upgrade_rustdesk_server_pro_to_{}_tip", "Bitte aktualisieren Sie RustDesk Server Pro auf die Version {} oder neuer!"), ("pull_group_failed_tip", "Aktualisierung der Gruppe fehlgeschlagen"), - ("Filter by intersection", "") + ("Filter by intersection", "Nach Schnittpunkt filtern") ].iter().cloned().collect(); } From 46c2720fc7c6e8f6b02e3486db43aa96e158f071 Mon Sep 17 00:00:00 2001 From: bovirus <1262554+bovirus@users.noreply.github.com> Date: Fri, 13 Oct 2023 17:16:13 +0200 Subject: [PATCH 23/50] Update Italian language --- src/lang/it.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lang/it.rs b/src/lang/it.rs index 194f0686c..854b4a9c0 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -365,6 +365,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Audio Input Device", "Dispositivo ingresso audio"), ("Use IP Whitelisting", "Usa elenco IP autorizzati"), ("Network", "Rete"), + ("Enable RDP", "Abilita RDP"), ("Pin Toolbar", "Blocca barra strumenti"), ("Unpin Toolbar", "Sblocca barra strumenti"), ("Recording", "Registrazione"), From 5e616dd50271f88170cf22835265c1c2857e8c8f Mon Sep 17 00:00:00 2001 From: rustdesk Date: Sat, 14 Oct 2023 11:07:08 +0800 Subject: [PATCH 24/50] bump to 1.2.4 --- .github/workflows/flutter-build.yml | 2 +- .github/workflows/flutter-tag.yml | 2 +- .github/workflows/history.yml | 2 +- Cargo.lock | 2 +- Cargo.toml | 2 +- appimage/AppImageBuilder-aarch64.yml | 4 ++-- appimage/AppImageBuilder-x86_64.yml | 4 ++-- flatpak/rustdesk.json | 4 ++-- flutter/pubspec.yaml | 2 +- res/PKGBUILD | 2 +- res/rpm-flutter-suse.spec | 2 +- res/rpm-flutter.spec | 2 +- res/rpm.spec | 2 +- 13 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/flutter-build.yml b/.github/workflows/flutter-build.yml index d363295a6..2aa6e564a 100644 --- a/.github/workflows/flutter-build.yml +++ b/.github/workflows/flutter-build.yml @@ -22,7 +22,7 @@ env: # vcpkg version: 2023.04.15 # for multiarch gcc compatibility VCPKG_COMMIT_ID: "501db0f17ef6df184fcdbfbe0f87cde2313b6ab1" - VERSION: "1.2.3" + VERSION: "1.2.4" NDK_VERSION: "r25c" #signing keys env variable checks ANDROID_SIGNING_KEY: '${{ secrets.ANDROID_SIGNING_KEY }}' diff --git a/.github/workflows/flutter-tag.yml b/.github/workflows/flutter-tag.yml index 4925f26c8..20083a3f9 100644 --- a/.github/workflows/flutter-tag.yml +++ b/.github/workflows/flutter-tag.yml @@ -15,4 +15,4 @@ jobs: secrets: inherit with: upload-artifact: true - upload-tag: "1.2.3" + upload-tag: "1.2.4" diff --git a/.github/workflows/history.yml b/.github/workflows/history.yml index 6921ea4cd..4c23eaa66 100644 --- a/.github/workflows/history.yml +++ b/.github/workflows/history.yml @@ -10,7 +10,7 @@ env: # vcpkg version: 2022.05.10 # for multiarch gcc compatibility VCPKG_COMMIT_ID: "501db0f17ef6df184fcdbfbe0f87cde2313b6ab1" - VERSION: "1.2.3" + VERSION: "1.2.4" jobs: build-for-history-windows: diff --git a/Cargo.lock b/Cargo.lock index 24a4a399c..ca9ddc2f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5142,7 +5142,7 @@ dependencies = [ [[package]] name = "rustdesk" -version = "1.2.3" +version = "1.2.4" dependencies = [ "android_logger", "arboard", diff --git a/Cargo.toml b/Cargo.toml index d24e7a061..f6c014483 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustdesk" -version = "1.2.3" +version = "1.2.4" authors = ["rustdesk "] edition = "2021" build= "build.rs" diff --git a/appimage/AppImageBuilder-aarch64.yml b/appimage/AppImageBuilder-aarch64.yml index 337e022be..a285fc767 100644 --- a/appimage/AppImageBuilder-aarch64.yml +++ b/appimage/AppImageBuilder-aarch64.yml @@ -2,7 +2,7 @@ version: 1 script: - rm -rf ./AppDir || true - - bsdtar -zxvf ../rustdesk-1.2.3.deb + - bsdtar -zxvf ../rustdesk-1.2.4.deb - tar -xvf ./data.tar.xz - mkdir ./AppDir - mv ./usr ./AppDir/usr @@ -18,7 +18,7 @@ AppDir: id: rustdesk name: rustdesk icon: rustdesk - version: 1.2.3 + version: 1.2.4 exec: usr/lib/rustdesk/rustdesk exec_args: $@ apt: diff --git a/appimage/AppImageBuilder-x86_64.yml b/appimage/AppImageBuilder-x86_64.yml index 650d2f201..62c674a0c 100644 --- a/appimage/AppImageBuilder-x86_64.yml +++ b/appimage/AppImageBuilder-x86_64.yml @@ -2,7 +2,7 @@ version: 1 script: - rm -rf ./AppDir || true - - bsdtar -zxvf ../rustdesk-1.2.3.deb + - bsdtar -zxvf ../rustdesk-1.2.4.deb - tar -xvf ./data.tar.xz - mkdir ./AppDir - mv ./usr ./AppDir/usr @@ -18,7 +18,7 @@ AppDir: id: rustdesk name: rustdesk icon: rustdesk - version: 1.2.3 + version: 1.2.4 exec: usr/lib/rustdesk/rustdesk exec_args: $@ apt: diff --git a/flatpak/rustdesk.json b/flatpak/rustdesk.json index fb62d9250..816838108 100644 --- a/flatpak/rustdesk.json +++ b/flatpak/rustdesk.json @@ -12,7 +12,7 @@ "name": "rustdesk", "buildsystem": "simple", "build-commands": [ - "bsdtar -zxvf rustdesk-1.2.3.deb", + "bsdtar -zxvf rustdesk-1.2.4.deb", "tar -xvf ./data.tar.xz", "cp -r ./usr/* /app/", "mkdir -p /app/bin && ln -s /app/lib/rustdesk/rustdesk /app/bin/rustdesk", @@ -26,7 +26,7 @@ "sources": [ { "type": "file", - "path": "../rustdesk-1.2.3.deb" + "path": "../rustdesk-1.2.4.deb" }, { "type": "file", diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index c542dabae..1ae62f341 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # 1.1.9-1 works for android, but for ios it becomes 1.1.91, need to set it to 1.1.9-a.1 for iOS, will get 1.1.9.1, but iOS store not allow 4 numbers -version: 1.2.3+39 +version: 1.2.4+39 environment: sdk: ">=2.17.0" diff --git a/res/PKGBUILD b/res/PKGBUILD index 0e83b6891..c43f57d22 100644 --- a/res/PKGBUILD +++ b/res/PKGBUILD @@ -1,5 +1,5 @@ pkgname=rustdesk -pkgver=1.2.3 +pkgver=1.2.4 pkgrel=0 epoch= pkgdesc="" diff --git a/res/rpm-flutter-suse.spec b/res/rpm-flutter-suse.spec index 8c71fa7de..4dfbe7c1a 100644 --- a/res/rpm-flutter-suse.spec +++ b/res/rpm-flutter-suse.spec @@ -1,5 +1,5 @@ Name: rustdesk -Version: 1.2.3 +Version: 1.2.4 Release: 0 Summary: RPM package License: GPL-3.0 diff --git a/res/rpm-flutter.spec b/res/rpm-flutter.spec index ca63093eb..4942d9f4e 100644 --- a/res/rpm-flutter.spec +++ b/res/rpm-flutter.spec @@ -1,5 +1,5 @@ Name: rustdesk -Version: 1.2.3 +Version: 1.2.4 Release: 0 Summary: RPM package License: GPL-3.0 diff --git a/res/rpm.spec b/res/rpm.spec index c92ad904a..8f6d15557 100644 --- a/res/rpm.spec +++ b/res/rpm.spec @@ -1,5 +1,5 @@ Name: rustdesk -Version: 1.2.3 +Version: 1.2.4 Release: 0 Summary: RPM package License: GPL-3.0 From 013d307bcddf08a0664593c1fca505344c179802 Mon Sep 17 00:00:00 2001 From: dignow Date: Sun, 8 Oct 2023 21:44:54 +0800 Subject: [PATCH 25/50] feat, multi_flutter_ui_sessions Signed-off-by: dignow --- flutter/lib/common.dart | 26 +- flutter/lib/common/widgets/dialog.dart | 2 +- flutter/lib/common/widgets/toolbar.dart | 19 +- flutter/lib/consts.dart | 10 + .../lib/desktop/pages/desktop_home_page.dart | 55 +- .../desktop/pages/desktop_setting_page.dart | 25 + flutter/lib/desktop/pages/remote_page.dart | 214 ++++--- .../lib/desktop/pages/remote_tab_page.dart | 29 +- .../lib/desktop/widgets/remote_toolbar.dart | 316 +++++----- .../lib/desktop/widgets/tabbar_widget.dart | 14 + flutter/lib/mobile/pages/remote_page.dart | 5 +- .../lib/models/desktop_render_texture.dart | 13 +- flutter/lib/models/input_model.dart | 41 +- flutter/lib/models/model.dart | 409 ++++++++++--- flutter/lib/models/native_model.dart | 20 +- flutter/lib/models/state_model.dart | 1 - flutter/lib/utils/multi_window_manager.dart | 59 +- libs/clipboard/src/lib.rs | 19 +- libs/hbb_common/protos/message.proto | 9 + libs/scrap/src/common/aom.rs | 12 +- libs/scrap/src/common/codec.rs | 6 +- libs/scrap/src/common/hwcodec.rs | 12 +- libs/scrap/src/common/vpxcodec.rs | 15 +- src/client.rs | 190 ++++-- src/client/helper.rs | 3 +- src/client/io_loop.rs | 280 +++++---- src/common.rs | 20 +- src/flutter.rs | 579 +++++++++++++----- src/flutter_ffi.rs | 242 +++++--- src/lang/ar.rs | 7 + src/lang/ca.rs | 7 + src/lang/cn.rs | 7 + src/lang/cs.rs | 7 + src/lang/da.rs | 7 + src/lang/de.rs | 9 +- src/lang/el.rs | 7 + src/lang/en.rs | 3 + src/lang/eo.rs | 7 + src/lang/es.rs | 7 + src/lang/fa.rs | 7 + src/lang/fr.rs | 7 + src/lang/hu.rs | 7 + src/lang/id.rs | 7 + src/lang/it.rs | 7 + src/lang/ja.rs | 7 + src/lang/ko.rs | 7 + src/lang/kz.rs | 7 + src/lang/lt.rs | 7 + src/lang/lv.rs | 7 + src/lang/nl.rs | 7 + src/lang/pl.rs | 7 + src/lang/pt_PT.rs | 7 + src/lang/ptbr.rs | 7 + src/lang/ro.rs | 7 + src/lang/ru.rs | 7 + src/lang/sk.rs | 7 + src/lang/sl.rs | 7 + src/lang/sq.rs | 7 + src/lang/sr.rs | 7 + src/lang/sv.rs | 7 + src/lang/template.rs | 7 + src/lang/th.rs | 7 + src/lang/tr.rs | 7 + src/lang/tw.rs | 7 + src/lang/ua.rs | 7 + src/lang/vn.rs | 7 + src/server.rs | 72 ++- src/server/audio_service.rs | 16 +- src/server/clipboard_service.rs | 8 +- src/server/connection.rs | 215 +++++-- src/server/display_service.rs | 262 ++++++++ src/server/input_service.rs | 46 +- src/server/portable_service.rs | 30 +- src/server/service.rs | 88 ++- src/server/video_service.rs | 496 ++++++--------- src/server/wayland.rs | 20 +- src/ui.rs | 3 - src/ui/header.tis | 2 +- src/ui/remote.rs | 14 +- src/ui/remote.tis | 4 +- src/ui_cm_interface.rs | 1 + src/ui_interface.rs | 20 +- src/ui_session_interface.rs | 74 ++- 83 files changed, 2954 insertions(+), 1319 deletions(-) create mode 100644 src/server/display_service.rs diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index a3ffcfd35..61d006b8a 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -1054,7 +1054,7 @@ Widget msgboxIcon(String type) { if (type == 'on-uac' || type == 'on-foreground-elevated') { iconData = Icons.admin_panel_settings; } - if (type == "info") { + if (type.contains('info')) { iconData = Icons.info; } if (iconData != null) { @@ -2317,7 +2317,7 @@ Widget dialogButton(String text, } } -int version_cmp(String v1, String v2) { +int versionCmp(String v1, String v2) { return bind.versionToNumber(v: v1) - bind.versionToNumber(v: v2); } @@ -2589,3 +2589,25 @@ String getDesktopTabLabel(String peerId, String alias) { } return label; } + +String getChooseDisplayBehavior() { + var current = bind.mainGetOptionSync(key: kKeyChooseDisplayBehavior); + if (![kChooseDisplayBehaviorSwitch, kChooseDisplayBehaviorOpen] + .contains(current)) { + current = kChooseDisplayBehaviorOpen; + } + return current; +} + +sessionRefreshVideo(SessionID sessionId, PeerInfo pi) async { + if (pi.currentDisplay == kAllDisplayValue) { + for (int i = 0; i < pi.displays.length; i++) { + await bind.sessionRefresh(sessionId: sessionId, display: i); + } + } else { + await bind.sessionRefresh(sessionId: sessionId, display: pi.currentDisplay); + } +} + +bool get isChooseDisplayToOpen => + getChooseDisplayBehavior() != kChooseDisplayBehaviorSwitch; diff --git a/flutter/lib/common/widgets/dialog.dart b/flutter/lib/common/widgets/dialog.dart index 2fe5d7637..d1e14d2fb 100644 --- a/flutter/lib/common/widgets/dialog.dart +++ b/flutter/lib/common/widgets/dialog.dart @@ -1295,7 +1295,7 @@ customImageQualityDialog(SessionID sessionId, String id, FFI ffi) async { ConnectionTypeState.find(id).direct.value == ConnectionType.strDirect; } catch (_) {} bool notShowFps = (await bind.mainIsUsingPublicServer() && direct != true) || - version_cmp(ffi.ffiModel.pi.version, '1.2.0') < 0; + versionCmp(ffi.ffiModel.pi.version, '1.2.0') < 0; final content = customImageQualityWidget( initQuality: qualityInitValue, diff --git a/flutter/lib/common/widgets/toolbar.dart b/flutter/lib/common/widgets/toolbar.dart index 0e8ed6b3d..1717e99f2 100644 --- a/flutter/lib/common/widgets/toolbar.dart +++ b/flutter/lib/common/widgets/toolbar.dart @@ -90,7 +90,7 @@ List toolbarControls(BuildContext context, String id, FFI ffi) { v.add( TTextMenu( child: Row(children: [ - Text(translate(pi.is_headless ? 'OS Account' : 'OS Password')), + Text(translate(pi.isHeadless ? 'OS Account' : 'OS Password')), Offstage( offstage: isDesktop, child: Icon(Icons.edit, color: MyTheme.accent).marginOnly(left: 12), @@ -99,13 +99,13 @@ List toolbarControls(BuildContext context, String id, FFI ffi) { trailingIcon: Transform.scale( scale: 0.8, child: InkWell( - onTap: () => pi.is_headless + onTap: () => pi.isHeadless ? showSetOSAccount(sessionId, ffi.dialogManager) : handleOsPasswordEditIcon(sessionId, ffi.dialogManager), child: Icon(Icons.edit), ), ), - onPressed: () => pi.is_headless + onPressed: () => pi.isHeadless ? showSetOSAccount(sessionId, ffi.dialogManager) : handleOsPasswordAction(sessionId, ffi.dialogManager), ), @@ -208,7 +208,8 @@ List toolbarControls(BuildContext context, String id, FFI ffi) { ffiModel.keyboard && pi.platform != kPeerPlatformAndroid && pi.platform != kPeerPlatformMacOS && - version_cmp(pi.version, '1.2.0') >= 0) { + versionCmp(pi.version, '1.2.0') >= 0 && + bind.peerGetDefaultSessionsCount(id: id) == 1) { v.add(TTextMenu( child: Text(translate('Switch Sides')), onPressed: () => @@ -217,8 +218,9 @@ List toolbarControls(BuildContext context, String id, FFI ffi) { // refresh if (pi.version.isNotEmpty) { v.add(TTextMenu( - child: Text(translate('Refresh')), - onPressed: () => bind.sessionRefresh(sessionId: sessionId))); + child: Text(translate('Refresh')), + onPressed: () => sessionRefreshVideo(sessionId, pi), + )); } // record var codecFormat = ffi.qualityMonitorModel.data.codecFormat; @@ -377,7 +379,7 @@ Future> toolbarDisplayToggle( // show remote cursor if (pi.platform != kPeerPlatformAndroid && !ffi.canvasModel.cursorEmbedded && - !pi.is_wayland) { + !pi.isWayland) { final state = ShowRemoteCursorState.find(id); final enabled = !ffiModel.viewOnly; final option = 'show-remote-cursor'; @@ -488,7 +490,8 @@ Future> toolbarDisplayToggle( value: rxValue.value, onChanged: (value) { if (value == null) return; - if (ffiModel.pi.currentDisplay != 0) { + if (ffiModel.pi.currentDisplay != 0 && + ffiModel.pi.currentDisplay != kAllDisplayValue) { msgBox(sessionId, 'custom-nook-nocancel-hasclose', 'info', 'Please switch to Display 1 first', '', ffi.dialogManager); return; diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index e0fffb27d..9c7fdf01d 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -4,10 +4,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/models/state_model.dart'; +const bool kOpenSamePeerInNewWindow = true; + const double kDesktopRemoteTabBarHeight = 28.0; const int kInvalidWindowId = -1; const int kMainWindowId = 0; +const kAllDisplayValue = -1; + const String kPeerPlatformWindows = "Windows"; const String kPeerPlatformLinux = "Linux"; const String kPeerPlatformMacOS = "Mac OS"; @@ -37,11 +41,13 @@ const String kWindowEventNewRemoteDesktop = "new_remote_desktop"; const String kWindowEventNewFileTransfer = "new_file_transfer"; const String kWindowEventNewPortForward = "new_port_forward"; const String kWindowEventActiveSession = "active_session"; +const String kWindowEventActiveDisplaySession = "active_display_session"; const String kWindowEventGetRemoteList = "get_remote_list"; const String kWindowEventGetSessionIdList = "get_session_id_list"; const String kWindowEventMoveTabToNewWindow = "move_tab_to_new_window"; const String kWindowEventGetCachedSessionData = "get_cached_session_data"; +const String kWindowEventOpenMonitorSession = "open_monitor_session"; const String kOptionOpenNewConnInTabs = "enable-open-new-connections-in-tabs"; const String kOptionOpenInTabs = "allow-open-in-tabs"; @@ -60,6 +66,10 @@ const int kWindowMainId = 0; const String kPointerEventKindTouch = "touch"; const String kPointerEventKindMouse = "mouse"; +const String kKeyChooseDisplayBehavior = 'choose-display-behavior'; +const String kChooseDisplayBehaviorSwitch = 'switch'; +const String kChooseDisplayBehaviorOpen = 'open'; + // the executable name of the portable version const String kEnvPortableExecutable = "RUSTDESK_APPNAME"; diff --git a/flutter/lib/desktop/pages/desktop_home_page.dart b/flutter/lib/desktop/pages/desktop_home_page.dart index c5d3718eb..8ea872c6c 100644 --- a/flutter/lib/desktop/pages/desktop_home_page.dart +++ b/flutter/lib/desktop/pages/desktop_home_page.dart @@ -187,12 +187,12 @@ class _DesktopHomePageState extends State ? Theme.of(context).scaffoldBackgroundColor : Theme.of(context).colorScheme.background, child: Tooltip( - message: translate('Settings'), - child: Icon( - Icons.more_vert_outlined, - size: 20, - color: hover.value ? textColor : textColor?.withOpacity(0.5), - )), + message: translate('Settings'), + child: Icon( + Icons.more_vert_outlined, + size: 20, + color: hover.value ? textColor : textColor?.withOpacity(0.5), + )), ), ), onHover: (value) => hover.value = value, @@ -256,27 +256,27 @@ class _DesktopHomePageState extends State child: Obx(() => RotatedBox( quarterTurns: 2, child: Tooltip( - message: translate('Refresh Password'), - child: Icon( - Icons.refresh, - color: refreshHover.value - ? textColor - : Color(0xFFDDDDDD), - size: 22, - )) - )), + message: translate('Refresh Password'), + child: Icon( + Icons.refresh, + color: refreshHover.value + ? textColor + : Color(0xFFDDDDDD), + size: 22, + )))), onHover: (value) => refreshHover.value = value, ).marginOnly(right: 8, top: 4), InkWell( child: Obx( () => Tooltip( - message: translate('Change Password'), - child: Icon( - Icons.edit, - color: - editHover.value ? textColor : Color(0xFFDDDDDD), - size: 22, - )).marginOnly(right: 8, top: 4), + message: translate('Change Password'), + child: Icon( + Icons.edit, + color: editHover.value + ? textColor + : Color(0xFFDDDDDD), + size: 22, + )).marginOnly(right: 8, top: 4), ), onTap: () => DesktopSettingPage.switch2page(1), onHover: (value) => editHover.value = value, @@ -604,8 +604,17 @@ class _DesktopHomePageState extends State debugPrint("Failed to parse window id '${call.arguments}': $e"); } if (windowId != null) { - await rustDeskWinManager.moveTabToNewWindow(windowId, args[1], args[2]); + await rustDeskWinManager.moveTabToNewWindow( + windowId, args[1], args[2]); } + } else if (call.method == kWindowEventOpenMonitorSession) { + final args = jsonDecode(call.arguments); + final windowId = args['window_id'] as int; + final peerId = args['peer_id'] as String; + final display = args['display'] as int; + final displayCount = args['display_count'] as int; + await rustDeskWinManager.openMonitorSession( + windowId, peerId, display, displayCount); } }); _uniLinksSubscription = listenUniLinks(); diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index a105f2135..905c082a0 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -11,6 +11,7 @@ import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart'; import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart'; import 'package:flutter_hbb/models/platform_model.dart'; +import 'package:flutter_hbb/models/desktop_render_texture.dart'; import 'package:flutter_hbb/models/server_model.dart'; import 'package:flutter_hbb/plugin/manager.dart'; import 'package:flutter_hbb/plugin/widgets/desktop_settings.dart'; @@ -268,6 +269,7 @@ class _GeneralState extends State<_General> { service(), theme(), hwcodec(), + chooseDisplay(), audio(context), record(context), _Card(title: 'Language', children: [language()]), @@ -376,6 +378,29 @@ class _GeneralState extends State<_General> { ); } + Widget chooseDisplay() { + if (!useTextureRender) return const Offstage(); + + var current = getChooseDisplayBehavior(); + onChanged(String value) { + bind.mainSetOption(key: kKeyChooseDisplayBehavior, value: value); + setState(() {}); + } + + return _Card(title: 'Choose Display Behavior', children: [ + _Radio(context, + value: kChooseDisplayBehaviorSwitch, + groupValue: current, + label: 'Switch Display', + onChanged: onChanged), + _Radio(context, + value: kChooseDisplayBehaviorOpen, + groupValue: current, + label: 'Open in New Window', + onChanged: onChanged), + ]); + } + Widget audio(BuildContext context) { String getDefault() { if (Platform.isWindows) return translate('System Sound'); diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index fee5bd8f4..abbb8785d 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -28,6 +28,7 @@ import '../widgets/tabbar_widget.dart'; final SimpleWrapper _firstEnterImage = SimpleWrapper(false); +// Used to skip session close if "move to new window" is clicked. final Map closeSessionOnDispose = {}; class RemotePage extends StatefulWidget { @@ -36,6 +37,8 @@ class RemotePage extends StatefulWidget { required this.id, required this.sessionId, required this.tabWindowId, + required this.display, + required this.displays, required this.password, required this.toolbarState, required this.tabController, @@ -46,6 +49,8 @@ class RemotePage extends StatefulWidget { final String id; final SessionID? sessionId; final int? tabWindowId; + final int? display; + final List? displays; final String? password; final ToolbarState toolbarState; final String? switchUuid; @@ -73,7 +78,7 @@ class _RemotePageState extends State late RxBool _zoomCursor; late RxBool _remoteCursorMoved; late RxBool _keyboardEnabled; - late RenderTexture _renderTexture; + final Map _renderTextures = {}; final _blockableOverlayState = BlockableOverlayState(); @@ -109,6 +114,8 @@ class _RemotePageState extends State switchUuid: widget.switchUuid, forceRelay: widget.forceRelay, tabWindowId: widget.tabWindowId, + display: widget.display, + displays: widget.displays, ); WidgetsBinding.instance.addPostFrameCallback((_) { SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []); @@ -118,9 +125,6 @@ class _RemotePageState extends State if (!Platform.isLinux) { Wakelock.enable(); } - // Register texture. - _renderTexture = RenderTexture(); - _renderTexture.create(sessionId); _ffi.ffiModel.updateEventListener(sessionId, widget.id); bind.pluginSyncUi(syncTo: kAppTypeDesktopRemote); @@ -207,7 +211,9 @@ class _RemotePageState extends State // https://github.com/flutter/flutter/issues/64935 super.dispose(); debugPrint("REMOTE PAGE dispose session $sessionId ${widget.id}"); - await _renderTexture.destroy(closeSession); + for (final texture in _renderTextures.values) { + await texture.destroy(closeSession); + } // ensure we leave this session, this is a double check _ffi.inputModel.enterOrLeave(false); DesktopMultiWindow.removeListener(this); @@ -245,6 +251,7 @@ class _RemotePageState extends State onEnterOrLeaveImageSetter: (func) => _onEnterOrLeaveImage4Toolbar = func, onEnterOrLeaveImageCleaner: () => _onEnterOrLeaveImage4Toolbar = null, + setRemoteState: setState, ); return Scaffold( backgroundColor: Theme.of(context).colorScheme.background, @@ -392,6 +399,38 @@ class _RemotePageState extends State ); } + Map _updateGetRenderTextures(int curDisplay) { + tryCreateTexture(int idx) { + if (!_renderTextures.containsKey(idx)) { + final renderTexture = RenderTexture(); + _renderTextures[idx] = renderTexture; + renderTexture.create(idx, sessionId); + } + } + + tryRemoveTexture(int idx) { + if (_renderTextures.containsKey(idx)) { + _renderTextures[idx]!.destroy(true); + _renderTextures.remove(idx); + } + } + + if (curDisplay == kAllDisplayValue) { + final displays = _ffi.ffiModel.pi.getCurDisplays(); + for (var i = 0; i < displays.length; i++) { + tryCreateTexture(i); + } + } else { + tryCreateTexture(curDisplay); + for (var i = 0; i < _ffi.ffiModel.pi.displays.length; i++) { + if (i != curDisplay) { + tryRemoveTexture(i); + } + } + } + return _renderTextures; + } + Widget getBodyForDesktop(BuildContext context) { var paints = [ MouseRegion(onEnter: (evt) { @@ -402,16 +441,20 @@ class _RemotePageState extends State Future.delayed(Duration.zero, () { Provider.of(context, listen: false).updateViewStyle(); }); - return ImagePaint( - id: widget.id, - zoomCursor: _zoomCursor, - cursorOverImage: _cursorOverImage, - keyboardEnabled: _keyboardEnabled, - remoteCursorMoved: _remoteCursorMoved, - textureId: _renderTexture.textureId, - useTextureRender: RenderTexture.useTextureRender, - listenerBuilder: (child) => - _buildRawTouchAndPointerRegion(child, enterView, leaveView), + final peerDisplay = CurrentDisplayState.find(widget.id); + return Obx( + () => _ffi.ffiModel.pi.isSet.isFalse + ? Container(color: Colors.transparent) + : Obx(() => ImagePaint( + id: widget.id, + zoomCursor: _zoomCursor, + cursorOverImage: _cursorOverImage, + keyboardEnabled: _keyboardEnabled, + remoteCursorMoved: _remoteCursorMoved, + renderTextures: _updateGetRenderTextures(peerDisplay.value), + listenerBuilder: (child) => _buildRawTouchAndPointerRegion( + child, enterView, leaveView), + )), ); })) ]; @@ -447,8 +490,7 @@ class ImagePaint extends StatefulWidget { final RxBool cursorOverImage; final RxBool keyboardEnabled; final RxBool remoteCursorMoved; - final RxInt textureId; - final bool useTextureRender; + final Map renderTextures; final Widget Function(Widget)? listenerBuilder; ImagePaint( @@ -458,8 +500,7 @@ class ImagePaint extends StatefulWidget { required this.cursorOverImage, required this.keyboardEnabled, required this.remoteCursorMoved, - required this.textureId, - required this.useTextureRender, + required this.renderTextures, this.listenerBuilder}) : super(key: key); @@ -530,27 +571,13 @@ class _ImagePaintState extends State { }); if (c.imageOverflow.isTrue && c.scrollStyle == ScrollStyle.scrollbar) { - final imageWidth = c.getDisplayWidth() * s; - final imageHeight = c.getDisplayHeight() * s; - final imageSize = Size(imageWidth, imageHeight); - late final Widget imageWidget; - if (widget.useTextureRender) { - imageWidget = SizedBox( - width: imageWidth, - height: imageHeight, - child: Obx(() => Texture( - textureId: widget.textureId.value, - filterQuality: - isViewOriginal() ? FilterQuality.none : FilterQuality.low, - )), - ); - } else { - imageWidget = CustomPaint( - size: imageSize, - painter: ImagePainter(image: m.image, x: 0, y: 0, scale: s), - ); - } - + final paintWidth = c.getDisplayWidth() * s; + final paintHeight = c.getDisplayHeight() * s; + final paintSize = Size(paintWidth, paintHeight); + final paintWidget = useTextureRender + ? _BuildPaintTextureRender( + c, s, Offset.zero, paintSize, isViewOriginal()) + : _buildScrollbarNonTextureRender(m, paintSize, s); return NotificationListener( onNotification: (notification) { final percentX = _horizontal.hasClients @@ -570,43 +597,79 @@ class _ImagePaintState extends State { }, child: mouseRegion( child: Obx(() => _buildCrossScrollbarFromLayout( - context, _buildListener(imageWidget), c.size, imageSize)), + context, _buildListener(paintWidget), c.size, paintSize)), )); } else { - late final Widget imageWidget; if (c.size.width > 0 && c.size.height > 0) { - if (widget.useTextureRender) { - final x = Platform.isLinux ? c.x.toInt().toDouble() : c.x; - final y = Platform.isLinux ? c.y.toInt().toDouble() : c.y; - imageWidget = Stack( - children: [ - Positioned( - left: x, - top: y, - width: c.getDisplayWidth() * s, - height: c.getDisplayHeight() * s, - child: Texture( - textureId: widget.textureId.value, - filterQuality: - isViewOriginal() ? FilterQuality.none : FilterQuality.low, + final paintWidget = useTextureRender + ? _BuildPaintTextureRender( + c, + s, + Offset( + Platform.isLinux ? c.x.toInt().toDouble() : c.x, + Platform.isLinux ? c.y.toInt().toDouble() : c.y, ), - ) - ], - ); - } else { - imageWidget = CustomPaint( - size: Size(c.size.width, c.size.height), - painter: - ImagePainter(image: m.image, x: c.x / s, y: c.y / s, scale: s), - ); - } - return mouseRegion(child: _buildListener(imageWidget)); + c.size, + isViewOriginal()) + : _buildScrollAuthNonTextureRender(m, c, s); + return mouseRegion(child: _buildListener(paintWidget)); } else { return Container(); } } } + Widget _buildScrollbarNonTextureRender( + ImageModel m, Size imageSize, double s) { + return CustomPaint( + size: imageSize, + painter: ImagePainter(image: m.image, x: 0, y: 0, scale: s), + ); + } + + Widget _buildScrollAuthNonTextureRender( + ImageModel m, CanvasModel c, double s) { + return CustomPaint( + size: Size(c.size.width, c.size.height), + painter: ImagePainter(image: m.image, x: c.x / s, y: c.y / s, scale: s), + ); + } + + Widget _BuildPaintTextureRender( + CanvasModel c, double s, Offset offset, Size size, bool isViewOriginal) { + final ffiModel = c.parent.target!.ffiModel; + final displays = ffiModel.pi.getCurDisplays(); + final children = []; + final rect = ffiModel.rect; + if (rect == null) { + return Container(); + } + final curDisplay = ffiModel.pi.currentDisplay; + for (var i = 0; i < displays.length; i++) { + final textureId = widget + .renderTextures[curDisplay == kAllDisplayValue ? i : curDisplay] + ?.textureId; + if (textureId != null) { + children.add(Positioned( + left: (displays[i].x - rect.left) * s + offset.dx, + top: (displays[i].y - rect.top) * s + offset.dy, + width: displays[i].width * s, + height: displays[i].height * s, + child: Obx(() => Texture( + textureId: textureId.value, + filterQuality: + isViewOriginal ? FilterQuality.none : FilterQuality.low, + )), + )); + } + } + return SizedBox( + width: size.width, + height: size.height, + child: Stack(children: children), + ); + } + MouseCursor _buildCursorOfCache( CursorModel cursor, double scale, CursorData? cache) { if (cache == null) { @@ -731,7 +794,11 @@ class _ImagePaintState extends State { ); } - return widget; + return Container( + child: widget, + width: layoutSize.width, + height: layoutSize.height, + ); } Widget _buildListener(Widget child) { @@ -770,9 +837,14 @@ class CursorPaint extends StatelessWidget { double cy = c.y; if (c.viewStyle.style == kRemoteViewStyleOriginal && c.scrollStyle == ScrollStyle.scrollbar) { - final d = c.parent.target!.ffiModel.display; - final imageWidth = d.width * c.scale; - final imageHeight = d.height * c.scale; + final rect = c.parent.target!.ffiModel.rect; + if (rect == null) { + // unreachable! + debugPrint('unreachable! The displays rect is null.'); + return Container(); + } + final imageWidth = rect.width * c.scale; + final imageHeight = rect.height * c.scale; cx = -imageWidth * c.scrollX; cy = -imageHeight * c.scrollY; } diff --git a/flutter/lib/desktop/pages/remote_tab_page.dart b/flutter/lib/desktop/pages/remote_tab_page.dart index feec8de6a..0cc23b39a 100644 --- a/flutter/lib/desktop/pages/remote_tab_page.dart +++ b/flutter/lib/desktop/pages/remote_tab_page.dart @@ -57,6 +57,8 @@ class _ConnectionTabPageState extends State { peerId = params['id']; final sessionId = params['session_id']; final tabWindowId = params['tab_window_id']; + final display = params['display']; + final displays = params['displays']; if (peerId != null) { ConnectionTypeState.init(peerId!); tabController.onSelected = (id) { @@ -80,6 +82,8 @@ class _ConnectionTabPageState extends State { id: peerId!, sessionId: sessionId == null ? null : SessionID(sessionId), tabWindowId: tabWindowId, + display: display, + displays: displays?.cast(), password: params['password'], toolbarState: _toolbarState, tabController: tabController, @@ -109,6 +113,8 @@ class _ConnectionTabPageState extends State { final switchUuid = args['switch_uuid']; final sessionId = args['session_id']; final tabWindowId = args['tab_window_id']; + final display = args['display']; + final displays = args['displays']; windowOnTop(windowId()); if (tabController.length == 0) { if (Platform.isMacOS && stateGlobal.closeOnFullscreen) { @@ -129,6 +135,8 @@ class _ConnectionTabPageState extends State { id: id, sessionId: sessionId == null ? null : SessionID(sessionId), tabWindowId: tabWindowId, + display: display, + displays: displays?.cast(), password: args['password'], toolbarState: _toolbarState, tabController: tabController, @@ -148,6 +156,15 @@ class _ConnectionTabPageState extends State { windowOnTop(windowId()); } return jumpOk; + } else if (call.method == kWindowEventActiveDisplaySession) { + final args = jsonDecode(call.arguments); + final id = args['id']; + final display = args['display']; + final jumpOk = tabController.jumpToByKeyAndDisplay(id, display); + if (jumpOk) { + windowOnTop(windowId()); + } + return jumpOk; } else if (call.method == kWindowEventGetRemoteList) { return tabController.state.value.tabs .map((e) => e.key) @@ -160,18 +177,20 @@ class _ConnectionTabPageState extends State { .join(';'); } else if (call.method == kWindowEventGetCachedSessionData) { // Ready to show new window and close old tab. - final peerId = call.arguments; + final args = jsonDecode(call.arguments); + final id = args['id']; + final close = args['close']; try { final remotePage = tabController.state.value.tabs - .firstWhere((tab) => tab.key == peerId) + .firstWhere((tab) => tab.key == id) .page as RemotePage; returnValue = remotePage.ffi.ffiModel.cachedPeerData.toString(); } catch (e) { debugPrint('Failed to get cached session data: $e'); } - if (returnValue != null) { - closeSessionOnDispose[peerId] = false; - tabController.closeBy(peerId); + if (close && returnValue != null) { + closeSessionOnDispose[id] = false; + tabController.closeBy(id); } } _update_remote_count(); diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index e7d81dfec..dddbc32a1 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -1,10 +1,12 @@ import 'dart:convert'; import 'dart:async'; import 'dart:io'; +import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hbb/common/widgets/toolbar.dart'; +import 'package:flutter_hbb/main.dart'; import 'package:flutter_hbb/models/chat_model.dart'; import 'package:flutter_hbb/models/state_model.dart'; import 'package:flutter_hbb/consts.dart'; @@ -325,7 +327,8 @@ class RemoteToolbar extends StatefulWidget { final FFI ffi; final ToolbarState state; final Function(Function(bool)) onEnterOrLeaveImageSetter; - final Function() onEnterOrLeaveImageCleaner; + final VoidCallback onEnterOrLeaveImageCleaner; + final Function(VoidCallback) setRemoteState; RemoteToolbar({ Key? key, @@ -334,6 +337,7 @@ class RemoteToolbar extends StatefulWidget { required this.state, required this.onEnterOrLeaveImageSetter, required this.onEnterOrLeaveImageCleaner, + required this.setRemoteState, }) : super(key: key); @override @@ -450,13 +454,17 @@ class _RemoteToolbarState extends State { toolbarItems.add(_MobileActionMenu(ffi: widget.ffi)); } - if (PrivacyModeState.find(widget.id).isFalse && pi.displays.length > 1) { - toolbarItems.add( - bind.mainGetUserDefaultOption(key: 'show_monitors_toolbar') == 'Y' - ? _MultiMonitorMenu(id: widget.id, ffi: widget.ffi) - : _MonitorMenu(id: widget.id, ffi: widget.ffi), - ); - } + toolbarItems.add(Obx(() { + if (PrivacyModeState.find(widget.id).isFalse && + pi.displaysCount.value > 1) { + return _MonitorMenu( + id: widget.id, + ffi: widget.ffi, + setRemoteState: widget.setRemoteState); + } else { + return Offstage(); + } + })); toolbarItems .add(_ControlMenu(id: widget.id, ffi: widget.ffi, state: widget.state)); @@ -581,11 +589,22 @@ class _MobileActionMenu extends StatelessWidget { class _MonitorMenu extends StatelessWidget { final String id; final FFI ffi; - const _MonitorMenu({Key? key, required this.id, required this.ffi}) - : super(key: key); + final Function(VoidCallback) setRemoteState; + const _MonitorMenu({ + Key? key, + required this.id, + required this.ffi, + required this.setRemoteState, + }) : super(key: key); + + bool get showMonitorsToolbar => + bind.mainGetUserDefaultOption(key: 'show_monitors_toolbar') == 'Y'; @override - Widget build(BuildContext context) { + Widget build(BuildContext context) => + showMonitorsToolbar ? buildMultiMonitorMenu() : buildMonitorMenu(); + + Widget buildMonitorMenu() { return _IconSubmenuButton( tooltip: 'Select Monitor', icon: icon(), @@ -595,7 +614,80 @@ class _MonitorMenu extends StatelessWidget { menuStyle: MenuStyle( padding: MaterialStatePropertyAll(EdgeInsets.symmetric(horizontal: 6))), - menuChildren: [Row(children: displays(context))]); + menuChildren: [Row(children: buildMonitorList(false))]); + } + + Widget buildMultiMonitorMenu() { + return Row(children: buildMonitorList(true)); + } + + List buildMonitorList(bool isMulti) { + final List monitorList = []; + final pi = ffi.ffiModel.pi; + + getMonitorText(int i) { + if (i == kAllDisplayValue) { + if (pi.displays.length == 2) { + return '1|2'; + } else { + return 'ALL'; + } + } else { + return (i + 1).toString(); + } + } + + buildMonitorButton(int i) => Obx(() { + RxInt display = CurrentDisplayState.find(id); + return _IconMenuButton( + tooltip: isMulti ? '' : '#${i + 1} monitor', + hMargin: isMulti ? null : 6, + vMargin: isMulti ? null : 12, + topLevel: false, + color: i == display.value + ? _ToolbarTheme.blueColor + : _ToolbarTheme.inactiveColor, + hoverColor: i == display.value + ? _ToolbarTheme.hoverBlueColor + : _ToolbarTheme.hoverInactiveColor, + icon: Container( + alignment: AlignmentDirectional.center, + constraints: + const BoxConstraints(minHeight: _ToolbarTheme.height), + child: Stack( + alignment: Alignment.center, + children: [ + SvgPicture.asset( + "assets/screen.svg", + colorFilter: + ColorFilter.mode(Colors.white, BlendMode.srcIn), + ), + Obx( + () => Text( + getMonitorText(i), + style: TextStyle( + color: i == display.value + ? _ToolbarTheme.blueColor + : _ToolbarTheme.inactiveColor, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ), + onPressed: () => onPressed(i, pi), + ); + }); + + for (int i = 0; i < pi.displays.length; i++) { + monitorList.add(buildMonitorButton(i)); + } + if (pi.isSupportMultiUiSession && pi.displays.length > 1) { + monitorList.add(buildMonitorButton(kAllDisplayValue)); + } + return monitorList; } icon() { @@ -610,7 +702,7 @@ class _MonitorMenu extends StatelessWidget { Obx(() { RxInt display = CurrentDisplayState.find(id); return Text( - '${display.value + 1}/${pi.displays.length}', + '${display.value == kAllDisplayValue ? 'A' : '${display.value + 1}'}/${pi.displays.length}', style: const TextStyle( color: _ToolbarTheme.blueColor, fontSize: 8, @@ -622,48 +714,44 @@ class _MonitorMenu extends StatelessWidget { ); } - List displays(BuildContext context) { - final List rowChildren = []; - final pi = ffi.ffiModel.pi; - for (int i = 0; i < pi.displays.length; i++) { - rowChildren.add(_IconMenuButton( - topLevel: false, - color: _ToolbarTheme.blueColor, - hoverColor: _ToolbarTheme.hoverBlueColor, - tooltip: "#${i + 1} monitor", - hMargin: 6, - vMargin: 12, - icon: Container( - alignment: AlignmentDirectional.center, - constraints: const BoxConstraints(minHeight: _ToolbarTheme.height), - child: Stack( - alignment: Alignment.center, - children: [ - SvgPicture.asset( - "assets/screen.svg", - colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn), - ), - Text( - (i + 1).toString(), - style: TextStyle( - color: _ToolbarTheme.blueColor, - fontSize: 12, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - ), - onPressed: () { - _menuDismissCallback(ffi); - RxInt display = CurrentDisplayState.find(id); - if (display.value != i) { - bind.sessionSwitchDisplay(sessionId: ffi.sessionId, value: i); - } - }, - )); + // Open new tab or window to show this monitor. + // For now just open new window. + openMonitorInNewTabOrWindow(int i, PeerInfo pi) { + if (kWindowId == null) { + // unreachable + debugPrint('openMonitorInNewTabOrWindow, unreachable! kWindowId is null'); + return; + } + DesktopMultiWindow.invokeMethod( + kMainWindowId, + kWindowEventOpenMonitorSession, + jsonEncode({ + 'window_id': kWindowId!, + 'peer_id': ffi.id, + 'display': i, + 'display_count': pi.displays.length, + })); + } + + openMonitorInTheSameTab(int i, PeerInfo pi) { + final displays = i == kAllDisplayValue + ? List.generate(pi.displays.length, (index) => index) + : [i]; + bind.sessionSwitchDisplay( + sessionId: ffi.sessionId, value: Int32List.fromList(displays)); + ffi.ffiModel.switchToNewDisplay(i, ffi.sessionId, id); + } + + onPressed(int i, PeerInfo pi) { + _menuDismissCallback(ffi); + RxInt display = CurrentDisplayState.find(id); + if (display.value != i) { + if (pi.isSupportMultiDisplay) { + openMonitorInNewTabOrWindow(i, pi); + } else { + openMonitorInTheSameTab(i, pi); + } } - return rowChildren; } } @@ -1044,14 +1132,14 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { Resolution? _localResolution; late final TextEditingController _customWidth = - TextEditingController(text: display.width.toString()); + TextEditingController(text: rect?.width.toInt().toString() ?? ''); late final TextEditingController _customHeight = - TextEditingController(text: display.height.toString()); + TextEditingController(text: rect?.height.toInt().toString() ?? ''); FFI get ffi => widget.ffi; PeerInfo get pi => widget.ffi.ffiModel.pi; FfiModel get ffiModel => widget.ffi.ffiModel; - Display get display => ffiModel.display; + Rect? get rect => ffiModel.rect; List get resolutions => pi.resolutions; bool get isWayland => bind.mainCurrentIsWayland(); @@ -1063,12 +1151,12 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { @override Widget build(BuildContext context) { - final isVirtualDisplay = display.isVirtualDisplayResolution; + final isVirtualDisplay = ffiModel.isVirtualDisplayResolution; final visible = ffiModel.keyboard && (isVirtualDisplay || resolutions.length > 1); if (!visible) return Offstage(); final showOriginalBtn = - display.isOriginalResolutionSet && !display.isOriginalResolution; + ffiModel.isOriginalResolutionSet && !ffiModel.isOriginalResolution; final showFitLocalBtn = !_isRemoteResolutionFitLocal(); _setGroupValue(); return _SubmenuButton( @@ -1085,12 +1173,15 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { } _setGroupValue() { + if (pi.currentDisplay == kAllDisplayValue) { + return; + } final lastGroupValue = stateGlobal.getLastResolutionGroupValue(widget.id, pi.currentDisplay); if (lastGroupValue == _kCustomResolutionValue) { _groupValue = _kCustomResolutionValue; } else { - _groupValue = '${display.width}x${display.height}'; + _groupValue = '${rect?.width.toInt()}x${rect?.height.toInt()}'; } } @@ -1118,20 +1209,23 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { _getLocalResolution() { _localResolution = null; - final String currentDisplay = bind.mainGetCurrentDisplay(); - if (currentDisplay.isNotEmpty) { + final String mainDisplay = bind.mainGetMainDisplay(); + if (mainDisplay.isNotEmpty) { try { - final display = json.decode(currentDisplay); + final display = json.decode(mainDisplay); if (display['w'] != null && display['h'] != null) { _localResolution = Resolution(display['w'], display['h']); } } catch (e) { - debugPrint('Failed to decode $currentDisplay, $e'); + debugPrint('Failed to decode $mainDisplay, $e'); } } } _onChanged(BuildContext context, String? value) async { + if (pi.currentDisplay == kAllDisplayValue) { + return; + } stateGlobal.setLastResolutionGroupValue( widget.id, pi.currentDisplay, value); if (value == null) return; @@ -1150,13 +1244,16 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { } if (w != null && h != null) { - if (w != display.width || h != display.height) { + if (w != rect?.width.toInt() || h != rect?.height.toInt()) { await _changeResolution(context, w, h); } } } _changeResolution(BuildContext context, int w, int h) async { + if (pi.currentDisplay == kAllDisplayValue) { + return; + } await bind.sessionChangeResolution( sessionId: ffi.sessionId, display: pi.currentDisplay, @@ -1164,8 +1261,11 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { height: h, ); Future.delayed(Duration(seconds: 3), () async { - final display = ffiModel.display; - if (w == display.width && h == display.height) { + final rect = ffiModel.rect; + if (rect == null) { + return; + } + if (w == rect.width.toInt() && h == rect.height.toInt()) { if (await widget.screenAdjustor.isWindowCanBeAdjusted()) { widget.screenAdjustor.doAdjustWindow(context); } @@ -1175,6 +1275,10 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { Widget _OriginalResolutionMenuButton( BuildContext context, bool showOriginalBtn) { + final display = pi.tryGetDisplayIfNotAllDisplay(); + if (display == null) { + return Offstage(); + } return Offstage( offstage: !showOriginalBtn, child: MenuButton( @@ -1262,7 +1366,7 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { return null; } - if (display.isVirtualDisplayResolution) { + if (ffiModel.isVirtualDisplayResolution) { return _localResolution!; } @@ -1284,8 +1388,8 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> { if (bestFitResolution == null) { return true; } - return bestFitResolution.width == display.width && - bestFitResolution.height == display.height; + return bestFitResolution.width == rect?.width.toInt() && + bestFitResolution.height == rect?.height.toInt(); } } @@ -1361,7 +1465,7 @@ class _KeyboardMenu extends StatelessWidget { continue; } - if (pi.is_wayland && mode.key != _kKeyMapMode) { + if (pi.isWayland && mode.key != _kKeyMapMode) { continue; } @@ -1404,7 +1508,7 @@ class _KeyboardMenu extends StatelessWidget { viewMode() { final ffiModel = ffi.ffiModel; - final enabled = version_cmp(pi.version, '1.2.0') >= 0 && ffiModel.keyboard; + final enabled = versionCmp(pi.version, '1.2.0') >= 0 && ffiModel.keyboard; return CkbMenuButton( value: ffiModel.viewOnly, onChanged: enabled @@ -2037,71 +2141,3 @@ Widget _buildPointerTrackWidget(Widget child, FFI ffi) { ), ); } - -class _MultiMonitorMenu extends StatelessWidget { - final String id; - final FFI ffi; - - const _MultiMonitorMenu({ - Key? key, - required this.id, - required this.ffi, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - final List rowChildren = []; - final pi = ffi.ffiModel.pi; - - for (int i = 0; i < pi.displays.length; i++) { - rowChildren.add( - Obx(() { - RxInt display = CurrentDisplayState.find(id); - return _IconMenuButton( - tooltip: "", - topLevel: false, - color: i == display.value - ? _ToolbarTheme.blueColor - : Colors.grey[800]!, - hoverColor: i == display.value - ? _ToolbarTheme.hoverBlueColor - : Colors.grey[850]!, - icon: Container( - alignment: AlignmentDirectional.center, - constraints: - const BoxConstraints(minHeight: _ToolbarTheme.height), - child: Stack( - alignment: Alignment.center, - children: [ - SvgPicture.asset( - "assets/screen.svg", - colorFilter: - ColorFilter.mode(Colors.white, BlendMode.srcIn), - ), - Obx( - () => Text( - (i + 1).toString(), - style: TextStyle( - color: i == display.value - ? _ToolbarTheme.blueColor - : Colors.grey[800]!, - fontSize: 12, - fontWeight: FontWeight.bold, - ), - ), - ), - ], - ), - ), - onPressed: () { - if (display.value != i) { - bind.sessionSwitchDisplay(sessionId: ffi.sessionId, value: i); - } - }, - ); - }), - ); - } - return Row(children: rowChildren); - } -} diff --git a/flutter/lib/desktop/widgets/tabbar_widget.dart b/flutter/lib/desktop/widgets/tabbar_widget.dart index dc58c1b5d..5ce5601a0 100644 --- a/flutter/lib/desktop/widgets/tabbar_widget.dart +++ b/flutter/lib/desktop/widgets/tabbar_widget.dart @@ -9,6 +9,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart' hide TabBarTheme; import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/consts.dart'; +import 'package:flutter_hbb/desktop/pages/remote_page.dart'; import 'package:flutter_hbb/main.dart'; import 'package:flutter_hbb/models/platform_model.dart'; import 'package:flutter_hbb/models/state_model.dart'; @@ -176,6 +177,19 @@ class DesktopTabController { jumpTo(state.value.tabs.indexWhere((tab) => tab.key == key), callOnSelected: callOnSelected); + bool jumpToByKeyAndDisplay(String key, int display) { + for (int i = 0; i < state.value.tabs.length; i++) { + final tab = state.value.tabs[i]; + if (tab.key == key) { + final ffi = (tab.page as RemotePage).ffi; + if (ffi.ffiModel.pi.currentDisplay == display) { + return jumpTo(i, callOnSelected: true); + } + } + } + return false; + } + void closeBy(String? key) { if (!isDesktop) return; assert(onRemoved != null); diff --git a/flutter/lib/mobile/pages/remote_page.dart b/flutter/lib/mobile/pages/remote_page.dart index b38adf63e..249355012 100644 --- a/flutter/lib/mobile/pages/remote_page.dart +++ b/flutter/lib/mobile/pages/remote_page.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -755,14 +756,14 @@ void showOptions( if (image != null) { displays.add(Padding(padding: const EdgeInsets.only(top: 8), child: image)); } - if (pi.displays.length > 1) { + if (pi.displays.length > 1 && pi.currentDisplay != kAllDisplayValue) { final cur = pi.currentDisplay; final children = []; for (var i = 0; i < pi.displays.length; ++i) { children.add(InkWell( onTap: () { if (i == cur) return; - bind.sessionSwitchDisplay(sessionId: gFFI.sessionId, value: i); + bind.sessionSwitchDisplay(sessionId: gFFI.sessionId, value: Int32List.fromList([i])); gFFI.dialogManager.dismissAll(); }, child: Ink( diff --git a/flutter/lib/models/desktop_render_texture.dart b/flutter/lib/models/desktop_render_texture.dart index b18d97b82..4d1c1a8f6 100644 --- a/flutter/lib/models/desktop_render_texture.dart +++ b/flutter/lib/models/desktop_render_texture.dart @@ -4,25 +4,30 @@ import 'package:texture_rgba_renderer/texture_rgba_renderer.dart'; import '../../common.dart'; import './platform_model.dart'; +final useTextureRender = bind.mainUseTextureRender(); + class RenderTexture { final RxInt textureId = RxInt(-1); int _textureKey = -1; + int _display = 0; SessionID? _sessionId; - static final useTextureRender = bind.mainUseTextureRender(); final textureRenderer = TextureRgbaRenderer(); RenderTexture(); - create(SessionID sessionId) { + int get display => _display; + + create(int d, SessionID sessionId) { if (useTextureRender) { + _display = d; _textureKey = bind.getNextTextureKey(); _sessionId = sessionId; textureRenderer.createTexture(_textureKey).then((id) async { if (id != -1) { final ptr = await textureRenderer.getTexturePtr(_textureKey); - platformFFI.registerTexture(sessionId, ptr); + platformFFI.registerTexture(sessionId, display, ptr); textureId.value = id; } }); @@ -32,7 +37,7 @@ class RenderTexture { destroy(bool unregisterTexture) async { if (useTextureRender && _textureKey != -1 && _sessionId != null) { if (unregisterTexture) { - platformFFI.registerTexture(_sessionId!, 0); + platformFFI.registerTexture(_sessionId!, display, 0); } await textureRenderer.closeTexture(_textureKey); _textureKey = -1; diff --git a/flutter/lib/models/input_model.dart b/flutter/lib/models/input_model.dart index 971bbb7e5..29c13f625 100644 --- a/flutter/lib/models/input_model.dart +++ b/flutter/lib/models/input_model.dart @@ -552,22 +552,22 @@ class InputModel { return v; } - Offset setNearestEdge(double x, double y, Display d) { - double left = x - d.x; - double right = d.x + d.width - 1 - x; - double top = y - d.y; - double bottom = d.y + d.height - 1 - y; + Offset setNearestEdge(double x, double y, Rect rect) { + double left = x - rect.left; + double right = rect.right - 1 - x; + double top = y - rect.top; + double bottom = rect.bottom - 1 - y; if (left < right && left < top && left < bottom) { - x = d.x; + x = rect.left; } if (right < left && right < top && right < bottom) { - x = d.x + d.width - 1; + x = rect.right - 1; } if (top < left && top < right && top < bottom) { - y = d.y; + y = rect.top; } if (bottom < left && bottom < right && bottom < top) { - y = d.y + d.height - 1; + y = rect.bottom - 1; } return Offset(x, y); } @@ -711,9 +711,12 @@ class InputModel { final nearThr = 3; var nearRight = (canvasModel.size.width - x) < nearThr; var nearBottom = (canvasModel.size.height - y) < nearThr; - final d = ffiModel.display; - final imageWidth = d.width * canvasModel.scale; - final imageHeight = d.height * canvasModel.scale; + final rect = ffiModel.rect; + if (rect == null) { + return null; + } + final imageWidth = rect.width * canvasModel.scale; + final imageHeight = rect.height * canvasModel.scale; if (canvasModel.scrollStyle == ScrollStyle.scrollbar) { x += imageWidth * canvasModel.scrollX; y += imageHeight * canvasModel.scrollY; @@ -741,11 +744,11 @@ class InputModel { y += step; } } - x += d.x; - y += d.y; + x += rect.left; + y += rect.top; if (onExit) { - final pos = setNearestEdge(x, y, d); + final pos = setNearestEdge(x, y, rect); x = pos.dx; y = pos.dy; } @@ -761,10 +764,10 @@ class InputModel { return null; } - int minX = d.x.toInt(); - int maxX = (d.x + d.width).toInt() - 1; - int minY = d.y.toInt(); - int maxY = (d.y + d.height).toInt() - 1; + int minX = rect.left.toInt(); + int maxX = (rect.left + rect.width).toInt() - 1; + int minY = rect.top.toInt(); + int maxY = (rect.top + rect.height).toInt() - 1; evtX = trySetNearestRange(evtX, minX, maxX, 5); evtY = trySetNearestRange(evtY, minY, maxY, 5); if (kind == kPointerEventKindMouse) { diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 3c42e02a3..7bb96a99b 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'dart:math'; +import 'dart:typed_data'; import 'dart:ui' as ui; import 'package:desktop_multi_window/desktop_multi_window.dart'; @@ -86,7 +87,7 @@ class CachedPeerData { class FfiModel with ChangeNotifier { CachedPeerData cachedPeerData = CachedPeerData(); PeerInfo _pi = PeerInfo(); - Display _display = Display(); + Rect? _rect; var _inputBlocked = false; final _permissions = {}; @@ -103,9 +104,15 @@ class FfiModel with ChangeNotifier { Timer? waitForImageTimer; RxBool waitForFirstImage = true.obs; - Map get permissions => _permissions; + Rect? get rect => _rect; + bool get isOriginalResolutionSet => + _pi.tryGetDisplayIfNotAllDisplay()?.isOriginalResolutionSet ?? false; + bool get isVirtualDisplayResolution => + _pi.tryGetDisplayIfNotAllDisplay()?.isVirtualDisplayResolution ?? false; + bool get isOriginalResolution => + _pi.tryGetDisplayIfNotAllDisplay()?.isOriginalResolution ?? false; - Display get display => _display; + Map get permissions => _permissions; bool? get secure => _secure; @@ -130,6 +137,24 @@ class FfiModel with ChangeNotifier { sessionId = parent.target!.sessionId; } + Rect? displaysRect() { + final displays = _pi.getCurDisplays(); + if (displays.isEmpty) { + return null; + } + double l = displays[0].x; + double t = displays[0].y; + double r = displays[0].x + displays[0].width; + double b = displays[0].y + displays[0].height; + for (var display in displays.sublist(1)) { + l = min(l, display.x); + t = min(t, display.y); + r = max(r, display.x + display.width); + b = max(b, display.y + display.height); + } + return Rect.fromLTRB(l, t, r, b); + } + toggleTouchMode() { if (!isPeerAndroid) { _touchMode = !_touchMode; @@ -154,7 +179,6 @@ class FfiModel with ChangeNotifier { clear() { _pi = PeerInfo(); - _display = Display(); _secure = null; _direct = null; _inputBlocked = false; @@ -207,8 +231,10 @@ class FfiModel with ChangeNotifier { updateLastCursorId(element); await handleCursorData(element); } - updateLastCursorId(data.lastCursorId); - handleCursorId(data.lastCursorId); + if (data.lastCursorId.isNotEmpty) { + updateLastCursorId(data.lastCursorId); + handleCursorId(data.lastCursorId); + } } // todo: why called by two position @@ -220,11 +246,12 @@ class FfiModel with ChangeNotifier { } else if (name == 'peer_info') { handlePeerInfo(evt, peerId); } else if (name == 'sync_peer_info') { - handleSyncPeerInfo(evt, sessionId); + handleSyncPeerInfo(evt, sessionId, peerId); } else if (name == 'connection_ready') { setConnectionType( peerId, evt['secure'] == 'true', evt['direct'] == 'true'); } else if (name == 'switch_display') { + // switch display is kept for backward compatibility handleSwitchDisplay(evt, sessionId, peerId); } else if (name == 'cursor_data') { updateLastCursorId(evt); @@ -279,7 +306,7 @@ class FfiModel with ChangeNotifier { await bind.sessionSwitchSides(sessionId: sessionId); closeConnection(id: peer_id); } else if (name == 'portable_service_running') { - parent.target?.elevationModel.onPortableServiceRunning(evt); + _handlePortableServiceRunning(peerId, evt); } else if (name == 'on_url_scheme_received') { // currently comes from "_url" ipc of mac and dbus of linux onUrlSchemeReceived(evt); @@ -354,20 +381,65 @@ class FfiModel with ChangeNotifier { platformFFI.setEventCallback(startEventListener(sessionId, peerId)); } - _updateCurDisplay(SessionID sessionId, Display newDisplay) { - if (newDisplay != _display) { - if (newDisplay.x != _display.x || newDisplay.y != _display.y) { - parent.target?.cursorModel - .updateDisplayOrigin(newDisplay.x, newDisplay.y); + _handlePortableServiceRunning(String peerId, Map evt) { + final running = evt['running'] == 'true'; + parent.target?.elevationModel.onPortableServiceRunning(running); + if (running) { + if (pi.primaryDisplay != kInvalidDisplayIndex) { + if (pi.currentDisplay != pi.primaryDisplay) { + // Notify to switch display + msgBox(sessionId, 'custom-nook-nocancel-hasclose-info', 'Prompt', + 'elevated_switch_display_msg', '', parent.target!.dialogManager); + bind.sessionSwitchDisplay( + sessionId: sessionId, + value: Int32List.fromList([pi.primaryDisplay])); + } } - _display = newDisplay; + } + } + + handleAliasChanged(Map evt) { + if (!isDesktop) return; + final String peerId = evt['id']; + final String alias = evt['alias']; + String label = getDesktopTabLabel(peerId, alias); + final rxTabLabel = PeerStringOption.find(evt['id'], 'tabLabel'); + if (rxTabLabel.value != label) { + rxTabLabel.value = label; + } + } + + updateCurDisplay(SessionID sessionId) { + final newRect = displaysRect(); + if (newRect == null) { + return; + } + if (newRect != _rect) { + _rect = newRect; + if (newRect.left != _rect?.left || newRect.top != _rect?.top) { + parent.target?.cursorModel + .updateDisplayOrigin(newRect.left, newRect.top); + } + parent.target?.canvasModel.updateViewStyle(); _updateSessionWidthHeight(sessionId); } } handleSwitchDisplay( Map evt, SessionID sessionId, String peerId) { - _pi.currentDisplay = int.parse(evt['display']); + final curDisplay = int.parse(evt['display']); + + // The message should be handled by the another UI session. + if (_pi.isSupportMultiDisplay) { + if (curDisplay != _pi.currentDisplay) { + return; + } + } + + if (_pi.currentDisplay != kAllDisplayValue) { + _pi.currentDisplay = curDisplay; + } + var newDisplay = Display(); newDisplay.x = double.tryParse(evt['x']) ?? newDisplay.x; newDisplay.y = double.tryParse(evt['y']) ?? newDisplay.y; @@ -378,11 +450,11 @@ class FfiModel with ChangeNotifier { int.tryParse(evt['original_width']) ?? kInvalidResolutionValue; newDisplay.originalHeight = int.tryParse(evt['original_height']) ?? kInvalidResolutionValue; + _pi.displays[curDisplay] = newDisplay; - _updateCurDisplay(sessionId, newDisplay); - + updateCurDisplay(sessionId); try { - CurrentDisplayState.find(peerId).value = _pi.currentDisplay; + CurrentDisplayState.find(peerId).value = curDisplay; } catch (e) { // } @@ -522,13 +594,30 @@ class FfiModel with ChangeNotifier { } _updateSessionWidthHeight(SessionID sessionId) { - parent.target?.canvasModel.updateViewStyle(); - if (display.width <= 0 || display.height <= 0) { + if (_rect == null) return; + if (_rect!.width <= 0 || _rect!.height <= 0) { debugPrintStack( - label: 'invalid display size (${display.width},${display.height})'); + label: 'invalid display size (${_rect!.width},${_rect!.height})'); } else { - bind.sessionSetSize( - sessionId: sessionId, width: display.width, height: display.height); + final displays = _pi.getCurDisplays(); + if (displays.length == 1) { + bind.sessionSetSize( + sessionId: sessionId, + display: + pi.currentDisplay == kAllDisplayValue ? 0 : pi.currentDisplay, + width: _rect!.width.toInt(), + height: _rect!.height.toInt(), + ); + } else { + for (int i = 0; i < displays.length; ++i) { + bind.sessionSetSize( + sessionId: sessionId, + display: i, + width: displays[i].width.toInt(), + height: displays[i].height.toInt(), + ); + } + } } } @@ -541,11 +630,20 @@ class FfiModel with ChangeNotifier { parent.target?.dialogManager.dismissAll(); _pi.version = evt['version']; + _pi.isSupportMultiUiSession = + bind.isSupportMultiUiSession(version: _pi.version); _pi.username = evt['username']; _pi.hostname = evt['hostname']; _pi.platform = evt['platform']; _pi.sasEnabled = evt['sas_enabled'] == 'true'; - _pi.currentDisplay = int.parse(evt['current_display']); + final currentDisplay = int.parse(evt['current_display']); + if (_pi.primaryDisplay == kInvalidDisplayIndex) { + _pi.primaryDisplay = currentDisplay; + } + + if (bind.peerGetDefaultSessionsCount(id: peerId) <= 1) { + _pi.currentDisplay = currentDisplay; + } try { CurrentDisplayState.find(peerId).value = _pi.currentDisplay; @@ -569,10 +667,10 @@ class FfiModel with ChangeNotifier { for (int i = 0; i < displays.length; ++i) { _pi.displays.add(evtToDisplay(displays[i])); } - stateGlobal.displaysCount.value = _pi.displays.length; + _pi.displaysCount.value = _pi.displays.length; if (_pi.currentDisplay < _pi.displays.length) { - _display = _pi.displays[_pi.currentDisplay]; - _updateSessionWidthHeight(sessionId); + // now replaced to _updateCurDisplay + updateCurDisplay(sessionId); } if (displays.isNotEmpty) { _reconnects = 1; @@ -590,12 +688,12 @@ class FfiModel with ChangeNotifier { sessionId: sessionId, arg: 'view-only')); } if (connType == ConnType.defaultConn) { - final platform_additions = evt['platform_additions']; - if (platform_additions != null && platform_additions != '') { + final platformDdditions = evt['platform_additions']; + if (platformDdditions != null && platformDdditions != '') { try { - _pi.platform_additions = json.decode(platform_additions); + _pi.platformDdditions = json.decode(platformDdditions); } catch (e) { - debugPrint('Failed to decode platform_additions $e'); + debugPrint('Failed to decode platformDdditions $e'); } } } @@ -670,7 +768,8 @@ class FfiModel with ChangeNotifier { } /// Handle the peer info synchronization event based on [evt]. - handleSyncPeerInfo(Map evt, SessionID sessionId) async { + handleSyncPeerInfo( + Map evt, SessionID sessionId, String peerId) async { if (evt['displays'] != null) { cachedPeerData.peerInfo['displays'] = evt['displays']; List displays = json.decode(evt['displays']); @@ -679,14 +778,54 @@ class FfiModel with ChangeNotifier { newDisplays.add(evtToDisplay(displays[i])); } _pi.displays = newDisplays; - stateGlobal.displaysCount.value = _pi.displays.length; - if (_pi.currentDisplay >= 0 && _pi.currentDisplay < _pi.displays.length) { - _updateCurDisplay(sessionId, _pi.displays[_pi.currentDisplay]); + _pi.displaysCount.value = _pi.displays.length; + if (_pi.currentDisplay == kAllDisplayValue) { + updateCurDisplay(sessionId); + // to-do: What if the displays are changed? + } else { + if (_pi.currentDisplay >= 0 && + _pi.currentDisplay < _pi.displays.length) { + updateCurDisplay(sessionId); + } else { + if (_pi.displays.isNotEmpty) { + // Notify to switch display + msgBox(sessionId, 'custom-nook-nocancel-hasclose-info', 'Prompt', + 'display_is_plugged_out_msg', '', parent.target!.dialogManager); + final newDisplay = pi.primaryDisplay == kInvalidDisplayIndex + ? 0 + : pi.primaryDisplay; + final displays = newDisplay; + bind.sessionSwitchDisplay( + sessionId: sessionId, value: Int32List.fromList([displays])); + + if (_pi.isSupportMultiUiSession) { + // If the peer supports multi-ui-session, no switch display message will be send back. + // We need to update the display manually. + switchToNewDisplay(newDisplay, sessionId, peerId); + } + } else { + msgBox(sessionId, 'nocancel-error', 'Prompt', 'No Displays', '', + parent.target!.dialogManager); + } + } } } notifyListeners(); } + // Directly switch to the new display without waiting for the response. + switchToNewDisplay(int display, SessionID sessionId, String peerId) { + // no need to wait for the response + pi.currentDisplay = display; + updateCurDisplay(sessionId); + try { + CurrentDisplayState.find(peerId).value = display; + } catch (e) { + // + } + parent.target?.recordingModel.onSwitchDisplay(); + } + updateBlockInputState(Map evt, String peerId) { _inputBlocked = evt['input_state'] == 'on'; notifyListeners(); @@ -709,7 +848,7 @@ class FfiModel with ChangeNotifier { } void setViewOnly(String id, bool value) { - if (version_cmp(_pi.version, '1.2.0') < 0) return; + if (versionCmp(_pi.version, '1.2.0') < 0) return; // tmp fix for https://github.com/rustdesk/rustdesk/pull/3706#issuecomment-1481242389 // because below rx not used in mobile version, so not initialized, below code will cause crash // current our flutter code quality is fucking shit now. !!!!!!!!!!!!!!!! @@ -749,16 +888,16 @@ class ImageModel with ChangeNotifier { addCallbackOnFirstImage(Function(String) cb) => callbacksOnFirstImage.add(cb); - onRgba(Uint8List rgba) { + onRgba(int display, Uint8List rgba) { final pid = parent.target?.id; img.decodeImageFromPixels( rgba, - parent.target?.ffiModel.display.width ?? 0, - parent.target?.ffiModel.display.height ?? 0, + parent.target?.ffiModel.rect?.width.toInt() ?? 0, + parent.target?.ffiModel.rect?.height.toInt() ?? 0, isWeb ? ui.PixelFormat.rgba8888 : ui.PixelFormat.bgra8888, onPixelsCopied: () { // Unlock the rgba memory from rust codes. - platformFFI.nextRgba(sessionId); + platformFFI.nextRgba(sessionId, display); }).then((image) { if (parent.target?.id != pid) return; try { @@ -1017,20 +1156,20 @@ class CanvasModel with ChangeNotifier { } bool get cursorEmbedded => - parent.target?.ffiModel.display.cursorEmbedded ?? false; + parent.target?.ffiModel._pi.cursorEmbedded ?? false; int getDisplayWidth() { final defaultWidth = (isDesktop || isWebDesktop) ? kDesktopDefaultDisplayWidth : kMobileDefaultDisplayWidth; - return parent.target?.ffiModel.display.width ?? defaultWidth; + return parent.target?.ffiModel.rect?.width.toInt() ?? defaultWidth; } int getDisplayHeight() { final defaultHeight = (isDesktop || isWebDesktop) ? kDesktopDefaultDisplayHeight : kMobileDefaultDisplayHeight; - return parent.target?.ffiModel.display.height ?? defaultHeight; + return parent.target?.ffiModel.rect?.height.toInt() ?? defaultHeight; } static double get windowBorderWidth => stateGlobal.windowBorderWidth.value; @@ -1619,7 +1758,27 @@ class QualityMonitorModel with ChangeNotifier { updateQualityStatus(Map evt) { try { if ((evt['speed'] as String).isNotEmpty) _data.speed = evt['speed']; - if ((evt['fps'] as String).isNotEmpty) _data.fps = evt['fps']; + if ((evt['fps'] as String).isNotEmpty) { + final fps = jsonDecode(evt['fps']) as Map; + final pi = parent.target?.ffiModel.pi; + if (pi != null) { + final currentDisplay = pi.currentDisplay; + if (currentDisplay != kAllDisplayValue) { + final fps2 = fps[currentDisplay.toString()]; + if (fps2 != null) { + _data.fps = fps2.toString(); + } + } else if (fps.isNotEmpty) { + final fpsList = []; + for (var i = 0; i < pi.displays.length; i++) { + fpsList.add((fps[i.toString()] ?? 0).toString()); + } + _data.fps = fpsList.join(' '); + } + } else { + _data.fps = null; + } + } if ((evt['delay'] as String).isNotEmpty) _data.delay = evt['delay']; if ((evt['target_bitrate'] as String).isNotEmpty) { _data.targetBitrate = evt['target_bitrate']; @@ -1646,8 +1805,15 @@ class RecordingModel with ChangeNotifier { int? width = parent.target?.canvasModel.getDisplayWidth(); int? height = parent.target?.canvasModel.getDisplayHeight(); if (sessionId == null || width == null || height == null) return; - bind.sessionRecordScreen( - sessionId: sessionId, start: true, width: width, height: height); + final currentDisplay = parent.target?.ffiModel.pi.currentDisplay; + if (currentDisplay != kAllDisplayValue) { + bind.sessionRecordScreen( + sessionId: sessionId, + start: true, + display: currentDisplay!, + width: width, + height: height); + } } toggle() async { @@ -1658,10 +1824,20 @@ class RecordingModel with ChangeNotifier { notifyListeners(); await bind.sessionRecordStatus(sessionId: sessionId, status: _start); if (_start) { - bind.sessionRefresh(sessionId: sessionId); + final pi = parent.target?.ffiModel.pi; + if (pi != null) { + sessionRefreshVideo(sessionId, pi); + } } else { - bind.sessionRecordScreen( - sessionId: sessionId, start: false, width: 0, height: 0); + final currentDisplay = parent.target?.ffiModel.pi.currentDisplay; + if (currentDisplay != kAllDisplayValue) { + bind.sessionRecordScreen( + sessionId: sessionId, + start: false, + display: currentDisplay!, + width: 0, + height: 0); + } } } @@ -1670,8 +1846,15 @@ class RecordingModel with ChangeNotifier { final sessionId = parent.target?.sessionId; if (sessionId == null) return; _start = false; - bind.sessionRecordScreen( - sessionId: sessionId, start: false, width: 0, height: 0); + final currentDisplay = parent.target?.ffiModel.pi.currentDisplay; + if (currentDisplay != kAllDisplayValue) { + bind.sessionRecordScreen( + sessionId: sessionId, + start: false, + display: currentDisplay!, + width: 0, + height: 0); + } } } @@ -1686,9 +1869,7 @@ class ElevationModel with ChangeNotifier { _running = false; } - onPortableServiceRunning(Map evt) { - _running = evt['running'] == 'true'; - } + onPortableServiceRunning(bool running) => _running = running; } enum ConnType { defaultConn, fileTransfer, portForward, rdp } @@ -1751,14 +1932,18 @@ class FFI { } /// Start with the given [id]. Only transfer file if [isFileTransfer], only port forward if [isPortForward]. - void start(String id, - {bool isFileTransfer = false, - bool isPortForward = false, - bool isRdp = false, - String? switchUuid, - String? password, - bool? forceRelay, - int? tabWindowId}) { + void start( + String id, { + bool isFileTransfer = false, + bool isPortForward = false, + bool isRdp = false, + String? switchUuid, + String? password, + bool? forceRelay, + int? tabWindowId, + int? display, + List? displays, + }) { closed = false; auditNote = ''; if (isMobile) mobileReset(); @@ -1788,11 +1973,34 @@ class FFI { forceRelay: forceRelay ?? false, password: password ?? '', ); + } else if (display != null) { + if (displays == null) { + debugPrint( + 'Unreachable, failed to add existed session to $id, the displays is null while display is $display'); + return; + } + final addRes = bind.sessionAddExistedSync(id: id, sessionId: sessionId); + if (addRes != '') { + debugPrint( + 'Unreachable, failed to add existed session to $id, $addRes'); + return; + } + bind.sessionTryAddDisplay( + sessionId: sessionId, displays: Int32List.fromList(displays)); + ffiModel.pi.currentDisplay = display; } final stream = bind.sessionStart(sessionId: sessionId, id: id); final cb = ffiModel.startEventListener(sessionId, id); final useTextureRender = bind.mainUseTextureRender(); + // Force refresh displays. + // The controlled side may not refresh the image when the (peer,display) is already subscribed. + if (displays != null) { + for (final display in displays) { + bind.sessionRefresh(sessionId: sessionId, display: display); + } + } + final SimpleWrapper isToNewWindowNotified = SimpleWrapper(false); // Preserved for the rgba data. stream.listen((message) { @@ -1801,8 +2009,9 @@ class FFI { // Session is read to be moved to a new window. // Get the cached data and handle the cached data. Future.delayed(Duration.zero, () async { + final args = jsonEncode({'id': id, 'close': display == null}); final cachedData = await DesktopMultiWindow.invokeMethod( - tabWindowId, kWindowEventGetCachedSessionData, id); + tabWindowId, kWindowEventGetCachedSessionData, args); if (cachedData == null) { // unreachable debugPrint('Unreachable, the cached data is empty.'); @@ -1814,7 +2023,7 @@ class FFI { return; } await ffiModel.handleCachedPeerData(data, id); - await bind.sessionRefresh(sessionId: sessionId); + await sessionRefreshVideo(sessionId, ffiModel.pi); }); isToNewWindowNotified.value = true; } @@ -1836,18 +2045,19 @@ class FFI { await cb(event); } } else if (message is EventToUI_Rgba) { + final display = message.field0; if (useTextureRender) { onEvent2UIRgba(); } else { // Fetch the image buffer from rust codes. - final sz = platformFFI.getRgbaSize(sessionId); + final sz = platformFFI.getRgbaSize(sessionId, display); if (sz == 0) { return; } - final rgba = platformFFI.getRgba(sessionId, sz); + final rgba = platformFFI.getRgba(sessionId, display, sz); if (rgba != null) { onEvent2UIRgba(); - imageModel.onRgba(rgba); + imageModel.onRgba(display, rgba); } } } @@ -1979,22 +2189,73 @@ class Features { bool privacyMode = false; } +const kInvalidDisplayIndex = -1; + class PeerInfo with ChangeNotifier { String version = ''; String username = ''; String hostname = ''; String platform = ''; bool sasEnabled = false; + bool isSupportMultiUiSession = false; int currentDisplay = 0; + int primaryDisplay = kInvalidDisplayIndex; List displays = []; Features features = Features(); List resolutions = []; - Map platform_additions = {}; + Map platformDdditions = {}; + RxInt displaysCount = 0.obs; RxBool isSet = false.obs; - bool get is_wayland => platform_additions['is_wayland'] == true; - bool get is_headless => platform_additions['headless'] == true; + bool get isWayland => platformDdditions['is_wayland'] == true; + bool get isHeadless => platformDdditions['headless'] == true; + + bool get isSupportMultiDisplay => + isDesktop && isSupportMultiUiSession && isChooseDisplayToOpen; + + bool get cursorEmbedded => tryGetDisplay()?.cursorEmbedded ?? false; + + Display? tryGetDisplay() { + if (displays.isEmpty) { + return null; + } + if (currentDisplay == kAllDisplayValue) { + return displays[0]; + } else { + if (currentDisplay > 0 && currentDisplay < displays.length) { + return displays[currentDisplay]; + } else { + return displays[0]; + } + } + } + + Display? tryGetDisplayIfNotAllDisplay() { + if (displays.isEmpty) { + return null; + } + if (currentDisplay == kAllDisplayValue) { + return null; + } + if (currentDisplay > 0 && currentDisplay < displays.length) { + return displays[currentDisplay]; + } else { + return null; + } + } + + List getCurDisplays() { + if (currentDisplay == kAllDisplayValue) { + return displays; + } else { + if (currentDisplay >= 0 && currentDisplay < displays.length) { + return [displays[currentDisplay]]; + } else { + return []; + } + } + } } const canvasKey = 'canvas'; @@ -2038,8 +2299,8 @@ Future initializeCursorAndCanvas(FFI ffi) async { currentDisplay = p['currentDisplay']; } if (p == null || currentDisplay != ffi.ffiModel.pi.currentDisplay) { - ffi.cursorModel - .updateDisplayOrigin(ffi.ffiModel.display.x, ffi.ffiModel.display.y); + ffi.cursorModel.updateDisplayOrigin( + ffi.ffiModel.rect?.left ?? 0, ffi.ffiModel.rect?.top ?? 0); return; } double xCursor = p['xCursor']; @@ -2047,8 +2308,8 @@ Future initializeCursorAndCanvas(FFI ffi) async { double xCanvas = p['xCanvas']; double yCanvas = p['yCanvas']; double scale = p['scale']; - ffi.cursorModel.updateDisplayOriginWithCursor( - ffi.ffiModel.display.x, ffi.ffiModel.display.y, xCursor, yCursor); + ffi.cursorModel.updateDisplayOriginWithCursor(ffi.ffiModel.rect?.left ?? 0, + ffi.ffiModel.rect?.top ?? 0, xCursor, yCursor); ffi.canvasModel.update(xCanvas, yCanvas, scale); } diff --git a/flutter/lib/models/native_model.dart b/flutter/lib/models/native_model.dart index 4b458f962..cdf7f54b2 100644 --- a/flutter/lib/models/native_model.dart +++ b/flutter/lib/models/native_model.dart @@ -21,7 +21,8 @@ class RgbaFrame extends Struct { external Pointer data; } -typedef F3 = Pointer Function(Pointer); +typedef F3 = Pointer Function(Pointer, int); +typedef F3Dart = Pointer Function(Pointer, Int32); typedef HandleEvent = Future Function(Map evt); /// FFI wrapper around the native Rust core. @@ -80,12 +81,12 @@ class PlatformFFI { String translate(String name, String locale) => _ffiBind.translate(name: name, locale: locale); - Uint8List? getRgba(SessionID sessionId, int bufSize) { + Uint8List? getRgba(SessionID sessionId, int display, int bufSize) { if (_session_get_rgba == null) return null; final sessionIdStr = sessionId.toString(); var a = sessionIdStr.toNativeUtf8(); try { - final buffer = _session_get_rgba!(a); + final buffer = _session_get_rgba!(a, display); if (buffer == nullptr) { return null; } @@ -96,12 +97,11 @@ class PlatformFFI { } } - int getRgbaSize(SessionID sessionId) => - _ffiBind.sessionGetRgbaSize(sessionId: sessionId); - void nextRgba(SessionID sessionId) => - _ffiBind.sessionNextRgba(sessionId: sessionId); - void registerTexture(SessionID sessionId, int ptr) => - _ffiBind.sessionRegisterTexture(sessionId: sessionId, ptr: ptr); + int getRgbaSize(SessionID sessionId, int display) => + _ffiBind.sessionGetRgbaSize(sessionId: sessionId, display: display); + void nextRgba(SessionID sessionId, int display) => _ffiBind.sessionNextRgba(sessionId: sessionId, display: display); + void registerTexture(SessionID sessionId, int display, int ptr) => + _ffiBind.sessionRegisterTexture(sessionId: sessionId, display: display, ptr: ptr); /// Init the FFI class, loads the native Rust core library. Future init(String appType) async { @@ -117,7 +117,7 @@ class PlatformFFI { : DynamicLibrary.process(); debugPrint('initializing FFI $_appType'); try { - _session_get_rgba = dylib.lookupFunction("session_get_rgba"); + _session_get_rgba = dylib.lookupFunction("session_get_rgba"); try { // SYSTEM user failed _dir = (await getApplicationDocumentsDirectory()).path; diff --git a/flutter/lib/models/state_model.dart b/flutter/lib/models/state_model.dart index e36bef924..2403a794c 100644 --- a/flutter/lib/models/state_model.dart +++ b/flutter/lib/models/state_model.dart @@ -18,7 +18,6 @@ class StateGlobal { final RxDouble _resizeEdgeSize = RxDouble(kWindowEdgeSize); final RxDouble _windowBorderWidth = RxDouble(kWindowBorderWidth); final RxBool showRemoteToolBar = false.obs; - final RxInt displaysCount = 0.obs; final svcStatus = SvcStatus.notReady.obs; // Only used for macOS bool closeOnFullscreen = false; diff --git a/flutter/lib/utils/multi_window_manager.dart b/flutter/lib/utils/multi_window_manager.dart index a8be78c74..8b80b79bf 100644 --- a/flutter/lib/utils/multi_window_manager.dart +++ b/flutter/lib/utils/multi_window_manager.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'dart:io'; import 'package:desktop_multi_window/desktop_multi_window.dart'; +import 'package:flutter_hbb/models/platform_model.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -67,6 +68,44 @@ class RustDeskMultiWindowManager { ); } + // This function must be called in the main window thread. + // Because the _remoteDesktopWindows is managed in that thread. + openMonitorSession( + int windowId, String peerId, int display, int displayCount) async { + if (_remoteDesktopWindows.length > 1) { + for (final windowId in _remoteDesktopWindows) { + if (await DesktopMultiWindow.invokeMethod( + windowId, + kWindowEventActiveDisplaySession, + jsonEncode({ + 'id': peerId, + 'display': display, + }))) { + return; + } + } + } + + final displays = display == kAllDisplayValue + ? List.generate(displayCount, (index) => index) + : [display]; + var params = { + 'type': WindowType.RemoteDesktop.index, + 'id': peerId, + 'tab_window_id': windowId, + 'display': display, + 'displays': displays, + }; + await _newSession( + false, + WindowType.RemoteDesktop, + kWindowEventNewRemoteDesktop, + peerId, + _remoteDesktopWindows, + jsonEncode(params), + ); + } + Future newSessionWindow( WindowType type, String remoteId, String msg, List windows) async { final windowController = await DesktopMultiWindow.createWindow(msg); @@ -148,11 +187,21 @@ class RustDeskMultiWindowManager { bool openInTabs = type != WindowType.RemoteDesktop || mainGetLocalBoolOptionSync(kOptionOpenNewConnInTabs); - if (windows.length > 1 || !openInTabs) { - for (final windowId in windows) { - if (await DesktopMultiWindow.invokeMethod( - windowId, kWindowEventActiveSession, remoteId)) { - return MultiWindowCallResult(windowId, null); + if (kOpenSamePeerInNewWindow) { + // Open in new window if the peer is already connected. + // No need to care about the previous session type. + if (type == WindowType.RemoteDesktop && + await bind.sessionGetFlutterOptionByPeerId(id: remoteId, k: '') != + null) { + openInTabs = false; + } + } else { + if (windows.length > 1 || !openInTabs) { + for (final windowId in windows) { + if (await DesktopMultiWindow.invokeMethod( + windowId, kWindowEventActiveSession, remoteId)) { + return MultiWindowCallResult(windowId, null); + } } } } diff --git a/libs/clipboard/src/lib.rs b/libs/clipboard/src/lib.rs index dc6892d61..7cba12d1a 100644 --- a/libs/clipboard/src/lib.rs +++ b/libs/clipboard/src/lib.rs @@ -5,7 +5,7 @@ use hbb_common::{ mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}, Mutex as TokioMutex, }, - ResultType, SessionID, + ResultType, }; use serde_derive::{Deserialize, Serialize}; use std::{ @@ -61,7 +61,7 @@ pub enum ClipboardFile { } struct MsgChannel { - session_uuid: SessionID, + peer_id: String, conn_id: i32, sender: UnboundedSender, receiver: Arc>>, @@ -90,12 +90,12 @@ impl ClipboardFile { } } -pub fn get_client_conn_id(session_uuid: &SessionID) -> Option { +pub fn get_client_conn_id(peer_id: &str) -> Option { VEC_MSG_CHANNEL .read() .unwrap() .iter() - .find(|x| x.session_uuid == session_uuid.to_owned()) + .find(|x| x.peer_id == peer_id) .map(|x| x.conn_id) } @@ -106,13 +106,10 @@ fn get_conn_id() -> i32 { } pub fn get_rx_cliprdr_client( - session_uuid: &SessionID, + peer_id: &str, ) -> (i32, Arc>>) { let mut lock = VEC_MSG_CHANNEL.write().unwrap(); - match lock - .iter() - .find(|x| x.session_uuid == session_uuid.to_owned()) - { + match lock.iter().find(|x| x.peer_id == peer_id) { Some(msg_channel) => (msg_channel.conn_id, msg_channel.receiver.clone()), None => { let (sender, receiver) = unbounded_channel(); @@ -120,7 +117,7 @@ pub fn get_rx_cliprdr_client( let receiver2 = receiver.clone(); let conn_id = get_conn_id(); let msg_channel = MsgChannel { - session_uuid: session_uuid.to_owned(), + peer_id: peer_id.to_owned(), conn_id, sender, receiver, @@ -140,7 +137,7 @@ pub fn get_rx_cliprdr_server(conn_id: i32) -> Arc ResultType { + fn encode_to_message(&mut self, frame: &[u8], ms: i64) -> ResultType { let mut frames = Vec::new(); for ref frame in self .encode(ms, frame, STRIDE_ALIGN) @@ -249,7 +249,7 @@ impl EncoderApi for AomEncoder { frames.push(Self::create_frame(frame)); } if frames.len() > 0 { - Ok(Self::create_msg(frames)) + Ok(Self::create_video_frame(frames)) } else { Err(anyhow!("no valid frame")) } @@ -311,16 +311,14 @@ impl AomEncoder { } #[inline] - pub fn create_msg(frames: Vec) -> Message { - let mut msg_out = Message::new(); + pub fn create_video_frame(frames: Vec) -> VideoFrame { let mut vf = VideoFrame::new(); let av1s = EncodedVideoFrames { frames: frames.into(), ..Default::default() }; vf.set_av1s(av1s); - msg_out.set_video_frame(vf); - msg_out + vf } #[inline] diff --git a/libs/scrap/src/common/codec.rs b/libs/scrap/src/common/codec.rs index cbd433c39..0a3eca248 100644 --- a/libs/scrap/src/common/codec.rs +++ b/libs/scrap/src/common/codec.rs @@ -23,8 +23,8 @@ use hbb_common::{ config::PeerConfig, log, message_proto::{ - supported_decoding::PreferCodec, video_frame, EncodedVideoFrames, Message, - SupportedDecoding, SupportedEncoding, + supported_decoding::PreferCodec, video_frame, EncodedVideoFrames, + SupportedDecoding, SupportedEncoding, VideoFrame, }, sysinfo::{System, SystemExt}, tokio::time::Instant, @@ -60,7 +60,7 @@ pub trait EncoderApi { where Self: Sized; - fn encode_to_message(&mut self, frame: &[u8], ms: i64) -> ResultType; + fn encode_to_message(&mut self, frame: &[u8], ms: i64) -> ResultType; fn use_yuv(&self) -> bool; diff --git a/libs/scrap/src/common/hwcodec.rs b/libs/scrap/src/common/hwcodec.rs index c1fbdfa6e..333f85b98 100644 --- a/libs/scrap/src/common/hwcodec.rs +++ b/libs/scrap/src/common/hwcodec.rs @@ -8,7 +8,7 @@ use hbb_common::{ bytes::Bytes, config::HwCodecConfig, log, - message_proto::{EncodedVideoFrame, EncodedVideoFrames, Message, VideoFrame}, + message_proto::{EncodedVideoFrame, EncodedVideoFrames, VideoFrame}, ResultType, }; use hwcodec::{ @@ -92,12 +92,7 @@ impl EncoderApi for HwEncoder { } } - fn encode_to_message( - &mut self, - frame: &[u8], - _ms: i64, - ) -> ResultType { - let mut msg_out = Message::new(); + fn encode_to_message(&mut self, frame: &[u8], _ms: i64) -> ResultType { let mut vf = VideoFrame::new(); let mut frames = Vec::new(); for frame in self.encode(frame).with_context(|| "Failed to encode")? { @@ -117,8 +112,7 @@ impl EncoderApi for HwEncoder { DataFormat::H264 => vf.set_h264s(frames), DataFormat::H265 => vf.set_h265s(frames), } - msg_out.set_video_frame(vf); - Ok(msg_out) + Ok(vf) } else { Err(anyhow!("no valid frame")) } diff --git a/libs/scrap/src/common/vpxcodec.rs b/libs/scrap/src/common/vpxcodec.rs index 1bdde0ca6..edf620465 100644 --- a/libs/scrap/src/common/vpxcodec.rs +++ b/libs/scrap/src/common/vpxcodec.rs @@ -4,7 +4,7 @@ use hbb_common::anyhow::{anyhow, Context}; use hbb_common::log; -use hbb_common::message_proto::{EncodedVideoFrame, EncodedVideoFrames, Message, VideoFrame}; +use hbb_common::message_proto::{EncodedVideoFrame, EncodedVideoFrames, VideoFrame}; use hbb_common::ResultType; use crate::codec::{base_bitrate, codec_thread_num, EncoderApi, Quality}; @@ -172,7 +172,7 @@ impl EncoderApi for VpxEncoder { } } - fn encode_to_message(&mut self, frame: &[u8], ms: i64) -> ResultType { + fn encode_to_message(&mut self, frame: &[u8], ms: i64) -> ResultType { let mut frames = Vec::new(); for ref frame in self .encode(ms, frame, STRIDE_ALIGN) @@ -186,7 +186,7 @@ impl EncoderApi for VpxEncoder { // to-do: flush periodically, e.g. 1 second if frames.len() > 0 { - Ok(VpxEncoder::create_msg(self.id, frames)) + Ok(VpxEncoder::create_video_frame(self.id, frames)) } else { Err(anyhow!("no valid frame")) } @@ -266,8 +266,10 @@ impl VpxEncoder { } #[inline] - pub fn create_msg(codec_id: VpxVideoCodecId, frames: Vec) -> Message { - let mut msg_out = Message::new(); + pub fn create_video_frame( + codec_id: VpxVideoCodecId, + frames: Vec, + ) -> VideoFrame { let mut vf = VideoFrame::new(); let vpxs = EncodedVideoFrames { frames: frames.into(), @@ -277,8 +279,7 @@ impl VpxEncoder { VpxVideoCodecId::VP8 => vf.set_vp8s(vpxs), VpxVideoCodecId::VP9 => vf.set_vp9s(vpxs), } - msg_out.set_video_frame(vf); - msg_out + vf } #[inline] diff --git a/src/client.rs b/src/client.rs index 4ecb0a0fb..f7c6c6d96 100644 --- a/src/client.rs +++ b/src/client.rs @@ -3,10 +3,7 @@ use std::{ net::SocketAddr, ops::Deref, str::FromStr, - sync::{ - atomic::{AtomicUsize, Ordering}, - mpsc, Arc, Mutex, RwLock, - }, + sync::{mpsc, Arc, Mutex, RwLock}, }; pub use async_trait::async_trait; @@ -60,6 +57,7 @@ use scrap::{ use crate::{ common::input::{MOUSE_BUTTON_LEFT, MOUSE_BUTTON_RIGHT, MOUSE_TYPE_DOWN, MOUSE_TYPE_UP}, is_keyboard_mode_supported, + ui_session_interface::{InvokeUiSession, Session}, }; #[cfg(not(feature = "flutter"))] @@ -675,9 +673,12 @@ impl Client { } #[cfg(not(any(target_os = "android", target_os = "ios")))] - fn try_stop_clipboard(_self_uuid: &uuid::Uuid) { + fn try_stop_clipboard(_self_id: &str) { #[cfg(feature = "flutter")] - if crate::flutter::sessions::other_sessions_running(_self_uuid) { + if crate::flutter::sessions::other_sessions_running( + _self_id.to_string(), + ConnType::DEFAULT_CONN, + ) { return; } TEXT_CLIPBOARD_STATE.lock().unwrap().running = false; @@ -1523,6 +1524,15 @@ impl LoginConfigHandler { msg_out } + /// Create a [`Message`] for refreshing video. + pub fn refresh_display(display: usize) -> Message { + let mut misc = Misc::new(); + misc.set_refresh_video_display(display as _); + let mut msg_out = Message::new(); + msg_out.set_misc(misc); + msg_out + } + /// Create a [`Message`] for saving custom image quality. /// /// # Arguments @@ -1789,89 +1799,158 @@ impl LoginConfigHandler { /// Media data. pub enum MediaData { - VideoQueue, + VideoQueue(usize), VideoFrame(Box), AudioFrame(Box), AudioFormat(AudioFormat), - Reset, - RecordScreen(bool, i32, i32, String), + Reset(usize), + RecordScreen(bool, usize, i32, i32, String), } pub type MediaSender = mpsc::Sender; +struct VideoHandlerController { + handler: VideoHandler, + count: u128, + duration: std::time::Duration, + skip_beginning: u32, +} + /// Start video and audio thread. /// Return two [`MediaSender`], they should be given to the media producer. /// /// # Arguments /// /// * `video_callback` - The callback for video frame. Being called when a video frame is ready. -pub fn start_video_audio_threads( +pub fn start_video_audio_threads( + session: Session, video_callback: F, ) -> ( MediaSender, MediaSender, - Arc>, - Arc, + Arc>>>, + Arc>>, ) where - F: 'static + FnMut(&mut scrap::ImageRgb) + Send, + F: 'static + FnMut(usize, &mut scrap::ImageRgb) + Send, + T: InvokeUiSession, { let (video_sender, video_receiver) = mpsc::channel::(); - let video_queue = Arc::new(ArrayQueue::::new(VIDEO_QUEUE_SIZE)); - let video_queue_cloned = video_queue.clone(); + let video_queue_map: Arc>>> = Default::default(); + let video_queue_map_cloned = video_queue_map.clone(); let mut video_callback = video_callback; - let mut duration = std::time::Duration::ZERO; - let mut count = 0; - let fps = Arc::new(AtomicUsize::new(0)); - let decode_fps = fps.clone(); - let mut skip_beginning = 0; + let fps_map = Arc::new(RwLock::new(HashMap::new())); + let decode_fps_map = fps_map.clone(); std::thread::spawn(move || { #[cfg(windows)] sync_cpu_usage(); - let mut video_handler = VideoHandler::new(); + let mut handler_controller_map = Vec::new(); + // let mut count = Vec::new(); + // let mut duration = std::time::Duration::ZERO; + // let mut skip_beginning = Vec::new(); loop { if let Ok(data) = video_receiver.recv() { match data { - MediaData::VideoFrame(_) | MediaData::VideoQueue => { - let vf = if let MediaData::VideoFrame(vf) = data { - *vf - } else { - if let Some(vf) = video_queue.pop() { - vf - } else { + MediaData::VideoFrame(_) | MediaData::VideoQueue(_) => { + let vf = match data { + MediaData::VideoFrame(vf) => *vf, + MediaData::VideoQueue(display) => { + if let Some(video_queue) = + video_queue_map.read().unwrap().get(&display) + { + if let Some(vf) = video_queue.pop() { + vf + } else { + continue; + } + } else { + continue; + } + } + _ => { + // unreachable!(); continue; } }; + let display = vf.display as usize; let start = std::time::Instant::now(); - if let Ok(true) = video_handler.handle_frame(vf) { - video_callback(&mut video_handler.rgb); - // fps calculation - // The first frame will be very slow - if skip_beginning < 5 { - skip_beginning += 1; - continue; + if handler_controller_map.len() <= display { + for _i in handler_controller_map.len()..=display { + handler_controller_map.push(VideoHandlerController { + handler: VideoHandler::new(), + count: 0, + duration: std::time::Duration::ZERO, + skip_beginning: 0, + }); } - duration += start.elapsed(); - count += 1; - if count % 10 == 0 { - fps.store( - (count * 1000 / duration.as_millis()) as usize, - Ordering::Relaxed, - ); - } - // Clear to get real-time fps - if count > 150 { - count = 0; - duration = Duration::ZERO; + } + if let Some(handler_controller) = handler_controller_map.get_mut(display) { + match handler_controller.handler.handle_frame(vf) { + Ok(true) => { + video_callback(display, &mut handler_controller.handler.rgb); + + // fps calculation + // The first frame will be very slow + if handler_controller.skip_beginning < 5 { + handler_controller.skip_beginning += 1; + continue; + } + + handler_controller.duration += start.elapsed(); + handler_controller.count += 1; + if handler_controller.count % 10 == 0 { + fps_map.write().unwrap().insert( + display, + (handler_controller.count * 1000 + / handler_controller.duration.as_millis()) + as usize, + ); + } + // Clear to get real-time fps + if handler_controller.count > 150 { + handler_controller.count = 0; + handler_controller.duration = Duration::ZERO; + } + } + Err(e) => { + // This is a simple workaround. + // + // I only see the following error: + // FailedCall("errcode=1 scrap::common::vpxcodec:libs\\scrap\\src\\common\\vpxcodec.rs:433:9") + // When switching from all displays to one display, the error occurs. + // eg: + // 1. Connect to a device with two displays (A and B). + // 2. Switch to display A. The error occurs. + // 3. If the error does not occur. Switch from A to display B. The error occurs. + // + // to-do: fix the error + log::error!("handle video frame error, {}", e); + session.refresh_video(display); + } + _ => {} } } } - MediaData::Reset => { - video_handler.reset(); + MediaData::Reset(display) => { + if let Some(handler_controler) = handler_controller_map.get_mut(display) { + handler_controler.handler.reset(); + } } - MediaData::RecordScreen(start, w, h, id) => { - video_handler.record_screen(start, w, h, id) + MediaData::RecordScreen(start, display, w, h, id) => { + if handler_controller_map.len() == 1 { + // Compatible with the sciter version(single ui session). + // For the sciter version, there're no multi-ui-sessions for one connection. + // The display is always 0, video_handler_controllers.len() is always 1. So we use the first video handler. + handler_controller_map[0] + .handler + .record_screen(start, w, h, id); + } else { + if let Some(handler_controler) = handler_controller_map.get_mut(display) + { + handler_controler.handler.record_screen(start, w, h, id); + } + } } _ => {} } @@ -1882,7 +1961,12 @@ where log::info!("Video decoder loop exits"); }); let audio_sender = start_audio_thread(); - return (video_sender, audio_sender, video_queue_cloned, decode_fps); + return ( + video_sender, + audio_sender, + video_queue_map_cloned, + decode_fps_map, + ); } /// Start an audio thread @@ -2500,7 +2584,7 @@ pub enum Data { SetConfirmOverrideFile((i32, i32, bool, bool, bool)), AddJob((i32, String, String, i32, bool, bool)), ResumeJob((i32, bool)), - RecordScreen(bool, i32, i32, String), + RecordScreen(bool, usize, i32, i32, String), ElevateDirect, ElevateWithLogon(String, String), NewVoiceCall, diff --git a/src/client/helper.rs b/src/client/helper.rs index 61844d908..09a8d5d24 100644 --- a/src/client/helper.rs +++ b/src/client/helper.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use hbb_common::{ get_time, message_proto::{Message, VoiceCallRequest, VoiceCallResponse}, @@ -7,7 +8,7 @@ use scrap::CodecFormat; #[derive(Debug, Default)] pub struct QualityStatus { pub speed: Option, - pub fps: Option, + pub fps: HashMap, pub delay: Option, pub target_bitrate: Option, pub codec_format: Option, diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index 28edf0e52..151bee0cc 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -1,33 +1,41 @@ -use std::collections::HashMap; -use std::num::NonZeroI64; -use std::sync::{ - atomic::{AtomicUsize, Ordering}, - Arc, +use std::{ + collections::HashMap, + num::NonZeroI64, + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, RwLock, + }, }; #[cfg(windows)] use clipboard::{cliprdr::CliprdrClientContext, empty_clipboard, ContextSend}; use crossbeam_queue::ArrayQueue; -use hbb_common::config::{PeerConfig, TransferSerde}; -use hbb_common::fs::{ - can_enable_overwrite_detection, get_job, get_string, new_send_confirm, DigestCheckResult, - RemoveJobMeta, -}; -use hbb_common::message_proto::permission_info::Permission; -use hbb_common::protobuf::Message as _; -use hbb_common::rendezvous_proto::ConnType; #[cfg(not(any(target_os = "android", target_os = "ios")))] use hbb_common::sleep; #[cfg(not(target_os = "ios"))] use hbb_common::tokio::sync::mpsc::error::TryRecvError; #[cfg(windows)] use hbb_common::tokio::sync::Mutex as TokioMutex; -use hbb_common::tokio::{ - self, - sync::mpsc, - time::{self, Duration, Instant, Interval}, +use hbb_common::{ + allow_err, + config::{PeerConfig, TransferSerde}, + fs, + fs::{ + can_enable_overwrite_detection, get_job, get_string, new_send_confirm, DigestCheckResult, + RemoveJobMeta, + }, + get_time, log, + message_proto::permission_info::Permission, + message_proto::*, + protobuf::Message as _, + rendezvous_proto::ConnType, + tokio::{ + self, + sync::mpsc, + time::{self, Duration, Instant, Interval}, + }, + Stream, }; -use hbb_common::{allow_err, fs, get_time, log, message_proto::*, Stream}; use scrap::CodecFormat; use crate::client::{ @@ -43,7 +51,7 @@ use crate::{client::Data, client::Interface}; pub struct Remote { handler: Session, - video_queue: Arc>, + video_queue_map: Arc>>>, video_sender: MediaSender, audio_sender: MediaSender, receiver: mpsc::UnboundedReceiver, @@ -61,27 +69,27 @@ pub struct Remote { #[cfg(windows)] client_conn_id: i32, // used for file clipboard data_count: Arc, - frame_count: Arc, + frame_count_map: Arc>>, video_format: CodecFormat, elevation_requested: bool, - fps_control: FpsControl, - decode_fps: Arc, + fps_control_map: HashMap, + decode_fps_map: Arc>>, } impl Remote { pub fn new( handler: Session, - video_queue: Arc>, + video_queue: Arc>>>, video_sender: MediaSender, audio_sender: MediaSender, receiver: mpsc::UnboundedReceiver, sender: mpsc::UnboundedSender, - frame_count: Arc, - decode_fps: Arc, + frame_count_map: Arc>>, + decode_fps: Arc>>, ) -> Self { Self { handler, - video_queue, + video_queue_map: video_queue, video_sender, audio_sender, receiver, @@ -96,13 +104,13 @@ impl Remote { #[cfg(windows)] client_conn_id: 0, data_count: Arc::new(AtomicUsize::new(0)), - frame_count, + frame_count_map, video_format: CodecFormat::Unknown, stop_voice_call_sender: None, voice_call_request_timestamp: None, elevation_requested: false, - fps_control: Default::default(), - decode_fps, + fps_control_map: Default::default(), + decode_fps_map: decode_fps, } } @@ -152,7 +160,7 @@ impl Remote { || self.handler.is_rdp(); if !is_conn_not_default { (self.client_conn_id, rx_clip_client_lock) = - clipboard::get_rx_cliprdr_client(&self.handler.session_id); + clipboard::get_rx_cliprdr_client(&self.handler.id); }; } #[cfg(windows)] @@ -229,12 +237,18 @@ impl Remote { let mut speed = self.data_count.swap(0, Ordering::Relaxed); speed = speed * 1000 / elapsed as usize; let speed = format!("{:.2}kB/s", speed as f32 / 1024 as f32); - let mut fps = self.frame_count.swap(0, Ordering::Relaxed) as _; - // Correcting the inaccuracy of status_timer - fps = fps * 1000 / elapsed as i32; + + let mut frame_count_map_write = self.frame_count_map.write().unwrap(); + let frame_count_map = frame_count_map_write.clone(); + frame_count_map_write.values_mut().for_each(|v| *v = 0); + drop(frame_count_map_write); + let fps = frame_count_map.iter().map(|(k, v)| { + // Correcting the inaccuracy of status_timer + (k.clone(), (*v as i32) * 1000 / elapsed as i32) + }).collect::>(); self.handler.update_quality_status(QualityStatus { - speed:Some(speed), - fps:Some(fps), + speed: Some(speed), + fps, ..Default::default() }); } @@ -260,7 +274,7 @@ impl Remote { #[cfg(not(any(target_os = "android", target_os = "ios")))] if _set_disconnected_ok { - Client::try_stop_clipboard(&self.handler.session_id); + Client::try_stop_clipboard(&self.handler.id); } #[cfg(windows)] @@ -760,10 +774,10 @@ impl Remote { } } } - Data::RecordScreen(start, w, h, id) => { + Data::RecordScreen(start, display, w, h, id) => { let _ = self .video_sender - .send(MediaData::RecordScreen(start, w, h, id)); + .send(MediaData::RecordScreen(start, display, w, h, id)); } Data::ElevateDirect => { let mut request = ElevationRequest::new(); @@ -904,89 +918,100 @@ impl Remote { None => false, } } + #[inline] fn fps_control(&mut self, direct: bool) { - let len = self.video_queue.len(); - let ctl = &mut self.fps_control; - // Current full speed decoding fps - let decode_fps = self.decode_fps.load(std::sync::atomic::Ordering::Relaxed); - if decode_fps == 0 { - return; - } - let limited_fps = if direct { - decode_fps * 9 / 10 // 30 got 27 - } else { - decode_fps * 4 / 5 // 30 got 24 - }; - // send full speed fps - let version = self.handler.lc.read().unwrap().version; - let max_encode_speed = 144 * 10 / 9; - if version >= hbb_common::get_version_number("1.2.1") - && (ctl.last_full_speed_fps.is_none() // First time - || ((ctl.last_full_speed_fps.unwrap_or_default() - decode_fps as i32).abs() >= 5 // diff 5 - && !(decode_fps > max_encode_speed // already exceed max encoding speed - && ctl.last_full_speed_fps.unwrap_or_default() > max_encode_speed as i32))) - { - let mut misc = Misc::new(); - misc.set_full_speed_fps(decode_fps as _); - let mut msg = Message::new(); - msg.set_misc(misc); - self.sender.send(Data::Message(msg)).ok(); - ctl.last_full_speed_fps = Some(decode_fps as _); - } - // decrease judgement - let debounce = if decode_fps > 10 { decode_fps / 2 } else { 5 }; // 500ms - let should_decrease = len >= debounce // exceed debounce - && len > ctl.last_queue_size + 5 // still caching - && !ctl.last_custom_fps.unwrap_or(i32::MAX) < limited_fps as i32; // NOT already set a smaller one + let decode_fps_read = self.decode_fps_map.read().unwrap(); + for (display, decode_fps) in decode_fps_read.iter() { + let video_queue_map_read = self.video_queue_map.read().unwrap(); + let Some(video_queue) = video_queue_map_read.get(display) else { + continue; + }; - // increase judgement - if len <= 1 { - ctl.idle_counter += 1; - } else { - ctl.idle_counter = 0; - } - let mut should_increase = false; - if let Some(last_custom_fps) = ctl.last_custom_fps { - // ever set - if last_custom_fps + 5 < limited_fps as i32 && ctl.idle_counter > 3 { - // limited_fps is 5 larger than last set, and idle time is more than 3 seconds - should_increase = true; + if !self.fps_control_map.contains_key(display) { + self.fps_control_map.insert(*display, FpsControl::default()); } - } - if should_decrease || should_increase { - // limited_fps to ensure decoding is faster than encoding - let mut custom_fps = limited_fps as i32; - if custom_fps < 1 { - custom_fps = 1; - } - // send custom fps - let mut misc = Misc::new(); - if version > hbb_common::get_version_number("1.2.1") { - // avoid confusion with custom image quality fps - misc.set_auto_adjust_fps(custom_fps as _); + let Some(ctl) = self.fps_control_map.get_mut(display) else { + return; + }; + + let len = video_queue.len(); + let decode_fps = *decode_fps; + let limited_fps = if direct { + decode_fps * 9 / 10 // 30 got 27 } else { - misc.set_option(OptionMessage { - custom_fps, - ..Default::default() - }); + decode_fps * 4 / 5 // 30 got 24 + }; + // send full speed fps + let version = self.handler.lc.read().unwrap().version; + let max_encode_speed = 144 * 10 / 9; + if version >= hbb_common::get_version_number("1.2.1") + && (ctl.last_full_speed_fps.is_none() // First time + || ((ctl.last_full_speed_fps.unwrap_or_default() - decode_fps as i32).abs() >= 5 // diff 5 + && !(decode_fps > max_encode_speed // already exceed max encoding speed + && ctl.last_full_speed_fps.unwrap_or_default() > max_encode_speed as i32))) + { + let mut misc = Misc::new(); + misc.set_full_speed_fps(decode_fps as _); + let mut msg = Message::new(); + msg.set_misc(misc); + self.sender.send(Data::Message(msg)).ok(); + ctl.last_full_speed_fps = Some(decode_fps as _); + } + // decrease judgement + let debounce = if decode_fps > 10 { decode_fps / 2 } else { 5 }; // 500ms + let should_decrease = len >= debounce // exceed debounce + && len > ctl.last_queue_size + 5 // still caching + && !ctl.last_custom_fps.unwrap_or(i32::MAX) < limited_fps as i32; // NOT already set a smaller one + + // increase judgement + if len <= 1 { + ctl.idle_counter += 1; + } else { + ctl.idle_counter = 0; + } + let mut should_increase = false; + if let Some(last_custom_fps) = ctl.last_custom_fps { + // ever set + if last_custom_fps + 5 < limited_fps as i32 && ctl.idle_counter > 3 { + // limited_fps is 5 larger than last set, and idle time is more than 3 seconds + should_increase = true; + } + } + if should_decrease || should_increase { + // limited_fps to ensure decoding is faster than encoding + let mut custom_fps = limited_fps as i32; + if custom_fps < 1 { + custom_fps = 1; + } + // send custom fps + let mut misc = Misc::new(); + if version > hbb_common::get_version_number("1.2.1") { + // avoid confusion with custom image quality fps + misc.set_auto_adjust_fps(custom_fps as _); + } else { + misc.set_option(OptionMessage { + custom_fps, + ..Default::default() + }); + } + let mut msg = Message::new(); + msg.set_misc(misc); + self.sender.send(Data::Message(msg)).ok(); + ctl.last_queue_size = len; + ctl.last_custom_fps = Some(custom_fps); + } + // send refresh + if ctl.refresh_times < 10 // enough + && (len > video_queue.capacity() / 2 + && (ctl.refresh_times == 0 || ctl.last_refresh_instant.elapsed().as_secs() > 30)) + { + // Refresh causes client set_display, left frames cause flickering. + while let Some(_) = video_queue.pop() {} + self.handler.refresh_video(*display); + ctl.refresh_times += 1; + ctl.last_refresh_instant = Instant::now(); } - let mut msg = Message::new(); - msg.set_misc(misc); - self.sender.send(Data::Message(msg)).ok(); - ctl.last_queue_size = len; - ctl.last_custom_fps = Some(custom_fps); - } - // send refresh - if ctl.refresh_times < 10 // enough - && (len > self.video_queue.capacity() / 2 - && (ctl.refresh_times == 0 || ctl.last_refresh_instant.elapsed().as_secs() > 30)) - { - // Refresh causes client set_display, left frames cause flickering. - while let Some(_) = self.video_queue.pop() {} - self.handler.refresh_video(); - ctl.refresh_times += 1; - ctl.last_refresh_instant = Instant::now(); } } @@ -1008,14 +1033,27 @@ impl Remote { ..Default::default() }) }; + + let display = vf.display as usize; + let mut video_queue_write = self.video_queue_map.write().unwrap(); + if !video_queue_write.contains_key(&display) { + video_queue_write.insert( + display, + ArrayQueue::::new(crate::client::VIDEO_QUEUE_SIZE), + ); + } if Self::contains_key_frame(&vf) { - while let Some(_) = self.video_queue.pop() {} + if let Some(video_queue) = video_queue_write.get_mut(&display) { + while let Some(_) = video_queue.pop() {} + } self.video_sender .send(MediaData::VideoFrame(Box::new(vf))) .ok(); } else { - self.video_queue.force_push(vf); - self.video_sender.send(MediaData::VideoQueue).ok(); + if let Some(video_queue) = video_queue_write.get_mut(&display) { + video_queue.force_push(vf); + } + self.video_sender.send(MediaData::VideoQueue(display)).ok(); } } Some(message::Union::Hash(hash)) => { @@ -1297,7 +1335,9 @@ impl Remote { } Some(misc::Union::SwitchDisplay(s)) => { self.handler.handle_peer_switch_display(&s); - self.video_sender.send(MediaData::Reset).ok(); + self.video_sender + .send(MediaData::Reset(s.display as _)) + .ok(); if s.width > 0 && s.height > 0 { self.handler.set_display( s.x, @@ -1674,7 +1714,7 @@ impl Remote { #[cfg(feature = "flutter")] if let Some(hbb_common::message_proto::cliprdr::Union::FormatList(_)) = &clip.union { if self.client_conn_id - != clipboard::get_client_conn_id(&crate::flutter::get_cur_session_id()).unwrap_or(0) + != clipboard::get_client_conn_id(&crate::flutter::get_cur_peer_id()).unwrap_or(0) { return; } diff --git a/src/common.rs b/src/common.rs index e5599389c..3b2f5ca3e 100644 --- a/src/common.rs +++ b/src/common.rs @@ -54,6 +54,8 @@ pub const PLATFORM_LINUX: &str = "Linux"; pub const PLATFORM_MACOS: &str = "Mac OS"; pub const PLATFORM_ANDROID: &str = "Android"; +const MIN_VER_MULTI_UI_SESSION: &str = "1.2.4"; + pub mod input { pub const MOUSE_TYPE_MOVE: i32 = 0; pub const MOUSE_TYPE_DOWN: i32 = 1; @@ -120,6 +122,16 @@ pub fn set_server_running(b: bool) { *SERVER_RUNNING.write().unwrap() = b; } +#[inline] +pub fn is_support_multi_ui_session(ver: &str) -> bool { + is_support_multi_ui_session_num(hbb_common::get_version_number(ver)) +} + +#[inline] +pub fn is_support_multi_ui_session_num(ver: i64) -> bool { + ver >= hbb_common::get_version_number(MIN_VER_MULTI_UI_SESSION) +} + // is server process, with "--server" args #[inline] pub fn is_server() -> bool { @@ -780,13 +792,17 @@ pub fn get_sysinfo() -> serde_json::Value { } let hostname = hostname(); // sys.hostname() return localhost on android in my test use serde_json::json; - let mut out = json!({ + #[cfg(any(target_os = "android", target_os = "ios"))] + let out; + #[cfg(not(any(target_os = "android", target_os = "ios")))] + let mut out; + out = json!({ "cpu": format!("{cpu}{num_cpus}/{num_pcpus} cores"), "memory": format!("{memory}GB"), "os": os, "hostname": hostname, }); - #[cfg(not(any(target_os = "android", target_os = "ios")))] + #[cfg(not(any(target_os = "android", target_os = "ios")))] { out["username"] = json!(crate::platform::get_active_username()); } diff --git a/src/flutter.rs b/src/flutter.rs index c9863078a..237775003 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -18,8 +18,6 @@ use hbb_common::{ }; use serde_json::json; -#[cfg(not(feature = "flutter_texture_render"))] -use std::sync::atomic::{AtomicBool, Ordering}; use std::{ collections::HashMap, ffi::CString, @@ -150,25 +148,40 @@ pub unsafe extern "C" fn free_c_args(ptr: *mut *mut c_char, len: c_int) { // Afterwards the vector will be dropped and thus freed. } +#[derive(Default)] +struct SessionHandler { + event_stream: Option>, + #[cfg(feature = "flutter_texture_render")] + notify_rendered: bool, + #[cfg(feature = "flutter_texture_render")] + renderer: VideoRenderer, +} + #[cfg(feature = "flutter_texture_render")] #[derive(Default, Clone)] pub struct FlutterHandler { - pub event_stream: Arc>>>, - notify_rendered: Arc>, - renderer: Arc>, + // ui session id -> display handler data + session_handlers: Arc>>, peer_info: Arc>, + #[cfg(feature = "plugin_framework")] #[cfg(not(any(target_os = "android", target_os = "ios")))] hooks: Arc>>, } #[cfg(not(feature = "flutter_texture_render"))] #[derive(Default, Clone)] -pub struct FlutterHandler { - pub event_stream: Arc>>>, +struct RgbaData { // SAFETY: [rgba] is guarded by [rgba_valid], and it's safe to reach [rgba] with `rgba_valid == true`. // We must check the `rgba_valid` before reading [rgba]. - pub rgba: Arc>>, - pub rgba_valid: Arc, + data: Vec, + valid: bool, +} + +#[cfg(not(feature = "flutter_texture_render"))] +#[derive(Default, Clone)] +pub struct FlutterHandler { + session_handlers: Arc>>, + display_rgbas: Arc>>, peer_info: Arc>, #[cfg(not(any(target_os = "android", target_os = "ios")))] hooks: Arc>>, @@ -184,14 +197,22 @@ pub type FlutterRgbaRendererPluginOnRgba = unsafe extern "C" fn( dst_rgba_stride: c_int, ); +#[cfg(feature = "flutter_texture_render")] +pub(super) type TextureRgbaPtr = usize; + +#[cfg(feature = "flutter_texture_render")] +struct DisplaySessionInfo { + // TextureRgba pointer in flutter native. + texture_rgba_ptr: TextureRgbaPtr, + size: (usize, usize), +} + // Video Texture Renderer in Flutter #[cfg(feature = "flutter_texture_render")] #[derive(Clone)] struct VideoRenderer { - // TextureRgba pointer in flutter native. - ptr: Arc>, - width: usize, - height: usize, + is_support_multi_ui_session: bool, + map_display_sessions: Arc>>, on_rgba_func: Option>, } @@ -217,9 +238,8 @@ impl Default for VideoRenderer { } }; Self { - ptr: Default::default(), - width: 0, - height: 0, + map_display_sessions: Default::default(), + is_support_multi_ui_session: false, on_rgba_func, } } @@ -228,33 +248,74 @@ impl Default for VideoRenderer { #[cfg(feature = "flutter_texture_render")] impl VideoRenderer { #[inline] - pub fn set_size(&mut self, width: usize, height: usize) { - self.width = width; - self.height = height; + fn set_size(&mut self, display: usize, width: usize, height: usize) { + let mut sessions_lock = self.map_display_sessions.write().unwrap(); + if let Some(info) = sessions_lock.get_mut(&display) { + info.size = (width, height); + } else { + sessions_lock.insert( + display, + DisplaySessionInfo { + texture_rgba_ptr: usize::default(), + size: (width, height), + }, + ); + } } - pub fn on_rgba(&self, rgba: &mut scrap::ImageRgb) { - let ptr = self.ptr.read().unwrap(); - if *ptr == usize::default() { + fn register_texture(&self, display: usize, ptr: usize) { + let mut sessions_lock = self.map_display_sessions.write().unwrap(); + if ptr == 0 { + sessions_lock.remove(&display); + } else { + if let Some(info) = sessions_lock.get_mut(&display) { + if info.texture_rgba_ptr != 0 && info.texture_rgba_ptr != ptr as TextureRgbaPtr { + log::error!("unreachable, texture_rgba_ptr is not null and not equal to ptr"); + } + info.texture_rgba_ptr = ptr as _; + } else { + if ptr != 0 { + sessions_lock.insert( + display, + DisplaySessionInfo { + texture_rgba_ptr: ptr as _, + size: (0, 0), + }, + ); + } + } + } + } + + pub fn on_rgba(&self, display: usize, rgba: &scrap::ImageRgb) { + let read_lock = self.map_display_sessions.read().unwrap(); + let opt_info = if !self.is_support_multi_ui_session { + read_lock.values().next() + } else { + read_lock.get(&display) + }; + let Some(info) = opt_info else { + return; + }; + if info.texture_rgba_ptr == usize::default() { return; } // It is also Ok to skip this check. - if self.width != rgba.w || self.height != rgba.h { + if info.size.0 != rgba.w || info.size.1 != rgba.h { log::error!( "width/height mismatch: ({},{}) != ({},{})", - self.width, - self.height, + info.size.0, + info.size.1, rgba.w, rgba.h ); return; } - if let Some(func) = &self.on_rgba_func { unsafe { func( - *ptr as _, + info.texture_rgba_ptr as _, rgba.raw.as_ptr() as _, rgba.raw.len() as _, rgba.w as _, @@ -266,34 +327,42 @@ impl VideoRenderer { } } +impl SessionHandler { + pub fn on_waiting_for_image_dialog_show(&mut self) { + #[cfg(any(feature = "flutter_texture_render"))] + { + self.notify_rendered = false; + } + // rgba array render will notify every frame + } +} + impl FlutterHandler { - /// Push an event to the event queue. - /// An event is stored as json in the event queue. + /// Push an event to all the event queues. + /// An event is stored as json in the event queues. /// /// # Arguments /// /// * `name` - The name of the event. /// * `event` - Fields of the event content. - pub fn push_event(&self, name: &str, event: Vec<(&str, &str)>) -> Option { + pub fn push_event(&self, name: &str, event: Vec<(&str, &str)>) { let mut h: HashMap<&str, &str> = event.iter().cloned().collect(); debug_assert!(h.get("name").is_none()); h.insert("name", name); let out = serde_json::ser::to_string(&h).unwrap_or("".to_owned()); - Some( - self.event_stream - .read() - .unwrap() - .as_ref()? - .add(EventToUI::Event(out)), - ) + for (_, session) in self.session_handlers.read().unwrap().iter() { + if let Some(stream) = &session.event_stream { + stream.add(EventToUI::Event(out.clone())); + } + } } - pub(crate) fn close_event_stream(&self) { - let mut stream_lock = self.event_stream.write().unwrap(); - if let Some(stream) = &*stream_lock { - stream.add(EventToUI::Event("close".to_owned())); + pub(crate) fn close_event_stream(&self, session_id: SessionID) { + // to-do: Make sure the following logic is correct. + // No need to remove the display handler, because it will be removed when the connection is closed. + if let Some(session) = self.session_handlers.write().unwrap().get_mut(&session_id) { + try_send_close_event(&session.event_stream); } - *stream_lock = None; } fn make_displays_msg(displays: &Vec) -> String { @@ -314,6 +383,7 @@ impl FlutterHandler { serde_json::ser::to_string(&msg_vec).unwrap_or("".to_owned()) } + #[cfg(feature = "plugin_framework")] #[cfg(not(any(target_os = "android", target_os = "ios")))] pub(crate) fn add_session_hook(&self, key: String, hook: SessionHook) -> bool { let mut hooks = self.hooks.write().unwrap(); @@ -325,6 +395,7 @@ impl FlutterHandler { true } + #[cfg(feature = "plugin_framework")] #[cfg(not(any(target_os = "android", target_os = "ios")))] pub(crate) fn remove_session_hook(&self, key: &String) -> bool { let mut hooks = self.hooks.write().unwrap(); @@ -335,27 +406,6 @@ impl FlutterHandler { let _ = hooks.remove(key); true } - - #[inline] - #[cfg(feature = "flutter_texture_render")] - pub fn register_texture(&self, ptr: usize) { - *self.renderer.read().unwrap().ptr.write().unwrap() = ptr; - } - - #[inline] - #[cfg(feature = "flutter_texture_render")] - pub fn set_size(&self, width: usize, height: usize) { - *self.notify_rendered.write().unwrap() = false; - self.renderer.write().unwrap().set_size(width, height); - } - - pub fn on_waiting_for_image_dialog_show(&self) { - #[cfg(any(feature = "flutter_texture_render"))] - { - *self.notify_rendered.write().unwrap() = false; - } - // rgba array render will notify every frame - } } impl InvokeUiSession for FlutterHandler { @@ -408,7 +458,10 @@ impl InvokeUiSession for FlutterHandler { "update_quality_status", vec![ ("speed", &status.speed.map_or(NULL, |it| it)), - ("fps", &status.fps.map_or(NULL, |it| it.to_string())), + ( + "fps", + &serde_json::ser::to_string(&status.fps).unwrap_or(NULL.to_owned()), + ), ("delay", &status.delay.map_or(NULL, |it| it.to_string())), ( "target_bitrate", @@ -529,7 +582,7 @@ impl InvokeUiSession for FlutterHandler { #[inline] #[cfg(not(feature = "flutter_texture_render"))] - fn on_rgba(&self, rgba: &mut scrap::ImageRgb) { + fn on_rgba(&self, display: usize, rgba: &mut scrap::ImageRgb) { // Give a chance for plugins or etc to hook a rgba data. #[cfg(not(any(target_os = "android", target_os = "ios")))] for (key, hook) in self.hooks.read().unwrap().iter() { @@ -541,27 +594,51 @@ impl InvokeUiSession for FlutterHandler { } // If the current rgba is not fetched by flutter, i.e., is valid. // We give up sending a new event to flutter. - if self.rgba_valid.load(Ordering::Relaxed) { - return; + let mut rgba_write_lock = self.display_rgbas.write().unwrap(); + if let Some(rgba_data) = rgba_write_lock.get_mut(&display) { + if rgba_data.valid { + return; + } else { + rgba_data.valid = true; + } + // Return the rgba buffer to the video handler for reusing allocated rgba buffer. + std::mem::swap::>(&mut rgba.raw, &mut rgba_data.data); + } else { + let mut rgba_data = RgbaData::default(); + std::mem::swap::>(&mut rgba.raw, &mut rgba_data.data); + rgba_write_lock.insert(display, rgba_data); } - self.rgba_valid.store(true, Ordering::Relaxed); - // Return the rgba buffer to the video handler for reusing allocated rgba buffer. - std::mem::swap::>(&mut rgba.raw, &mut *self.rgba.write().unwrap()); - if let Some(stream) = &*self.event_stream.read().unwrap() { - stream.add(EventToUI::Rgba); + drop(rgba_write_lock); + + // Non-texture-render UI does not support multiple displays in the one UI session. + // It's Ok to notify each session for now. + for h in self.session_handlers.read().unwrap().values() { + if let Some(stream) = &h.event_stream { + stream.add(EventToUI::Rgba(display)); + } } } #[inline] #[cfg(feature = "flutter_texture_render")] - fn on_rgba(&self, rgba: &mut scrap::ImageRgb) { - self.renderer.read().unwrap().on_rgba(rgba); - if *self.notify_rendered.read().unwrap() { - return; + fn on_rgba(&self, display: usize, rgba: &mut scrap::ImageRgb) { + let mut try_notify_sessions = Vec::new(); + for (id, session) in self.session_handlers.read().unwrap().iter() { + session.renderer.on_rgba(display, rgba); + if !session.notify_rendered { + try_notify_sessions.push(id.clone()); + } } - if let Some(stream) = &*self.event_stream.read().unwrap() { - stream.add(EventToUI::Rgba); - *self.notify_rendered.write().unwrap() = true; + if try_notify_sessions.len() > 0 { + let mut write_lock = self.session_handlers.write().unwrap(); + for id in try_notify_sessions.iter() { + if let Some(session) = write_lock.get_mut(id) { + if let Some(stream) = &session.event_stream { + stream.add(EventToUI::Rgba(display)); + session.notify_rendered = true; + } + } + } } } @@ -578,6 +655,17 @@ impl InvokeUiSession for FlutterHandler { let features = serde_json::ser::to_string(&features).unwrap_or("".to_owned()); let resolutions = serialize_resolutions(&pi.resolutions.resolutions); *self.peer_info.write().unwrap() = pi.clone(); + #[cfg(feature = "flutter_texture_render")] + { + self.session_handlers + .write() + .unwrap() + .values_mut() + .for_each(|h| { + h.renderer.is_support_multi_ui_session = + crate::common::is_support_multi_ui_session(&pi.version); + }); + } self.push_event( "peer_info", vec![ @@ -701,21 +789,31 @@ impl InvokeUiSession for FlutterHandler { } #[inline] - fn get_rgba(&self) -> *const u8 { + fn get_rgba(&self, _display: usize) -> *const u8 { #[cfg(not(feature = "flutter_texture_render"))] - if self.rgba_valid.load(Ordering::Relaxed) { - return self.rgba.read().unwrap().as_ptr(); + if let Some(rgba_data) = self.display_rgbas.read().unwrap().get(&_display) { + if rgba_data.valid { + return rgba_data.data.as_ptr(); + } } std::ptr::null_mut() } #[inline] - fn next_rgba(&self) { + fn next_rgba(&self, _display: usize) { #[cfg(not(feature = "flutter_texture_render"))] - self.rgba_valid.store(false, Ordering::Relaxed); + if let Some(rgba_data) = self.display_rgbas.write().unwrap().get_mut(&_display) { + rgba_data.valid = true; + } } } +// This function is only used for the default connection session. +pub fn session_add_existed(peer_id: String, session_id: SessionID) -> ResultType<()> { + sessions::insert_peer_session_id(peer_id, ConnType::DEFAULT_CONN, session_id); + Ok(()) +} + /// Create a new remote session with the given id. /// /// # Arguments @@ -733,18 +831,6 @@ pub fn session_add( force_relay: bool, password: String, ) -> ResultType { - LocalConfig::set_remote_id(&id); - - let session: Session = Session { - session_id: session_id.clone(), - id: id.to_owned(), - password, - server_keyboard_enabled: Arc::new(RwLock::new(true)), - server_file_transfer_enabled: Arc::new(RwLock::new(true)), - server_clipboard_enabled: Arc::new(RwLock::new(true)), - ..Default::default() - }; - let conn_type = if is_file_transfer { ConnType::FILE_TRANSFER } else if is_port_forward { @@ -757,6 +843,26 @@ pub fn session_add( ConnType::DEFAULT_CONN }; + // to-do: check the same id session. + if let Some(session) = sessions::get_session_by_session_id(&session_id) { + if session.lc.read().unwrap().conn_type != conn_type { + bail!("same session id is found with different conn type?"); + } + // The same session is added before? + bail!("same session id is found"); + } + + LocalConfig::set_remote_id(&id); + + let session: Session = Session { + id: id.to_owned(), + password, + server_keyboard_enabled: Arc::new(RwLock::new(true)), + server_file_transfer_enabled: Arc::new(RwLock::new(true)), + server_clipboard_enabled: Arc::new(RwLock::new(true)), + ..Default::default() + }; + let switch_uuid = if switch_uuid.is_empty() { None } else { @@ -768,12 +874,8 @@ pub fn session_add( .write() .unwrap() .initialize(id.to_owned(), conn_type, switch_uuid, force_relay); - let session = Arc::new(session.clone()); - if let Some(same_id_session) = sessions::add_session(session_id.to_owned(), session.clone()) { - log::error!("Should not happen"); - same_id_session.close(); - } + sessions::insert_session(session_id.to_owned(), conn_type, session.clone()); Ok(session) } @@ -789,18 +891,39 @@ pub fn session_start_( id: &str, event_stream: StreamSink, ) -> ResultType<()> { - if let Some(session) = sessions::get_session(session_id) { - #[cfg(feature = "flutter_texture_render")] - log::info!( - "Session {} start, render by flutter texture rgba plugin", - id + // is_connected is used to indicate whether to start a peer connection. For two cases: + // 1. "Move tab to new window" + // 2. multi ui session within the same peer connnection. + let mut is_connected = false; + let mut is_found = false; + for s in sessions::get_sessions() { + if let Some(h) = s.session_handlers.write().unwrap().get_mut(session_id) { + is_connected = h.event_stream.is_some(); + try_send_close_event(&h.event_stream); + h.event_stream = Some(event_stream); + is_found = true; + break; + } + } + if !is_found { + bail!( + "No session with peer id {}, session id: {}", + id, + session_id.to_string() ); - #[cfg(not(feature = "flutter_texture_render"))] - log::info!("Session {} start, render by flutter paint widget", id); - let is_pre_added = session.event_stream.read().unwrap().is_some(); - session.close_event_stream(); - *session.event_stream.write().unwrap() = Some(event_stream); - if !is_pre_added { + } + + if let Some(session) = sessions::get_session_by_session_id(session_id) { + let is_first_ui_session = session.session_handlers.read().unwrap().len() == 1; + if !is_connected && is_first_ui_session { + #[cfg(feature = "flutter_texture_render")] + log::info!( + "Session {} start, render by flutter texture rgba plugin", + id + ); + #[cfg(not(feature = "flutter_texture_render"))] + log::info!("Session {} start, render by flutter paint widget", id); + let session = (*session).clone(); std::thread::spawn(move || { let round = session.connection_round_state.lock().unwrap().new_round(); @@ -813,19 +936,26 @@ pub fn session_start_( } } +#[inline] +fn try_send_close_event(event_stream: &Option>) { + if let Some(stream) = &event_stream { + stream.add(EventToUI::Event("close".to_owned())); + } +} + #[cfg(not(any(target_os = "android", target_os = "ios")))] pub fn update_text_clipboard_required() { let is_required = sessions::get_sessions() .iter() - .any(|session| session.is_text_clipboard_required()); + .any(|s| s.is_text_clipboard_required()); Client::set_is_text_clipboard_required(is_required); } #[cfg(not(any(target_os = "android", target_os = "ios")))] pub fn send_text_clipboard_msg(msg: Message) { - for session in sessions::get_sessions() { - if session.is_text_clipboard_required() { - session.send(Data::Message(msg.clone())); + for s in sessions::get_sessions() { + if s.is_text_clipboard_required() { + s.send(Data::Message(msg.clone())); } } } @@ -998,6 +1128,11 @@ pub fn get_cur_session_id() -> SessionID { CUR_SESSION_ID.read().unwrap().clone() } +pub fn get_cur_peer_id() -> String { + sessions::get_peer_id_by_session_id(&get_cur_session_id(), ConnType::DEFAULT_CONN) + .unwrap_or("".to_string()) +} + pub fn set_cur_session_id(session_id: SessionID) { if get_cur_session_id() != session_id { *CUR_SESSION_ID.write().unwrap() = session_id; @@ -1034,47 +1169,76 @@ fn char_to_session_id(c: *const char) -> ResultType { SessionID::from_str(str).map_err(|e| anyhow!("{:?}", e)) } -pub fn session_get_rgba_size(_session_id: SessionID) -> usize { +pub fn session_get_rgba_size(_session_id: SessionID, _display: usize) -> usize { #[cfg(not(feature = "flutter_texture_render"))] - if let Some(session) = sessions::get_session(&_session_id) { - return session.rgba.read().unwrap().len(); + if let Some(session) = sessions::get_session_by_session_id(&_session_id) { + return session + .display_rgbas + .read() + .unwrap() + .get(&_display) + .map_or(0, |rgba| rgba.data.len()); } 0 } #[no_mangle] -pub extern "C" fn session_get_rgba(session_uuid_str: *const char) -> *const u8 { +pub extern "C" fn session_get_rgba(session_uuid_str: *const char, display: usize) -> *const u8 { if let Ok(session_id) = char_to_session_id(session_uuid_str) { - if let Some(session) = sessions::get_session(&session_id) { - return session.get_rgba(); + if let Some(s) = sessions::get_session_by_session_id(&session_id) { + return s.ui_handler.get_rgba(display); } } std::ptr::null() } -pub fn session_next_rgba(session_id: SessionID) { - if let Some(session) = sessions::get_session(&session_id) { - return session.next_rgba(); +pub fn session_next_rgba(session_id: SessionID, display: usize) { + if let Some(s) = sessions::get_session_by_session_id(&session_id) { + return s.ui_handler.next_rgba(display); } } #[inline] -pub fn session_register_texture(_session_id: SessionID, _ptr: usize) { +pub fn session_set_size(_session_id: SessionID, _display: usize, _width: usize, _height: usize) { #[cfg(feature = "flutter_texture_render")] - if let Some(session) = sessions::get_session(&_session_id) { - session.register_texture(_ptr); - return; + for s in sessions::get_sessions() { + if let Some(h) = s + .ui_handler + .session_handlers + .write() + .unwrap() + .get_mut(&_session_id) + { + h.notify_rendered = false; + h.renderer.set_size(_display, _width, _height); + break; + } } } #[inline] -pub fn push_session_event( - session_id: &SessionID, - name: &str, - event: Vec<(&str, &str)>, -) -> Option { - sessions::get_session(session_id)?.push_event(name, event) +pub fn session_register_texture(_session_id: SessionID, _display: usize, _ptr: usize) { + #[cfg(feature = "flutter_texture_render")] + for s in sessions::get_sessions() { + if let Some(h) = s + .ui_handler + .session_handlers + .read() + .unwrap() + .get(&_session_id) + { + h.renderer.register_texture(_display, _ptr); + break; + } + } +} + +#[inline] +pub fn push_session_event(session_id: &SessionID, name: &str, event: Vec<(&str, &str)>) { + if let Some(s) = sessions::get_session_by_session_id(session_id) { + s.push_event(name, event); + } } #[inline] @@ -1123,7 +1287,7 @@ fn session_send_touch_scale( ) { match v.get("v").and_then(|s| s.as_i64()) { Some(scale) => { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.send_touch_scale(scale as _, alt, ctrl, shift, command); } } @@ -1147,7 +1311,7 @@ fn session_send_touch_pan( v.get("y").and_then(|y| y.as_i64()), ) { (Some(x), Some(y)) => { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session .send_touch_pan_event(pan_event, x as _, y as _, alt, ctrl, shift, command); } @@ -1191,6 +1355,15 @@ pub fn session_send_pointer(session_id: SessionID, msg: String) { } } +#[inline] +pub fn session_on_waiting_for_image_dialog_show(session_id: SessionID) { + for s in sessions::get_sessions() { + if let Some(h) = s.session_handlers.write().unwrap().get_mut(&session_id) { + h.on_waiting_for_image_dialog_show(); + } + } +} + /// Hooks for session. #[derive(Clone)] pub enum SessionHook { @@ -1199,7 +1372,7 @@ pub enum SessionHook { #[inline] pub fn get_cur_session() -> Option { - sessions::get_session(&*CUR_SESSION_ID.read().unwrap()) + sessions::get_session_by_session_id(&*CUR_SESSION_ID.read().unwrap()) } // sessions mod is used to avoid the big lock of sessions' map. @@ -1207,22 +1380,118 @@ pub mod sessions { use super::*; lazy_static::lazy_static! { - static ref SESSIONS: RwLock> = Default::default(); + // peer -> peer session, peer session -> ui sessions + static ref SESSIONS: RwLock> = Default::default(); } #[inline] - pub fn add_session(session_id: SessionID, session: FlutterSession) -> Option { - SESSIONS.write().unwrap().insert(session_id, session) + pub fn get_session_count(peer_id: String, conn_type: ConnType) -> usize { + SESSIONS + .read() + .unwrap() + .get(&(peer_id, conn_type)) + .map(|s| s.ui_handler.session_handlers.read().unwrap().len()) + .unwrap_or(0) } #[inline] - pub fn remove_session(session_id: &SessionID) -> Option { - SESSIONS.write().unwrap().remove(session_id) + pub fn get_peer_id_by_session_id(id: &SessionID, conn_type: ConnType) -> Option { + SESSIONS + .read() + .unwrap() + .iter() + .find_map(|((peer_id, t), s)| { + if *t == conn_type + && s.ui_handler + .session_handlers + .read() + .unwrap() + .contains_key(id) + { + Some(peer_id.clone()) + } else { + None + } + }) } #[inline] - pub fn get_session(session_id: &SessionID) -> Option { - SESSIONS.read().unwrap().get(session_id).cloned() + pub fn get_session_by_session_id(id: &SessionID) -> Option { + SESSIONS + .read() + .unwrap() + .values() + .find(|s| { + s.ui_handler + .session_handlers + .read() + .unwrap() + .contains_key(id) + }) + .cloned() + } + + #[inline] + pub fn get_session_by_peer_id(peer_id: String, conn_type: ConnType) -> Option { + SESSIONS.read().unwrap().get(&(peer_id, conn_type)).cloned() + } + + #[inline] + pub fn remove_session_by_session_id(id: &SessionID) -> Option { + let mut remove_peer_key = None; + for (peer_key, s) in SESSIONS.write().unwrap().iter_mut() { + let mut write_lock = s.ui_handler.session_handlers.write().unwrap(); + if write_lock.remove(id).is_some() { + if write_lock.is_empty() { + remove_peer_key = Some(peer_key.clone()); + } + break; + } + } + SESSIONS.write().unwrap().remove(&remove_peer_key?) + } + + #[inline] + pub fn insert_session(session_id: SessionID, conn_type: ConnType, session: FlutterSession) { + SESSIONS + .write() + .unwrap() + .entry((session.id.clone(), conn_type)) + .or_insert(session) + .ui_handler + .session_handlers + .write() + .unwrap() + .insert(session_id, Default::default()); + } + + #[inline] + pub fn insert_peer_session_id( + peer_id: String, + conn_type: ConnType, + session_id: SessionID, + ) -> bool { + if let Some(s) = SESSIONS.read().unwrap().get(&(peer_id, conn_type)) { + #[cfg(not(feature = "flutter_texture_render"))] + let h = SessionHandler::default(); + #[cfg(feature = "flutter_texture_render")] + let mut h = SessionHandler::default(); + #[cfg(feature = "flutter_texture_render")] + { + h.renderer.is_support_multi_ui_session = crate::common::is_support_multi_ui_session( + &s.ui_handler.peer_info.read().unwrap().version, + ); + } + let _ = s + .ui_handler + .session_handlers + .write() + .unwrap() + .insert(session_id, h); + true + } else { + false + } } #[inline] @@ -1230,26 +1499,14 @@ pub mod sessions { SESSIONS.read().unwrap().values().cloned().collect() } - #[inline] - pub fn get_session_by_peer_id(peer_id: &str) -> Option { - SESSIONS - .read() - .unwrap() - .values() - .find(|session| session.id == peer_id) - .map(|s| s.clone()) - .clone() - } - #[inline] #[cfg(not(any(target_os = "android", target_os = "ios")))] - pub fn other_sessions_running(session_id: &SessionID) -> bool { + pub fn other_sessions_running(peer_id: String, conn_type: ConnType) -> bool { SESSIONS .read() .unwrap() - .keys() - .filter(|k| *k != session_id) - .count() - != 0 + .get(&(peer_id, conn_type)) + .map(|s| s.session_handlers.read().unwrap().len() != 0) + .unwrap_or(false) } } diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index b4ae89bc9..080ad8178 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -4,7 +4,7 @@ use crate::{ client::file_trait::FileManager, common::is_keyboard_mode_supported, common::make_fd_to_json, - flutter::{self, session_add, session_start_, sessions}, + flutter::{self, session_add, session_add_existed, session_start_, sessions}, input::*, ui_interface::{self, *}, }; @@ -16,6 +16,7 @@ use hbb_common::{ config::{self, LocalConfig, PeerConfig, PeerInfoSerde}, fs, lazy_static, log, message_proto::KeyboardMode, + rendezvous_proto::ConnType, ResultType, }; use std::{ @@ -67,7 +68,7 @@ pub fn stop_global_event_stream(app_type: String) { } pub enum EventToUI { Event(String), - Rgba, + Rgba(usize), } pub fn host_stop_system_key_propagate(_stopped: bool) { @@ -75,8 +76,26 @@ pub fn host_stop_system_key_propagate(_stopped: bool) { crate::platform::windows::stop_system_key_propagate(_stopped); } -// FIXME: -> ResultType<()> cannot be parsed by frb_codegen -// thread 'main' panicked at 'Failed to parse function output type `ResultType<()>`', $HOME\.cargo\git\checkouts\flutter_rust_bridge-ddba876d3ebb2a1e\e5adce5\frb_codegen\src\parser\mod.rs:151:25 +// This function is only used to count the number of control sessions. +pub fn peer_get_default_sessions_count(id: String) -> SyncReturn { + SyncReturn(sessions::get_session_count(id, ConnType::DEFAULT_CONN)) +} + +pub fn session_add_existed_sync(id: String, session_id: SessionID) -> SyncReturn { + if let Err(e) = session_add_existed(id.clone(), session_id) { + SyncReturn(format!("Failed to add session with id {}, {}", &id, e)) + } else { + SyncReturn("".to_owned()) + } +} + +pub fn session_try_add_display(session_id: SessionID, displays: Vec) -> SyncReturn<()> { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { + session.capture_displays(displays, vec![], vec![]); + } + SyncReturn(()) +} + pub fn session_add_sync( session_id: SessionID, id: String, @@ -112,7 +131,7 @@ pub fn session_start( } pub fn session_get_remember(session_id: SessionID) -> Option { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { Some(session.get_remember()) } else { None @@ -120,7 +139,7 @@ pub fn session_get_remember(session_id: SessionID) -> Option { } pub fn session_get_toggle_option(session_id: SessionID, arg: String) -> Option { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { Some(session.get_toggle_option(arg)) } else { None @@ -133,7 +152,7 @@ pub fn session_get_toggle_option_sync(session_id: SessionID, arg: String) -> Syn } pub fn session_get_option(session_id: SessionID, arg: String) -> Option { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { Some(session.get_option(arg)) } else { None @@ -147,55 +166,61 @@ pub fn session_login( password: String, remember: bool, ) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.login(os_username, os_password, password, remember); } } pub fn session_close(session_id: SessionID) { - if let Some(session) = sessions::remove_session(&session_id) { - session.close_event_stream(); + if let Some(session) = sessions::remove_session_by_session_id(&session_id) { + session.close_event_stream(session_id); session.close(); } } -pub fn session_refresh(session_id: SessionID) { - if let Some(session) = sessions::get_session(&session_id) { - session.refresh_video(); +pub fn session_refresh(session_id: SessionID, display: usize) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { + session.refresh_video(display); } } -pub fn session_record_screen(session_id: SessionID, start: bool, width: usize, height: usize) { - if let Some(session) = sessions::get_session(&session_id) { - session.record_screen(start, width as _, height as _); +pub fn session_record_screen( + session_id: SessionID, + start: bool, + display: usize, + width: usize, + height: usize, +) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { + session.record_screen(start, display as _, width as _, height as _); } } pub fn session_record_status(session_id: SessionID, status: bool) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.record_status(status); } } pub fn session_reconnect(session_id: SessionID, force_relay: bool) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.reconnect(force_relay); } } pub fn session_toggle_option(session_id: SessionID, value: String) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { log::warn!("toggle option {}", &value); session.toggle_option(value.clone()); } #[cfg(not(any(target_os = "android", target_os = "ios")))] - if sessions::get_session(&session_id).is_some() && value == "disable-clipboard" { + if sessions::get_session_by_session_id(&session_id).is_some() && value == "disable-clipboard" { crate::flutter::update_text_clipboard_required(); } } pub fn session_get_flutter_option(session_id: SessionID, k: String) -> Option { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { Some(session.get_flutter_option(k)) } else { None @@ -203,13 +228,14 @@ pub fn session_get_flutter_option(session_id: SessionID, k: String) -> Option Option { - if let Some(session) = sessions::get_session_by_peer_id(&id) { + if let Some(session) = sessions::get_session_by_peer_id(id, ConnType::DEFAULT_CONN) { Some(session.get_flutter_option(k)) } else { None @@ -238,7 +264,7 @@ pub fn set_local_kb_layout_type(kb_layout_type: String) { } pub fn session_get_view_style(session_id: SessionID) -> Option { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { Some(session.get_view_style()) } else { None @@ -246,13 +272,13 @@ pub fn session_get_view_style(session_id: SessionID) -> Option { } pub fn session_set_view_style(session_id: SessionID, value: String) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.save_view_style(value); } } pub fn session_get_scroll_style(session_id: SessionID) -> Option { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { Some(session.get_scroll_style()) } else { None @@ -260,13 +286,13 @@ pub fn session_get_scroll_style(session_id: SessionID) -> Option { } pub fn session_set_scroll_style(session_id: SessionID, value: String) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.save_scroll_style(value); } } pub fn session_get_image_quality(session_id: SessionID) -> Option { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { Some(session.get_image_quality()) } else { None @@ -274,13 +300,13 @@ pub fn session_get_image_quality(session_id: SessionID) -> Option { } pub fn session_set_image_quality(session_id: SessionID, value: String) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.save_image_quality(value); } } pub fn session_get_keyboard_mode(session_id: SessionID) -> Option { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { Some(session.get_keyboard_mode()) } else { None @@ -289,7 +315,7 @@ pub fn session_get_keyboard_mode(session_id: SessionID) -> Option { pub fn session_set_keyboard_mode(session_id: SessionID, value: String) { let mut _mode_updated = false; - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.save_keyboard_mode(value.clone()); _mode_updated = true; } @@ -300,7 +326,7 @@ pub fn session_set_keyboard_mode(session_id: SessionID, value: String) { } pub fn session_get_reverse_mouse_wheel(session_id: SessionID) -> Option { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { Some(session.get_reverse_mouse_wheel()) } else { None @@ -308,13 +334,13 @@ pub fn session_get_reverse_mouse_wheel(session_id: SessionID) -> Option } pub fn session_set_reverse_mouse_wheel(session_id: SessionID, value: String) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.save_reverse_mouse_wheel(value); } } pub fn session_get_custom_image_quality(session_id: SessionID) -> Option> { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { Some(session.get_custom_image_quality()) } else { None @@ -322,7 +348,7 @@ pub fn session_get_custom_image_quality(session_id: SessionID) -> Option SyncReturn { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { if let Ok(mode) = KeyboardMode::from_str(&mode[..]) { SyncReturn(is_keyboard_mode_supported( &mode, @@ -337,32 +363,36 @@ pub fn session_is_keyboard_mode_supported(session_id: SessionID, mode: String) - } pub fn session_set_custom_image_quality(session_id: SessionID, value: i32) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.save_custom_image_quality(value); } } pub fn session_set_custom_fps(session_id: SessionID, fps: i32) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.set_custom_fps(fps); } } pub fn session_lock_screen(session_id: SessionID) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.lock_screen(); } } pub fn session_ctrl_alt_del(session_id: SessionID) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.ctrl_alt_del(); } } -pub fn session_switch_display(session_id: SessionID, value: i32) { - if let Some(session) = sessions::get_session(&session_id) { - session.switch_display(value); +pub fn session_switch_display(session_id: SessionID, value: Vec) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { + if value.len() == 1 { + session.switch_display(value[0]); + } else { + session.capture_displays(vec![], vec![], value); + } } } @@ -374,7 +404,7 @@ pub fn session_handle_flutter_key_event( lock_modes: i32, down_or_up: bool, ) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { let keyboard_mode = session.get_keyboard_mode(); session.handle_flutter_key_event( &keyboard_mode, @@ -395,7 +425,7 @@ pub fn session_handle_flutter_key_event( // This will cause the keyboard input to take no effect. pub fn session_enter_or_leave(_session_id: SessionID, _enter: bool) -> SyncReturn<()> { #[cfg(not(any(target_os = "android", target_os = "ios")))] - if let Some(session) = sessions::get_session(&_session_id) { + if let Some(session) = sessions::get_session_by_session_id(&_session_id) { let keyboard_mode = session.get_keyboard_mode(); if _enter { set_cur_session_id_(_session_id, &keyboard_mode); @@ -417,14 +447,14 @@ pub fn session_input_key( shift: bool, command: bool, ) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { // #[cfg(any(target_os = "android", target_os = "ios"))] session.input_key(&name, down, press, alt, ctrl, shift, command); } } pub fn session_input_string(session_id: SessionID, value: String) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { // #[cfg(any(target_os = "android", target_os = "ios"))] session.input_string(&value); } @@ -432,33 +462,33 @@ pub fn session_input_string(session_id: SessionID, value: String) { // chat_client_mode pub fn session_send_chat(session_id: SessionID, text: String) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.send_chat(text); } } pub fn session_peer_option(session_id: SessionID, name: String, value: String) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.set_option(name, value); } } pub fn session_get_peer_option(session_id: SessionID, name: String) -> String { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { return session.get_option(name); } "".to_string() } pub fn session_input_os_password(session_id: SessionID, value: String) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.input_os_password(value, true); } } // File Action pub fn session_read_remote_dir(session_id: SessionID, path: String, include_hidden: bool) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.read_remote_dir(path, include_hidden); } } @@ -472,7 +502,7 @@ pub fn session_send_files( include_hidden: bool, is_remote: bool, ) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.send_files(act_id, path, to, file_num, include_hidden, is_remote); } } @@ -485,7 +515,7 @@ pub fn session_set_confirm_override_file( remember: bool, is_upload: bool, ) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.set_confirm_override_file(act_id, file_num, need_override, remember, is_upload); } } @@ -497,7 +527,7 @@ pub fn session_remove_file( file_num: i32, is_remote: bool, ) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.remove_file(act_id, path, file_num, is_remote); } } @@ -509,7 +539,7 @@ pub fn session_read_dir_recursive( is_remote: bool, show_hidden: bool, ) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.remove_dir_all(act_id, path, is_remote, show_hidden); } } @@ -520,19 +550,19 @@ pub fn session_remove_all_empty_dirs( path: String, is_remote: bool, ) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.remove_dir(act_id, path, is_remote); } } pub fn session_cancel_job(session_id: SessionID, act_id: i32) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.cancel_job(act_id); } } pub fn session_create_dir(session_id: SessionID, act_id: i32, path: String, is_remote: bool) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.create_dir(act_id, path, is_remote); } } @@ -549,14 +579,14 @@ pub fn session_read_local_dir_sync( } pub fn session_get_platform(session_id: SessionID, is_remote: bool) -> String { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { return session.get_platform(is_remote); } "".to_string() } pub fn session_load_last_transfer_jobs(session_id: SessionID) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { return session.load_last_jobs(); } else { // a tip for flutter dev @@ -576,46 +606,44 @@ pub fn session_add_job( include_hidden: bool, is_remote: bool, ) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.add_job(act_id, path, to, file_num, include_hidden, is_remote); } } pub fn session_resume_job(session_id: SessionID, act_id: i32, is_remote: bool) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.resume_job(act_id, is_remote); } } pub fn session_elevate_direct(session_id: SessionID) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.elevate_direct(); } } pub fn session_elevate_with_logon(session_id: SessionID, username: String, password: String) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.elevate_with_logon(username, password); } } pub fn session_switch_sides(session_id: SessionID) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.switch_sides(); } } pub fn session_change_resolution(session_id: SessionID, display: i32, width: i32, height: i32) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.change_resolution(display, width, height); } } -pub fn session_set_size(_session_id: SessionID, _width: usize, _height: usize) { +pub fn session_set_size(_session_id: SessionID, _display: usize, _width: usize, _height: usize) { #[cfg(feature = "flutter_texture_render")] - if let Some(session) = sessions::get_session(&_session_id) { - session.set_size(_width, _height); - } + super::flutter::session_set_size(_session_id, _display, _width, _height) } pub fn main_get_sound_inputs() -> Vec { @@ -1008,18 +1036,22 @@ pub fn main_handle_relay_id(id: String) -> String { handle_relay_id(id) } -pub fn main_get_current_display() -> SyncReturn { - #[cfg(not(target_os = "ios"))] - let display_info = match crate::video_service::get_current_display() { - Ok((_, _, display)) => serde_json::to_string(&HashMap::from([ - ("w", display.width()), - ("h", display.height()), - ])) - .unwrap_or_default(), - Err(..) => "".to_string(), - }; +pub fn main_get_main_display() -> SyncReturn { #[cfg(target_os = "ios")] let display_info = "".to_owned(); + #[cfg(not(target_os = "ios"))] + let mut display_info = "".to_owned(); + #[cfg(not(target_os = "ios"))] + if let Ok(displays) = crate::display_service::try_get_displays() { + // to-do: Need to detect current display index. + if let Some(display) = displays.iter().next() { + display_info = serde_json::to_string(&HashMap::from([ + ("w", display.width()), + ("h", display.height()), + ])) + .unwrap_or_default(); + } + } SyncReturn(display_info) } @@ -1029,31 +1061,31 @@ pub fn session_add_port_forward( remote_host: String, remote_port: i32, ) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.add_port_forward(local_port, remote_host, remote_port); } } pub fn session_remove_port_forward(session_id: SessionID, local_port: i32) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.remove_port_forward(local_port); } } pub fn session_new_rdp(session_id: SessionID) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.new_rdp(); } } pub fn session_request_voice_call(session_id: SessionID) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.request_voice_call(); } } pub fn session_close_voice_call(session_id: SessionID) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.close_voice_call(); } } @@ -1238,20 +1270,20 @@ pub fn session_send_mouse(session_id: SessionID, msg: String) { _ => 0, } << 3; } - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.send_mouse(mask, x, y, alt, ctrl, shift, command); } } } pub fn session_restart_remote_device(session_id: SessionID) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.restart_remote_device(); } } pub fn session_get_audit_server_sync(session_id: SessionID, typ: String) -> SyncReturn { - let res = if let Some(session) = sessions::get_session(&session_id) { + let res = if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.get_audit_server(typ) } else { "".to_owned() @@ -1260,13 +1292,13 @@ pub fn session_get_audit_server_sync(session_id: SessionID, typ: String) -> Sync } pub fn session_send_note(session_id: SessionID, note: String) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.send_note(note) } } pub fn session_alternative_codecs(session_id: SessionID) -> String { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { let (vp8, av1, h264, h265) = session.alternative_codecs(); let msg = HashMap::from([("vp8", vp8), ("av1", av1), ("h264", h264), ("h265", h265)]); serde_json::ser::to_string(&msg).unwrap_or("".to_owned()) @@ -1276,15 +1308,13 @@ pub fn session_alternative_codecs(session_id: SessionID) -> String { } pub fn session_change_prefer_codec(session_id: SessionID) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.change_prefer_codec(); } } pub fn session_on_waiting_for_image_dialog_show(session_id: SessionID) { - if let Some(session) = sessions::get_session(&session_id) { - session.ui_handler.on_waiting_for_image_dialog_show(); - } + super::flutter::session_on_waiting_for_image_dialog_show(session_id); } pub fn main_set_home_dir(_home: String) { @@ -1434,16 +1464,22 @@ pub fn translate(name: String, locale: String) -> SyncReturn { SyncReturn(crate::client::translate_locale(name, &locale)) } -pub fn session_get_rgba_size(session_id: SessionID) -> SyncReturn { - SyncReturn(super::flutter::session_get_rgba_size(session_id)) +pub fn session_get_rgba_size(session_id: SessionID, display: usize) -> SyncReturn { + SyncReturn(super::flutter::session_get_rgba_size(session_id, display)) } -pub fn session_next_rgba(session_id: SessionID) -> SyncReturn<()> { - SyncReturn(super::flutter::session_next_rgba(session_id)) +pub fn session_next_rgba(session_id: SessionID, display: usize) -> SyncReturn<()> { + SyncReturn(super::flutter::session_next_rgba(session_id, display)) } -pub fn session_register_texture(session_id: SessionID, ptr: usize) -> SyncReturn<()> { - SyncReturn(super::flutter::session_register_texture(session_id, ptr)) +pub fn session_register_texture( + session_id: SessionID, + display: usize, + ptr: usize, +) -> SyncReturn<()> { + SyncReturn(super::flutter::session_register_texture( + session_id, display, ptr, + )) } pub fn query_onlines(ids: Vec) { @@ -1522,7 +1558,7 @@ pub fn main_update_me() -> SyncReturn { } pub fn set_cur_session_id(session_id: SessionID) { - if let Some(session) = sessions::get_session(&session_id) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { set_cur_session_id_(session_id, &session.get_keyboard_mode()) } } @@ -1808,6 +1844,10 @@ pub fn plugin_install(_id: String, _b: bool) { } } +pub fn is_support_multi_ui_session(version: String) -> SyncReturn { + SyncReturn(crate::common::is_support_multi_ui_session(&version)) +} + #[cfg(target_os = "android")] pub mod server_side { use hbb_common::{config, log}; diff --git a/src/lang/ar.rs b/src/lang/ar.rs index 7921dbde4..30c8bea5f 100644 --- a/src/lang/ar.rs +++ b/src/lang/ar.rs @@ -558,5 +558,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Filter by intersection", ""), ("Remove wallpaper during incoming sessions", ""), ("Test", ""), + ("switch_display_elevated_connections_tip", ""), + ("display_is_plugged_out_msg", ""), + ("No displays", ""), + ("elevated_switch_display_msg", ""), + ("Choose Display Behavior", ""), + ("Switch Display", ""), + ("Open in New Window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ca.rs b/src/lang/ca.rs index 61cbef359..d9c570796 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -558,5 +558,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Filter by intersection", ""), ("Remove wallpaper during incoming sessions", ""), ("Test", ""), + ("switch_display_elevated_connections_tip", ""), + ("display_is_plugged_out_msg", ""), + ("No displays", ""), + ("elevated_switch_display_msg", ""), + ("Choose Display Behavior", ""), + ("Switch Display", ""), + ("Open in New Window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/cn.rs b/src/lang/cn.rs index ea8f4ee4e..24d2ec78a 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -558,5 +558,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Filter by intersection", "按交集过滤"), ("Remove wallpaper during incoming sessions", "接受会话时移除桌面壁纸"), ("Test", "测试"), + ("switch_display_elevated_connections_tip", "提权后,被控有多个连接,不能切换到非主显示器。若要控制多显示器,请安装后再试。"), + ("display_is_plugged_out_msg", "显示器被拔出,切换到第一个显示器。"), + ("No displays", "没有显示器。"), + ("elevated_switch_display_msg", "切换到主显示器,因为提权后,不支持多显示器画面。"), + ("Choose Display Behavior", "选择显示器的行为"), + ("Switch Display", "切换显示器"), + ("Open in New Window", "在新的窗口中打开"), ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index ff0b87502..4f54e0540 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -558,5 +558,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Filter by intersection", ""), ("Remove wallpaper during incoming sessions", ""), ("Test", ""), + ("switch_display_elevated_connections_tip", ""), + ("display_is_plugged_out_msg", ""), + ("No displays", ""), + ("elevated_switch_display_msg", ""), + ("Choose Display Behavior", ""), + ("Switch Display", ""), + ("Open in New Window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index 6b577e5b8..17792e10b 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -558,5 +558,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Filter by intersection", ""), ("Remove wallpaper during incoming sessions", ""), ("Test", ""), + ("switch_display_elevated_connections_tip", ""), + ("display_is_plugged_out_msg", ""), + ("No displays", ""), + ("elevated_switch_display_msg", ""), + ("Choose Display Behavior", ""), + ("Switch Display", ""), + ("Open in New Window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/de.rs b/src/lang/de.rs index aac437388..0628846d3 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -557,6 +557,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("pull_group_failed_tip", "Aktualisierung der Gruppe fehlgeschlagen"), ("Remove wallpaper during incoming sessions", ""), ("Test", ""), - ("Filter by intersection", "Nach Schnittpunkt filtern") + ("Filter by intersection", "Nach Schnittpunkt filtern"), + ("switch_display_elevated_connections_tip", ""), + ("display_is_plugged_out_msg", ""), + ("No displays", ""), + ("elevated_switch_display_msg", ""), + ("Choose Display Behavior", ""), + ("Switch Display", ""), + ("Open in New Window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/el.rs b/src/lang/el.rs index 169ee2359..fa545e182 100644 --- a/src/lang/el.rs +++ b/src/lang/el.rs @@ -558,5 +558,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Filter by intersection", ""), ("Remove wallpaper during incoming sessions", ""), ("Test", ""), + ("switch_display_elevated_connections_tip", ""), + ("display_is_plugged_out_msg", ""), + ("No displays", ""), + ("elevated_switch_display_msg", ""), + ("Choose Display Behavior", ""), + ("Switch Display", ""), + ("Open in New Window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/en.rs b/src/lang/en.rs index ea5c18399..e2495780e 100644 --- a/src/lang/en.rs +++ b/src/lang/en.rs @@ -222,5 +222,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("upgrade_rustdesk_server_pro_to_{}_tip", "Please upgrade RustDesk Server Pro to version {} or newer!"), ("pull_group_failed_tip", "Failed to refresh group"), ("doc_fix_wayland", "https://rustdesk.com/docs/en/manual/linux/#x11-required"), + ("switch_display_elevated_connections_tip", "Switching to non-primary display is not supported in the elevated mode when there are multiple connections. Please try again after installation if you want to control multiple displays."), + ("display_is_plugged_out_msg", "The diplay is plugged out, switch to the first display."), + ("elevated_switch_display_msg", "Switch to the primary display because multiple display is not supported in elevated mode."), ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index 59c4db9fe..56f24354c 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -558,5 +558,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Filter by intersection", ""), ("Remove wallpaper during incoming sessions", ""), ("Test", ""), + ("switch_display_elevated_connections_tip", ""), + ("display_is_plugged_out_msg", ""), + ("No displays", ""), + ("elevated_switch_display_msg", ""), + ("Choose Display Behavior", ""), + ("Switch Display", ""), + ("Open in New Window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index c8abc4e1b..46b0dc63c 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -558,5 +558,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Filter by intersection", ""), ("Remove wallpaper during incoming sessions", ""), ("Test", ""), + ("switch_display_elevated_connections_tip", ""), + ("display_is_plugged_out_msg", ""), + ("No displays", ""), + ("elevated_switch_display_msg", ""), + ("Choose Display Behavior", ""), + ("Switch Display", ""), + ("Open in New Window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fa.rs b/src/lang/fa.rs index e5b20d39c..802b6473c 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -558,5 +558,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Filter by intersection", ""), ("Remove wallpaper during incoming sessions", ""), ("Test", ""), + ("switch_display_elevated_connections_tip", ""), + ("display_is_plugged_out_msg", ""), + ("No displays", ""), + ("elevated_switch_display_msg", ""), + ("Choose Display Behavior", ""), + ("Switch Display", ""), + ("Open in New Window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index 075167af1..bec161910 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -558,5 +558,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Filter by intersection", ""), ("Remove wallpaper during incoming sessions", ""), ("Test", ""), + ("switch_display_elevated_connections_tip", ""), + ("display_is_plugged_out_msg", ""), + ("No displays", ""), + ("elevated_switch_display_msg", ""), + ("Choose Display Behavior", ""), + ("Switch Display", ""), + ("Open in New Window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index 3c3e50205..9fb02464e 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -558,5 +558,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Filter by intersection", ""), ("Remove wallpaper during incoming sessions", ""), ("Test", ""), + ("switch_display_elevated_connections_tip", ""), + ("display_is_plugged_out_msg", ""), + ("No displays", ""), + ("elevated_switch_display_msg", ""), + ("Choose Display Behavior", ""), + ("Switch Display", ""), + ("Open in New Window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index d162971b9..ac6b1276d 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -558,5 +558,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Filter by intersection", ""), ("Remove wallpaper during incoming sessions", ""), ("Test", ""), + ("switch_display_elevated_connections_tip", ""), + ("display_is_plugged_out_msg", ""), + ("No displays", ""), + ("elevated_switch_display_msg", ""), + ("Choose Display Behavior", ""), + ("Switch Display", ""), + ("Open in New Window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index d21870c26..8d6f94b00 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -559,5 +559,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Filter by intersection", "Filtra per incrocio"), ("Remove wallpaper during incoming sessions", ""), ("Test", ""), + ("switch_display_elevated_connections_tip", ""), + ("display_is_plugged_out_msg", ""), + ("No displays", ""), + ("elevated_switch_display_msg", ""), + ("Choose Display Behavior", ""), + ("Switch Display", ""), + ("Open in New Window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index 61dafdb34..7bad54921 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -558,5 +558,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Filter by intersection", ""), ("Remove wallpaper during incoming sessions", ""), ("Test", ""), + ("switch_display_elevated_connections_tip", ""), + ("display_is_plugged_out_msg", ""), + ("No displays", ""), + ("elevated_switch_display_msg", ""), + ("Choose Display Behavior", ""), + ("Switch Display", ""), + ("Open in New Window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index f4c9076ca..5d0b7eab7 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -558,5 +558,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Filter by intersection", ""), ("Remove wallpaper during incoming sessions", ""), ("Test", ""), + ("switch_display_elevated_connections_tip", ""), + ("display_is_plugged_out_msg", ""), + ("No displays", ""), + ("elevated_switch_display_msg", ""), + ("Choose Display Behavior", ""), + ("Switch Display", ""), + ("Open in New Window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/kz.rs b/src/lang/kz.rs index 20efc25c4..24f97be4b 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -558,5 +558,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Filter by intersection", ""), ("Remove wallpaper during incoming sessions", ""), ("Test", ""), + ("switch_display_elevated_connections_tip", ""), + ("display_is_plugged_out_msg", ""), + ("No displays", ""), + ("elevated_switch_display_msg", ""), + ("Choose Display Behavior", ""), + ("Switch Display", ""), + ("Open in New Window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lt.rs b/src/lang/lt.rs index c360d3ecb..b35f33498 100644 --- a/src/lang/lt.rs +++ b/src/lang/lt.rs @@ -558,5 +558,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Filter by intersection", ""), ("Remove wallpaper during incoming sessions", ""), ("Test", ""), + ("switch_display_elevated_connections_tip", ""), + ("display_is_plugged_out_msg", ""), + ("No displays", ""), + ("elevated_switch_display_msg", ""), + ("Choose Display Behavior", ""), + ("Switch Display", ""), + ("Open in New Window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lv.rs b/src/lang/lv.rs index 9d72e407a..ffa33d061 100644 --- a/src/lang/lv.rs +++ b/src/lang/lv.rs @@ -558,5 +558,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Filter by intersection", "Filtrēt pēc krustpunkta"), ("Remove wallpaper during incoming sessions", ""), ("Test", ""), + ("switch_display_elevated_connections_tip", ""), + ("display_is_plugged_out_msg", ""), + ("No displays", ""), + ("elevated_switch_display_msg", ""), + ("Choose Display Behavior", ""), + ("Switch Display", ""), + ("Open in New Window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nl.rs b/src/lang/nl.rs index 3b30015c3..f67b3dd23 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -558,5 +558,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Filter by intersection", ""), ("Remove wallpaper during incoming sessions", ""), ("Test", ""), + ("switch_display_elevated_connections_tip", ""), + ("display_is_plugged_out_msg", ""), + ("No displays", ""), + ("elevated_switch_display_msg", ""), + ("Choose Display Behavior", ""), + ("Switch Display", ""), + ("Open in New Window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pl.rs b/src/lang/pl.rs index 77b3204b8..81c23ef84 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -558,5 +558,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Filter by intersection", ""), ("Remove wallpaper during incoming sessions", ""), ("Test", ""), + ("switch_display_elevated_connections_tip", ""), + ("display_is_plugged_out_msg", ""), + ("No displays", ""), + ("elevated_switch_display_msg", ""), + ("Choose Display Behavior", ""), + ("Switch Display", ""), + ("Open in New Window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index 93d20600b..360cfd4fa 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -558,5 +558,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Filter by intersection", ""), ("Remove wallpaper during incoming sessions", ""), ("Test", ""), + ("switch_display_elevated_connections_tip", ""), + ("display_is_plugged_out_msg", ""), + ("No displays", ""), + ("elevated_switch_display_msg", ""), + ("Choose Display Behavior", ""), + ("Switch Display", ""), + ("Open in New Window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index 62e2ef812..8df3e9ea8 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -558,5 +558,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Filter by intersection", ""), ("Remove wallpaper during incoming sessions", ""), ("Test", ""), + ("switch_display_elevated_connections_tip", ""), + ("display_is_plugged_out_msg", ""), + ("No displays", ""), + ("elevated_switch_display_msg", ""), + ("Choose Display Behavior", ""), + ("Switch Display", ""), + ("Open in New Window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ro.rs b/src/lang/ro.rs index 6d755a802..1822d79f2 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -558,5 +558,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Filter by intersection", ""), ("Remove wallpaper during incoming sessions", ""), ("Test", ""), + ("switch_display_elevated_connections_tip", ""), + ("display_is_plugged_out_msg", ""), + ("No displays", ""), + ("elevated_switch_display_msg", ""), + ("Choose Display Behavior", ""), + ("Switch Display", ""), + ("Open in New Window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index 22e920b8e..a0be18469 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -558,5 +558,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Filter by intersection", "Фильтровать по пересечению"), ("Remove wallpaper during incoming sessions", ""), ("Test", ""), + ("switch_display_elevated_connections_tip", ""), + ("display_is_plugged_out_msg", ""), + ("No displays", ""), + ("elevated_switch_display_msg", ""), + ("Choose Display Behavior", ""), + ("Switch Display", ""), + ("Open in New Window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index 3f0ad7bb3..75bb447f4 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -558,5 +558,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Filter by intersection", ""), ("Remove wallpaper during incoming sessions", ""), ("Test", ""), + ("switch_display_elevated_connections_tip", ""), + ("display_is_plugged_out_msg", ""), + ("No displays", ""), + ("elevated_switch_display_msg", ""), + ("Choose Display Behavior", ""), + ("Switch Display", ""), + ("Open in New Window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sl.rs b/src/lang/sl.rs index 4d05f7e1b..5f9f8d5f3 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -558,5 +558,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Filter by intersection", ""), ("Remove wallpaper during incoming sessions", ""), ("Test", ""), + ("switch_display_elevated_connections_tip", ""), + ("display_is_plugged_out_msg", ""), + ("No displays", ""), + ("elevated_switch_display_msg", ""), + ("Choose Display Behavior", ""), + ("Switch Display", ""), + ("Open in New Window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sq.rs b/src/lang/sq.rs index ce69547d3..3fef0845d 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -558,5 +558,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Filter by intersection", ""), ("Remove wallpaper during incoming sessions", ""), ("Test", ""), + ("switch_display_elevated_connections_tip", ""), + ("display_is_plugged_out_msg", ""), + ("No displays", ""), + ("elevated_switch_display_msg", ""), + ("Choose Display Behavior", ""), + ("Switch Display", ""), + ("Open in New Window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sr.rs b/src/lang/sr.rs index d1e5cd206..3382ac2b6 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -558,5 +558,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Filter by intersection", ""), ("Remove wallpaper during incoming sessions", ""), ("Test", ""), + ("switch_display_elevated_connections_tip", ""), + ("display_is_plugged_out_msg", ""), + ("No displays", ""), + ("elevated_switch_display_msg", ""), + ("Choose Display Behavior", ""), + ("Switch Display", ""), + ("Open in New Window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sv.rs b/src/lang/sv.rs index 7deaac905..eca0c4f19 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -558,5 +558,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Filter by intersection", ""), ("Remove wallpaper during incoming sessions", ""), ("Test", ""), + ("switch_display_elevated_connections_tip", ""), + ("display_is_plugged_out_msg", ""), + ("No displays", ""), + ("elevated_switch_display_msg", ""), + ("Choose Display Behavior", ""), + ("Switch Display", ""), + ("Open in New Window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index f3187966e..256bb7d76 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -558,5 +558,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Filter by intersection", ""), ("Remove wallpaper during incoming sessions", ""), ("Test", ""), + ("switch_display_elevated_connections_tip", ""), + ("display_is_plugged_out_msg", ""), + ("No displays", ""), + ("elevated_switch_display_msg", ""), + ("Choose Display Behavior", ""), + ("Switch Display", ""), + ("Open in New Window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/th.rs b/src/lang/th.rs index 2b2f63d34..5477cb666 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -558,5 +558,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Filter by intersection", ""), ("Remove wallpaper during incoming sessions", ""), ("Test", ""), + ("switch_display_elevated_connections_tip", ""), + ("display_is_plugged_out_msg", ""), + ("No displays", ""), + ("elevated_switch_display_msg", ""), + ("Choose Display Behavior", ""), + ("Switch Display", ""), + ("Open in New Window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index 69de33892..c8d323d8a 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -558,5 +558,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Filter by intersection", ""), ("Remove wallpaper during incoming sessions", ""), ("Test", ""), + ("switch_display_elevated_connections_tip", ""), + ("display_is_plugged_out_msg", ""), + ("No displays", ""), + ("elevated_switch_display_msg", ""), + ("Choose Display Behavior", ""), + ("Switch Display", ""), + ("Open in New Window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index 15c404937..ad37066f3 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -558,5 +558,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Filter by intersection", ""), ("Remove wallpaper during incoming sessions", ""), ("Test", ""), + ("switch_display_elevated_connections_tip", ""), + ("display_is_plugged_out_msg", ""), + ("No displays", ""), + ("elevated_switch_display_msg", ""), + ("Choose Display Behavior", ""), + ("Switch Display", ""), + ("Open in New Window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ua.rs b/src/lang/ua.rs index d3b3b9181..7b510a224 100644 --- a/src/lang/ua.rs +++ b/src/lang/ua.rs @@ -558,5 +558,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Filter by intersection", ""), ("Remove wallpaper during incoming sessions", ""), ("Test", ""), + ("switch_display_elevated_connections_tip", ""), + ("display_is_plugged_out_msg", ""), + ("No displays", ""), + ("elevated_switch_display_msg", ""), + ("Choose Display Behavior", ""), + ("Switch Display", ""), + ("Open in New Window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index e29da9ae0..309d34f7a 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -558,5 +558,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Filter by intersection", ""), ("Remove wallpaper during incoming sessions", ""), ("Test", ""), + ("switch_display_elevated_connections_tip", ""), + ("display_is_plugged_out_msg", ""), + ("No displays", ""), + ("elevated_switch_display_msg", ""), + ("Choose Display Behavior", ""), + ("Switch Display", ""), + ("Open in New Window", ""), ].iter().cloned().collect(); } diff --git a/src/server.rs b/src/server.rs index c296e60be..0e1abfd55 100644 --- a/src/server.rs +++ b/src/server.rs @@ -26,7 +26,7 @@ use hbb_common::{ }; #[cfg(not(any(target_os = "android", target_os = "ios")))] use service::ServiceTmpl; -use service::{GenericService, Service, Subscriber}; +use service::{EmptyExtraFieldService, GenericService, Service, Subscriber}; use crate::ipc::Data; @@ -53,6 +53,7 @@ pub const NAME_POS: &'static str = ""; } mod connection; +pub mod display_service; #[cfg(windows)] pub mod portable_service; mod service; @@ -80,7 +81,7 @@ lazy_static::lazy_static! { pub struct Server { connections: ConnMap, - services: HashMap<&'static str, Box>, + services: HashMap>, id_count: i32, } @@ -94,11 +95,15 @@ pub fn new() -> ServerPtr { id_count: hbb_common::rand::random::() % 1000 + 1000, // ensure positive }; server.add_service(Box::new(audio_service::new())); - server.add_service(Box::new(video_service::new())); + #[cfg(not(any(target_os = "android", target_os = "ios")))] + server.add_service(Box::new(display_service::new())); + server.add_service(Box::new(video_service::new( + *display_service::PRIMARY_DISPLAY_IDX, + ))); #[cfg(not(any(target_os = "android", target_os = "ios")))] { server.add_service(Box::new(clipboard_service::new())); - if !video_service::capture_cursor_embedded() { + if !display_service::capture_cursor_embedded() { server.add_service(Box::new(input_service::new_cursor())); server.add_service(Box::new(input_service::new_pos())); } @@ -253,9 +258,19 @@ async fn create_relay_connection_( } impl Server { + fn is_video_service_name(name: &str) -> bool { + name.starts_with(video_service::NAME) + } + pub fn add_connection(&mut self, conn: ConnInner, noperms: &Vec<&'static str>) { + let primary_video_service_name = + video_service::get_service_name(*display_service::PRIMARY_DISPLAY_IDX); for s in self.services.values() { - if !noperms.contains(&s.name()) { + let name = s.name(); + if Self::is_video_service_name(&name) && name != primary_video_service_name { + continue; + } + if !noperms.contains(&(&name as _)) { s.on_subscribe(conn.clone()); } } @@ -287,8 +302,12 @@ impl Server { self.services.insert(name, service); } + pub fn contains(&self, name: &str) -> bool { + self.services.contains_key(name) + } + pub fn subscribe(&mut self, name: &str, conn: ConnInner, sub: bool) { - if let Some(s) = self.services.get(&name) { + if let Some(s) = self.services.get(name) { if s.is_subed(conn.id()) == sub { return; } @@ -305,6 +324,47 @@ impl Server { self.id_count += 1; self.id_count } + + pub fn set_video_service_opt(&self, display: Option, opt: &str, value: &str) { + for (k, v) in self.services.iter() { + if let Some(display) = display { + if k != &video_service::get_service_name(display) { + continue; + } + } + + if Self::is_video_service_name(k) { + v.set_option(opt, value); + } + } + } + + fn capture_displays( + &mut self, + conn: ConnInner, + displays: &[usize], + include: bool, + exclude: bool, + ) { + let displays = displays + .iter() + .map(|d| video_service::get_service_name(*d)) + .collect::>(); + let keys = self.services.keys().cloned().collect::>(); + for name in keys.iter() { + if Self::is_video_service_name(&name) { + if displays.contains(&name) { + if include { + self.subscribe(&name, conn.clone(), true); + } + } else { + if exclude { + self.subscribe(&name, conn.clone(), false); + } + } + } + } + } } impl Drop for Server { diff --git a/src/server/audio_service.rs b/src/server/audio_service.rs index ac3cd6b95..98aa7fa31 100644 --- a/src/server/audio_service.rs +++ b/src/server/audio_service.rs @@ -24,16 +24,16 @@ static RESTARTING: AtomicBool = AtomicBool::new(false); #[cfg(not(any(target_os = "linux", target_os = "android")))] pub fn new() -> GenericService { - let sp = GenericService::new(NAME, true); - sp.repeat::(33, cpal_impl::run); - sp + let svc = EmptyExtraFieldService::new(NAME.to_owned(), true); + GenericService::repeat::(&svc.clone(), 33, cpal_impl::run); + svc.sp } #[cfg(any(target_os = "linux", target_os = "android"))] pub fn new() -> GenericService { - let sp = GenericService::new(NAME, true); - sp.run(pa_impl::run); - sp + let svc = EmptyExtraFieldService::new(NAME.to_owned(), true); + GenericService::run(&svc.clone(), pa_impl::run); + svc.sp } pub fn restart() { @@ -48,7 +48,7 @@ pub fn restart() { mod pa_impl { use super::*; #[tokio::main(flavor = "current_thread")] - pub async fn run(sp: GenericService) -> ResultType<()> { + pub async fn run(sp: EmptyExtraFieldService) -> ResultType<()> { hbb_common::sleep(0.1).await; // one moment to wait for _pa ipc RESTARTING.store(false, Ordering::SeqCst); #[cfg(target_os = "linux")] @@ -125,7 +125,7 @@ mod cpal_impl { } } - pub fn run(sp: GenericService, state: &mut State) -> ResultType<()> { + pub fn run(sp: EmptyExtraFieldService, state: &mut State) -> ResultType<()> { sp.snapshot(|sps| { match &state.stream { None => { diff --git a/src/server/clipboard_service.rs b/src/server/clipboard_service.rs index 25597d11c..be521b152 100644 --- a/src/server/clipboard_service.rs +++ b/src/server/clipboard_service.rs @@ -28,12 +28,12 @@ impl super::service::Reset for State { } pub fn new() -> GenericService { - let sp = GenericService::new(NAME, true); - sp.repeat::(INTERVAL, run); - sp + let svc = EmptyExtraFieldService::new(NAME.to_owned(), true); + GenericService::repeat::(&svc.clone(), INTERVAL, run); + svc.sp } -fn run(sp: GenericService, state: &mut State) -> ResultType<()> { +fn run(sp: EmptyExtraFieldService, state: &mut State) -> ResultType<()> { if let Some(ctx) = state.ctx.as_mut() { if let Some(msg) = check_clipboard(ctx, None) { sp.send(msg); diff --git a/src/server/connection.rs b/src/server/connection.rs index fa2b8261b..990ee733c 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -15,7 +15,7 @@ use crate::{ new_voice_call_request, new_voice_call_response, start_audio_thread, MediaData, MediaSender, }, common::{get_default_sound_input, set_sound_input}, - video_service, + display_service, video_service, }; #[cfg(any(target_os = "android", target_os = "ios"))] use crate::{common::DEVICE_NAME, flutter::connection_manager::start_channel}; @@ -160,6 +160,7 @@ pub enum AuthConnType { pub struct Connection { inner: ConnInner, + display_idx: usize, stream: super::Stream, server: super::ServerPtrWeak, hash: Hash, @@ -303,6 +304,7 @@ impl Connection { tx: Some(tx), tx_video: Some(tx_video), }, + display_idx: *display_service::PRIMARY_DISPLAY_IDX, stream, server, hash, @@ -609,6 +611,9 @@ impl Connection { _ => {}, } } + Some(message::Union::PeerInfo(_)) => { + conn.refresh_video_display(None); + } _ => {} } if let Err(err) = conn.stream.send(msg).await { @@ -1098,19 +1103,20 @@ impl Connection { pi.username = username; pi.sas_enabled = sas_enabled; pi.features = Some(Features { - privacy_mode: video_service::is_privacy_mode_supported(), + privacy_mode: display_service::is_privacy_mode_supported(), ..Default::default() }) .into(); - // `try_reset_current_display` is needed because `get_displays` may change the current display, - // which may cause the mismatch of current display and the current display name. - #[cfg(not(any(target_os = "android", target_os = "ios")))] - video_service::try_reset_current_display(); #[cfg(not(any(target_os = "android", target_os = "ios")))] { pi.resolutions = Some(SupportedResolutions { - resolutions: video_service::get_current_display() - .map(|(_, _, d)| crate::platform::resolutions(&d.name())) + resolutions: display_service::try_get_displays() + .map(|displays| { + displays + .get(self.display_idx) + .map(|d| crate::platform::resolutions(&d.name())) + .unwrap_or(vec![]) + }) .unwrap_or(vec![]), ..Default::default() }) @@ -1126,16 +1132,15 @@ impl Connection { self.send(msg_out).await; } - match super::video_service::get_displays().await { + match super::display_service::get_displays().await { Err(err) => { res.set_error(format!("{}", err)); } - Ok((current, displays)) => { + Ok(displays) => { pi.displays = displays.clone(); - pi.current_display = current as _; + pi.current_display = self.display_idx as _; res.set_peer_info(pi); sub_service = true; - *super::video_service::LAST_SYNC_DISPLAYS.write().unwrap() = displays; } } Self::on_remote_authorized(); @@ -1289,6 +1294,8 @@ impl Connection { #[inline] #[cfg(not(any(target_os = "android", target_os = "ios")))] fn input_key(&self, msg: KeyEvent, press: bool) { + // to-do: if is the legacy mode, and the key is function key "LockScreen". + // Switch to the primary display. self.tx_input.send(MessageInput::Key((msg, press))).ok(); } @@ -1945,15 +1952,13 @@ impl Connection { }, Some(message::Union::Misc(misc)) => match misc.union { Some(misc::Union::SwitchDisplay(s)) => { - video_service::switch_display(s.display).await; - #[cfg(not(any(target_os = "android", target_os = "ios")))] - if s.width != 0 && s.height != 0 { - self.change_resolution(&Resolution { - width: s.width, - height: s.height, - ..Default::default() - }); - } + self.handle_switch_display(s).await; + } + Some(misc::Union::CaptureDisplays(displays)) => { + let add = displays.add.iter().map(|d| *d as usize).collect::>(); + let sub = displays.sub.iter().map(|d| *d as usize).collect::>(); + let set = displays.set.iter().map(|d| *d as usize).collect::>(); + self.capture_displays(&add, &sub, &set).await; } Some(misc::Union::ChatMessage(c)) => { self.send_to_cm(ipc::Data::ChatMessage { text: c.text }); @@ -1965,10 +1970,14 @@ impl Connection { } Some(misc::Union::RefreshVideo(r)) => { if r { - super::video_service::refresh(); + self.refresh_video_display(None); } self.update_auto_disconnect_timer(); } + Some(misc::Union::RefreshVideoDisplay(display)) => { + self.refresh_video_display(Some(display as usize)); + self.update_auto_disconnect_timer(); + } Some(misc::Union::VideoReceived(_)) => { video_service::notify_video_frame_fetched( self.inner.id, @@ -2084,6 +2093,75 @@ impl Connection { true } + fn refresh_video_display(&self, display: Option) { + video_service::refresh(); + self.server.upgrade().map(|s| { + s.read().unwrap().set_video_service_opt( + display, + video_service::OPTION_REFRESH, + super::service::SERVICE_OPTION_VALUE_TRUE, + ) + }); + } + + async fn handle_switch_display(&mut self, s: SwitchDisplay) { + #[cfg(windows)] + if portable_client::running() + && *CONN_COUNT.lock().unwrap() > 1 + && s.display != (*display_service::PRIMARY_DISPLAY_IDX as i32) + { + log::info!("Switch to non-primary display is not supported in the elevated mode when there are multiple connections."); + let mut msg_out = Message::new(); + let res = MessageBox { + msgtype: "nook-nocancel-hasclose".to_owned(), + title: "Prompt".to_owned(), + text: "switch_display_elevated_connections_tip".to_owned(), + link: "".to_owned(), + ..Default::default() + }; + msg_out.set_message_box(res); + self.send(msg_out).await; + return; + } + + let display_idx = s.display as usize; + if self.display_idx != display_idx { + if let Some(server) = self.server.upgrade() { + self.switch_display_to(display_idx, server.clone()); + + #[cfg(not(any(target_os = "android", target_os = "ios")))] + if s.width != 0 && s.height != 0 { + self.change_resolution(&Resolution { + width: s.width, + height: s.height, + ..Default::default() + }); + } + + // send display changed message + if let Some(msg_out) = + video_service::make_display_changed_msg(self.display_idx, None) + { + self.send(msg_out).await; + } + } + } + } + + fn switch_display_to(&mut self, display_idx: usize, server: Arc>) { + let new_service_name = video_service::get_service_name(display_idx); + let old_service_name = video_service::get_service_name(self.display_idx); + let mut lock = server.write().unwrap(); + if display_idx != *display_service::PRIMARY_DISPLAY_IDX { + if !lock.contains(&new_service_name) { + lock.add_service(Box::new(video_service::new(display_idx))); + } + } + lock.subscribe(&old_service_name, self.inner.clone(), false); + lock.subscribe(&new_service_name, self.inner.clone(), true); + self.display_idx = display_idx; + } + #[cfg(windows)] async fn handle_elevation_request(&mut self, para: portable_client::StartPara) { let mut err; @@ -2106,36 +2184,64 @@ impl Connection { self.update_auto_disconnect_timer(); } + async fn capture_displays(&mut self, add: &[usize], sub: &[usize], set: &[usize]) { + if let Some(sever) = self.server.upgrade() { + let mut lock = sever.write().unwrap(); + for display in add.iter() { + let service_name = video_service::get_service_name(*display); + if !lock.contains(&service_name) { + lock.add_service(Box::new(video_service::new(*display))); + } + } + for display in set.iter() { + let service_name = video_service::get_service_name(*display); + if !lock.contains(&service_name) { + lock.add_service(Box::new(video_service::new(*display))); + } + } + if !add.is_empty() { + lock.capture_displays(self.inner.clone(), add, true, false); + } else if !sub.is_empty() { + lock.capture_displays(self.inner.clone(), sub, false, true); + } else { + lock.capture_displays(self.inner.clone(), set, true, true); + } + drop(lock); + } + } + #[cfg(not(any(target_os = "android", target_os = "ios")))] fn change_resolution(&mut self, r: &Resolution) { if self.keyboard { - if let Ok((_, _, display)) = video_service::get_current_display() { - let name = display.name(); - #[cfg(all(windows, feature = "virtual_display_driver"))] - if let Some(_ok) = - crate::virtual_display_manager::change_resolution_if_is_virtual_display( + if let Ok(displays) = display_service::try_get_displays() { + if let Some(display) = displays.get(self.display_idx) { + let name = display.name(); + #[cfg(all(windows, feature = "virtual_display_driver"))] + if let Some(_ok) = + crate::virtual_display_manager::change_resolution_if_is_virtual_display( + &name, + r.width as _, + r.height as _, + ) + { + return; + } + display_service::set_last_changed_resolution( &name, - r.width as _, - r.height as _, - ) - { - return; - } - video_service::set_last_changed_resolution( - &name, - (display.width() as _, display.height() as _), - (r.width, r.height), - ); - if let Err(e) = - crate::platform::change_resolution(&name, r.width as _, r.height as _) - { - log::error!( - "Failed to change resolution '{}' to ({},{}):{:?}", - &name, - r.width, - r.height, - e + (display.width() as _, display.height() as _), + (r.width, r.height), ); + if let Err(e) = + crate::platform::change_resolution(&name, r.width as _, r.height as _) + { + log::error!( + "Failed to change resolution '{}' to ({},{}):{:?}", + &name, + r.width, + r.height, + e + ); + } } } } @@ -2281,7 +2387,7 @@ impl Connection { if self.keyboard { match q { BoolOption::Yes => { - let msg_out = if !video_service::is_privacy_mode_supported() { + let msg_out = if !display_service::is_privacy_mode_supported() { crate::common::make_privacy_mode_msg_with_details( back_notification::PrivacyModeState::PrvNotSupported, "Unsupported. 1 Multi-screen is not supported. 2 Please confirm the license is activated.".to_string(), @@ -2289,8 +2395,11 @@ impl Connection { } else { match privacy_mode::turn_on_privacy(self.inner.id) { Ok(true) => { - let err_msg = - video_service::test_create_capturer(self.inner.id, 5_000); + let err_msg = video_service::test_create_capturer( + self.inner.id, + self.display_idx, + 5_000, + ); if err_msg.is_empty() { video_service::set_privacy_mode_conn_id(self.inner.id); crate::common::make_privacy_mode_msg( @@ -2326,7 +2435,7 @@ impl Connection { self.send(msg_out).await; } BoolOption::No => { - let msg_out = if !video_service::is_privacy_mode_supported() { + let msg_out = if !display_service::is_privacy_mode_supported() { crate::common::make_privacy_mode_msg_with_details( back_notification::PrivacyModeState::PrvNotSupported, "Unsupported. 1 Multi-screen is not supported. 2 Please confirm the license is activated.".to_string(), @@ -2801,11 +2910,11 @@ mod raii { active_conns_lock.retain(|&c| c != self.0); #[cfg(not(any(target_os = "android", target_os = "ios")))] if active_conns_lock.is_empty() { - video_service::reset_resolutions(); + display_service::reset_resolutions(); } #[cfg(all(windows, feature = "virtual_display_driver"))] if active_conns_lock.is_empty() { - video_service::try_plug_out_virtual_display(); + display_service::try_plug_out_virtual_display(); } #[cfg(all(windows))] if active_conns_lock.is_empty() { diff --git a/src/server/display_service.rs b/src/server/display_service.rs new file mode 100644 index 000000000..dd4410312 --- /dev/null +++ b/src/server/display_service.rs @@ -0,0 +1,262 @@ +use super::*; +#[cfg(all(windows, feature = "virtual_display_driver"))] +use crate::virtual_display_manager; +#[cfg(windows)] +use hbb_common::get_version_number; +use hbb_common::protobuf::MessageField; +use scrap::Display; + +pub const NAME: &'static str = "display"; + +struct ChangedResolution { + original: (i32, i32), + changed: (i32, i32), +} + +lazy_static::lazy_static! { + static ref IS_CAPTURER_MAGNIFIER_SUPPORTED: bool = is_capturer_mag_supported(); + static ref CHANGED_RESOLUTIONS: Arc>> = Default::default(); + // Initial primary display index. + // It should only be updated when the rustdesk server is started, and should not be updated when displays changed. + pub static ref PRIMARY_DISPLAY_IDX: usize = get_primary(); +} + +#[inline] +pub fn set_last_changed_resolution(display_name: &str, original: (i32, i32), changed: (i32, i32)) { + let mut lock = CHANGED_RESOLUTIONS.write().unwrap(); + match lock.get_mut(display_name) { + Some(res) => res.changed = changed, + None => { + lock.insert( + display_name.to_owned(), + ChangedResolution { original, changed }, + ); + } + } +} + +#[inline] +#[cfg(not(any(target_os = "android", target_os = "ios")))] +pub fn reset_resolutions() { + for (name, res) in CHANGED_RESOLUTIONS.read().unwrap().iter() { + let (w, h) = res.original; + if let Err(e) = crate::platform::change_resolution(name, w as _, h as _) { + log::error!( + "Failed to reset resolution of display '{}' to ({},{}): {}", + name, + w, + h, + e + ); + } + } +} + +#[inline] +fn is_capturer_mag_supported() -> bool { + #[cfg(windows)] + return scrap::CapturerMag::is_supported(); + #[cfg(not(windows))] + false +} + +#[inline] +pub fn capture_cursor_embedded() -> bool { + scrap::is_cursor_embedded() +} + +#[inline] +pub fn is_privacy_mode_supported() -> bool { + #[cfg(windows)] + return *IS_CAPTURER_MAGNIFIER_SUPPORTED + && get_version_number(&crate::VERSION) > get_version_number("1.1.9"); + #[cfg(not(windows))] + return false; +} + +#[derive(Default)] +struct StateDisplay { + synced_displays: Vec, +} + +impl super::service::Reset for StateDisplay { + fn reset(&mut self) { + self.synced_displays.clear(); + } +} + +pub fn new() -> GenericService { + let svc = EmptyExtraFieldService::new(NAME.to_owned(), false); + GenericService::repeat::(&svc.clone(), 300, run); + svc.sp +} + +fn check_get_displays_changed_msg(last_synced_displays: &mut Vec) -> Option { + let displays = try_get_displays().ok()?; + if displays.len() == last_synced_displays.len() { + return None; + } + + // Display to DisplayInfo + let displays = to_display_info(&displays); + if last_synced_displays.len() == 0 { + *last_synced_displays = displays; + None + } else { + let mut pi = PeerInfo { + ..Default::default() + }; + pi.displays = displays.clone(); + pi.current_display = 0; + let mut msg_out = Message::new(); + msg_out.set_peer_info(pi); + *last_synced_displays = displays; + Some(msg_out) + } +} + +#[cfg(all(windows, feature = "virtual_display_driver"))] +pub fn try_plug_out_virtual_display() { + let _res = virtual_display_manager::plug_out_headless(); +} + +fn run(sp: EmptyExtraFieldService, state: &mut StateDisplay) -> ResultType<()> { + if let Some(msg_out) = check_get_displays_changed_msg(&mut state.synced_displays) { + sp.send(msg_out); + log::info!("Displays changed"); + } + Ok(()) +} + +#[inline] +pub(super) fn get_original_resolution( + display_name: &str, + w: usize, + h: usize, +) -> MessageField { + #[cfg(all(windows, feature = "virtual_display_driver"))] + let is_virtual_display = crate::virtual_display_manager::is_virtual_display(&display_name); + #[cfg(not(all(windows, feature = "virtual_display_driver")))] + let is_virtual_display = false; + Some(if is_virtual_display { + Resolution { + width: 0, + height: 0, + ..Default::default() + } + } else { + let mut changed_resolutions = CHANGED_RESOLUTIONS.write().unwrap(); + let (width, height) = match changed_resolutions.get(display_name) { + Some(res) => { + if res.changed.0 != w as i32 || res.changed.1 != h as i32 { + // If the resolution is changed by third process, remove the record in changed_resolutions. + changed_resolutions.remove(display_name); + (w as _, h as _) + } else { + res.original + } + } + None => (w as _, h as _), + }; + Resolution { + width, + height, + ..Default::default() + } + }) + .into() +} + +pub fn to_display_info(all: &Vec) -> Vec { + all.iter() + .map(|d| { + let display_name = d.name(); + let original_resolution = get_original_resolution(&display_name, d.width(), d.height()); + DisplayInfo { + x: d.origin().0 as _, + y: d.origin().1 as _, + width: d.width() as _, + height: d.height() as _, + name: display_name, + online: d.is_online(), + cursor_embedded: false, + original_resolution, + ..Default::default() + } + }) + .collect::>() +} + +pub fn is_inited_msg() -> Option { + #[cfg(target_os = "linux")] + if !scrap::is_x11() { + return super::wayland::is_inited(); + } + None +} + +pub async fn get_displays() -> ResultType> { + #[cfg(target_os = "linux")] + { + if !scrap::is_x11() { + return super::wayland::get_displays().await; + } + } + Ok(to_display_info(&try_get_displays()?)) +} + +#[inline] +pub fn get_primary() -> usize { + #[cfg(target_os = "linux")] + { + if !scrap::is_x11() { + return match super::wayland::get_primary() { + Ok(n) => n, + Err(_) => 0, + }; + } + } + + try_get_displays().map(|d| get_primary_2(&d)).unwrap_or(0) +} + +#[inline] +pub fn get_primary_2(all: &Vec) -> usize { + all.iter().position(|d| d.is_primary()).unwrap_or(0) +} + +#[inline] +#[cfg(all(windows, feature = "virtual_display_driver"))] +fn no_displays(displays: &Vec) -> bool { + let display_len = displays.len(); + if display_len == 0 { + true + } else if display_len == 1 { + let display = &displays[0]; + let dummy_display_side_max_size = 800; + display.width() <= dummy_display_side_max_size + && display.height() <= dummy_display_side_max_size + } else { + false + } +} + +#[inline] +#[cfg(not(all(windows, feature = "virtual_display_driver")))] +pub fn try_get_displays() -> ResultType> { + Ok(Display::all()?) +} + +#[cfg(all(windows, feature = "virtual_display_driver"))] +pub fn try_get_displays() -> ResultType> { + let mut displays = Display::all()?; + if no_displays(&displays) { + log::debug!("no displays, create virtual display"); + if let Err(e) = virtual_display_manager::plug_in_headless() { + log::error!("plug in headless failed {}", e); + } else { + displays = Display::all()?; + } + } + Ok(displays) +} diff --git a/src/server/input_service.rs b/src/server/input_service.rs index b721149e9..d40bf02c1 100644 --- a/src/server/input_service.rs +++ b/src/server/input_service.rs @@ -17,7 +17,7 @@ use rdev::{self, EventType, Key as RdevKey, KeyCode, RawKey}; use rdev::{CGEventSourceStateID, CGEventTapLocation, VirtualInput}; use std::{ convert::TryFrom, - ops::Sub, + ops::{Deref, DerefMut, Sub}, sync::atomic::{AtomicBool, Ordering}, thread, time::{self, Duration, Instant}, @@ -236,18 +236,43 @@ fn should_disable_numlock(evt: &KeyEvent) -> bool { pub const NAME_CURSOR: &'static str = "mouse_cursor"; pub const NAME_POS: &'static str = "mouse_pos"; -pub type MouseCursorService = ServiceTmpl; +#[derive(Clone)] +pub struct MouseCursorService { + pub sp: ServiceTmpl, +} -pub fn new_cursor() -> MouseCursorService { - let sp = MouseCursorService::new(NAME_CURSOR, true); - sp.repeat::(33, run_cursor); - sp +impl Deref for MouseCursorService { + type Target = ServiceTmpl; + + fn deref(&self) -> &Self::Target { + &self.sp + } +} + +impl DerefMut for MouseCursorService { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.sp + } +} + +impl MouseCursorService { + pub fn new(name: String, need_snapshot: bool) -> Self { + Self { + sp: ServiceTmpl::::new(name, need_snapshot), + } + } +} + +pub fn new_cursor() -> ServiceTmpl { + let svc = MouseCursorService::new(NAME_CURSOR.to_owned(), true); + ServiceTmpl::::repeat::(&svc.clone(), 33, run_cursor); + svc.sp } pub fn new_pos() -> GenericService { - let sp = GenericService::new(NAME_POS, false); - sp.repeat::(33, run_pos); - sp + let svc = EmptyExtraFieldService::new(NAME_POS.to_owned(), false); + GenericService::repeat::(&svc.clone(), 33, run_pos); + svc.sp } #[inline] @@ -258,7 +283,7 @@ fn update_last_cursor_pos(x: i32, y: i32) { } } -fn run_pos(sp: GenericService, state: &mut StatePos) -> ResultType<()> { +fn run_pos(sp: EmptyExtraFieldService, state: &mut StatePos) -> ResultType<()> { let (_, (x, y)) = *LATEST_SYS_CURSOR_POS.lock().unwrap(); if x == INVALID_CURSOR_POS || y == INVALID_CURSOR_POS { return Ok(()); @@ -988,7 +1013,6 @@ pub async fn lock_screen() { crate::platform::lock_screen(); } } - super::video_service::switch_to_primary().await; } pub fn handle_key(evt: &KeyEvent) { diff --git a/src/server/portable_service.rs b/src/server/portable_service.rs index 155f2acb4..7b77c1516 100644 --- a/src/server/portable_service.rs +++ b/src/server/portable_service.rs @@ -25,7 +25,6 @@ use winapi::{ use crate::{ ipc::{self, new_listener, Connection, Data, DataPortableService}, platform::set_path_permission, - video_service::get_current_display, }; use super::video_qos; @@ -224,6 +223,8 @@ mod utils { pub mod server { use hbb_common::message_proto::PointerDeviceEvent; + use crate::display_service; + use super::*; lazy_static::lazy_static! { @@ -324,12 +325,17 @@ pub mod server { continue; } if c.is_none() { - *crate::video_service::CURRENT_DISPLAY.lock().unwrap() = current_display; - let Ok((_, _current, display)) = get_current_display() else { - log::error!("Failed to get current display"); + let Ok(mut displays) = display_service::try_get_displays() else { + log::error!("Failed to get displays"); *EXIT.lock().unwrap() = true; return; }; + if displays.len() <= current_display { + log::error!("Invalid display index:{}", current_display); + *EXIT.lock().unwrap() = true; + return; + } + let display = displays.remove(current_display); display_width = display.width(); display_height = display.height(); match Capturer::new(display, use_yuv) { @@ -522,6 +528,8 @@ pub mod server { pub mod client { use hbb_common::{anyhow::Context, message_proto::PointerDeviceEvent}; + use crate::display_service; + use super::*; lazy_static::lazy_static! { @@ -665,8 +673,8 @@ pub mod client { shmem.write(ADDR_CAPTURE_WOULDBLOCK, &utils::i32_to_vec(TRUE)); } let (mut width, mut height) = (0, 0); - if let Ok((_, current, display)) = get_current_display() { - if current_display == current { + if let Ok(displays) = display_service::try_get_displays() { + if let Some(display) = displays.get(current_display) { width = display.width(); height = display.height(); } @@ -910,7 +918,15 @@ pub mod client { } if portable_service_running { log::info!("Create shared memory capturer"); - return Ok(Box::new(CapturerPortable::new(current_display, use_yuv))); + if current_display == *display_service::PRIMARY_DISPLAY_IDX { + return Ok(Box::new(CapturerPortable::new(current_display, use_yuv))); + } else { + bail!( + "Ignore capture display index: {}, the primary display index is: {}", + current_display, + *display_service::PRIMARY_DISPLAY_IDX + ); + } } else { log::debug!("Create capturer dxgi|gdi"); return Ok(Box::new( diff --git a/src/server/service.rs b/src/server/service.rs index 1cbed5acc..78a637776 100644 --- a/src/server/service.rs +++ b/src/server/service.rs @@ -1,16 +1,19 @@ use super::*; use std::{ collections::HashSet, + ops::{Deref, DerefMut}, thread::{self, JoinHandle}, time, }; pub trait Service: Send + Sync { - fn name(&self) -> &'static str; + fn name(&self) -> String; fn on_subscribe(&self, sub: ConnInner); fn on_unsubscribe(&self, id: i32); fn is_subed(&self, id: i32) -> bool; fn join(&self); + fn get_option(&self, opt: &str) -> Option; + fn set_option(&self, opt: &str, val: &str) -> Option; } pub trait Subscriber: Default + Send + Sync + 'static { @@ -20,12 +23,13 @@ pub trait Subscriber: Default + Send + Sync + 'static { #[derive(Default)] pub struct ServiceInner> { - name: &'static str, + name: String, handle: Option>, subscribes: HashMap, new_subscribes: HashMap, active: bool, need_snapshot: bool, + options: HashMap, } pub trait Reset { @@ -37,6 +41,35 @@ pub struct ServiceSwap>(ServiceTmpl); pub type GenericService = ServiceTmpl; pub const HIBERNATE_TIMEOUT: u64 = 30; pub const MAX_ERROR_TIMEOUT: u64 = 1_000; +pub const SERVICE_OPTION_VALUE_TRUE: &str = "1"; +pub const SERVICE_OPTION_VALUE_FALSE: &str = "0"; + +#[derive(Clone)] +pub struct EmptyExtraFieldService { + pub sp: GenericService, +} + +impl Deref for EmptyExtraFieldService { + type Target = ServiceTmpl; + + fn deref(&self) -> &Self::Target { + &self.sp + } +} + +impl DerefMut for EmptyExtraFieldService { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.sp + } +} + +impl EmptyExtraFieldService { + pub fn new(name: String, need_snapshot: bool) -> Self { + Self { + sp: GenericService::new(name, need_snapshot), + } + } +} impl> ServiceInner { fn send_new_subscribes(&mut self, msg: Arc) { @@ -60,8 +93,8 @@ impl> ServiceInner { impl> Service for ServiceTmpl { #[inline] - fn name(&self) -> &'static str { - self.0.read().unwrap().name + fn name(&self) -> String { + self.0.read().unwrap().name.clone() } fn is_subed(&self, id: i32) -> bool { @@ -96,6 +129,18 @@ impl> Service for ServiceTmpl { } } } + + fn get_option(&self, opt: &str) -> Option { + self.0.read().unwrap().options.get(opt).cloned() + } + + fn set_option(&self, opt: &str, val: &str) -> Option { + self.0 + .write() + .unwrap() + .options + .insert(opt.to_string(), val.to_string()) + } } impl> Clone for ServiceTmpl { @@ -105,7 +150,7 @@ impl> Clone for ServiceTmpl { } impl> ServiceTmpl { - pub fn new(name: &'static str, need_snapshot: bool) -> Self { + pub fn new(name: String, need_snapshot: bool) -> Self { Self(Arc::new(RwLock::new(ServiceInner:: { name, active: true, @@ -114,6 +159,21 @@ impl> ServiceTmpl { }))) } + #[inline] + pub fn is_option_true(&self, opt: &str) -> bool { + self.get_option(opt) + .map_or(false, |v| v == SERVICE_OPTION_VALUE_TRUE) + } + + #[inline] + pub fn set_option_bool(&self, opt: &str, val: bool) { + if val { + self.set_option(opt, SERVICE_OPTION_VALUE_TRUE); + } else { + self.set_option(opt, SERVICE_OPTION_VALUE_FALSE); + } + } + #[inline] pub fn has_subscribes(&self) -> bool { self.0.read().unwrap().has_subscribes() @@ -189,14 +249,15 @@ impl> ServiceTmpl { } } - pub fn repeat(&self, interval_ms: u64, callback: F) + pub fn repeat(svc: &Svc, interval_ms: u64, callback: F) where - F: 'static + FnMut(Self, &mut S) -> ResultType<()> + Send, + F: 'static + FnMut(Svc, &mut S) -> ResultType<()> + Send, S: 'static + Default + Reset, + Svc: 'static + Clone + Send + DerefMut>, { let interval = time::Duration::from_millis(interval_ms); let mut callback = callback; - let sp = self.clone(); + let sp = svc.clone(); let thread = thread::spawn(move || { let mut state = S::default(); let mut may_reset = false; @@ -223,14 +284,15 @@ impl> ServiceTmpl { } log::info!("Service {} exit", sp.name()); }); - self.0.write().unwrap().handle = Some(thread); + svc.0.write().unwrap().handle = Some(thread); } - pub fn run(&self, callback: F) + pub fn run(svc: &Svc, callback: F) where - F: 'static + FnMut(Self) -> ResultType<()> + Send, + F: 'static + FnMut(Svc) -> ResultType<()> + Send, + Svc: 'static + Clone + Send + DerefMut>, { - let sp = self.clone(); + let sp = svc.clone(); let mut callback = callback; let thread = thread::spawn(move || { let mut error_timeout = HIBERNATE_TIMEOUT; @@ -259,7 +321,7 @@ impl> ServiceTmpl { } log::info!("Service {} exit", sp.name()); }); - self.0.write().unwrap().handle = Some(thread); + svc.0.write().unwrap().handle = Some(thread); } #[inline] diff --git a/src/server/video_service.rs b/src/server/video_service.rs index 07db5e3be..cfeeb1fa8 100644 --- a/src/server/video_service.rs +++ b/src/server/video_service.rs @@ -18,15 +18,11 @@ // to-do: // https://slhck.info/video/2017/03/01/rate-control.html -use super::{video_qos::VideoQoS, *}; -#[cfg(all(windows, feature = "virtual_display_driver"))] -use crate::virtual_display_manager; +use super::{service::ServiceTmpl, video_qos::VideoQoS, *}; #[cfg(windows)] use crate::{platform::windows::is_process_consent_running, privacy_win_mag}; -#[cfg(windows)] -use hbb_common::get_version_number; use hbb_common::{ - protobuf::MessageField, + anyhow::anyhow, tokio::sync::{ mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}, Mutex as TokioMutex, @@ -51,71 +47,18 @@ use std::{ }; pub const NAME: &'static str = "video"; - -struct ChangedResolution { - original: (i32, i32), - changed: (i32, i32), -} +pub const OPTION_DISPLAY_CHANGED: &'static str = "changed"; +pub const OPTION_REFRESH: &'static str = "refresh"; lazy_static::lazy_static! { - pub static ref CURRENT_DISPLAY: Arc> = Arc::new(Mutex::new(usize::MAX)); - static ref LAST_ACTIVE: Arc> = Arc::new(Mutex::new(Instant::now())); - static ref SWITCH: Arc> = Default::default(); static ref FRAME_FETCHED_NOTIFIER: (UnboundedSender<(i32, Option)>, Arc)>>>) = { let (tx, rx) = unbounded_channel(); (tx, Arc::new(TokioMutex::new(rx))) }; static ref PRIVACY_MODE_CONN_ID: Mutex = Mutex::new(0); - static ref IS_CAPTURER_MAGNIFIER_SUPPORTED: bool = is_capturer_mag_supported(); pub static ref VIDEO_QOS: Arc> = Default::default(); pub static ref IS_UAC_RUNNING: Arc> = Default::default(); pub static ref IS_FOREGROUND_WINDOW_ELEVATED: Arc> = Default::default(); - pub static ref LAST_SYNC_DISPLAYS: Arc>> = Default::default(); - static ref CHANGED_RESOLUTIONS: Arc>> = Default::default(); -} - -#[inline] -pub fn set_last_changed_resolution(display_name: &str, original: (i32, i32), changed: (i32, i32)) { - let mut lock = CHANGED_RESOLUTIONS.write().unwrap(); - match lock.get_mut(display_name) { - Some(res) => res.changed = changed, - None => { - lock.insert( - display_name.to_owned(), - ChangedResolution { original, changed }, - ); - } - } -} - -#[inline] -#[cfg(not(any(target_os = "android", target_os = "ios")))] -pub fn reset_resolutions() { - for (name, res) in CHANGED_RESOLUTIONS.read().unwrap().iter() { - let (w, h) = res.original; - if let Err(e) = crate::platform::change_resolution(name, w as _, h as _) { - log::error!( - "Failed to reset resolution of display '{}' to ({},{}): {}", - name, - w, - h, - e - ); - } - } -} - -#[inline] -fn is_capturer_mag_supported() -> bool { - #[cfg(windows)] - return scrap::CapturerMag::is_supported(); - #[cfg(not(windows))] - false -} - -#[inline] -pub fn capture_cursor_embedded() -> bool { - scrap::is_cursor_embedded() } #[inline] @@ -133,15 +76,6 @@ pub fn get_privacy_mode_conn_id() -> i32 { *PRIVACY_MODE_CONN_ID.lock().unwrap() } -#[inline] -pub fn is_privacy_mode_supported() -> bool { - #[cfg(windows)] - return *IS_CAPTURER_MAGNIFIER_SUPPORTED - && get_version_number(&crate::VERSION) > get_version_number("1.1.9"); - #[cfg(not(windows))] - return false; -} - struct VideoFrameController { cur: Instant, send_conn_ids: HashSet, @@ -192,10 +126,37 @@ impl VideoFrameController { } } -pub fn new() -> GenericService { - let sp = GenericService::new(NAME, true); - sp.run(run); - sp +#[derive(Clone)] +pub struct VideoService { + sp: GenericService, + idx: usize, +} + +impl Deref for VideoService { + type Target = ServiceTmpl; + + fn deref(&self) -> &Self::Target { + &self.sp + } +} + +impl DerefMut for VideoService { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.sp + } +} + +pub fn get_service_name(idx: usize) -> String { + format!("{}{}", NAME, idx) +} + +pub fn new(idx: usize) -> GenericService { + let vs = VideoService { + sp: GenericService::new(get_service_name(idx), true), + idx, + }; + GenericService::run(&vs, run); + vs.sp } fn check_display_changed( @@ -221,19 +182,10 @@ fn check_display_changed( if n != last_n { return true; }; - - for (i, d) in displays.iter().enumerate() { - if d.is_primary() { - if i != last_current { - return true; - }; - if d.width() != last_width || d.height() != last_height { - return true; - }; - } + match displays.get(last_current) { + Some(d) => d.width() != last_width || d.height() != last_height, + None => true, } - - return false; } // Capturer object is expensive, avoiding to create it frequently. @@ -317,14 +269,27 @@ fn create_capturer( } // This function works on privacy mode. Windows only for now. -pub fn test_create_capturer(privacy_mode_id: i32, timeout_millis: u64) -> String { +pub fn test_create_capturer( + privacy_mode_id: i32, + display_idx: usize, + timeout_millis: u64, +) -> String { let test_begin = Instant::now(); loop { - let err = match get_current_display() { - Ok((_, current, display)) => { - match create_capturer(privacy_mode_id, display, true, current, false) { - Ok(_) => return "".to_owned(), - Err(e) => e, + let err = match try_get_displays() { + Ok(mut displays) => { + if displays.len() <= display_idx { + anyhow!( + "Failed to get display {}, the displays' count is {}", + display_idx, + displays.len() + ) + } else { + let display = displays.remove(display_idx); + match create_capturer(privacy_mode_id, display, true, display_idx, false) { + Ok(_) => return "".to_owned(), + Err(e) => e, + } } } Err(e) => e, @@ -352,6 +317,7 @@ fn check_uac_switch(privacy_mode_id: i32, capturer_privacy_mode_id: i32) -> Resu } pub(super) struct CapturerInfo { + pub name: String, pub origin: (i32, i32), pub width: usize, pub height: usize, @@ -376,7 +342,11 @@ impl DerefMut for CapturerInfo { } } -fn get_capturer(use_yuv: bool, portable_service_running: bool) -> ResultType { +fn get_capturer( + current: usize, + use_yuv: bool, + portable_service_running: bool, +) -> ResultType { #[cfg(target_os = "linux")] { if !scrap::is_x11() { @@ -384,8 +354,18 @@ fn get_capturer(use_yuv: bool, portable_service_running: bool) -> ResultType ResultType ResultType ResultType Option> { - let displays = try_get_displays().ok()?; - let last_sync_displays = &*LAST_SYNC_DISPLAYS.read().unwrap(); - if displays.len() != last_sync_displays.len() { - // No need to check if the resolutions are changed by third process. - Some(displays) - } else { - None - } -} - -fn check_get_displays_changed_msg() -> Option { - let displays = check_displays_new()?; - // Display to DisplayInfo - let (current, displays) = get_displays_2(&displays); - let mut pi = PeerInfo { - ..Default::default() - }; - pi.displays = displays.clone(); - pi.current_display = current as _; - let mut msg_out = Message::new(); - msg_out.set_peer_info(pi); - *LAST_SYNC_DISPLAYS.write().unwrap() = displays; - Some(msg_out) -} - -#[cfg(all(windows, feature = "virtual_display_driver"))] -pub fn try_plug_out_virtual_display() { - let _res = virtual_display_manager::plug_out_headless(); -} - -fn run(sp: GenericService) -> ResultType<()> { +fn run(vs: VideoService) -> ResultType<()> { #[cfg(not(any(target_os = "android", target_os = "ios")))] let _wake_lock = get_wake_lock(); // ensure_inited() is needed because clear() may be called. + // to-do: wayland ensure_inited should pass current display index. + // But for now, we do not support multi-screen capture on wayland. #[cfg(target_os = "linux")] super::wayland::ensure_inited()?; #[cfg(windows)] @@ -483,7 +435,9 @@ fn run(sp: GenericService) -> ResultType<()> { #[cfg(not(windows))] let last_portable_service_running = false; - let mut c = get_capturer(true, last_portable_service_running)?; + let display_idx = vs.idx; + let sp = vs.sp; + let mut c = get_capturer(display_idx, true, last_portable_service_running)?; let mut video_qos = VIDEO_QOS.lock().unwrap(); video_qos.refresh(None); @@ -506,37 +460,18 @@ fn run(sp: GenericService) -> ResultType<()> { c.set_use_yuv(encoder.use_yuv()); VIDEO_QOS.lock().unwrap().store_bitrate(encoder.bitrate()); - if *SWITCH.lock().unwrap() { - log::debug!("Broadcasting display switch"); - let mut misc = Misc::new(); - let display_name = get_current_display() - .map(|(_, _, d)| d.name()) - .unwrap_or_default(); - let original_resolution = get_original_resolution(&display_name, c.width, c.height); - misc.set_switch_display(SwitchDisplay { - display: c.current as _, - x: c.origin.0 as _, - y: c.origin.1 as _, - width: c.width as _, - height: c.height as _, - cursor_embedded: capture_cursor_embedded(), - #[cfg(not(any(target_os = "android", target_os = "ios")))] - resolutions: Some(SupportedResolutions { - resolutions: if display_name.is_empty() { - vec![] - } else { - crate::platform::resolutions(&display_name) - }, - ..SupportedResolutions::default() - }) - .into(), - original_resolution, - ..Default::default() - }); - let mut msg_out = Message::new(); - msg_out.set_misc(misc); - *SWITCH.lock().unwrap() = false; - sp.send(msg_out); + if sp.is_option_true(OPTION_DISPLAY_CHANGED) { + log::debug!("Broadcasting display changed"); + broadcast_display_changed( + display_idx, + &sp, + Some((c.name.clone(), c.origin.clone(), c.width, c.height)), + ); + sp.set_option_bool(OPTION_DISPLAY_CHANGED, false); + } + + if sp.is_option_true(OPTION_REFRESH) { + sp.set_option_bool(OPTION_REFRESH, false); } let mut frame_controller = VideoFrameController::new(); @@ -572,13 +507,7 @@ fn run(sp: GenericService) -> ResultType<()> { } drop(video_qos); - if *SWITCH.lock().unwrap() { - bail!("SWITCH"); - } - if c.current != *CURRENT_DISPLAY.lock().unwrap() { - #[cfg(target_os = "linux")] - super::wayland::clear(); - *SWITCH.lock().unwrap() = true; + if sp.is_option_true(OPTION_DISPLAY_CHANGED) || sp.is_option_true(OPTION_REFRESH) { bail!("SWITCH"); } if codec_name != Encoder::negotiated_codec() { @@ -604,23 +533,12 @@ fn run(sp: GenericService) -> ResultType<()> { // Capturer on macos does not return Err event the solution is changed. #[cfg(target_os = "macos")] if check_display_changed(c.ndisplay, c.current, c.width, c.height) { + sp.set_option_bool(OPTION_DISPLAY_CHANGED, true); log::info!("Displays changed"); - *SWITCH.lock().unwrap() = true; - bail!("SWITCH"); - } - - if let Some(msg_out) = check_get_displays_changed_msg() { - sp.send(msg_out); - log::info!("Displays changed"); - #[cfg(target_os = "linux")] - super::wayland::clear(); - *SWITCH.lock().unwrap() = true; bail!("SWITCH"); } } - *LAST_ACTIVE.lock().unwrap() = now; - frame_controller.reset(); #[cfg(any(target_os = "android", target_os = "ios"))] @@ -631,8 +549,14 @@ fn run(sp: GenericService) -> ResultType<()> { match frame { scrap::Frame::RAW(data) => { if data.len() != 0 { - let send_conn_ids = - handle_one_frame(&sp, data, ms, &mut encoder, recorder.clone())?; + let send_conn_ids = handle_one_frame( + display_idx, + &sp, + data, + ms, + &mut encoder, + recorder.clone(), + )?; frame_controller.set_send(now, send_conn_ids); } } @@ -649,7 +573,7 @@ fn run(sp: GenericService) -> ResultType<()> { let time = now - start; let ms = (time.as_secs() * 1000 + time.subsec_millis() as u64) as i64; let send_conn_ids = - handle_one_frame(&sp, &frame, ms, &mut encoder, recorder.clone())?; + handle_one_frame(display_idx, &sp, &frame, ms, &mut encoder, recorder.clone())?; frame_controller.set_send(now, send_conn_ids); #[cfg(windows)] { @@ -693,7 +617,7 @@ fn run(sp: GenericService) -> ResultType<()> { log::info!("Displays changed"); #[cfg(target_os = "linux")] super::wayland::clear(); - *SWITCH.lock().unwrap() = true; + sp.set_option_bool(OPTION_DISPLAY_CHANGED, true); bail!("SWITCH"); } @@ -829,6 +753,7 @@ fn check_privacy_mode_changed(sp: &GenericService, privacy_mode_id: i32) -> Resu #[inline] fn handle_one_frame( + display: usize, sp: &GenericService, frame: &[u8], ms: i64, @@ -844,7 +769,10 @@ fn handle_one_frame( })?; let mut send_conn_ids: HashSet = Default::default(); - if let Ok(msg) = encoder.encode_to_message(frame, ms) { + if let Ok(mut vf) = encoder.encode_to_message(frame, ms) { + vf.display = display as _; + let mut msg = Message::new(); + msg.set_video_frame(vf); #[cfg(not(target_os = "ios"))] recorder .lock() @@ -856,69 +784,6 @@ fn handle_one_frame( Ok(send_conn_ids) } -#[inline] -fn get_original_resolution(display_name: &str, w: usize, h: usize) -> MessageField { - #[cfg(all(windows, feature = "virtual_display_driver"))] - let is_virtual_display = crate::virtual_display_manager::is_virtual_display(&display_name); - #[cfg(not(all(windows, feature = "virtual_display_driver")))] - let is_virtual_display = false; - Some(if is_virtual_display { - Resolution { - width: 0, - height: 0, - ..Default::default() - } - } else { - let mut changed_resolutions = CHANGED_RESOLUTIONS.write().unwrap(); - let (width, height) = match changed_resolutions.get(display_name) { - Some(res) => { - if res.changed.0 != w as i32 || res.changed.1 != h as i32 { - // If the resolution is changed by third process, remove the record in changed_resolutions. - changed_resolutions.remove(display_name); - (w as _, h as _) - } else { - res.original - } - } - None => (w as _, h as _), - }; - Resolution { - width, - height, - ..Default::default() - } - }) - .into() -} - -pub(super) fn get_displays_2(all: &Vec) -> (usize, Vec) { - let mut displays = Vec::new(); - let mut primary = 0; - for (i, d) in all.iter().enumerate() { - if d.is_primary() { - primary = i; - } - let display_name = d.name(); - let original_resolution = get_original_resolution(&display_name, d.width(), d.height()); - displays.push(DisplayInfo { - x: d.origin().0 as _, - y: d.origin().1 as _, - width: d.width() as _, - height: d.height() as _, - name: display_name, - online: d.is_online(), - cursor_embedded: false, - original_resolution, - ..Default::default() - }); - } - let mut lock = CURRENT_DISPLAY.lock().unwrap(); - if *lock >= displays.len() { - *lock = primary - } - (*lock, displays) -} - pub fn is_inited_msg() -> Option { #[cfg(target_os = "linux")] if !scrap::is_x11() { @@ -927,65 +792,10 @@ pub fn is_inited_msg() -> Option { None } -// switch to primary display if long time (30 seconds) no users -#[inline] -pub fn try_reset_current_display() { - if LAST_ACTIVE.lock().unwrap().elapsed().as_secs() >= 30 { - *CURRENT_DISPLAY.lock().unwrap() = usize::MAX; - } - *LAST_ACTIVE.lock().unwrap() = time::Instant::now(); -} - -pub async fn get_displays() -> ResultType<(usize, Vec)> { - #[cfg(target_os = "linux")] - { - if !scrap::is_x11() { - return super::wayland::get_displays().await; - } - } - Ok(get_displays_2(&try_get_displays()?)) -} - -pub async fn switch_display(i: i32) { - let i = i as usize; - if let Ok((_, displays)) = get_displays().await { - if i < displays.len() { - *CURRENT_DISPLAY.lock().unwrap() = i; - } - } -} - #[inline] pub fn refresh() { #[cfg(target_os = "android")] Display::refresh_size(); - *SWITCH.lock().unwrap() = true; -} - -fn get_primary() -> usize { - #[cfg(target_os = "linux")] - { - if !scrap::is_x11() { - return match super::wayland::get_primary() { - Ok(n) => n, - Err(_) => 0, - }; - } - } - - if let Ok(all) = try_get_displays() { - for (i, d) in all.iter().enumerate() { - if d.is_primary() { - return i; - } - } - } - 0 -} - -#[inline] -pub async fn switch_to_primary() { - switch_display(get_primary() as _).await; } #[inline] @@ -1034,30 +844,6 @@ fn try_get_displays() -> ResultType> { Ok(Display::all()?) } -pub(super) fn get_current_display_2(mut all: Vec) -> ResultType<(usize, usize, Display)> { - let mut current = *CURRENT_DISPLAY.lock().unwrap() as usize; - if all.len() == 0 { - bail!("No displays"); - } - let n = all.len(); - if current >= n { - current = 0; - for (i, d) in all.iter().enumerate() { - if d.is_primary() { - current = i; - break; - } - } - *CURRENT_DISPLAY.lock().unwrap() = current; - } - return Ok((n, current, all.remove(current))); -} - -#[inline] -pub fn get_current_display() -> ResultType<(usize, usize, Display)> { - get_current_display_2(try_get_displays()?) -} - #[cfg(windows)] fn start_uac_elevation_check() { static START: Once = Once::new(); @@ -1090,3 +876,63 @@ fn get_wake_lock() -> crate::platform::WakeLock { }; crate::platform::WakeLock::new(display, idle, sleep) } + +#[inline] +fn broadcast_display_changed( + display_idx: usize, + sp: &GenericService, + display_meta: Option<(String, (i32, i32), usize, usize)>, +) { + if let Some(msg_out) = make_display_changed_msg(display_idx, display_meta) { + sp.send(msg_out); + } +} + +fn get_display_info_simple_meta(display_idx: usize) -> Option<(String, (i32, i32), usize, usize)> { + let displays = display_service::try_get_displays().ok()?; + if let Some(display) = displays.get(display_idx) { + Some(( + display.name(), + display.origin(), + display.width(), + display.height(), + )) + } else { + None + } +} + +pub fn make_display_changed_msg( + display_idx: usize, + display_meta: Option<(String, (i32, i32), usize, usize)>, +) -> Option { + let mut misc = Misc::new(); + let (name, origin, width, height) = match display_meta { + Some(d) => d, + None => get_display_info_simple_meta(display_idx)?, + }; + let original_resolution = display_service::get_original_resolution(&name, width, height); + misc.set_switch_display(SwitchDisplay { + display: display_idx as _, + x: origin.0, + y: origin.1, + width: width as _, + height: height as _, + cursor_embedded: display_service::capture_cursor_embedded(), + #[cfg(not(any(target_os = "android", target_os = "ios")))] + resolutions: Some(SupportedResolutions { + resolutions: if name.is_empty() { + vec![] + } else { + crate::platform::resolutions(&name) + }, + ..SupportedResolutions::default() + }) + .into(), + original_resolution, + ..Default::default() + }); + let mut msg_out = Message::new(); + msg_out.set_misc(misc); + Some(msg_out) +} diff --git a/src/server/wayland.rs b/src/server/wayland.rs index 44cf8bf4a..f26b27b20 100644 --- a/src/server/wayland.rs +++ b/src/server/wayland.rs @@ -142,9 +142,11 @@ pub(super) async fn check_init() -> ResultType<()> { if *CAP_DISPLAY_INFO.read().unwrap() == 0 { let mut lock = CAP_DISPLAY_INFO.write().unwrap(); if *lock == 0 { - let all = Display::all()?; + let mut all = Display::all()?; let num = all.len(); - let (primary, mut displays) = super::video_service::get_displays_2(&all); + let primary = super::display_service::get_primary_2(&all); + let current = primary; + let mut displays = super::display_service::to_display_info(&all); for display in displays.iter_mut() { display.cursor_embedded = is_cursor_embedded(); } @@ -154,12 +156,11 @@ pub(super) async fn check_init() -> ResultType<()> { rects.push((d.origin(), d.width(), d.height())); } - let (ndisplay, current, display) = - super::video_service::get_current_display_2(all)?; + let display = all.remove(current); let (origin, width, height) = (display.origin(), display.width(), display.height()); log::debug!( "#displays={}, current={}, origin: {:?}, width={}, height={}, cpus={}/{}", - ndisplay, + num, current, &origin, width, @@ -213,16 +214,14 @@ pub(super) async fn check_init() -> ResultType<()> { Ok(()) } -pub(super) async fn get_displays() -> ResultType<(usize, Vec)> { +pub(super) async fn get_displays() -> ResultType> { check_init().await?; let addr = *CAP_DISPLAY_INFO.read().unwrap(); if addr != 0 { let cap_display_info: *const CapDisplayInfo = addr as _; unsafe { let cap_display_info = &*cap_display_info; - let primary = cap_display_info.primary; - let displays = cap_display_info.displays.clone(); - Ok((primary, displays)) + Ok(cap_display_info.displays.clone()) } } else { bail!("Failed to get capturer display info"); @@ -268,6 +267,9 @@ pub(super) fn get_capturer() -> ResultType { let cap_display_info = &*cap_display_info; let rect = cap_display_info.rects[cap_display_info.current]; Ok(super::video_service::CapturerInfo { + name: cap_display_info.displays[cap_display_info.current] + .name + .clone(), origin: rect.0, width: rect.1, height: rect.2, diff --git a/src/ui.rs b/src/ui.rs index 27793b31f..c8f044421 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,7 +1,6 @@ use std::{ collections::HashMap, iter::FromIterator, - process::Child, sync::{Arc, Mutex}, }; @@ -22,7 +21,6 @@ mod cm; pub mod inline; pub mod remote; -pub type Children = Arc)>>; #[allow(dead_code)] type Status = (i32, bool, i64, String); @@ -34,7 +32,6 @@ lazy_static::lazy_static! { #[cfg(not(any(feature = "flutter", feature = "cli")))] lazy_static::lazy_static! { pub static ref CUR_SESSION: Arc>>> = Default::default(); - static ref CHILDREN : Children = Default::default(); } struct UIHostHandler; diff --git a/src/ui/header.tis b/src/ui/header.tis index 2adc37027..f4a05b9f7 100644 --- a/src/ui/header.tis +++ b/src/ui/header.tis @@ -301,7 +301,7 @@ class Header: Reactor.Component { if (recording) handler.refresh_video(); else - handler.record_screen(false, display_width, display_height); + handler.record_screen(false, 0, display_width, display_height); } event click $(#screen) (_, me) { diff --git a/src/ui/remote.rs b/src/ui/remote.rs index 95ea977e2..979d6d7f6 100644 --- a/src/ui/remote.rs +++ b/src/ui/remote.rs @@ -122,7 +122,11 @@ impl InvokeUiSession for SciterHandler { "updateQualityStatus", &make_args!( status.speed.map_or(Value::null(), |it| it.into()), - status.fps.map_or(Value::null(), |it| it.into()), + status + .fps + .iter() + .next() + .map_or(Value::null(), |(_, v)| (*v).into()), status.delay.map_or(Value::null(), |it| it.into()), status.target_bitrate.map_or(Value::null(), |it| it.into()), status @@ -223,7 +227,7 @@ impl InvokeUiSession for SciterHandler { self.call("adaptSize", &make_args!()); } - fn on_rgba(&self, rgba: &mut scrap::ImageRgb) { + fn on_rgba(&self, _display: usize, rgba: &mut scrap::ImageRgb) { VIDEO .lock() .unwrap() @@ -304,11 +308,11 @@ impl InvokeUiSession for SciterHandler { } /// RGBA is directly rendered by [on_rgba]. No need to store the rgba for the sciter ui. - fn get_rgba(&self) -> *const u8 { + fn get_rgba(&self, _display: usize) -> *const u8 { std::ptr::null() } - fn next_rgba(&self) {} + fn next_rgba(&self, _display: usize) {} } pub struct SciterSession(Session); @@ -450,7 +454,7 @@ impl sciter::EventHandler for SciterSession { fn save_image_quality(String); fn save_custom_image_quality(i32); fn refresh_video(); - fn record_screen(bool, i32, i32); + fn record_screen(bool, i32, i32, i32); fn record_status(bool); fn get_toggle_option(String); fn is_privacy_mode_supported(); diff --git a/src/ui/remote.tis b/src/ui/remote.tis index e9694ccc2..022d43668 100644 --- a/src/ui/remote.tis +++ b/src/ui/remote.tis @@ -23,7 +23,7 @@ handler.setDisplay = function(x, y, w, h, cursor_embedded) { display_origin_y = y; display_cursor_embedded = cursor_embedded; adaptDisplay(); - if (recording) handler.record_screen(true, w, h); + if (recording) handler.record_screen(true, 0, w, h); } // in case toolbar not shown correctly @@ -478,7 +478,7 @@ function self.closing() { var (x, y, w, h) = view.box(#rectw, #border, #screen); if (is_file_transfer) save_file_transfer_close_state(); if (is_file_transfer || is_port_forward || size_adapted) handler.save_size(x, y, w, h); - if (recording) handler.record_screen(false, display_width, display_height); + if (recording) handler.record_screen(false, 0, display_width, display_height); } var qualityMonitor; diff --git a/src/ui_cm_interface.rs b/src/ui_cm_interface.rs index ceb185443..da36646e6 100644 --- a/src/ui_cm_interface.rs +++ b/src/ui_cm_interface.rs @@ -164,6 +164,7 @@ impl ConnectionManager { } #[inline] + #[cfg(windows)] fn is_authorized(&self, id: i32) -> bool { CLIENTS .read() diff --git a/src/ui_interface.rs b/src/ui_interface.rs index ed2b4f4fc..b338e68c5 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -2,14 +2,14 @@ use hbb_common::password_security; use hbb_common::{ allow_err, - config::{self, Config, LocalConfig, PeerConfig}, - directories_next, log, tokio, -}; -use hbb_common::{ bytes::Bytes, + config::{self, Config, LocalConfig, PeerConfig}, config::{CONNECT_TIMEOUT, RENDEZVOUS_PORT}, + directories_next, futures::future::join_all, + log, rendezvous_proto::*, + tokio, }; #[cfg(not(any(target_os = "android", target_os = "ios")))] use hbb_common::{ @@ -17,9 +17,10 @@ use hbb_common::{ tokio::{sync::mpsc, time}, }; use serde_derive::Serialize; +#[cfg(not(any(target_os = "android", target_os = "ios")))] +use std::process::Child; use std::{ collections::HashMap, - process::Child, sync::{Arc, Mutex}, }; @@ -31,6 +32,7 @@ use crate::ipc; type Message = RendezvousMessage; +#[cfg(not(any(target_os = "android", target_os = "ios")))] pub type Children = Arc)>>; #[derive(Clone, Debug, Serialize)] @@ -594,7 +596,13 @@ pub fn current_is_wayland() -> bool { #[inline] pub fn get_new_version() -> String { - (*SOFTWARE_UPDATE_URL.lock().unwrap().rsplit('/').next().unwrap_or("")).to_string() + (*SOFTWARE_UPDATE_URL + .lock() + .unwrap() + .rsplit('/') + .next() + .unwrap_or("")) + .to_string() } #[inline] diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index d932fd112..0fccabed2 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -7,10 +7,7 @@ use std::{collections::HashMap, sync::atomic::AtomicBool}; use std::{ ops::{Deref, DerefMut}, str::FromStr, - sync::{ - atomic::{AtomicUsize, Ordering}, - Arc, Mutex, RwLock, - }, + sync::{atomic::Ordering, Arc, Mutex, RwLock}, time::SystemTime, }; use uuid::Uuid; @@ -28,7 +25,7 @@ use hbb_common::{ sync::mpsc, time::{Duration as TokioDuration, Instant}, }, - SessionID, Stream, + Stream, }; use crate::client::io_loop::Remote; @@ -49,8 +46,7 @@ const CHANGE_RESOLUTION_VALID_TIMEOUT_SECS: u64 = 15; #[derive(Clone, Default)] pub struct Session { - pub session_id: SessionID, // different from the one in LoginConfigHandler, used for flutter UI message pass - pub id: String, // peer id + pub id: String, // peer id pub password: String, pub args: Vec, pub lc: Arc>, @@ -286,12 +282,22 @@ impl Session { && !self.lc.read().unwrap().disable_clipboard.v } - pub fn refresh_video(&self) { - self.send(Data::Message(LoginConfigHandler::refresh())); + pub fn refresh_video(&self, display: usize) { + if crate::common::is_support_multi_ui_session_num(self.lc.read().unwrap().version) { + self.send(Data::Message(LoginConfigHandler::refresh_display(display))); + } else { + self.send(Data::Message(LoginConfigHandler::refresh())); + } } - pub fn record_screen(&self, start: bool, w: i32, h: i32) { - self.send(Data::RecordScreen(start, w, h, self.id.clone())); + pub fn record_screen(&self, start: bool, display: i32, w: i32, h: i32) { + self.send(Data::RecordScreen( + start, + display as usize, + w, + h, + self.id.clone(), + )); } pub fn record_status(&self, status: bool) { @@ -603,6 +609,19 @@ impl Session { self.send(Data::Message(msg_out)); } + pub fn capture_displays(&self, add: Vec, sub: Vec, set: Vec) { + let mut misc = Misc::new(); + misc.set_capture_displays(CaptureDisplays { + add, + sub, + set, + ..Default::default() + }); + let mut msg_out = Message::new(); + msg_out.set_misc(misc); + self.send(Data::Message(msg_out)); + } + pub fn switch_display(&self, display: i32) { let (w, h) = match self.lc.read().unwrap().get_custom_resolution(display) { Some((w, h)) => (w, h), @@ -1164,7 +1183,7 @@ pub trait InvokeUiSession: Send + Sync + Clone + 'static + Sized + Default { fn update_block_input_state(&self, on: bool); fn job_progress(&self, id: i32, file_num: i32, speed: f64, finished_size: f64); fn adapt_size(&self); - fn on_rgba(&self, rgba: &mut scrap::ImageRgb); + fn on_rgba(&self, display: usize, rgba: &mut scrap::ImageRgb); fn msgbox(&self, msgtype: &str, title: &str, text: &str, link: &str, retry: bool); #[cfg(any(target_os = "android", target_os = "ios"))] fn clipboard(&self, content: String); @@ -1175,8 +1194,8 @@ pub trait InvokeUiSession: Send + Sync + Clone + 'static + Sized + Default { fn on_voice_call_closed(&self, reason: &str); fn on_voice_call_waiting(&self); fn on_voice_call_incoming(&self); - fn get_rgba(&self) -> *const u8; - fn next_rgba(&self); + fn get_rgba(&self, display: usize) -> *const u8; + fn next_rgba(&self, display: usize); } impl Deref for Session { @@ -1237,7 +1256,7 @@ impl Interface for Session { if pi.displays.is_empty() { self.lc.write().unwrap().handle_peer_info(&pi); self.update_privacy_mode(); - self.msgbox("error", "Remote Error", "No Display", ""); + self.msgbox("error", "Remote Error", "No Displays", ""); return; } self.try_change_init_resolution(pi.current_display); @@ -1447,24 +1466,29 @@ pub async fn io_loop(handler: Session, round: u32) { } return; } - let frame_count = Arc::new(AtomicUsize::new(0)); - let frame_count_cl = frame_count.clone(); + let frame_count_map: Arc>> = Default::default(); + let frame_count_map_cl = frame_count_map.clone(); let ui_handler = handler.ui_handler.clone(); - let (video_sender, audio_sender, video_queue, decode_fps) = - start_video_audio_threads(move |data: &mut scrap::ImageRgb| { - frame_count_cl.fetch_add(1, Ordering::Relaxed); - ui_handler.on_rgba(data); - }); + let (video_sender, audio_sender, video_queue_map, decode_fps_map) = start_video_audio_threads( + handler.clone(), + move |display: usize, data: &mut scrap::ImageRgb| { + let mut write_lock = frame_count_map_cl.write().unwrap(); + let count = write_lock.get(&display).unwrap_or(&0) + 1; + write_lock.insert(display, count); + drop(write_lock); + ui_handler.on_rgba(display, data); + }, + ); let mut remote = Remote::new( handler, - video_queue, + video_queue_map, video_sender, audio_sender, receiver, sender, - frame_count, - decode_fps, + frame_count_map, + decode_fps_map, ); remote.io_loop(&key, &token, round).await; remote.sync_jobs_status_to_local().await; From 2f2a7d1f89ccfb1684fff70fc6b7d962c56c6110 Mon Sep 17 00:00:00 2001 From: dignow Date: Sun, 8 Oct 2023 22:32:48 +0800 Subject: [PATCH 26/50] feat, multi flutter ui sessions, change settings to 'Display' Signed-off-by: dignow --- flutter/lib/common/widgets/peer_card.dart | 2 +- .../desktop/pages/desktop_setting_page.dart | 48 +++++++++---------- src/lang/ar.rs | 4 +- src/lang/ca.rs | 4 +- src/lang/cn.rs | 4 +- src/lang/cs.rs | 4 +- src/lang/da.rs | 4 +- src/lang/de.rs | 4 +- src/lang/el.rs | 4 +- src/lang/eo.rs | 4 +- src/lang/es.rs | 4 +- src/lang/fa.rs | 4 +- src/lang/fr.rs | 4 +- src/lang/hu.rs | 4 +- src/lang/id.rs | 4 +- src/lang/it.rs | 4 +- src/lang/ja.rs | 4 +- src/lang/ko.rs | 4 +- src/lang/kz.rs | 4 +- src/lang/lt.rs | 4 +- src/lang/lv.rs | 4 +- src/lang/nl.rs | 4 +- src/lang/pl.rs | 4 +- src/lang/pt_PT.rs | 4 +- src/lang/ptbr.rs | 4 +- src/lang/ro.rs | 4 +- src/lang/ru.rs | 4 +- src/lang/sk.rs | 4 +- src/lang/sl.rs | 4 +- src/lang/sq.rs | 4 +- src/lang/sr.rs | 4 +- src/lang/sv.rs | 4 +- src/lang/template.rs | 4 +- src/lang/th.rs | 4 +- src/lang/tr.rs | 4 +- src/lang/tw.rs | 4 +- src/lang/ua.rs | 4 +- src/lang/vn.rs | 4 +- 38 files changed, 97 insertions(+), 97 deletions(-) diff --git a/flutter/lib/common/widgets/peer_card.dart b/flutter/lib/common/widgets/peer_card.dart index a843c3d41..922f88225 100644 --- a/flutter/lib/common/widgets/peer_card.dart +++ b/flutter/lib/common/widgets/peer_card.dart @@ -600,7 +600,7 @@ abstract class BasePeerCard extends StatelessWidget { await _openNewConnInAction(id, 'Open in New Tab', kOptionOpenInTabs); _openInWindowsAction(String id) async => await _openNewConnInAction( - id, 'Open in New Window', kOptionOpenInWindows); + id, 'Open in new window', kOptionOpenInWindows); _openNewConnInOptAction(String id) async => mainGetLocalBoolOptionSync(kOptionOpenNewConnInTabs) diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index 905c082a0..9daaea9f6 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -269,7 +269,6 @@ class _GeneralState extends State<_General> { service(), theme(), hwcodec(), - chooseDisplay(), audio(context), record(context), _Card(title: 'Language', children: [language()]), @@ -378,29 +377,6 @@ class _GeneralState extends State<_General> { ); } - Widget chooseDisplay() { - if (!useTextureRender) return const Offstage(); - - var current = getChooseDisplayBehavior(); - onChanged(String value) { - bind.mainSetOption(key: kKeyChooseDisplayBehavior, value: value); - setState(() {}); - } - - return _Card(title: 'Choose Display Behavior', children: [ - _Radio(context, - value: kChooseDisplayBehaviorSwitch, - groupValue: current, - label: 'Switch Display', - onChanged: onChanged), - _Radio(context, - value: kChooseDisplayBehaviorOpen, - groupValue: current, - label: 'Open in New Window', - onChanged: onChanged), - ]); - } - Widget audio(BuildContext context) { String getDefault() { if (Platform.isWindows) return translate('System Sound'); @@ -1148,6 +1124,7 @@ class _DisplayState extends State<_Display> { controller: scrollController, physics: DraggableNeverScrollableScrollPhysics(), children: [ + chooseDisplay(context), viewStyle(context), scrollStyle(context), imageQuality(context), @@ -1156,6 +1133,29 @@ class _DisplayState extends State<_Display> { ]).marginOnly(bottom: _kListViewBottomMargin)); } + Widget chooseDisplay(BuildContext context) { + if (!useTextureRender) return const Offstage(); + + var current = getChooseDisplayBehavior(); + onChanged(String value) { + bind.mainSetOption(key: kKeyChooseDisplayBehavior, value: value); + setState(() {}); + } + + return _Card(title: 'Choose Display Behavior', children: [ + _Radio(context, + value: kChooseDisplayBehaviorSwitch, + groupValue: current, + label: 'Switch display', + onChanged: onChanged), + _Radio(context, + value: kChooseDisplayBehaviorOpen, + groupValue: current, + label: 'Open in new window', + onChanged: onChanged), + ]); + } + Widget viewStyle(BuildContext context) { final key = 'view_style'; onChanged(String value) async { diff --git a/src/lang/ar.rs b/src/lang/ar.rs index 30c8bea5f..99bb7c3e0 100644 --- a/src/lang/ar.rs +++ b/src/lang/ar.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No displays", ""), ("elevated_switch_display_msg", ""), ("Choose Display Behavior", ""), - ("Switch Display", ""), - ("Open in New Window", ""), + ("Switch display", ""), + ("Open in new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ca.rs b/src/lang/ca.rs index d9c570796..b4938f8e5 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No displays", ""), ("elevated_switch_display_msg", ""), ("Choose Display Behavior", ""), - ("Switch Display", ""), - ("Open in New Window", ""), + ("Switch display", ""), + ("Open in new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/cn.rs b/src/lang/cn.rs index 24d2ec78a..15a74e11c 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No displays", "没有显示器。"), ("elevated_switch_display_msg", "切换到主显示器,因为提权后,不支持多显示器画面。"), ("Choose Display Behavior", "选择显示器的行为"), - ("Switch Display", "切换显示器"), - ("Open in New Window", "在新的窗口中打开"), + ("Switch display", "切换显示器"), + ("Open in new window", "在新的窗口中打开"), ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index 4f54e0540..df57c1d9e 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No displays", ""), ("elevated_switch_display_msg", ""), ("Choose Display Behavior", ""), - ("Switch Display", ""), - ("Open in New Window", ""), + ("Switch display", ""), + ("Open in new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index 17792e10b..6f9092230 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No displays", ""), ("elevated_switch_display_msg", ""), ("Choose Display Behavior", ""), - ("Switch Display", ""), - ("Open in New Window", ""), + ("Switch display", ""), + ("Open in new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/de.rs b/src/lang/de.rs index 0628846d3..55f7b2dd0 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No displays", ""), ("elevated_switch_display_msg", ""), ("Choose Display Behavior", ""), - ("Switch Display", ""), - ("Open in New Window", ""), + ("Switch display", ""), + ("Open in new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/el.rs b/src/lang/el.rs index fa545e182..597829043 100644 --- a/src/lang/el.rs +++ b/src/lang/el.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No displays", ""), ("elevated_switch_display_msg", ""), ("Choose Display Behavior", ""), - ("Switch Display", ""), - ("Open in New Window", ""), + ("Switch display", ""), + ("Open in new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index 56f24354c..03692ff77 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No displays", ""), ("elevated_switch_display_msg", ""), ("Choose Display Behavior", ""), - ("Switch Display", ""), - ("Open in New Window", ""), + ("Switch display", ""), + ("Open in new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index 46b0dc63c..de1ec9fed 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No displays", ""), ("elevated_switch_display_msg", ""), ("Choose Display Behavior", ""), - ("Switch Display", ""), - ("Open in New Window", ""), + ("Switch display", ""), + ("Open in new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fa.rs b/src/lang/fa.rs index 802b6473c..07829de99 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No displays", ""), ("elevated_switch_display_msg", ""), ("Choose Display Behavior", ""), - ("Switch Display", ""), - ("Open in New Window", ""), + ("Switch display", ""), + ("Open in new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index bec161910..810dbddc1 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No displays", ""), ("elevated_switch_display_msg", ""), ("Choose Display Behavior", ""), - ("Switch Display", ""), - ("Open in New Window", ""), + ("Switch display", ""), + ("Open in new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index 9fb02464e..ce8cb5add 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No displays", ""), ("elevated_switch_display_msg", ""), ("Choose Display Behavior", ""), - ("Switch Display", ""), - ("Open in New Window", ""), + ("Switch display", ""), + ("Open in new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index ac6b1276d..f1f18ca5c 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No displays", ""), ("elevated_switch_display_msg", ""), ("Choose Display Behavior", ""), - ("Switch Display", ""), - ("Open in New Window", ""), + ("Switch display", ""), + ("Open in new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index 8d6f94b00..be1157776 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -564,7 +564,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No displays", ""), ("elevated_switch_display_msg", ""), ("Choose Display Behavior", ""), - ("Switch Display", ""), - ("Open in New Window", ""), + ("Switch display", ""), + ("Open in new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index 7bad54921..43daac2de 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No displays", ""), ("elevated_switch_display_msg", ""), ("Choose Display Behavior", ""), - ("Switch Display", ""), - ("Open in New Window", ""), + ("Switch display", ""), + ("Open in new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index 5d0b7eab7..94ad160f8 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No displays", ""), ("elevated_switch_display_msg", ""), ("Choose Display Behavior", ""), - ("Switch Display", ""), - ("Open in New Window", ""), + ("Switch display", ""), + ("Open in new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/kz.rs b/src/lang/kz.rs index 24f97be4b..81003b1b2 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No displays", ""), ("elevated_switch_display_msg", ""), ("Choose Display Behavior", ""), - ("Switch Display", ""), - ("Open in New Window", ""), + ("Switch display", ""), + ("Open in new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lt.rs b/src/lang/lt.rs index b35f33498..d77e543ac 100644 --- a/src/lang/lt.rs +++ b/src/lang/lt.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No displays", ""), ("elevated_switch_display_msg", ""), ("Choose Display Behavior", ""), - ("Switch Display", ""), - ("Open in New Window", ""), + ("Switch display", ""), + ("Open in new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lv.rs b/src/lang/lv.rs index ffa33d061..d10238608 100644 --- a/src/lang/lv.rs +++ b/src/lang/lv.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No displays", ""), ("elevated_switch_display_msg", ""), ("Choose Display Behavior", ""), - ("Switch Display", ""), - ("Open in New Window", ""), + ("Switch display", ""), + ("Open in new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nl.rs b/src/lang/nl.rs index f67b3dd23..f5a1d5322 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No displays", ""), ("elevated_switch_display_msg", ""), ("Choose Display Behavior", ""), - ("Switch Display", ""), - ("Open in New Window", ""), + ("Switch display", ""), + ("Open in new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pl.rs b/src/lang/pl.rs index 81c23ef84..4f0fe343c 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No displays", ""), ("elevated_switch_display_msg", ""), ("Choose Display Behavior", ""), - ("Switch Display", ""), - ("Open in New Window", ""), + ("Switch display", ""), + ("Open in new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index 360cfd4fa..e794b9996 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No displays", ""), ("elevated_switch_display_msg", ""), ("Choose Display Behavior", ""), - ("Switch Display", ""), - ("Open in New Window", ""), + ("Switch display", ""), + ("Open in new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index 8df3e9ea8..a75da7507 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No displays", ""), ("elevated_switch_display_msg", ""), ("Choose Display Behavior", ""), - ("Switch Display", ""), - ("Open in New Window", ""), + ("Switch display", ""), + ("Open in new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ro.rs b/src/lang/ro.rs index 1822d79f2..f03d0b96d 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No displays", ""), ("elevated_switch_display_msg", ""), ("Choose Display Behavior", ""), - ("Switch Display", ""), - ("Open in New Window", ""), + ("Switch display", ""), + ("Open in new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index a0be18469..302a4e209 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No displays", ""), ("elevated_switch_display_msg", ""), ("Choose Display Behavior", ""), - ("Switch Display", ""), - ("Open in New Window", ""), + ("Switch display", ""), + ("Open in new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index 75bb447f4..be35fa666 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No displays", ""), ("elevated_switch_display_msg", ""), ("Choose Display Behavior", ""), - ("Switch Display", ""), - ("Open in New Window", ""), + ("Switch display", ""), + ("Open in new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sl.rs b/src/lang/sl.rs index 5f9f8d5f3..311106d74 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No displays", ""), ("elevated_switch_display_msg", ""), ("Choose Display Behavior", ""), - ("Switch Display", ""), - ("Open in New Window", ""), + ("Switch display", ""), + ("Open in new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sq.rs b/src/lang/sq.rs index 3fef0845d..31a2096bf 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No displays", ""), ("elevated_switch_display_msg", ""), ("Choose Display Behavior", ""), - ("Switch Display", ""), - ("Open in New Window", ""), + ("Switch display", ""), + ("Open in new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sr.rs b/src/lang/sr.rs index 3382ac2b6..dc11a8af0 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No displays", ""), ("elevated_switch_display_msg", ""), ("Choose Display Behavior", ""), - ("Switch Display", ""), - ("Open in New Window", ""), + ("Switch display", ""), + ("Open in new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sv.rs b/src/lang/sv.rs index eca0c4f19..ffeaea2fc 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No displays", ""), ("elevated_switch_display_msg", ""), ("Choose Display Behavior", ""), - ("Switch Display", ""), - ("Open in New Window", ""), + ("Switch display", ""), + ("Open in new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index 256bb7d76..a62790a5d 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No displays", ""), ("elevated_switch_display_msg", ""), ("Choose Display Behavior", ""), - ("Switch Display", ""), - ("Open in New Window", ""), + ("Switch display", ""), + ("Open in new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/th.rs b/src/lang/th.rs index 5477cb666..eb9dd789f 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No displays", ""), ("elevated_switch_display_msg", ""), ("Choose Display Behavior", ""), - ("Switch Display", ""), - ("Open in New Window", ""), + ("Switch display", ""), + ("Open in new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index c8d323d8a..acf67740d 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No displays", ""), ("elevated_switch_display_msg", ""), ("Choose Display Behavior", ""), - ("Switch Display", ""), - ("Open in New Window", ""), + ("Switch display", ""), + ("Open in new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index ad37066f3..a89a4b547 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No displays", ""), ("elevated_switch_display_msg", ""), ("Choose Display Behavior", ""), - ("Switch Display", ""), - ("Open in New Window", ""), + ("Switch display", ""), + ("Open in new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ua.rs b/src/lang/ua.rs index 7b510a224..5e10c9383 100644 --- a/src/lang/ua.rs +++ b/src/lang/ua.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No displays", ""), ("elevated_switch_display_msg", ""), ("Choose Display Behavior", ""), - ("Switch Display", ""), - ("Open in New Window", ""), + ("Switch display", ""), + ("Open in new window", ""), ].iter().cloned().collect(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index 309d34f7a..860cd2dbd 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("No displays", ""), ("elevated_switch_display_msg", ""), ("Choose Display Behavior", ""), - ("Switch Display", ""), - ("Open in New Window", ""), + ("Switch display", ""), + ("Open in new window", ""), ].iter().cloned().collect(); } From 3bb7123dd50834bb8de7f36e36a04b495b409c16 Mon Sep 17 00:00:00 2001 From: dignow Date: Sun, 8 Oct 2023 23:32:11 +0800 Subject: [PATCH 27/50] fix build, sciter Signed-off-by: dignow --- src/client.rs | 2 +- src/client/io_loop.rs | 2 +- src/flutter_ffi.rs | 2 +- src/server/connection.rs | 2 ++ src/ui/header.tis | 6 ++++-- src/ui/remote.rs | 2 +- src/ui_session_interface.rs | 10 ++++++++-- 7 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/client.rs b/src/client.rs index f7c6c6d96..eff490ebb 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1926,7 +1926,7 @@ where // // to-do: fix the error log::error!("handle video frame error, {}", e); - session.refresh_video(display); + session.refresh_video(display as _); } _ => {} } diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index 151bee0cc..4c830a2f9 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -1008,7 +1008,7 @@ impl Remote { { // Refresh causes client set_display, left frames cause flickering. while let Some(_) = video_queue.pop() {} - self.handler.refresh_video(*display); + self.handler.refresh_video(*display as _); ctl.refresh_times += 1; ctl.last_refresh_instant = Instant::now(); } diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 080ad8178..77da2942c 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -180,7 +180,7 @@ pub fn session_close(session_id: SessionID) { pub fn session_refresh(session_id: SessionID, display: usize) { if let Some(session) = sessions::get_session_by_session_id(&session_id) { - session.refresh_video(display); + session.refresh_video(display as _); } } diff --git a/src/server/connection.rs b/src/server/connection.rs index 990ee733c..5f9502397 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -1970,6 +1970,8 @@ impl Connection { } Some(misc::Union::RefreshVideo(r)) => { if r { + // Refresh all videos. + // Compatibility with old versions and sciter(remote). self.refresh_video_display(None); } self.update_auto_disconnect_timer(); diff --git a/src/ui/header.tis b/src/ui/header.tis index f4a05b9f7..a6cb2645d 100644 --- a/src/ui/header.tis +++ b/src/ui/header.tis @@ -298,8 +298,9 @@ class Header: Reactor.Component { recording = !recording; header.update(); handler.record_status(recording); + // 0 is just a dummy value. It will be ignored by the handler. if (recording) - handler.refresh_video(); + handler.refresh_video(0); else handler.record_screen(false, 0, display_width, display_height); } @@ -370,7 +371,8 @@ class Header: Reactor.Component { } event click $(#refresh) { - handler.refresh_video(); + // 0 is just a dummy value. It will be ignored by the handler. + handler.refresh_video(0); } event click $(#block-input) { diff --git a/src/ui/remote.rs b/src/ui/remote.rs index 979d6d7f6..71ef8f84d 100644 --- a/src/ui/remote.rs +++ b/src/ui/remote.rs @@ -453,7 +453,7 @@ impl sciter::EventHandler for SciterSession { fn save_view_style(String); fn save_image_quality(String); fn save_custom_image_quality(i32); - fn refresh_video(); + fn refresh_video(i32); fn record_screen(bool, i32, i32, i32); fn record_status(bool); fn get_toggle_option(String); diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index 0fccabed2..ff559f19c 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -282,14 +282,20 @@ impl Session { && !self.lc.read().unwrap().disable_clipboard.v } - pub fn refresh_video(&self, display: usize) { + #[cfg(feature = "flutter")] + pub fn refresh_video(&self, display: i32) { if crate::common::is_support_multi_ui_session_num(self.lc.read().unwrap().version) { - self.send(Data::Message(LoginConfigHandler::refresh_display(display))); + self.send(Data::Message(LoginConfigHandler::refresh_display(display as _))); } else { self.send(Data::Message(LoginConfigHandler::refresh())); } } + #[cfg(not(feature = "flutter"))] + pub fn refresh_video(&self, _display: i32) { + self.send(Data::Message(LoginConfigHandler::refresh())); + } + pub fn record_screen(&self, start: bool, display: i32, w: i32, h: i32) { self.send(Data::RecordScreen( start, From c10fc26ccea2901680d76e6f53a6d45d75fcecb8 Mon Sep 17 00:00:00 2001 From: dignow Date: Sun, 8 Oct 2023 23:59:18 +0800 Subject: [PATCH 28/50] fix build, android Signed-off-by: dignow --- src/flutter_ffi.rs | 4 ++-- src/keyboard.rs | 1 + src/platform/mod.rs | 1 + src/ui_session_interface.rs | 9 ++++++--- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 77da2942c..93c7ced7e 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -1563,10 +1563,10 @@ pub fn set_cur_session_id(session_id: SessionID) { } } -fn set_cur_session_id_(session_id: SessionID, keyboard_mode: &str) { +fn set_cur_session_id_(session_id: SessionID, _keyboard_mode: &str) { super::flutter::set_cur_session_id(session_id); #[cfg(windows)] - crate::keyboard::update_grab_get_key_name(keyboard_mode); + crate::keyboard::update_grab_get_key_name(_keyboard_mode); } pub fn install_show_run_without_install() -> SyncReturn { diff --git a/src/keyboard.rs b/src/keyboard.rs index 5a6c4bace..9a6ac49d5 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -214,6 +214,7 @@ static mut IS_0X021D_DOWN: bool = false; #[cfg(target_os = "macos")] static mut IS_LEFT_OPTION_DOWN: bool = false; +#[cfg(not(any(target_os = "android", target_os = "ios")))] fn get_keyboard_mode() -> String { #[cfg(not(any(feature = "flutter", feature = "cli")))] if let Some(session) = CUR_SESSION.lock().unwrap().as_ref() { diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 6a428d9c3..71aefe817 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -83,6 +83,7 @@ pub const PA_SAMPLE_RATE: u32 = 48000; pub(crate) struct InstallingService; // please use new impl InstallingService { + #[cfg(any(target_os = "windows", target_os = "linux"))] pub fn new() -> Self { *INSTALLING_SERVICE.lock().unwrap() = true; Self diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index ff559f19c..568767f81 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -3,11 +3,12 @@ use async_trait::async_trait; use bytes::Bytes; use rdev::{Event, EventType::*, KeyCode}; #[cfg(not(any(target_os = "android", target_os = "ios")))] -use std::{collections::HashMap, sync::atomic::AtomicBool}; +use std::sync::atomic::{AtomicBool, Ordering}; use std::{ + collections::HashMap, ops::{Deref, DerefMut}, str::FromStr, - sync::{atomic::Ordering, Arc, Mutex, RwLock}, + sync::{Arc, Mutex, RwLock}, time::SystemTime, }; use uuid::Uuid; @@ -285,7 +286,9 @@ impl Session { #[cfg(feature = "flutter")] pub fn refresh_video(&self, display: i32) { if crate::common::is_support_multi_ui_session_num(self.lc.read().unwrap().version) { - self.send(Data::Message(LoginConfigHandler::refresh_display(display as _))); + self.send(Data::Message(LoginConfigHandler::refresh_display( + display as _, + ))); } else { self.send(Data::Message(LoginConfigHandler::refresh())); } From e363cd98131da94b6b7cf1230c884cb411c43007 Mon Sep 17 00:00:00 2001 From: dignow Date: Mon, 9 Oct 2023 14:21:17 +0800 Subject: [PATCH 29/50] 'Choose Display Behavior' to 'Choose display behavior' Signed-off-by: dignow --- flutter/lib/desktop/pages/desktop_setting_page.dart | 2 +- src/lang/ar.rs | 2 +- src/lang/ca.rs | 2 +- src/lang/cn.rs | 2 +- src/lang/cs.rs | 2 +- src/lang/da.rs | 2 +- src/lang/de.rs | 2 +- src/lang/el.rs | 2 +- src/lang/eo.rs | 2 +- src/lang/es.rs | 2 +- src/lang/fa.rs | 2 +- src/lang/fr.rs | 2 +- src/lang/hu.rs | 2 +- src/lang/id.rs | 2 +- src/lang/it.rs | 2 +- src/lang/ja.rs | 2 +- src/lang/ko.rs | 2 +- src/lang/kz.rs | 2 +- src/lang/lt.rs | 2 +- src/lang/lv.rs | 2 +- src/lang/pl.rs | 2 +- src/lang/pt_PT.rs | 2 +- src/lang/ptbr.rs | 2 +- src/lang/ro.rs | 2 +- src/lang/ru.rs | 2 +- src/lang/sk.rs | 2 +- src/lang/sl.rs | 2 +- src/lang/sq.rs | 2 +- src/lang/sr.rs | 2 +- src/lang/sv.rs | 2 +- src/lang/template.rs | 2 +- src/lang/th.rs | 2 +- src/lang/tr.rs | 2 +- src/lang/tw.rs | 2 +- src/lang/ua.rs | 2 +- src/lang/vn.rs | 2 +- 36 files changed, 36 insertions(+), 36 deletions(-) diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index 9daaea9f6..84c941c88 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -1142,7 +1142,7 @@ class _DisplayState extends State<_Display> { setState(() {}); } - return _Card(title: 'Choose Display Behavior', children: [ + return _Card(title: 'Choose display behavior', children: [ _Radio(context, value: kChooseDisplayBehaviorSwitch, groupValue: current, diff --git a/src/lang/ar.rs b/src/lang/ar.rs index 99bb7c3e0..90252faa7 100644 --- a/src/lang/ar.rs +++ b/src/lang/ar.rs @@ -562,7 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose Display Behavior", ""), + ("Choose display behavior", ""), ("Switch display", ""), ("Open in new window", ""), ].iter().cloned().collect(); diff --git a/src/lang/ca.rs b/src/lang/ca.rs index b4938f8e5..90887b622 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -562,7 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose Display Behavior", ""), + ("Choose display behavior", ""), ("Switch display", ""), ("Open in new window", ""), ].iter().cloned().collect(); diff --git a/src/lang/cn.rs b/src/lang/cn.rs index 15a74e11c..3793c2e5d 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -562,7 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", "显示器被拔出,切换到第一个显示器。"), ("No displays", "没有显示器。"), ("elevated_switch_display_msg", "切换到主显示器,因为提权后,不支持多显示器画面。"), - ("Choose Display Behavior", "选择显示器的行为"), + ("Choose display behavior", "选择显示器的行为"), ("Switch display", "切换显示器"), ("Open in new window", "在新的窗口中打开"), ].iter().cloned().collect(); diff --git a/src/lang/cs.rs b/src/lang/cs.rs index df57c1d9e..71e152668 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -562,7 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose Display Behavior", ""), + ("Choose display behavior", ""), ("Switch display", ""), ("Open in new window", ""), ].iter().cloned().collect(); diff --git a/src/lang/da.rs b/src/lang/da.rs index 6f9092230..f11954d02 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -562,7 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose Display Behavior", ""), + ("Choose display behavior", ""), ("Switch display", ""), ("Open in new window", ""), ].iter().cloned().collect(); diff --git a/src/lang/de.rs b/src/lang/de.rs index 55f7b2dd0..dc80bad04 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -562,7 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose Display Behavior", ""), + ("Choose display behavior", ""), ("Switch display", ""), ("Open in new window", ""), ].iter().cloned().collect(); diff --git a/src/lang/el.rs b/src/lang/el.rs index 597829043..b1baabb2b 100644 --- a/src/lang/el.rs +++ b/src/lang/el.rs @@ -562,7 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose Display Behavior", ""), + ("Choose display behavior", ""), ("Switch display", ""), ("Open in new window", ""), ].iter().cloned().collect(); diff --git a/src/lang/eo.rs b/src/lang/eo.rs index 03692ff77..0ce93fdff 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -562,7 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose Display Behavior", ""), + ("Choose display behavior", ""), ("Switch display", ""), ("Open in new window", ""), ].iter().cloned().collect(); diff --git a/src/lang/es.rs b/src/lang/es.rs index de1ec9fed..aa040d252 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -562,7 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose Display Behavior", ""), + ("Choose display behavior", ""), ("Switch display", ""), ("Open in new window", ""), ].iter().cloned().collect(); diff --git a/src/lang/fa.rs b/src/lang/fa.rs index 07829de99..b7561283c 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -562,7 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose Display Behavior", ""), + ("Choose display behavior", ""), ("Switch display", ""), ("Open in new window", ""), ].iter().cloned().collect(); diff --git a/src/lang/fr.rs b/src/lang/fr.rs index 810dbddc1..f0d3c1d8f 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -562,7 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose Display Behavior", ""), + ("Choose display behavior", ""), ("Switch display", ""), ("Open in new window", ""), ].iter().cloned().collect(); diff --git a/src/lang/hu.rs b/src/lang/hu.rs index ce8cb5add..5a5e280d6 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -562,7 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose Display Behavior", ""), + ("Choose display behavior", ""), ("Switch display", ""), ("Open in new window", ""), ].iter().cloned().collect(); diff --git a/src/lang/id.rs b/src/lang/id.rs index f1f18ca5c..cf3702176 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -562,7 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose Display Behavior", ""), + ("Choose display behavior", ""), ("Switch display", ""), ("Open in new window", ""), ].iter().cloned().collect(); diff --git a/src/lang/it.rs b/src/lang/it.rs index be1157776..fb84fbbdc 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -563,7 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose Display Behavior", ""), + ("Choose display behavior", ""), ("Switch display", ""), ("Open in new window", ""), ].iter().cloned().collect(); diff --git a/src/lang/ja.rs b/src/lang/ja.rs index 43daac2de..7f85fcbb0 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -562,7 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose Display Behavior", ""), + ("Choose display behavior", ""), ("Switch display", ""), ("Open in new window", ""), ].iter().cloned().collect(); diff --git a/src/lang/ko.rs b/src/lang/ko.rs index 94ad160f8..1cce7aa7d 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -562,7 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose Display Behavior", ""), + ("Choose display behavior", ""), ("Switch display", ""), ("Open in new window", ""), ].iter().cloned().collect(); diff --git a/src/lang/kz.rs b/src/lang/kz.rs index 81003b1b2..3f52f8c7c 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -562,7 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose Display Behavior", ""), + ("Choose display behavior", ""), ("Switch display", ""), ("Open in new window", ""), ].iter().cloned().collect(); diff --git a/src/lang/lt.rs b/src/lang/lt.rs index d77e543ac..f1e4e790f 100644 --- a/src/lang/lt.rs +++ b/src/lang/lt.rs @@ -562,7 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose Display Behavior", ""), + ("Choose display behavior", ""), ("Switch display", ""), ("Open in new window", ""), ].iter().cloned().collect(); diff --git a/src/lang/lv.rs b/src/lang/lv.rs index d10238608..44c53889f 100644 --- a/src/lang/lv.rs +++ b/src/lang/lv.rs @@ -562,7 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose Display Behavior", ""), + ("Choose display behavior", ""), ("Switch display", ""), ("Open in new window", ""), ].iter().cloned().collect(); diff --git a/src/lang/pl.rs b/src/lang/pl.rs index 4f0fe343c..beb47a4ec 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -562,7 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose Display Behavior", ""), + ("Choose display behavior", ""), ("Switch display", ""), ("Open in new window", ""), ].iter().cloned().collect(); diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index e794b9996..b0df8eefa 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -562,7 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose Display Behavior", ""), + ("Choose display behavior", ""), ("Switch display", ""), ("Open in new window", ""), ].iter().cloned().collect(); diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index a75da7507..ceeb1aa22 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -562,7 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose Display Behavior", ""), + ("Choose display behavior", ""), ("Switch display", ""), ("Open in new window", ""), ].iter().cloned().collect(); diff --git a/src/lang/ro.rs b/src/lang/ro.rs index f03d0b96d..b194edd25 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -562,7 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose Display Behavior", ""), + ("Choose display behavior", ""), ("Switch display", ""), ("Open in new window", ""), ].iter().cloned().collect(); diff --git a/src/lang/ru.rs b/src/lang/ru.rs index 302a4e209..b132d88b4 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -562,7 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose Display Behavior", ""), + ("Choose display behavior", ""), ("Switch display", ""), ("Open in new window", ""), ].iter().cloned().collect(); diff --git a/src/lang/sk.rs b/src/lang/sk.rs index be35fa666..55ab7fb64 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -562,7 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose Display Behavior", ""), + ("Choose display behavior", ""), ("Switch display", ""), ("Open in new window", ""), ].iter().cloned().collect(); diff --git a/src/lang/sl.rs b/src/lang/sl.rs index 311106d74..13440aa70 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -562,7 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose Display Behavior", ""), + ("Choose display behavior", ""), ("Switch display", ""), ("Open in new window", ""), ].iter().cloned().collect(); diff --git a/src/lang/sq.rs b/src/lang/sq.rs index 31a2096bf..718f00518 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -562,7 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose Display Behavior", ""), + ("Choose display behavior", ""), ("Switch display", ""), ("Open in new window", ""), ].iter().cloned().collect(); diff --git a/src/lang/sr.rs b/src/lang/sr.rs index dc11a8af0..755c52784 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -562,7 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose Display Behavior", ""), + ("Choose display behavior", ""), ("Switch display", ""), ("Open in new window", ""), ].iter().cloned().collect(); diff --git a/src/lang/sv.rs b/src/lang/sv.rs index ffeaea2fc..80a7098fc 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -562,7 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose Display Behavior", ""), + ("Choose display behavior", ""), ("Switch display", ""), ("Open in new window", ""), ].iter().cloned().collect(); diff --git a/src/lang/template.rs b/src/lang/template.rs index a62790a5d..ae4885298 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -562,7 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose Display Behavior", ""), + ("Choose display behavior", ""), ("Switch display", ""), ("Open in new window", ""), ].iter().cloned().collect(); diff --git a/src/lang/th.rs b/src/lang/th.rs index eb9dd789f..a6f10fb4f 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -562,7 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose Display Behavior", ""), + ("Choose display behavior", ""), ("Switch display", ""), ("Open in new window", ""), ].iter().cloned().collect(); diff --git a/src/lang/tr.rs b/src/lang/tr.rs index acf67740d..abc1d4515 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -562,7 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose Display Behavior", ""), + ("Choose display behavior", ""), ("Switch display", ""), ("Open in new window", ""), ].iter().cloned().collect(); diff --git a/src/lang/tw.rs b/src/lang/tw.rs index a89a4b547..cd0da2655 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -562,7 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose Display Behavior", ""), + ("Choose display behavior", ""), ("Switch display", ""), ("Open in new window", ""), ].iter().cloned().collect(); diff --git a/src/lang/ua.rs b/src/lang/ua.rs index 5e10c9383..ce5561d52 100644 --- a/src/lang/ua.rs +++ b/src/lang/ua.rs @@ -562,7 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose Display Behavior", ""), + ("Choose display behavior", ""), ("Switch display", ""), ("Open in new window", ""), ].iter().cloned().collect(); diff --git a/src/lang/vn.rs b/src/lang/vn.rs index 860cd2dbd..d77125ce3 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -562,7 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose Display Behavior", ""), + ("Choose display behavior", ""), ("Switch display", ""), ("Open in new window", ""), ].iter().cloned().collect(); From b52cf070f5e95c906640dc02a42e63f944174a8e Mon Sep 17 00:00:00 2001 From: dignow Date: Mon, 9 Oct 2023 17:22:22 +0800 Subject: [PATCH 30/50] multi flutter ui sessions, refact 'Show displays as individual windows' Signed-off-by: dignow --- flutter/lib/common.dart | 15 +++------ flutter/lib/common/widgets/toolbar.dart | 18 ++++++++++ flutter/lib/consts.dart | 5 ++- .../desktop/pages/desktop_setting_page.dart | 29 ++-------------- .../lib/desktop/widgets/remote_toolbar.dart | 33 +++++++++++++++++-- flutter/lib/models/model.dart | 5 ++- libs/hbb_common/src/config.rs | 12 +++++++ src/client.rs | 11 +++++++ src/flutter_ffi.rs | 14 ++++++++ src/lang/ar.rs | 3 +- src/lang/ca.rs | 3 +- src/lang/cn.rs | 3 +- src/lang/cs.rs | 3 +- src/lang/da.rs | 3 +- src/lang/de.rs | 3 +- src/lang/el.rs | 3 +- src/lang/eo.rs | 3 +- src/lang/es.rs | 3 +- src/lang/fa.rs | 3 +- src/lang/fr.rs | 3 +- src/lang/hu.rs | 3 +- src/lang/id.rs | 3 +- src/lang/it.rs | 3 +- src/lang/ja.rs | 3 +- src/lang/ko.rs | 3 +- src/lang/kz.rs | 3 +- src/lang/lt.rs | 3 +- src/lang/lv.rs | 3 +- src/lang/nl.rs | 3 +- src/lang/pl.rs | 3 +- src/lang/pt_PT.rs | 3 +- src/lang/ptbr.rs | 3 +- src/lang/ro.rs | 3 +- src/lang/ru.rs | 3 +- src/lang/sk.rs | 3 +- src/lang/sl.rs | 3 +- src/lang/sq.rs | 3 +- src/lang/sr.rs | 3 +- src/lang/sv.rs | 3 +- src/lang/template.rs | 3 +- src/lang/th.rs | 3 +- src/lang/tr.rs | 3 +- src/lang/tw.rs | 3 +- src/lang/ua.rs | 3 +- src/lang/vn.rs | 3 +- src/ui_session_interface.rs | 8 +++++ 46 files changed, 140 insertions(+), 118 deletions(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 61d006b8a..73b4bf1a5 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -2590,15 +2590,6 @@ String getDesktopTabLabel(String peerId, String alias) { return label; } -String getChooseDisplayBehavior() { - var current = bind.mainGetOptionSync(key: kKeyChooseDisplayBehavior); - if (![kChooseDisplayBehaviorSwitch, kChooseDisplayBehaviorOpen] - .contains(current)) { - current = kChooseDisplayBehaviorOpen; - } - return current; -} - sessionRefreshVideo(SessionID sessionId, PeerInfo pi) async { if (pi.currentDisplay == kAllDisplayValue) { for (int i = 0; i < pi.displays.length; i++) { @@ -2609,5 +2600,7 @@ sessionRefreshVideo(SessionID sessionId, PeerInfo pi) async { } } -bool get isChooseDisplayToOpen => - getChooseDisplayBehavior() != kChooseDisplayBehaviorSwitch; +bool isChooseDisplayToOpenInNewWindow(PeerInfo pi, SessionID sessionId) => + pi.isSupportMultiDisplay && + bind.sessionGetDisplaysAsIndividualWindows(sessionId: sessionId) == 'Y'; + diff --git a/flutter/lib/common/widgets/toolbar.dart b/flutter/lib/common/widgets/toolbar.dart index 1717e99f2..ceb28b0a3 100644 --- a/flutter/lib/common/widgets/toolbar.dart +++ b/flutter/lib/common/widgets/toolbar.dart @@ -515,5 +515,23 @@ Future> toolbarDisplayToggle( }, child: Text(translate('Swap control-command key')))); } + + if (pi.isSupportMultiDisplay && + PrivacyModeState.find(id).isFalse && + pi.displaysCount.value > 1 && + bind.mainGetUserDefaultOption(key: kKeyShowMonitorsToolbar) == 'Y') { + final value = + bind.sessionGetDisplaysAsIndividualWindows(sessionId: ffi.sessionId) == + 'Y'; + v.add(TToggleMenu( + value: value, + onChanged: (value) { + if (value == null) return; + bind.sessionSetDisplaysAsIndividualWindows( + sessionId: sessionId, value: value ? 'Y' : ''); + }, + child: Text(translate('Show displays as individual windows')))); + } + return v; } diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index 9c7fdf01d..1452270d1 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -66,9 +66,8 @@ const int kWindowMainId = 0; const String kPointerEventKindTouch = "touch"; const String kPointerEventKindMouse = "mouse"; -const String kKeyChooseDisplayBehavior = 'choose-display-behavior'; -const String kChooseDisplayBehaviorSwitch = 'switch'; -const String kChooseDisplayBehaviorOpen = 'open'; +const String kKeyShowDisplaysAsIndividualWindows = 'displays_as_individual_windows'; +const String kKeyShowMonitorsToolbar = 'show_monitors_toolbar'; // the executable name of the portable version const String kEnvPortableExecutable = "RUSTDESK_APPNAME"; diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index 84c941c88..259511bfd 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -11,7 +11,6 @@ import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart'; import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart'; import 'package:flutter_hbb/models/platform_model.dart'; -import 'package:flutter_hbb/models/desktop_render_texture.dart'; import 'package:flutter_hbb/models/server_model.dart'; import 'package:flutter_hbb/plugin/manager.dart'; import 'package:flutter_hbb/plugin/widgets/desktop_settings.dart'; @@ -1124,7 +1123,6 @@ class _DisplayState extends State<_Display> { controller: scrollController, physics: DraggableNeverScrollableScrollPhysics(), children: [ - chooseDisplay(context), viewStyle(context), scrollStyle(context), imageQuality(context), @@ -1133,29 +1131,6 @@ class _DisplayState extends State<_Display> { ]).marginOnly(bottom: _kListViewBottomMargin)); } - Widget chooseDisplay(BuildContext context) { - if (!useTextureRender) return const Offstage(); - - var current = getChooseDisplayBehavior(); - onChanged(String value) { - bind.mainSetOption(key: kKeyChooseDisplayBehavior, value: value); - setState(() {}); - } - - return _Card(title: 'Choose display behavior', children: [ - _Radio(context, - value: kChooseDisplayBehaviorSwitch, - groupValue: current, - label: 'Switch display', - onChanged: onChanged), - _Radio(context, - value: kChooseDisplayBehaviorOpen, - groupValue: current, - label: 'Open in new window', - onChanged: onChanged), - ]); - } - Widget viewStyle(BuildContext context) { final key = 'view_style'; onChanged(String value) async { @@ -1314,7 +1289,7 @@ class _DisplayState extends State<_Display> { Widget other(BuildContext context) { return _Card(title: 'Other Default Options', children: [ otherRow('View Mode', 'view_only'), - otherRow('show_monitors_tip', 'show_monitors_toolbar'), + otherRow('show_monitors_tip', kKeyShowMonitorsToolbar), otherRow('Collapse toolbar', 'collapse_toolbar'), otherRow('Show remote cursor', 'show_remote_cursor'), otherRow('Zoom cursor', 'zoom-cursor'), @@ -1325,6 +1300,8 @@ class _DisplayState extends State<_Display> { otherRow('Lock after session end', 'lock_after_session_end'), otherRow('Privacy mode', 'privacy_mode'), otherRow('Reverse mouse wheel', 'reverse_mouse_wheel'), + otherRow('Show displays as individual windows', + kKeyShowDisplaysAsIndividualWindows), ]); } } diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index dddbc32a1..1bf4d438a 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -598,7 +598,7 @@ class _MonitorMenu extends StatelessWidget { }) : super(key: key); bool get showMonitorsToolbar => - bind.mainGetUserDefaultOption(key: 'show_monitors_toolbar') == 'Y'; + bind.mainGetUserDefaultOption(key: kKeyShowMonitorsToolbar) == 'Y'; @override Widget build(BuildContext context) => @@ -614,13 +614,40 @@ class _MonitorMenu extends StatelessWidget { menuStyle: MenuStyle( padding: MaterialStatePropertyAll(EdgeInsets.symmetric(horizontal: 6))), - menuChildren: [Row(children: buildMonitorList(false))]); + menuChildren: [buildMonitorSubmenuWidget()]); } Widget buildMultiMonitorMenu() { return Row(children: buildMonitorList(true)); } + Widget buildMonitorSubmenuWidget() { + final pi = ffi.ffiModel.pi; + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row(children: buildMonitorList(false)), + pi.isSupportMultiDisplay ? Divider() : Offstage(), + pi.isSupportMultiDisplay ? chooseDisplayBehavior() : Offstage(), + ], + ); + } + + Widget chooseDisplayBehavior() { + final value = + bind.sessionGetDisplaysAsIndividualWindows(sessionId: ffi.sessionId) == + 'Y'; + return CkbMenuButton( + value: value, + onChanged: (value) async { + if (value == null) return; + await bind.sessionSetDisplaysAsIndividualWindows( + sessionId: ffi.sessionId, value: value ? 'Y' : ''); + }, + ffi: ffi, + child: Text(translate('Show displays as individual windows'))); + } + List buildMonitorList(bool isMulti) { final List monitorList = []; final pi = ffi.ffiModel.pi; @@ -746,7 +773,7 @@ class _MonitorMenu extends StatelessWidget { _menuDismissCallback(ffi); RxInt display = CurrentDisplayState.find(id); if (display.value != i) { - if (pi.isSupportMultiDisplay) { + if (isChooseDisplayToOpenInNewWindow(pi, ffi.sessionId)) { openMonitorInNewTabOrWindow(i, pi); } else { openMonitorInTheSameTab(i, pi); diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 7bb96a99b..a0fde0ca4 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -430,7 +430,7 @@ class FfiModel with ChangeNotifier { final curDisplay = int.parse(evt['display']); // The message should be handled by the another UI session. - if (_pi.isSupportMultiDisplay) { + if (isChooseDisplayToOpenInNewWindow(_pi, sessionId)) { if (curDisplay != _pi.currentDisplay) { return; } @@ -2211,8 +2211,7 @@ class PeerInfo with ChangeNotifier { bool get isWayland => platformDdditions['is_wayland'] == true; bool get isHeadless => platformDdditions['headless'] == true; - bool get isSupportMultiDisplay => - isDesktop && isSupportMultiUiSession && isChooseDisplayToOpen; + bool get isSupportMultiDisplay => isDesktop && isSupportMultiUiSession; bool get cursorEmbedded => tryGetDisplay()?.cursorEmbedded ?? false; diff --git a/libs/hbb_common/src/config.rs b/libs/hbb_common/src/config.rs index 5a7c7d4b4..02f3c719e 100644 --- a/libs/hbb_common/src/config.rs +++ b/libs/hbb_common/src/config.rs @@ -284,6 +284,12 @@ pub struct PeerConfig { skip_serializing_if = "String::is_empty" )] pub reverse_mouse_wheel: String, + #[serde( + default = "PeerConfig::default_displays_as_individual_windows", + deserialize_with = "PeerConfig::deserialize_displays_as_individual_windows", + skip_serializing_if = "String::is_empty" + )] + pub displays_as_individual_windows: String, #[serde( default, @@ -328,6 +334,7 @@ impl Default for PeerConfig { keyboard_mode: Default::default(), view_only: Default::default(), reverse_mouse_wheel: Self::default_reverse_mouse_wheel(), + displays_as_individual_windows: Self::default_displays_as_individual_windows(), custom_resolutions: Default::default(), options: Self::default_options(), ui_flutter: Default::default(), @@ -1144,6 +1151,11 @@ impl PeerConfig { deserialize_reverse_mouse_wheel, UserDefaultConfig::read().get("reverse_mouse_wheel") ); + serde_field_string!( + default_displays_as_individual_windows, + deserialize_displays_as_individual_windows, + UserDefaultConfig::read().get("displays_as_individual_windows") + ); fn default_custom_image_quality() -> Vec { let f: f64 = UserDefaultConfig::read() diff --git a/src/client.rs b/src/client.rs index eff490ebb..445aaf225 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1207,6 +1207,17 @@ impl LoginConfigHandler { self.save_config(config); } + /// Save reverse mouse wheel ("", "Y") to the current config. + /// + /// # Arguments + /// + /// * `value` - The "displays_as_individual_windows" value ("", "Y"). + pub fn save_displays_as_individual_windows(&mut self, value: String) { + let mut config = self.load_config(); + config.displays_as_individual_windows = value; + self.save_config(config); + } + /// Save scroll style to the current config. /// /// # Arguments diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 93c7ced7e..e5f5d6573 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -339,6 +339,20 @@ pub fn session_set_reverse_mouse_wheel(session_id: SessionID, value: String) { } } +pub fn session_get_displays_as_individual_windows(session_id: SessionID) -> SyncReturn> { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { + SyncReturn(Some(session.get_displays_as_individual_windows())) + } else { + SyncReturn(None) + } +} + +pub fn session_set_displays_as_individual_windows(session_id: SessionID, value: String) { + if let Some(session) = sessions::get_session_by_session_id(&session_id) { + session.save_displays_as_individual_windows(value); + } +} + pub fn session_get_custom_image_quality(session_id: SessionID) -> Option> { if let Some(session) = sessions::get_session_by_session_id(&session_id) { Some(session.get_custom_image_quality()) diff --git a/src/lang/ar.rs b/src/lang/ar.rs index 90252faa7..e30621c21 100644 --- a/src/lang/ar.rs +++ b/src/lang/ar.rs @@ -562,8 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose display behavior", ""), - ("Switch display", ""), ("Open in new window", ""), + ("Show displays as individual windows", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ca.rs b/src/lang/ca.rs index 90887b622..e70c2c044 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -562,8 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose display behavior", ""), - ("Switch display", ""), ("Open in new window", ""), + ("Show displays as individual windows", ""), ].iter().cloned().collect(); } diff --git a/src/lang/cn.rs b/src/lang/cn.rs index 3793c2e5d..ba2796157 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -562,8 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", "显示器被拔出,切换到第一个显示器。"), ("No displays", "没有显示器。"), ("elevated_switch_display_msg", "切换到主显示器,因为提权后,不支持多显示器画面。"), - ("Choose display behavior", "选择显示器的行为"), - ("Switch display", "切换显示器"), ("Open in new window", "在新的窗口中打开"), + ("Show displays as individual windows", "在单个窗口中打开显示器"), ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index 71e152668..8907e78e0 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -562,8 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose display behavior", ""), - ("Switch display", ""), ("Open in new window", ""), + ("Show displays as individual windows", ""), ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index f11954d02..9d76b8e2e 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -562,8 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose display behavior", ""), - ("Switch display", ""), ("Open in new window", ""), + ("Show displays as individual windows", ""), ].iter().cloned().collect(); } diff --git a/src/lang/de.rs b/src/lang/de.rs index dc80bad04..d051700b0 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -562,8 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose display behavior", ""), - ("Switch display", ""), ("Open in new window", ""), + ("Show displays as individual windows", ""), ].iter().cloned().collect(); } diff --git a/src/lang/el.rs b/src/lang/el.rs index b1baabb2b..472b494e7 100644 --- a/src/lang/el.rs +++ b/src/lang/el.rs @@ -562,8 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose display behavior", ""), - ("Switch display", ""), ("Open in new window", ""), + ("Show displays as individual windows", ""), ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index 0ce93fdff..ccc674749 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -562,8 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose display behavior", ""), - ("Switch display", ""), ("Open in new window", ""), + ("Show displays as individual windows", ""), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index aa040d252..1b1b83afa 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -562,8 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose display behavior", ""), - ("Switch display", ""), ("Open in new window", ""), + ("Show displays as individual windows", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fa.rs b/src/lang/fa.rs index b7561283c..ff90b0430 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -562,8 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose display behavior", ""), - ("Switch display", ""), ("Open in new window", ""), + ("Show displays as individual windows", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index f0d3c1d8f..2195d4fdd 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -562,8 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose display behavior", ""), - ("Switch display", ""), ("Open in new window", ""), + ("Show displays as individual windows", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index 5a5e280d6..24ac09d05 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -562,8 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose display behavior", ""), - ("Switch display", ""), ("Open in new window", ""), + ("Show displays as individual windows", ""), ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index cf3702176..09e7853e4 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -562,8 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose display behavior", ""), - ("Switch display", ""), ("Open in new window", ""), + ("Show displays as individual windows", ""), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index fb84fbbdc..4416508bd 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -563,8 +563,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose display behavior", ""), - ("Switch display", ""), ("Open in new window", ""), + ("Show displays as individual windows", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index 7f85fcbb0..eeed3e147 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -562,8 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose display behavior", ""), - ("Switch display", ""), ("Open in new window", ""), + ("Show displays as individual windows", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index 1cce7aa7d..dcf28718a 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -562,8 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose display behavior", ""), - ("Switch display", ""), ("Open in new window", ""), + ("Show displays as individual windows", ""), ].iter().cloned().collect(); } diff --git a/src/lang/kz.rs b/src/lang/kz.rs index 3f52f8c7c..43c36e32c 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -562,8 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose display behavior", ""), - ("Switch display", ""), ("Open in new window", ""), + ("Show displays as individual windows", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lt.rs b/src/lang/lt.rs index f1e4e790f..02d4714c2 100644 --- a/src/lang/lt.rs +++ b/src/lang/lt.rs @@ -562,8 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose display behavior", ""), - ("Switch display", ""), ("Open in new window", ""), + ("Show displays as individual windows", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lv.rs b/src/lang/lv.rs index 44c53889f..8de908d9c 100644 --- a/src/lang/lv.rs +++ b/src/lang/lv.rs @@ -562,8 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose display behavior", ""), - ("Switch display", ""), ("Open in new window", ""), + ("Show displays as individual windows", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nl.rs b/src/lang/nl.rs index f5a1d5322..71d1c1107 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -562,8 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose Display Behavior", ""), - ("Switch display", ""), ("Open in new window", ""), + ("Show displays as individual windows", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pl.rs b/src/lang/pl.rs index beb47a4ec..c8b2a4cc3 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -562,8 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose display behavior", ""), - ("Switch display", ""), ("Open in new window", ""), + ("Show displays as individual windows", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index b0df8eefa..85863d2ae 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -562,8 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose display behavior", ""), - ("Switch display", ""), ("Open in new window", ""), + ("Show displays as individual windows", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index ceeb1aa22..be4cdfd8e 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -562,8 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose display behavior", ""), - ("Switch display", ""), ("Open in new window", ""), + ("Show displays as individual windows", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ro.rs b/src/lang/ro.rs index b194edd25..311a13bec 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -562,8 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose display behavior", ""), - ("Switch display", ""), ("Open in new window", ""), + ("Show displays as individual windows", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index b132d88b4..e05322b88 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -562,8 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose display behavior", ""), - ("Switch display", ""), ("Open in new window", ""), + ("Show displays as individual windows", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index 55ab7fb64..23751b734 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -562,8 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose display behavior", ""), - ("Switch display", ""), ("Open in new window", ""), + ("Show displays as individual windows", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sl.rs b/src/lang/sl.rs index 13440aa70..8f84b181a 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -562,8 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose display behavior", ""), - ("Switch display", ""), ("Open in new window", ""), + ("Show displays as individual windows", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sq.rs b/src/lang/sq.rs index 718f00518..3d26b5635 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -562,8 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose display behavior", ""), - ("Switch display", ""), ("Open in new window", ""), + ("Show displays as individual windows", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sr.rs b/src/lang/sr.rs index 755c52784..03f4776b7 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -562,8 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose display behavior", ""), - ("Switch display", ""), ("Open in new window", ""), + ("Show displays as individual windows", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sv.rs b/src/lang/sv.rs index 80a7098fc..ce2dab684 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -562,8 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose display behavior", ""), - ("Switch display", ""), ("Open in new window", ""), + ("Show displays as individual windows", ""), ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index ae4885298..04d3d53ef 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -562,8 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose display behavior", ""), - ("Switch display", ""), ("Open in new window", ""), + ("Show displays as individual windows", ""), ].iter().cloned().collect(); } diff --git a/src/lang/th.rs b/src/lang/th.rs index a6f10fb4f..75afe0324 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -562,8 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose display behavior", ""), - ("Switch display", ""), ("Open in new window", ""), + ("Show displays as individual windows", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index abc1d4515..e51cff3db 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -562,8 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose display behavior", ""), - ("Switch display", ""), ("Open in new window", ""), + ("Show displays as individual windows", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index cd0da2655..c70cf8f35 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -562,8 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose display behavior", ""), - ("Switch display", ""), ("Open in new window", ""), + ("Show displays as individual windows", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ua.rs b/src/lang/ua.rs index ce5561d52..94e0bc423 100644 --- a/src/lang/ua.rs +++ b/src/lang/ua.rs @@ -562,8 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose display behavior", ""), - ("Switch display", ""), ("Open in new window", ""), + ("Show displays as individual windows", ""), ].iter().cloned().collect(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index d77125ce3..895e7491a 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -562,8 +562,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("display_is_plugged_out_msg", ""), ("No displays", ""), ("elevated_switch_display_msg", ""), - ("Choose display behavior", ""), - ("Switch display", ""), ("Open in new window", ""), + ("Show displays as individual windows", ""), ].iter().cloned().collect(); } diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index 568767f81..0eea85173 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -236,10 +236,18 @@ impl Session { self.lc.read().unwrap().reverse_mouse_wheel.clone() } + pub fn get_displays_as_individual_windows(&self) -> String { + self.lc.read().unwrap().displays_as_individual_windows.clone() + } + pub fn save_reverse_mouse_wheel(&self, value: String) { self.lc.write().unwrap().save_reverse_mouse_wheel(value); } + pub fn save_displays_as_individual_windows(&self, value: String) { + self.lc.write().unwrap().save_displays_as_individual_windows(value); + } + pub fn save_view_style(&self, value: String) { self.lc.write().unwrap().save_view_style(value); } From c13d67dea52bfb511c899fe84533cd94feaf3ef9 Mon Sep 17 00:00:00 2001 From: SergeyMy <131688106+SergeyMy@users.noreply.github.com> Date: Sat, 14 Oct 2023 09:45:06 +0500 Subject: [PATCH 31/50] Update ru.rs --- src/lang/ru.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lang/ru.rs b/src/lang/ru.rs index 22e920b8e..8dcc132df 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -556,7 +556,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("upgrade_rustdesk_server_pro_to_{}_tip", "Обновите RustDesk Server Pro до версии {} или новее!"), ("pull_group_failed_tip", "Невозможно обновить группу"), ("Filter by intersection", "Фильтровать по пересечению"), - ("Remove wallpaper during incoming sessions", ""), - ("Test", ""), + ("Remove wallpaper during incoming sessions", "Удалить обои в сеансе"), + ("Test", "Тест"), ].iter().cloned().collect(); } From 68ef1fc9e0a07f3160216044046814e0c5a0bb99 Mon Sep 17 00:00:00 2001 From: 21pages Date: Sat, 14 Oct 2023 18:50:41 +0800 Subject: [PATCH 32/50] show wallpaper only when support, show test on checked Signed-off-by: 21pages --- Cargo.lock | 2 +- .../desktop/pages/desktop_setting_page.dart | 55 +++++++++++++------ src/flutter_ffi.rs | 4 ++ src/platform/linux.rs | 8 +++ src/platform/windows.rs | 4 ++ src/ui.rs | 5 ++ src/ui/index.tis | 3 +- src/ui_interface.rs | 15 ++++- 8 files changed, 75 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ca9ddc2f4..519476e93 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6602,7 +6602,7 @@ dependencies = [ [[package]] name = "wallpaper" version = "3.2.0" -source = "git+https://github.com/21pages/wallpaper.rs#2bbb70acd93be179c69cb96cb8c3dda487e6f5fd" +source = "git+https://github.com/21pages/wallpaper.rs#ce4a0cd3f58327c7cc44d15a63706fb0c022bacf" dependencies = [ "dirs 5.0.1", "enquote", diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index a105f2135..3bbade76e 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -323,24 +323,7 @@ class _GeneralState extends State<_General> { 'enable-confirm-closing-tabs', isServer: false), _OptionCheckBox(context, 'Adaptive bitrate', 'enable-abr'), - if (Platform.isWindows || Platform.isLinux) - Row( - children: [ - Flexible( - child: _OptionCheckBox( - context, - 'Remove wallpaper during incoming sessions', - 'allow-remove-wallpaper'), - ), - _CountDownButton( - text: 'Test', - second: 5, - onPressed: () { - bind.mainTestWallpaper(second: 5); - }, - ) - ], - ), + wallpaper(), _OptionCheckBox( context, 'Open connection in new tab', @@ -367,6 +350,42 @@ class _GeneralState extends State<_General> { return _Card(title: 'Other', children: children); } + Widget wallpaper() { + return futureBuilder(future: () async { + final support = await bind.mainSupportRemoveWallpaper(); + return support; + }(), hasData: (data) { + if (data is bool && data == true) { + final option = 'allow-remove-wallpaper'; + bool value = mainGetBoolOptionSync(option); + return Row( + children: [ + Flexible( + child: _OptionCheckBox( + context, + 'Remove wallpaper during incoming sessions', + option, + update: () { + setState(() {}); + }, + ), + ), + if (value) + _CountDownButton( + text: 'Test', + second: 5, + onPressed: () { + bind.mainTestWallpaper(second: 5); + }, + ) + ], + ); + } + + return Offstage(); + }); + } + Widget hwcodec() { return Offstage( offstage: !bind.mainHasHwcodec(), diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index b4ae89bc9..883835c26 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -1625,6 +1625,10 @@ pub fn main_test_wallpaper(_second: u64) { }); } +pub fn main_support_remove_wallpaper() -> bool { + support_remove_wallpaper() +} + /// Send a url scheme throught the ipc. /// /// * macOS only diff --git a/src/platform/linux.rs b/src/platform/linux.rs index a4975d3aa..37b27cf64 100644 --- a/src/platform/linux.rs +++ b/src/platform/linux.rs @@ -1341,6 +1341,14 @@ impl WallPaperRemover { old_path_dark, }) } + + pub fn support() -> bool { + let desktop = std::env::var("XDG_CURRENT_DESKTOP").unwrap_or_default(); + if wallpaper::gnome::is_compliant(&desktop) || desktop.as_str() == "XFCE" { + return wallpaper::get().is_ok(); + } + false + } } impl Drop for WallPaperRemover { diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 9cc4fd39f..f664b1aee 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -2392,6 +2392,10 @@ impl WallPaperRemover { Ok(Self { old_path }) } + pub fn support() -> bool { + wallpaper::get().is_ok() || !Self::get_recent_wallpaper().unwrap_or_default().is_empty() + } + fn get_recent_wallpaper() -> ResultType { // SystemParametersInfoW may return %appdata%\Microsoft\Windows\Themes\TranscodedWallpaper, not real path and may not real cache // https://www.makeuseof.com/find-desktop-wallpapers-file-location-windows-11/ diff --git a/src/ui.rs b/src/ui.rs index 27793b31f..fbe715fa9 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -598,6 +598,10 @@ impl UI { fn get_login_device_info(&self) -> String { get_login_device_info_json() } + + fn support_remove_wallpaper(&self) -> bool { + support_remove_wallpaper() + } } impl sciter::EventHandler for UI { @@ -683,6 +687,7 @@ impl sciter::EventHandler for UI { fn default_video_save_directory(); fn handle_relay_id(String); fn get_login_device_info(); + fn support_remove_wallpaper(); } } diff --git a/src/ui/index.tis b/src/ui/index.tis index 67deed7d7..7a46e3b3f 100644 --- a/src/ui/index.tis +++ b/src/ui/index.tis @@ -210,6 +210,7 @@ class Enhancements: Reactor.Component { function render() { var has_hwcodec = handler.has_hwcodec(); + var support_remove_wallpaper = handler.support_remove_wallpaper(); var me = this; self.timer(1ms, function() { me.toggleMenuState() }); return
  • {translate('Enhancements')} @@ -217,7 +218,7 @@ class Enhancements: Reactor.Component { {has_hwcodec ?
  • {svg_checkmark}{translate("Hardware Codec")} (beta)
  • : ""}
  • {svg_checkmark}{translate("Adaptive bitrate")} (beta)
  • {translate("Recording")}
  • - {is_osx ? "" :
  • {svg_checkmark}{translate("Remove wallpaper during incoming sessions")}
  • } + {support_remove_wallpaper ?
  • {svg_checkmark}{translate("Remove wallpaper during incoming sessions")}
  • : ""} ; } diff --git a/src/ui_interface.rs b/src/ui_interface.rs index ed2b4f4fc..f61e683ed 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -594,7 +594,13 @@ pub fn current_is_wayland() -> bool { #[inline] pub fn get_new_version() -> String { - (*SOFTWARE_UPDATE_URL.lock().unwrap().rsplit('/').next().unwrap_or("")).to_string() + (*SOFTWARE_UPDATE_URL + .lock() + .unwrap() + .rsplit('/') + .next() + .unwrap_or("")) + .to_string() } #[inline] @@ -1248,3 +1254,10 @@ pub fn handle_relay_id(id: String) -> String { id } } + +pub fn support_remove_wallpaper() -> bool { + #[cfg(any(target_os = "windows", target_os = "linux"))] + return crate::platform::WallPaperRemover::support(); + #[cfg(not(any(target_os = "windows", target_os = "linux")))] + return false; +} From 3070b0019edb05331c817a993e5188f7b3ae01f4 Mon Sep 17 00:00:00 2001 From: Mr-Update <37781396+Mr-Update@users.noreply.github.com> Date: Sat, 14 Oct 2023 16:01:09 +0200 Subject: [PATCH 33/50] Update de.rs @grummbeer You are right. --- src/lang/de.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lang/de.rs b/src/lang/de.rs index aac437388..0eac4fd6a 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -557,6 +557,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("pull_group_failed_tip", "Aktualisierung der Gruppe fehlgeschlagen"), ("Remove wallpaper during incoming sessions", ""), ("Test", ""), - ("Filter by intersection", "Nach Schnittpunkt filtern") + ("Filter by intersection", "Nach Schnittmenge filtern") ].iter().cloned().collect(); } From 8a2ab30302b4ddbf13f5e41e056605001fbabb31 Mon Sep 17 00:00:00 2001 From: Mr-Update <37781396+Mr-Update@users.noreply.github.com> Date: Sat, 14 Oct 2023 16:07:02 +0200 Subject: [PATCH 34/50] Update de.rs @grummbeer You are right. --- src/lang/de.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lang/de.rs b/src/lang/de.rs index 0eac4fd6a..159a121cf 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -555,8 +555,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", "Beim Start auf Softwareaktualisierung prüfen"), ("upgrade_rustdesk_server_pro_to_{}_tip", "Bitte aktualisieren Sie RustDesk Server Pro auf die Version {} oder neuer!"), ("pull_group_failed_tip", "Aktualisierung der Gruppe fehlgeschlagen"), - ("Remove wallpaper during incoming sessions", ""), - ("Test", ""), - ("Filter by intersection", "Nach Schnittmenge filtern") + ("Filter by intersection", "Nach Schnittmenge filtern"), + ("Remove wallpaper during incoming sessions", "Hintergrundbild während eingehender Sitzungen entfernen"), + ("Test", "Test"), ].iter().cloned().collect(); } From b969307c5e7a5d4e1a66570fa2f8ea6568571d2b Mon Sep 17 00:00:00 2001 From: Ibnul Mutaki Date: Sat, 14 Oct 2023 21:33:05 +0700 Subject: [PATCH 35/50] improve id translation --- src/lang/id.rs | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/lang/id.rs b/src/lang/id.rs index d162971b9..45707c4a4 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -5,7 +5,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Your Desktop", "Desktop Anda"), ("desk_tip", "Desktop Anda dapat diakses dengan ID dan kata sandi ini."), ("Password", "Kata sandi"), - ("Ready", "Siap"), + ("Ready", "Sudah siap"), ("Established", "Didirikan"), ("connecting_status", "Menghubungkan ke jaringan RustDesk..."), ("Enable Service", "Aktifkan Layanan"), @@ -175,7 +175,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("setup_server_tip", "Untuk mendapatkan koneksi yang lebih baik, disarankan untuk menginstal di server anda sendiri"), ("Too short, at least 6 characters.", "Terlalu pendek, setidaknya 6 karekter."), ("The confirmation is not identical.", "Konfirmasi tidak identik."), - ("Permissions", "Izin"), + ("Permissions", "Perizinan"), ("Accept", "Terima"), ("Dismiss", "Hentikan"), ("Disconnect", "Terputus"), @@ -241,7 +241,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Remove from Favorites", "Hapus dari favorit"), ("Empty", "Kosong"), ("Invalid folder name", "Nama folder tidak valid"), - ("Socks5 Proxy", "Proxy Socks5"), + ("Socks5 Proxy", "Proksi Socks5"), ("Hostname", "Hostname"), ("Discovered", "Telah ditemukan"), ("install_daemon_tip", "Untuk memulai saat boot, Anda perlu menginstal system service."), @@ -358,7 +358,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Unlock Network Settings", "Buka Keamanan Pengaturan Jaringan"), ("Server", "Server"), ("Direct IP Access", "Akses IP Langsung"), - ("Proxy", "Proxy"), + ("Proxy", "Proksi"), ("Apply", "Terapkan"), ("Disconnect all devices?", "Putuskan sambungan semua perangkat?"), ("Clear", "Bersihkan"), @@ -367,7 +367,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Network", "Jaringan"), ("Pin Toolbar", "Sematkan Toolbar"), ("Unpin Toolbar", "Batal sematkan Toolbar"), - ("Recording", "Sedang Merekam"), + ("Recording", "Perekaman"), ("Directory", "Direktori"), ("Automatically record incoming sessions", "Secara otomatis merekam sesi masuk"), ("Change", "Ubah"), @@ -375,9 +375,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Stop session recording", "Hentikan sesi perekaman"), ("Enable Recording Session", "Aktifkan Sesi Perekaman"), ("Allow recording session", "Izinkan sesi perekaman"), - ("Enable LAN Discovery", "Aktifkan Penemuan LAN"), - ("Deny LAN Discovery", "Tolak Penemuan LAN"), - ("Write a message", "Menulis pesan"), + ("Enable LAN Discovery", "Aktifkan Pencarian Jaringan Lokal (LAN)"), + ("Deny LAN Discovery", "Tolak Pencarian Jaringan Lokal (LAN)"), + ("Write a message", "Tulis pesan"), ("Prompt", ""), ("Please wait for confirmation of UAC...", "Harap tunggu konfirmasi UAC"), ("elevated_foreground_window_tip", "Jendela remote desktop ini memerlukan hak akses khusus, jadi anda tidak bisa menggunakan mouse dan keyboard untuk sementara. Anda bisa meminta pihak pengguna yang diremote untuk menyembunyikan jendela ini atau klik tombol elevasi di jendela pengaturan koneksi. Untuk menghindari masalah ini, direkomendasikan untuk menginstall aplikasi secara permanen"), @@ -401,9 +401,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Accept sessions via click", "Izinkan sesi dengan klik"), ("Accept sessions via both", "Izinkan sesi dengan keduanya"), ("Please wait for the remote side to accept your session request...", "Harap tunggu pihak pengguna remote untuk menerima permintaan sesi..."), - ("One-time Password", "Kata sandi satu kali"), - ("Use one-time password", "Gunakan kata sandi satu kali"), - ("One-time password length", "Panjang kata sandi satu kali pakai"), + ("One-time Password", "Kata sandi sekali pakai"), + ("Use one-time password", "Gunakan kata sandi sekali pakai"), + ("One-time password length", "Panjang kata sandi sekali pakai"), ("Request access to your device", "Permintaan akses ke perangkat ini"), ("Hide connection management window", "Sembunyikan jendela pengaturan koneksi"), ("hide_cm_tip", "Izinkan untuk menyembunyikan hanya jika menerima sesi melalui kata sandi dan menggunakan kata sandi permanen"), @@ -457,7 +457,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Codec", "Codec"), ("Resolution", "Resolusi"), ("No transfers in progress", "Tidak ada transfer data yang sedang berlangsung"), - ("Set one-time password length", "Atur panjang kata sandi satu kali pakai"), + ("Set one-time password length", "Atur panjang kata sandi sekali pakai"), ("install_cert_tip", "Install sertifikat RustDesk"), ("confirm_install_cert_tip", "Ini adalah sertifikat pengujian RustDesk, yang dapat dipercaya. Sertifikat ini akan digunakan untuk menginstal driver RustDesk saat diperlukan"), ("RDP Settings", "Pengaturan RDP"), @@ -469,7 +469,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Your Device", "Perangkat anda"), ("empty_recent_tip", "Tidak ada sesi terbaru!"), ("empty_favorite_tip", "Belum ada rekan favorit?\nTemukan seseorang untuk terhubung dan tambahkan ke favorit!"), - ("empty_lan_tip", "Sepertinya kami belum menemukan rekan"), + ("empty_lan_tip", "Sepertinya kami belum memiliki rekan"), ("empty_address_book_tip", "Tampaknya saat ini tidak ada rekan yang terdaftar dalam buku alamat Anda"), ("eg: admin", "contoh: admin"), ("Empty Username", "Nama pengguna kosong"), @@ -517,8 +517,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Open", "Buka"), ("logout_tip", "Apakah Anda yakin ingin keluar?"), ("Service", "Service"), - ("Start", "Mulai"), - ("Stop", "Berhenti"), + ("Start", "Jalankan"), + ("Stop", "Hentikan"), ("exceed_max_devices", "Anda telah mencapai jumlah maksimal perangkat yang dikelola"), ("Sync with recent sessions", "Sinkronkan dengan sesi terbaru"), ("Sort tags", "Urutkan tag"), @@ -550,13 +550,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("I Agree", "Saya setuju"), ("Decline", "Tolak"), ("Timeout in minutes", "Batasan Waktu dalam Menit"), - ("auto_disconnect_option_tip", "Secara otomatis akan menutup sesi ketika pengguna tidak beraktivitas"), + ("auto_disconnect_option_tip", "Secara otomatis akan menutup sesi ketika tidak ada aktivitas"), ("Connection failed due to inactivity", "Secara otomatis akan terputus ketik tidak ada aktivitas."), ("Check for software update on startup", "Periksa pembaruan aplikasi saat sistem dinyalakan."), ("upgrade_rustdesk_server_pro_to_{}_tip", "Silahkan perbarui RustDesk Server Pro ke versi {} atau yang lebih baru!"), ("pull_group_failed_tip", "Gagal memperbarui grup"), - ("Filter by intersection", ""), - ("Remove wallpaper during incoming sessions", ""), - ("Test", ""), + ("Filter by intersection", "Filter berdasarkan interseksi"), + ("Remove wallpaper during incoming sessions", "Hilangkan latar dinding ketika ada sesi yang masuk"), + ("Test", "Tes"), ].iter().cloned().collect(); } From a12fac780bebe551b830e37dd7322fe02763195c Mon Sep 17 00:00:00 2001 From: Ibnul Mutaki Date: Sat, 14 Oct 2023 21:50:29 +0700 Subject: [PATCH 36/50] improve ID translation --- src/lang/id.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/lang/id.rs b/src/lang/id.rs index ae0a787ef..2f37c3f07 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -555,14 +555,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Check for software update on startup", "Periksa pembaruan aplikasi saat sistem dinyalakan."), ("upgrade_rustdesk_server_pro_to_{}_tip", "Silahkan perbarui RustDesk Server Pro ke versi {} atau yang lebih baru!"), ("pull_group_failed_tip", "Gagal memperbarui grup"), - ("Filter by intersection", "Filter berdasarkan interseksi"), + ("Filter by intersection", "Filter berdasarkan persilangan"), ("Remove wallpaper during incoming sessions", "Hilangkan latar dinding ketika ada sesi yang masuk"), ("Test", "Tes"), - ("switch_display_elevated_connections_tip", ""), - ("display_is_plugged_out_msg", ""), - ("No displays", ""), - ("elevated_switch_display_msg", ""), - ("Open in new window", ""), - ("Show displays as individual windows", ""), + ("switch_display_elevated_connections_tip", "Pada mode elevasi, jika terdapat beberapa tampilan yang aktif, maka tidak diizinkan berpindah ke yang bukan tampilan utama, silahkan coba lagi setelah proses instalasi jika kamu ingin melakukan kontrol ke tampilan layar lainnya"), + ("display_is_plugged_out_msg", "Layar terputus, pindah ke layar pertama"), + ("No displays", "Tidak ada tampilan"), + ("elevated_switch_display_msg", "Pindah ke tampilan utama, pada mode elevasi, pengggunaan lebih dari satu layar tidak diizinkan"), + ("Open in new window", "Buka di jendela baru"), + ("Show displays as individual windows", "Tampilkan layar sebagai jendela terpisah"), ].iter().cloned().collect(); } From fb5ba257efd63d5ebe5fcd740da883c04e20eb32 Mon Sep 17 00:00:00 2001 From: Kleofass Date: Sat, 14 Oct 2023 18:43:17 +0300 Subject: [PATCH 37/50] Update lv.rs --- src/lang/lv.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lang/lv.rs b/src/lang/lv.rs index 8de908d9c..1af747f23 100644 --- a/src/lang/lv.rs +++ b/src/lang/lv.rs @@ -556,13 +556,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("upgrade_rustdesk_server_pro_to_{}_tip", "Lūdzu, jauniniet RustDesk Server Pro uz versiju {} vai jaunāku!"), ("pull_group_failed_tip", "Neizdevās atsvaidzināt grupu"), ("Filter by intersection", "Filtrēt pēc krustpunkta"), - ("Remove wallpaper during incoming sessions", ""), - ("Test", ""), - ("switch_display_elevated_connections_tip", ""), - ("display_is_plugged_out_msg", ""), - ("No displays", ""), - ("elevated_switch_display_msg", ""), - ("Open in new window", ""), - ("Show displays as individual windows", ""), + ("Remove wallpaper during incoming sessions", "Noņemt fona tapeti ienākošo sesiju laikā"), + ("Test", "Pārbaudīt"), + ("switch_display_elevated_connections_tip", "Pārslēgšanās uz ne primāro displeju netiek atbalstīta paaugstinātajā režīmā, ja ir vairāki savienojumi. Lūdzu, mēģiniet vēlreiz pēc instalēšanas, ja vēlaties kontrolēt vairākus displejus."), + ("display_is_plugged_out_msg", "Displejs ir atvienots, pārslēdzieties uz pirmo displeju."), + ("No displays", "Nav displeju"), + ("elevated_switch_display_msg", "Pārslēdzieties uz primāro displeju, jo paaugstinātajā režīmā netiek atbalstīti vairāki displeji."), + ("Open in new window", "Atvērt jaunā logā"), + ("Show displays as individual windows", "Rādīt displejus kā atsevišķus logus"), ].iter().cloned().collect(); } From c18c1e59df7a1cdfe5930f17bfad27785f6aaa85 Mon Sep 17 00:00:00 2001 From: Mr-Update <37781396+Mr-Update@users.noreply.github.com> Date: Sat, 14 Oct 2023 20:25:39 +0200 Subject: [PATCH 38/50] Update de.rs --- src/lang/de.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/lang/de.rs b/src/lang/de.rs index 6cf00c419..b67ef32b3 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -558,12 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Filter by intersection", "Nach Schnittmenge filtern"), ("Remove wallpaper during incoming sessions", "Hintergrundbild während eingehender Sitzungen entfernen"), ("Test", "Test"), - ("Filter by intersection", "Nach Schnittpunkt filtern"), - ("switch_display_elevated_connections_tip", ""), - ("display_is_plugged_out_msg", ""), - ("No displays", ""), - ("elevated_switch_display_msg", ""), - ("Open in new window", ""), - ("Show displays as individual windows", ""), + ("switch_display_elevated_connections_tip", "Das Umschalten auf eine nicht primäre Anzeige wird mit erhöhten Rechten nicht unterstützt, wenn mehrere Verbindungen bestehen. Bitte versuchen Sie es nach der Installation erneut, wenn Sie mehrere Anzeigen steuern möchten."), + ("display_is_plugged_out_msg", "Das Anzeigegerät ist nicht angeschlossen, schalten Sie auf das erste Anzeigegerät um."), + ("No displays", "Keine Anzeigegeräte"), + ("elevated_switch_display_msg", "Wechseln Sie zur primären Anzeige, da die Mehrfachanzeige im erweiterten Modus nicht unterstützt wird."), + ("Open in new window", "In einem neuen Fenster öffnen"), + ("Show displays as individual windows", "Anzeigen als einzelne Fenster darstellen"), ].iter().cloned().collect(); } From 64c8c5a014935579ed822c4a5d07fe5746f73c7a Mon Sep 17 00:00:00 2001 From: SergeyMy <131688106+SergeyMy@users.noreply.github.com> Date: Sun, 15 Oct 2023 08:21:38 +0500 Subject: [PATCH 39/50] Update ru.rs --- src/lang/ru.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lang/ru.rs b/src/lang/ru.rs index 8c3de1bba..c250b59c0 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -560,9 +560,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Test", "Тест"), ("switch_display_elevated_connections_tip", ""), ("display_is_plugged_out_msg", ""), - ("No displays", ""), + ("No displays", "Нет дисплеев"), ("elevated_switch_display_msg", ""), - ("Open in new window", ""), - ("Show displays as individual windows", ""), + ("Open in new window", "Открыть в новом окне"), + ("Show displays as individual windows", "Показывать дисплеи в отдельных окнах"), ].iter().cloned().collect(); } From 5f92465d0fe789526bd8c109a4bde56d3020d13f Mon Sep 17 00:00:00 2001 From: rustdesk Date: Sun, 15 Oct 2023 11:30:45 +0800 Subject: [PATCH 40/50] move is_x11 out of loop, https://github.com/rustdesk/rustdesk/discussions/6042 --- src/server/video_service.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/server/video_service.rs b/src/server/video_service.rs index cfeeb1fa8..2afa02aca 100644 --- a/src/server/video_service.rs +++ b/src/server/video_service.rs @@ -61,6 +61,10 @@ lazy_static::lazy_static! { pub static ref IS_FOREGROUND_WINDOW_ELEVATED: Arc> = Default::default(); } +// https://github.com/rustdesk/rustdesk/discussions/6042, avoiding dbus call +#[cfg(target_os = "linux")] +static IS_X11: AtomicBool = AtomicBool::new(false); + #[inline] pub fn notify_video_frame_fetched(conn_id: i32, frame_tm: Option) { FRAME_FETCHED_NOTIFIER.0.send((conn_id, frame_tm)).ok(); @@ -168,7 +172,7 @@ fn check_display_changed( #[cfg(target_os = "linux")] { // wayland do not support changing display for now - if !scrap::is_x11() { + if !IS_X11.load(Ordering::SeqCst)() { return false; } } @@ -349,7 +353,7 @@ fn get_capturer( ) -> ResultType { #[cfg(target_os = "linux")] { - if !scrap::is_x11() { + if !IS_X11.load(Ordering::SeqCst)() { return super::wayland::get_capturer(); } } @@ -425,6 +429,11 @@ fn run(vs: VideoService) -> ResultType<()> { #[cfg(not(any(target_os = "android", target_os = "ios")))] let _wake_lock = get_wake_lock(); + #[cfg(target_os = "linux")] + { + IS_X11.store(scrap::is_x11(), Ordering::SeqCst); + } + // ensure_inited() is needed because clear() may be called. // to-do: wayland ensure_inited should pass current display index. // But for now, we do not support multi-screen capture on wayland. @@ -598,7 +607,7 @@ fn run(vs: VideoService) -> ResultType<()> { #[cfg(target_os = "linux")] { would_block_count += 1; - if !scrap::is_x11() { + if !IS_X11.load(Ordering::SeqCst)() { if would_block_count >= 100 { // to-do: Unknown reason for WouldBlock 100 times (seconds = 100 * 1 / fps) // https://github.com/rustdesk/rustdesk/blob/63e6b2f8ab51743e77a151e2b7ff18816f5fa2fb/libs/scrap/src/common/wayland.rs#L81 @@ -786,7 +795,7 @@ fn handle_one_frame( pub fn is_inited_msg() -> Option { #[cfg(target_os = "linux")] - if !scrap::is_x11() { + if !IS_X11.load(Ordering::SeqCst)() { return super::wayland::is_inited(); } None From 63ba4f4f91a23350de1eb06ba657851eea1b09f1 Mon Sep 17 00:00:00 2001 From: rustdesk Date: Sun, 15 Oct 2023 11:44:44 +0800 Subject: [PATCH 41/50] fix ci --- src/server/video_service.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/server/video_service.rs b/src/server/video_service.rs index 2afa02aca..9acb5d541 100644 --- a/src/server/video_service.rs +++ b/src/server/video_service.rs @@ -37,6 +37,8 @@ use scrap::{ vpxcodec::{VpxEncoderConfig, VpxVideoCodecId}, CodecName, Display, TraitCapturer, }; +#[cfg(target_os = "linux")] +use std::sync::atomic::{AtomicBool, Ordering}; #[cfg(windows)] use std::sync::Once; use std::{ From 3702b0c694a20e027bbf04fbdbf0fe359d125d06 Mon Sep 17 00:00:00 2001 From: rustdesk Date: Sun, 15 Oct 2023 11:56:56 +0800 Subject: [PATCH 42/50] fix ci --- src/server/video_service.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/server/video_service.rs b/src/server/video_service.rs index 9acb5d541..1dd793fc3 100644 --- a/src/server/video_service.rs +++ b/src/server/video_service.rs @@ -174,7 +174,7 @@ fn check_display_changed( #[cfg(target_os = "linux")] { // wayland do not support changing display for now - if !IS_X11.load(Ordering::SeqCst)() { + if !IS_X11.load(Ordering::SeqCst) { return false; } } @@ -355,7 +355,7 @@ fn get_capturer( ) -> ResultType { #[cfg(target_os = "linux")] { - if !IS_X11.load(Ordering::SeqCst)() { + if !IS_X11.load(Ordering::SeqCst) { return super::wayland::get_capturer(); } } @@ -609,7 +609,7 @@ fn run(vs: VideoService) -> ResultType<()> { #[cfg(target_os = "linux")] { would_block_count += 1; - if !IS_X11.load(Ordering::SeqCst)() { + if !IS_X11.load(Ordering::SeqCst) { if would_block_count >= 100 { // to-do: Unknown reason for WouldBlock 100 times (seconds = 100 * 1 / fps) // https://github.com/rustdesk/rustdesk/blob/63e6b2f8ab51743e77a151e2b7ff18816f5fa2fb/libs/scrap/src/common/wayland.rs#L81 @@ -797,7 +797,7 @@ fn handle_one_frame( pub fn is_inited_msg() -> Option { #[cfg(target_os = "linux")] - if !IS_X11.load(Ordering::SeqCst)() { + if !IS_X11.load(Ordering::SeqCst) { return super::wayland::is_inited(); } None From 5c9ee03389deb327f8c4a4c52d0ac086e744811d Mon Sep 17 00:00:00 2001 From: 21pages Date: Sun, 15 Oct 2023 16:05:29 +0800 Subject: [PATCH 43/50] fix ios ci don't know why call session_get_rgba Signed-off-by: 21pages --- flutter/ios/Runner/AppDelegate.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter/ios/Runner/AppDelegate.swift b/flutter/ios/Runner/AppDelegate.swift index 730c9adcb..89e443af6 100644 --- a/flutter/ios/Runner/AppDelegate.swift +++ b/flutter/ios/Runner/AppDelegate.swift @@ -14,6 +14,6 @@ import Flutter public func dummyMethodToEnforceBundling() { dummy_method_to_enforce_bundling(); - session_get_rgba(nil); + session_get_rgba(nil, 0); } } From 2cc0bf22cb26778f9e04f34341304cc7eda7fb33 Mon Sep 17 00:00:00 2001 From: dignow Date: Mon, 16 Oct 2023 00:20:41 +0800 Subject: [PATCH 44/50] fix, non-texture render, next_rgba Signed-off-by: dignow --- src/flutter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/flutter.rs b/src/flutter.rs index 237775003..a9cd98149 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -803,7 +803,7 @@ impl InvokeUiSession for FlutterHandler { fn next_rgba(&self, _display: usize) { #[cfg(not(feature = "flutter_texture_render"))] if let Some(rgba_data) = self.display_rgbas.write().unwrap().get_mut(&_display) { - rgba_data.valid = true; + rgba_data.valid = false; } } } From ea8576d3443f86d1365195e7bad197d9f6ee5883 Mon Sep 17 00:00:00 2001 From: bovirus <1262554+bovirus@users.noreply.github.com> Date: Sun, 15 Oct 2023 21:08:24 +0200 Subject: [PATCH 45/50] Update Italian language --- src/lang/it.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lang/it.rs b/src/lang/it.rs index 4416508bd..cb7ed922c 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -557,13 +557,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("upgrade_rustdesk_server_pro_to_{}_tip", "Aggiorna RustDesk Server Pro alla versione {} o successiva!"), ("pull_group_failed_tip", "Impossibile aggiornare il gruppo"), ("Filter by intersection", "Filtra per incrocio"), - ("Remove wallpaper during incoming sessions", ""), - ("Test", ""), - ("switch_display_elevated_connections_tip", ""), - ("display_is_plugged_out_msg", ""), - ("No displays", ""), - ("elevated_switch_display_msg", ""), - ("Open in new window", ""), - ("Show displays as individual windows", ""), + ("Remove wallpaper during incoming sessions", "Rimuovi lo sfondo durante le sessioni in entrata"), + ("Test", "Test"), + ("switch_display_elevated_connections_tip", "Nella modalità elevata quando sono presenti più connessioni non è supportato il passaggio allo schermo non primario. Se vuoi controllare più schermi riprova dopo l'installazione."), + ("display_is_plugged_out_msg", "Lo schermo è scollegato, passo al primo schermo."), + ("No displays", "Nessuno schermo"), + ("elevated_switch_display_msg", "Passo allo schermo principale perché in modalità elevata non sono supportati più schermi."), + ("Open in new window", "Apri in una nuova finestra"), + ("Show displays as individual windows", "Visualizza schermi come finestre individuali"), ].iter().cloned().collect(); } From fde8196874db595598efb3f21eaca4d283f15b9c Mon Sep 17 00:00:00 2001 From: dignow Date: Mon, 16 Oct 2023 07:26:55 +0800 Subject: [PATCH 46/50] fix several bugs 1. updateCurDisplay, canvas origin (x,y) 2. Do not show "Show displays as individual windows" on non-texture render version. Signed-off-by: dignow --- flutter/lib/common.dart | 3 ++- flutter/lib/common/widgets/toolbar.dart | 4 +++- flutter/lib/desktop/widgets/remote_toolbar.dart | 11 +++++++---- flutter/lib/models/model.dart | 4 ++-- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 73b4bf1a5..1da2dc60a 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -14,6 +14,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_hbb/common/formatter/id_formatter.dart'; import 'package:flutter_hbb/desktop/widgets/refresh_wrapper.dart'; import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; +import 'package:flutter_hbb/models/desktop_render_texture.dart'; import 'package:flutter_hbb/main.dart'; import 'package:flutter_hbb/models/peer_model.dart'; import 'package:flutter_hbb/models/state_model.dart'; @@ -2602,5 +2603,5 @@ sessionRefreshVideo(SessionID sessionId, PeerInfo pi) async { bool isChooseDisplayToOpenInNewWindow(PeerInfo pi, SessionID sessionId) => pi.isSupportMultiDisplay && + useTextureRender && bind.sessionGetDisplaysAsIndividualWindows(sessionId: sessionId) == 'Y'; - diff --git a/flutter/lib/common/widgets/toolbar.dart b/flutter/lib/common/widgets/toolbar.dart index ceb28b0a3..ebaff8954 100644 --- a/flutter/lib/common/widgets/toolbar.dart +++ b/flutter/lib/common/widgets/toolbar.dart @@ -9,6 +9,7 @@ import 'package:flutter_hbb/common/widgets/dialog.dart'; import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/models/model.dart'; import 'package:flutter_hbb/models/platform_model.dart'; +import 'package:flutter_hbb/models/desktop_render_texture.dart'; import 'package:get/get.dart'; bool isEditOsPassword = false; @@ -516,7 +517,8 @@ Future> toolbarDisplayToggle( child: Text(translate('Swap control-command key')))); } - if (pi.isSupportMultiDisplay && + if (useTextureRender && + pi.isSupportMultiDisplay && PrivacyModeState.find(id).isFalse && pi.displaysCount.value > 1 && bind.mainGetUserDefaultOption(key: kKeyShowMonitorsToolbar) == 'Y') { diff --git a/flutter/lib/desktop/widgets/remote_toolbar.dart b/flutter/lib/desktop/widgets/remote_toolbar.dart index 1bf4d438a..92038f68b 100644 --- a/flutter/lib/desktop/widgets/remote_toolbar.dart +++ b/flutter/lib/desktop/widgets/remote_toolbar.dart @@ -9,6 +9,7 @@ import 'package:flutter_hbb/common/widgets/toolbar.dart'; import 'package:flutter_hbb/main.dart'; import 'package:flutter_hbb/models/chat_model.dart'; import 'package:flutter_hbb/models/state_model.dart'; +import 'package:flutter_hbb/models/desktop_render_texture.dart'; import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/utils/multi_window_manager.dart'; import 'package:flutter_hbb/plugin/widgets/desc_ui.dart'; @@ -600,6 +601,9 @@ class _MonitorMenu extends StatelessWidget { bool get showMonitorsToolbar => bind.mainGetUserDefaultOption(key: kKeyShowMonitorsToolbar) == 'Y'; + bool get supportIndividualWindows => + useTextureRender && ffi.ffiModel.pi.isSupportMultiDisplay; + @override Widget build(BuildContext context) => showMonitorsToolbar ? buildMultiMonitorMenu() : buildMonitorMenu(); @@ -622,13 +626,12 @@ class _MonitorMenu extends StatelessWidget { } Widget buildMonitorSubmenuWidget() { - final pi = ffi.ffiModel.pi; return Column( mainAxisSize: MainAxisSize.min, children: [ Row(children: buildMonitorList(false)), - pi.isSupportMultiDisplay ? Divider() : Offstage(), - pi.isSupportMultiDisplay ? chooseDisplayBehavior() : Offstage(), + supportIndividualWindows ? Divider() : Offstage(), + supportIndividualWindows ? chooseDisplayBehavior() : Offstage(), ], ); } @@ -711,7 +714,7 @@ class _MonitorMenu extends StatelessWidget { for (int i = 0; i < pi.displays.length; i++) { monitorList.add(buildMonitorButton(i)); } - if (pi.isSupportMultiUiSession && pi.displays.length > 1) { + if (supportIndividualWindows && pi.displays.length > 1) { monitorList.add(buildMonitorButton(kAllDisplayValue)); } return monitorList; diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index a0fde0ca4..d257f3290 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -19,6 +19,7 @@ import 'package:flutter_hbb/models/peer_tab_model.dart'; import 'package:flutter_hbb/models/server_model.dart'; import 'package:flutter_hbb/models/user_model.dart'; import 'package:flutter_hbb/models/state_model.dart'; +import 'package:flutter_hbb/models/desktop_render_texture.dart'; import 'package:flutter_hbb/plugin/event.dart'; import 'package:flutter_hbb/plugin/manager.dart'; import 'package:flutter_hbb/plugin/widgets/desc_ui.dart'; @@ -415,11 +416,11 @@ class FfiModel with ChangeNotifier { return; } if (newRect != _rect) { - _rect = newRect; if (newRect.left != _rect?.left || newRect.top != _rect?.top) { parent.target?.cursorModel .updateDisplayOrigin(newRect.left, newRect.top); } + _rect = newRect; parent.target?.canvasModel.updateViewStyle(); _updateSessionWidthHeight(sessionId); } @@ -1991,7 +1992,6 @@ class FFI { } final stream = bind.sessionStart(sessionId: sessionId, id: id); final cb = ffiModel.startEventListener(sessionId, id); - final useTextureRender = bind.mainUseTextureRender(); // Force refresh displays. // The controlled side may not refresh the image when the (peer,display) is already subscribed. From 0a7a6c64ce2c88df668bc83bb193ffb32a0b1f6e Mon Sep 17 00:00:00 2001 From: dignow Date: Mon, 16 Oct 2023 07:34:38 +0800 Subject: [PATCH 47/50] remove 'Show displays as individual windows' if non-texture render Signed-off-by: dignow --- flutter/lib/desktop/pages/desktop_setting_page.dart | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/flutter/lib/desktop/pages/desktop_setting_page.dart b/flutter/lib/desktop/pages/desktop_setting_page.dart index bc65e236c..f74535047 100644 --- a/flutter/lib/desktop/pages/desktop_setting_page.dart +++ b/flutter/lib/desktop/pages/desktop_setting_page.dart @@ -10,6 +10,7 @@ import 'package:flutter_hbb/common/widgets/setting_widgets.dart'; import 'package:flutter_hbb/consts.dart'; import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart'; import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart'; +import 'package:flutter_hbb/models/desktop_render_texture.dart'; import 'package:flutter_hbb/models/platform_model.dart'; import 'package:flutter_hbb/models/server_model.dart'; import 'package:flutter_hbb/plugin/manager.dart'; @@ -1306,7 +1307,7 @@ class _DisplayState extends State<_Display> { } Widget other(BuildContext context) { - return _Card(title: 'Other Default Options', children: [ + final children = [ otherRow('View Mode', 'view_only'), otherRow('show_monitors_tip', kKeyShowMonitorsToolbar), otherRow('Collapse toolbar', 'collapse_toolbar'), @@ -1319,9 +1320,12 @@ class _DisplayState extends State<_Display> { otherRow('Lock after session end', 'lock_after_session_end'), otherRow('Privacy mode', 'privacy_mode'), otherRow('Reverse mouse wheel', 'reverse_mouse_wheel'), - otherRow('Show displays as individual windows', - kKeyShowDisplaysAsIndividualWindows), - ]); + ]; + if (useTextureRender) { + children.add(otherRow('Show displays as individual windows', + kKeyShowDisplaysAsIndividualWindows)); + } + return _Card(title: 'Other Default Options', children: children); } } From 11388849de8b97a6cb09ddba02984fb105323bbe Mon Sep 17 00:00:00 2001 From: fufesou Date: Mon, 16 Oct 2023 10:47:16 +0800 Subject: [PATCH 48/50] fix, connect the same peer Signed-off-by: fufesou --- flutter/lib/consts.dart | 2 -- flutter/lib/utils/multi_window_manager.dart | 21 +++++---------------- 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index 1452270d1..d92f42a10 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -4,8 +4,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_hbb/common.dart'; import 'package:flutter_hbb/models/state_model.dart'; -const bool kOpenSamePeerInNewWindow = true; - const double kDesktopRemoteTabBarHeight = 28.0; const int kInvalidWindowId = -1; const int kMainWindowId = 0; diff --git a/flutter/lib/utils/multi_window_manager.dart b/flutter/lib/utils/multi_window_manager.dart index 8b80b79bf..f36370e40 100644 --- a/flutter/lib/utils/multi_window_manager.dart +++ b/flutter/lib/utils/multi_window_manager.dart @@ -2,7 +2,6 @@ import 'dart:convert'; import 'dart:io'; import 'package:desktop_multi_window/desktop_multi_window.dart'; -import 'package:flutter_hbb/models/platform_model.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -187,21 +186,11 @@ class RustDeskMultiWindowManager { bool openInTabs = type != WindowType.RemoteDesktop || mainGetLocalBoolOptionSync(kOptionOpenNewConnInTabs); - if (kOpenSamePeerInNewWindow) { - // Open in new window if the peer is already connected. - // No need to care about the previous session type. - if (type == WindowType.RemoteDesktop && - await bind.sessionGetFlutterOptionByPeerId(id: remoteId, k: '') != - null) { - openInTabs = false; - } - } else { - if (windows.length > 1 || !openInTabs) { - for (final windowId in windows) { - if (await DesktopMultiWindow.invokeMethod( - windowId, kWindowEventActiveSession, remoteId)) { - return MultiWindowCallResult(windowId, null); - } + if (windows.length > 1 || !openInTabs) { + for (final windowId in windows) { + if (await DesktopMultiWindow.invokeMethod( + windowId, kWindowEventActiveSession, remoteId)) { + return MultiWindowCallResult(windowId, null); } } } From 27fe5664127340eb58c39334f4881a52e35eb806 Mon Sep 17 00:00:00 2001 From: Andrzej Rudnik Date: Mon, 16 Oct 2023 11:45:27 +0200 Subject: [PATCH 49/50] Update pl.rs --- src/lang/pl.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/lang/pl.rs b/src/lang/pl.rs index c8b2a4cc3..88dc5cda5 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -490,7 +490,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("no_desktop_title_tip", "Żaden pulpit nie jest dostępny"), ("no_desktop_text_tip", "Proszę zainstalować pulpit GNOME"), ("No need to elevate", "Podniesienie uprawnień nie jest wymagane"), - ("System Sound", "Dźwięk Systemowy"), + ("System Sound", "Dźwięk systemowy"), ("Default", "Domyślne"), ("New RDP", "Nowe RDP"), ("Fingerprint", "Sygnatura"), @@ -556,13 +556,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("upgrade_rustdesk_server_pro_to_{}_tip", "Proszę zaktualizować RustDesk Server Pro do wersji {} lub nowszej!"), ("pull_group_failed_tip", "Błąd odświeżania grup"), ("Filter by intersection", ""), - ("Remove wallpaper during incoming sessions", ""), - ("Test", ""), - ("switch_display_elevated_connections_tip", ""), - ("display_is_plugged_out_msg", ""), - ("No displays", ""), - ("elevated_switch_display_msg", ""), - ("Open in new window", ""), - ("Show displays as individual windows", ""), + ("Remove wallpaper during incoming sessions", "Usuń tapetę podczas sesji przychodzących"), + ("Test", "Test"), + ("switch_display_elevated_connections_tip", "Przełączanie na ekran inny niż główny nie jest obsługiwane przy podniesionych uprawnieniach, gdy istnieje wiele połączeń. Jeśli chcesz sterować wieloma ekranami, należy zainstalować program."), + ("display_is_plugged_out_msg", "Ekran został odłączony, przełącz się na pierwszy ekran."), + ("No displays", "Brak ekranów"), + ("elevated_switch_display_msg", "Przełącz się na ekran główny, ponieważ wyświetlanie kilku ekranów nie jest obsługiwane przy podniesionych uprawnieniach."), + ("Open in new window", "Otwórz w nowym oknie"), + ("Show displays as individual windows", "Pokaż ekrany w osobnych oknach"), ].iter().cloned().collect(); } From 5fc75cb4cd6e3dc892e412ed8d58449104bdc661 Mon Sep 17 00:00:00 2001 From: solokot Date: Mon, 16 Oct 2023 13:06:04 +0300 Subject: [PATCH 50/50] Add files via upload --- src/lang/ru.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lang/ru.rs b/src/lang/ru.rs index c250b59c0..9d3872871 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -126,7 +126,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Balanced", "Сбалансировано"), ("Optimize reaction time", "Оптимальное время реакции"), ("Custom", "Своё"), - ("Show remote cursor", "Показать удалённый курсор"), + ("Show remote cursor", "Показывать удалённый курсор"), ("Show quality monitor", "Показать качество"), ("Disable clipboard", "Отключить буфер обмена"), ("Lock after session end", "Выход из учётной записи после завершения сеанса"), @@ -556,12 +556,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("upgrade_rustdesk_server_pro_to_{}_tip", "Обновите RustDesk Server Pro до версии {} или новее!"), ("pull_group_failed_tip", "Невозможно обновить группу"), ("Filter by intersection", "Фильтровать по пересечению"), - ("Remove wallpaper during incoming sessions", "Удалить обои в сеансе"), + ("Remove wallpaper during incoming sessions", "Удалять обои в сеансе"), ("Test", "Тест"), - ("switch_display_elevated_connections_tip", ""), - ("display_is_plugged_out_msg", ""), + ("switch_display_elevated_connections_tip", "Переключение на неосновной дисплей не поддерживается в режиме повышенных прав при наличии нескольких подключений. Повторите попытку после установки, если хотите управлять несколькими дисплеями."), + ("display_is_plugged_out_msg", "Дисплей отключён, переключитесь на первый дисплей."), ("No displays", "Нет дисплеев"), - ("elevated_switch_display_msg", ""), + ("elevated_switch_display_msg", "Переключитесь на основной дисплей, поскольку в режиме повышенных прав несколько дисплеев не поддерживаются."), ("Open in new window", "Открыть в новом окне"), ("Show displays as individual windows", "Показывать дисплеи в отдельных окнах"), ].iter().cloned().collect();