Differences

This shows you the differences between the selected revisions of the page.

history 2022-09-12 history 2026-06-23 11:38 (current)
Line 1: Line 1:
-/* +====== Recent Version History ======
- * Headwind Remote: Open Source Remote Access Software for Android +
- * https://headwind-remote.com +
- +
- * Copyright (C) 2022 headwind-remote.com +
-+
- * Licensed under the Apache License, Version 2.0 (the "License"); +
- * you may not use this file except in compliance with the License. +
- * You may obtain a copy of the License at +
-+
- *      http://www.apache.org/licenses/LICENSE-2.0 +
-+
- * Unless required by applicable law or agreed to in writing, software +
- * distributed under the License is distributed on an "AS IS" BASIS, +
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +
- * See the License for the specific language governing permissions and +
- * limitations under the License. +
- */+
-package com.hmdm.control;+This is a full list of changes for each release of WinSCP. See also [[project_history|Project history]] and [[incompatible_changes|Incompatible changes between versions]].
-import android.app.AlertDialog; +===== [[6.6.3]] 6.6.3 (not released yet) ((2026-06-23)) =====
-import android.app.Dialog; +
-import android.content.BroadcastReceiver; +
-import android.content.ClipData; +
-import android.content.ClipboardManager; +
-import android.content.Context; +
-import android.content.DialogInterface; +
-import android.content.Intent; +
-import android.content.IntentFilter; +
-import android.graphics.PixelFormat; +
-import android.media.projection.MediaProjectionManager; +
-import android.os.Build; +
-import android.os.Bundle; +
-import android.os.Handler; +
-import android.util.DisplayMetrics; +
-import android.util.Log; +
-import android.view.Gravity; +
-import android.view.Menu; +
-import android.view.MenuItem; +
-import android.view.View; +
-import android.view.WindowManager; +
-import android.widget.EditText; +
-import android.widget.ImageView; +
-import android.widget.TextView; +
-import android.widget.Toast;+
-import androidx.appcompat.app.AppCompatActivity; +  * Translations completed: Czech, Dutch, Finnish, Polish and Simplified Chinese.
-import androidx.appcompat.widget.Toolbar; +
-import androidx.localbroadcastmanager.content.LocalBroadcastManager;+
-import com.hmdm.control.janus.SharingEngineJanus;+===== [[6.6.2]] 6.6.2 RC ((2026-06-17)) =====
-public class MainActivity extends AppCompatActivity implements SharingEngineJanus.EventListener, SharingEngineJanus.StateListener {+  * Experimental 64-bit version of WinSCP. [[bug>618]] 
 +  * Optionally not showing error message when connection is lost while idle. [[bug>2360]] 
 +  * SSH core and SSH private key tools (PuTTYgen and Pageant) upgraded to [[&url(puttychanges)|PuTTY 0.84]]. \\ It brings the following change: 
 +    * Security issue: fixed a remotely triggerable double-free in RSA key exchange. [[pbug>rsakex-double-free]] 
 +    * Minor security issue: fixed a remotely triggerable crash in NIST ECDSA signature verification. [[pbug>ecdsa-remotely-triggerable-assertion]] 
 +    * Bug fix: spurious //"Network error: Socket is not connected"// when authenticating to some HTTP proxies. [[pbug>http-proxy-auth-wsaenotconn]] 
 +  * TLS/SSL core upgraded to OpenSSL 3.5.7. 
 +  * XML parser upgraded to Expat 2.8.1. 
 +  * Restored faster C TLS/SSL AES implementation. 
 +  * Configurable warning when opening large file in an internal editor. [[bug>2437]] 
 +  * Informing that when preserving directory timestamps is enabled, using multiple connections for transfer is not possible. [[bug>2439]] 
 + * Warning when pasting a session URL with unsafe settings. 
 +  * When opening session in PuTTY to a host for which WinSCP has multiple host keys cached, using the last key or the key that PuTTY has cached. [[bug>2440]] 
 +  * Always (re)registering drag&drop shell extension during installation, even when the extension is not replaced. 
 +  * Allowed Console interface tool to have ''.exe'' extension to avoid false positive detections by some antiviruses. [[bug>2434]] 
 +  * Using //"username"// and //"hostname"// as one word. 
 +  * Reading all system settings from 64-bit registry. 
 +  * Allow assigning ''null'' to ''Session.SessionLogPath''. [[bug>2438]] 
 +  * Avoiding using ''SSH_FXF_EXCL'' together with ''SSH_FXF_TRUNC'' SFTP file opening flags. [[bug>2444]] 
 +  * Optimized file system monitoring when looking for dummy directory during drag&drop downloads. [[bug>2445]] 
 +  * Change: Not allowing WebDAV redirects to other hosts by default. [[bug>2447]] 
 +  * Change: Not allowing WebDAV redirects to an unencrypted URL by default. [[bug>2448]] 
 +  * Updated to JCL library 2.9 commit c669fd12. 
 +  * Bug fix: Failure when trying to connect via HTTP proxy to FTP host with excessively long login details. [[bug>2435]] 
 +  * Bug fix: Buffer overflow in Console interface tool. [[bug>2436]] 
 +  * Bug fix: Failure setting ''Session.DebugLogPath'' when running in impersonated context. [[bug>2441]] 
 +  * Bug fix: Message boxes from secondary windows (like the internal editor) caused application to move to the background when when the main window was minimized. [[bug>2443]] 
 +  * Bug fix: Heap over-read via crafted encrypted filename. [[bug>2449]] 
 +  * Bug fix: Slashes in filenames can cause path traversal when invalid filename characters replacement is disabled. [[bug>2450]]
-····private ImageView imageViewConnStatus; +===== [[6.6.1]] 6.6.1 beta ((2026-04-01)) =====
-····private TextView textViewConnStatus; +
-····private EditText editTextSessionId; +
-····private EditText editTextPassword; +
- ···private TextView textViewComment; +
-····private TextView textViewConnect; +
-····private TextView textViewSendLink; +
- ···private TextView textViewExit;+
-····private ImageView overlayDot+··* Support for OpenSSH ssh-agent. [[bug>;1682]] 
- ···private Handler handler = new Handler()+ ·* Optionally connecting all workspace/folder sessions immediately. [[bug>;1026]] 
- ···private int overlayDotAlpha+ ·* Preserving panel scroll position after rename. [[bug>;2425]] 
- ···private int overlayDotDirection = 1;+ ·* ''Ctrl+C'' works in list views on 'Server and protocol information' dialog. 
 +  * Preventing moving or copying a file or folder over ancestor folder with the same name. [[bug>2427]] 
 +  * WebDAV/HTTP core upgraded to neon 0.37.1
 +  * XML parser upgraded to Expat 2.7.5. 
 +  * Bug fix: Some menus were not working on displays to the left or above the primary display. [[bug>;2423]] 
 +  * Bug fix: Mouse wheel downwards scrolling did not work on toolbar drop down lists. 
 +  * Bug fix: Once any control of permissions popup box was focused the popup no longer closed when user clicked outside of it. 
 +  * Bug fix: Failure when closing Transfer settings dialog with //X// button while a permissions popup box control is focused. [[bug>2420]] 
 +  * Bug fix: Failure when switching to a session that is being reconnected. 
 +  * Bug fix: Failure when the first bit of an SFTP response is set. [[bug>2422]] 
 +  * Bug fix: Copying to clipboard with ''Ctrl+C'' from 'Server and protocol information' was broken. 
 +  * Bug fix: Protocol additional information scrolling was broken. 
 +  * Bug fix: Master password dialog was missing //Help// button. 
 +  * Bug fix: Checking if edited/opened file was modified externally didn't work for inactive sessions. [[bug>2426]] 
 +  * Bug fix: Wrapped settings values from Raw Site Settings dialog were not preserved. 
 +  * Bug fix: Some files modified by local custom command in SCP session fail to upload back. [[bug>2428]] 
 +  * Bug fix: Whole //Key exchange// page was incorrectly hidden when //"Handles SSH key re-exchange badly"// bug was enabled. 
 +  * Bug fix: Some message boxes leak GDI handle. [[bug>2430]] 
 +  * Bug fix: Login dialog leaks GDI handles. [[bug>2431]]
-····private Dialog exitOnIdleDialog; +===== [[6.6]] 6.6 beta ((2026-02-02)) =====
- ···private int exitCounter; +
-····private static final int EXIT_PROMPT_SEC = 10;+
-····private static final int OVERLAY_DOT_ANIMATION_INCREMENT = 20+··* Synchronizing two local directories. [[bug>2020]] 
-    private static final int OVERLAY_DOT_ANIMATION_DELAY = 200;+  * Compiler upgraded to Clang/bcc32c. [[bug>;618]] 
 +  * Inactive sessions can be automatically reconnected. [[bug>2232]] 
 +  * Added dark theme support to: [[bug>1696]] 
 +    * Login dialog. [[bug>2345]] 
 +   * Transfer Options dialog. 
 +    * Message boxes. 
 +    * Queue column headers. [[bug>;2356]] 
 +    * Progress window. 
 +    * Authentication Progress window. [[bug>2358]] 
 +    * Bug fix: Scrollbar colors did not always reflect the color theme 
 +  * Using modern directory selection dialog that scales correctly and allows creating new directory. [[bug>2373]] [[bug>2389]] 
 +  * Optimized GUI when working with large subdirectory selection. [[bug>2396]] 
 +  * Change: Default to UTF-8 encoding in internal editor. [[bug>2397]] 
 +  * New DigiCert EV code signing certificate valid until March 2029 is used for signing binaries. 
 +  * TLS/SSL core upgraded to OpenSSL 3.5.5. 
 +  * WebDAV/HTTP core upgraded to neon 0.36.0. 
 +  * XML parser upgraded to Expat 2.7.4. 
 +  * Installer upgraded to Inno Setup 6.7.0 with dark mode support enabled. 
 +  * Increased WinSCP memory limit to 4 GB. [[bug>2412]] 
 +  * Defined and implemented interface for the .NET library. By @mjkent. [[bug>856]] 
 +  * Optimized TLS/SSL AES implementation. 
 +  * Restoring ability to restart Explorer to allow upgrade of drag&drop shell extension, when installing for current user, as after-restart replacement is not possible without Administrator privileges. [[bug>2381]] 
 +  * MSI toolset updated to WiX 5. 
 +  * Commands to copy paths to the clipboard on the Synchronization checklist window. 
 +  * Cryptography optimization. 
 +  * Support long AWS/S3 session tokens. [[bug>2403]] 
 +  * Prevent hang when new device is attached or removed while some mapped network drive is not available. [[bug>2382]] 
 +  * Copy and paste improvements: 
 +    * Consistently renaming local files dropped or pasted back to their source directory to avoid collisions. 
 +    * Bug fix: When copying local files to clipboard from system context menu, "cut" state of previously cut files was not cleared. 
 +  * Not redundantly verifying WebDAV or S3 certificate in Windows Certificate store if it is already marked as trusted in session settings. [[bug>2404]] 
 +  * Provide SNI when opening FTP data connection. [[bug>2410]] 
 +  * Optimized synchronization checklist sorting. 
 +  * Support for Beyond Compare 5 in Compare Files extension. [[bug>2417]] 
 +  * Convert unsupported SSH proxy to SSH tunnel when importing site from PuTTY. [[bug>2408]] 
 +  * FTP directory listing falls back to the other active/passive mode, consistently with file transfers. 
 +  * Consistently calling command to open window with specific directory //Explore//, instead of sometimes //Browse//. 
 +  * Consistently referring to file last modification timestamp column as //Date modified//. 
 +  * With INI file provided on command-line, using the same INI file when starting a new instance. 
 +  * Windows shell local file copy status window is centered on the main window. 
 +  * Made taskbar flashing configurable in GUI. [[bug>2411]] 
 +  * Control labels on transfer settings dialogs do not show keyboard accelerator cue, until ''Alt'' key is pressed. 
 +  * Not using drag images even with directory trees. [[bug>1274]] 
 +  * Allow configuring checksum commands. [[bug>2394]] 
 +  * Updated to JCL library 2.8.1. 
 +  * Updating jump list only when running with GUI. 
 +  * Made space on permissions box for longer translations. [[bug>2398]] 
 +  * Opening //Default Apps// //Settings// page directly to open it in the foreground and avoid flashing //Control Panel// window. 
 +  * Improving order in which Windows Narrator reads window controls. 
 +  * All edit boxes with history consistently do not auto complete and show 16 entries in the drop down. 
 +  * Removed obsolete //Preserve remote timestamp// session settings. 
 +  * Bug fix: Local file with invalid characters replaced could not be explored from the Synchronization checklist window. 
 +  * Bug fix: Files modified by local custom command are not always uploaded to the correct remote directory. [[bug>2370]] 
 +  * Bug fix: List of network drives in drive drop down and directory tree did not always match. 
 +  * Bug fix: Host key prompt did not have the default button. 
 +  * Bug fix: When the local path specified on Open directory/Location profile dialog is not existing, when browsing for a new path, the trailing part of the nonexisting path was appended to the new path. 
 +  * Bug fix: Trying to enter an invalid link in local panel fails silently. 
 +  * Bug fix: After FTP data connection fails to open further use of the session is broken. 
 +  * Bug fix: Pasting cut files from the clipboard into a local panel copies them instead of moving them. [[bug>2400]] 
 +  * Bug fix: Some edits did not save their value to history when submitting with ''Enter''. 
 +  * Bug fix: Too long edit history dropdown can overflow monitor bounds. [[bug>2432]] 
 +  * Bug fix: Message box texts and some control labels are not visible to screen readers. [[bug>2413]] 
 +  * Bug fix: Failure when clicking tab close button while the session is already being closed. [[bug>2416]]
-····private SharingEngine sharingEngine;+===== [[6.5.7]] 6.5.7 (not released yet) ((2026-06-17)) =====
-····private SettingsHelper settingsHelper;+··* Translations completed: Croatian, Finnish, Georgian, Italian and Serbian. 
 +  * TLS/SSL core upgraded to OpenSSL 3.3.7. 
 +  * Back-propagated fixes from 6.6.2 beta release: 
 +    * Bug fix: Failure setting ';'Session.DebugLogPath'' when running in impersonated context. [[bug>2441]] 
 +    * Security issue: fixed a remotely triggerable double-free in RSA key exchange. [[pbug>rsakex-double-free]] 
 +    * Minor security issue: fixed a remotely triggerable crash in NIST ECDSA signature verification. [[pbug>ecdsa-remotely-triggerable-assertion]] 
 +  * Bug fix: A specially crafted PKCS#7 or S/MIME signed message could trigger a use-after-free during PKCS#7 signature verification. CVE-2026-45447 fix from OpenSSL 3.4.6.
-····private String sessionId; +===== [[6.5.6]] 6.5.6 ((2026-03-25)) =====
- ···private String password; +
- ···private String adminName;+
-····private final static String ATTR_SESSION_ID = "sessionId"; +··* Translations completed: Macedonian, and updated: Lithuanian, and Russian. 
-    private final static String ATTR_PASSWORD = "password"; +··* TLS/SSL core upgraded to OpenSSL 3.3.6. 
-    private final static String ATTR_ADMIN_NAME = "adminName";+  * Back-propagated improvements from 6.6–6.6.1 beta release: 
 +    * New DigiCert EV code signing certificate valid until March 2029 is used for signing binaries. 
 +····* XML parser upgraded to Expat 2.7.5. 
 +    * Support for Beyond Compare 5 in Compare Files extension. [[bug>2417]] 
 +    * Bug fix: Checking if edited/opened file was modified externally didn't work for inactive sessions. [[bug>2426]]
-····private boolean needReconnect = false;+===== [[6.5.5]] 6.5.5 ((2025-11-19)) =====
-····private MediaProjectionManager projectionManager;+··* Translation updated: Vietnamese. 
 +  * Bug fix: Pasting files using local directory tree context menu pastes them to the current directory, instead of the selected one. 
 +  * Bug fix: Failure when opening site imported from PuTTY with unsupported SSH proxy. [[bug>;2407]] 
 +  * Bug fix: Incorrect hostname validation when connecting to S3 endpoint with certificate that does not cover root S3 hostname. [[bug>2409]]
-····private BroadcastReceiver mSharingServiceReceiver = new BroadcastReceiver() { +===== [[6.5.4]] 6.5.4 ((2025-10-16)) =====
-        @Override +
-        public void onReceive(Context context, Intent intent) { +
-            if (intent == null || intent.getAction() == null) { +
-················return; +
-            } +
-            if (intent.getAction().equals(Const.ACTION_SCREEN_SHARING_START)) { +
-················notifySharingStart();+
-············} else if (intent.getAction().equals(Const.ACTION_SCREEN_SHARING_STOP)) { +··* Translations updated: Belarusian and Georgian
- ···············notifySharingStop()+  * TLS/SSL core upgraded to OpenSSL 3.3.5. 
- ···············adminName = null+··* XML parser upgraded to Expat 2.7.3. 
- ···············updateUI()+· * Added new ''ap-southeast-6'' AWS region. 
- ···············cancelSharingTimeout()+ ·* Bug fix: When restored after operation completed while minimized the window is disabled. [[bug>;2393]] 
- ···············scheduleExitOnIdle();+ ·* Bug fix: Command ';'md5sums'' is incorrectly used to calculate MD5 checksum instead of ''md5sum''. [[bug>2392]] 
 + ·* Bug fix: Incomplete FTP upload when the source stream/stdin reads less than requested. [[bug>;2395]] 
 + ·* Bug fix: ''Shift''-clicking //OK// button on Synchronization checklist window when synchronization in the background was not possible still closed the window. 
 + ·* Bug fix: Failure after reloading file panel when number of files decreases. [[bug>;2402]] 
 +  * Bug fix: Failure or silently missing headers when when S3 request headers were too long.
-············} else if (intent.getAction().equals(Const.ACTION_SCREEN_SHARING_FAILED)) { +===== [[6.5.3]] 6.5.3 ((2025-07-16)) =====
-                String message = intent.getStringExtra(Const.EXTRA_MESSAGE); +
-                if (message != null) { +
-                    Toast.makeText(MainActivity.this, message, Toast.LENGTH_LONG).show()+
- ···············} +
-················adminName = null+
-                updateUI(); +
-                cancelSharingTimeout(); +
-                scheduleExitOnIdle();+
-············} else if (intent.getAction().equals(Const.ACTION_CONNECTION_FAILURE)) { +··* Translations updated: Belarusian, Brazilian Portuguese [[bug>2386]] and Slovenian. 
- ···············sharingEngine.setState(Const.STATE_DISCONNECTED)+··* TLS/SSL core upgraded to OpenSSL 3.3.4. 
- ···············Toast.makeText(MainActivity.this, R.string.connection_failure_hint, Toast.LENGTH_LONG).show()+ ·* Temporarily not updating drag&drop shell extension for minor changes when installing for current user. [[bug>;2381]] 
- ···············updateUI();+ ·* Bug fix: Failure when reading empty certificate authority configuration. 
 +··* Bug fix: Failure when running Maximized. [[bug>2387]] 
 +··* Bug fix: Incorrect scaling of navigation tree on Login and Preferences dialogs. [[bug>;2385]] 
 + ·* Bug fix: Incorrect scaling of drag&;drop shell extension status display on Preferences dialog.
-············} else if (intent.getAction().equals(Const.ACTION_SCREEN_SHARING_PERMISSION_NEEDED)) { +===== [[6.5.2]] 6.5.2 ((2025-06-18)) =====
-················startActivityForResult(projectionManager.createScreenCaptureIntent(), Const.REQUEST_SCREEN_SHARE)+
- ···········} +
-        } +
-····};+
-····@Override +··* Translation updated: Brazilian Portuguese and Polish
-   protected void onCreate(Bundle savedInstanceState) +  * Added new ''ap-east-2'' AWS region
-       super.onCreate(savedInstanceState); +  * Bug fix: Some translations (notably Japanese) are not loaded. [[bug>2372]] 
-       setContentView(R.layout.activity_main); +  * Bug fix: Directory tree indentation is scaled incorrectly when starting on scaled display on system with scaled primary monitor. [[bug>2374]] 
- +  * Bug fix: Wrong icon size is used when starting on secondary monitor with different scaling than the primary one
-       if (savedInstanceState != null) { +  * Bug fix: Failure when proxy hostname resolution fails with SFTP/SCP protocols. [[bug>2376]] 
-            restoreInstanceState(savedInstanceState); +  * Bug fix: Avoid replacing ''%2F'' with a slash and ''%2E'' with a dot in special cases on upload to avoid path traversal. [[bug>2377]] 
-        } +  * Bug fix: Failure when canceling reconnection on authentication banner, when the connection was already closed by the server. [[bug>2379]] 
- +  * Bug fix: Local directories sometimes cannot be deleted. [[bug>2380]]
-        Toolbar toolbar = findViewById(R.id.toolbar); +
-        setSupportActionBar(toolbar); +
-        settingsHelper = SettingsHelper.getInstance(this); +
-        sharingEngine = SharingEngineFactory.getSharingEngine(); +
-        sharingEngine.setEventListener(this); +
-        sharingEngine.setStateListener(this); +
- +
-        DisplayMetrics metrics = new DisplayMetrics(); +
-        ScreenSharingHelper.getRealScreenSize(this, metrics); +
-        float videoScale = ScreenSharingHelper.adjustScreenMetrics(metrics); +
-        settingsHelper.setFloat(SettingsHelper.KEY_VIDEO_SCALE, videoScale); +
-        ScreenSharingHelper.setScreenMetrics(this, metrics.widthPixels, metrics.heightPixels, metrics.densityDpi); +
- +
-        sharingEngine.setScreenWidth(metrics.widthPixels); +
-        sharingEngine.setScreenHeight(metrics.heightPixels); +
- +
-        IntentFilter intentFilter = new IntentFilter(Const.ACTION_SCREEN_SHARING_START); +
-        intentFilter.addAction(Const.ACTION_SCREEN_SHARING_STOP); +
-        intentFilter.addAction(Const.ACTION_SCREEN_SHARING_PERMISSION_NEEDED); +
-        intentFilter.addAction(Const.ACTION_SCREEN_SHARING_FAILED); +
-        intentFilter.addAction(Const.ACTION_CONNECTION_FAILURE); +
-        LocalBroadcastManager.getInstance(this).registerReceiver(mSharingServiceReceiver, intentFilter); +
- +
-        projectionManager = (MediaProjectionManager)getSystemService(Context.MEDIA_PROJECTION_SERVICE); +
- +
-        initUI(); +
-        setDefaultSettings(); +
-    } +
- +
-    @Override +
-    protected void onResume() { +
-        super.onResume(); +
-        updateUI(); +
- +
-        startService(new Intent(MainActivity.this, GestureDispatchService.class)); +
-//        connect(); +
-        checkAccessibility(); +
-    } +
- +
-    private void checkAccessibility() { +
-        if (!Utils.isAccessibilityPermissionGranted(this)) { +
-            textViewConnect.setVisibility(View.INVISIBLE); +
-            new AlertDialog.Builder(this) +
-                    .setMessage(R.string.accessibility_hint) +
-                    .setPositiveButton(R.string.continue_button, new DialogInterface.OnClickListener() { +
-                        @Override +
-                        public void onClick(DialogInterface dialog, int which) { +
-                            Intent intent = new Intent(android.provider.Settings.ACTION_ACCESSIBILITY_SETTINGS); +
-                            try { +
-                                startActivityForResult(intent, 0); +
-                            } catch (Exception e) { +
-                                // Accessibility settings cannot be opened +
-                                reportAccessibilityUnavailable(); +
-                            } +
-                        } +
-                    }) +
-                    .setCancelable(false) +
-                    .create() +
-                    .show(); +
-        } else { +
-            configureAndConnect(); +
-        } +
-    } +
- +
-    private void reportAccessibilityUnavailable() { +
-        new AlertDialog.Builder(this) +
-                .setMessage(R.string.accessibility_unavailable_error) +
-                .setPositiveButton(R.string.button_exit, new DialogInterface.OnClickListener() { +
-                    @Override +
-                    public void onClick(DialogInterface dialog, int which) { +
-                        exitApp(); +
-                    } +
-                }) +
-                .setCancelable(false) +
-                .create() +
-                .show(); +
-    } +
- +
-    private void configureAndConnect() { +
-        if (settingsHelper.getString(SettingsHelper.KEY_SERVER_URL) == null) { +
-            // Not configured yet +
-            settingsHelper.setString(SettingsHelper.KEY_SERVER_URL, BuildConfig.DEFAULT_SERVER_URL); +
-            settingsHelper.setString(SettingsHelper.KEY_SECRET, BuildConfig.DEFAULT_SECRET); +
-            settingsHelper.setBoolean(SettingsHelper.KEY_USE_DEFAULT, !BuildConfig.DEFAULT_SECRET.equals("")); +
-            Intent intent = new Intent(this, SettingsActivity.class); +
-            startActivityForResult(intent, Const.REQUEST_SETTINGS); +
-            return; +
-        } +
- +
-        if (needReconnect) { +
-            // Here we go after changing settings +
-            needReconnect = false; +
-            if (sharingEngine.getState() != Const.STATE_DISCONNECTED) { +
-                sharingEngine.disconnect(MainActivity.this, (success, errorReason) -> connect()); +
-············} else { +
-                connect(); +
-            } +
-        } else { +
-            if (sharingEngine.getState() == Const.STATE_DISCONNECTED && sharingEngine.getErrorReason() == null) { +
-                connect(); +
-            } +
-        } +
-    } +
- +
-    @Override +
-    public void onDestroy() { +
-        try { +
-            LocalBroadcastManager.getInstance(this).unregisterReceiver(mSharingServiceReceiver); +
-        } catch (Exception e) { +
-        } +
-        super.onDestroy(); +
-    } +
- +
-    @Override +
-    public void onSaveInstanceState(Bundle savedInstanceState) { +
-        savedInstanceState.putString(ATTR_SESSION_ID, sessionId); +
-        savedInstanceState.putString(ATTR_PASSWORD, password); +
-        savedInstanceState.putString(ATTR_ADMIN_NAME, adminName); +
-        super.onSaveInstanceState(savedInstanceState); +
-    } +
- +
-    private void restoreInstanceState(Bundle savedInstanceState) { +
-        sessionId = savedInstanceState.getString(ATTR_SESSION_ID); +
-        password = savedInstanceState.getString(ATTR_PASSWORD); +
-        adminName = savedInstanceState.getString(ATTR_ADMIN_NAME); +
-    } +
- +
-    @Override +
-    public void onBackPressed() { +
-        Toast.makeText(this, R.string.back_pressed, Toast.LENGTH_LONG).show(); +
-    } +
- +
-    @Override +
-    public boolean onCreateOptionsMenu(Menu menu) { +
-        // Inflate the menu; this adds items to the action bar if it is present. +
-        getMenuInflater().inflate(R.menu.menu_main, menu); +
-        return true; +
-    } +
- +
-    @Override +
-    public boolean onOptionsItemSelected(MenuItem item) { +
-        // Handle action bar item clicks here. The action bar will +
-        // automatically handle clicks on the Home/Up button, so long +
-        // as you specify a parent activity in AndroidManifest.xml. +
-        int id = item.getItemId(); +
- +
-        if (id == R.id.action_settings) { +
-            if (adminName != null) { +
-                Toast.makeText(this, R.string.settings_unavailable, Toast.LENGTH_LONG).show(); +
-                return true; +
-            } +
-            Intent intent = new Intent(this, SettingsActivity.class); +
-            startActivityForResult(intent, Const.REQUEST_SETTINGS); +
-            cancelExitOnIdle(); +
-            return true; +
-        } else if (id == R.id.action_about) { +
-            showAbout(); +
-            return true; +
-        } +
- +
-        return super.onOptionsItemSelected(item); +
-    } +
- +
-    @Override +
-    public void onActivityResult(int requestCode, int resultCode, Intent data) { +
-        super.onActivityResult(requestCode, resultCode, data); +
- +
-        if (requestCode == Const.REQUEST_SETTINGS) { +
-            if (resultCode == Const.RESULT_DIRTY) { +
-                needReconnect = true; +
-            } else { +
-                scheduleExitOnIdle(); +
-            } +
-        } else if (requestCode == Const.REQUEST_SCREEN_SHARE) { +
-            if (resultCode != RESULT_OK) { +
-                Toast.makeText(this, R.string.screen_cast_denied, Toast.LENGTH_LONG).show(); +
-                adminName = null; +
-                updateUI(); +
-                cancelSharingTimeout(); +
-                scheduleExitOnIdle(); +
-            } else { +
-                ScreenSharingHelper.startSharing(this, resultCode, data); +
-            } +
-        } +
-    } +
- +
-    private void initUI() { +
-        imageViewConnStatus = findViewById(R.id.image_conn_status); +
-        textViewConnStatus = findViewById(R.id.conn_status); +
-        editTextSessionId = findViewById(R.id.session_id_edit); +
-        editTextPassword = findViewById(R.id.password_edit); +
-        textViewComment = findViewById(R.id.comment); +
-        textViewConnect = findViewById(R.id.reconnect); +
-        textViewSendLink = findViewById(R.id.send_link); +
-        textViewExit = findViewById(R.id.disconnect_exit); +
- +
-        textViewConnect.setOnClickListener(v -> connect()); +
- +
-        textViewSendLink.setOnClickListener(v -> sendLink()); +
- +
-        textViewExit.setOnClickListener(v -> gracefulExit()); +
-    } +
- +
-    private void gracefulExit() { +
-        if (adminName != null) { +
-            notifySharingStop(); +
-            ScreenSharingHelper.stopSharing(MainActivity.this, true); +
-        } +
-        sharingEngine.disconnect(MainActivity.this, (success, errorReason) -> exitApp()); +
-       // 5 sec timeout to exit +
-        handler.postDelayed(() -> exitApp(), 5000); +
-   } +
- +
- ···private void exitApp() +
-       Intent intent = new Intent(MainActivity.this, ScreenSharingService.class)+
-       stopService(intent); +
-       intent = new Intent(MainActivity.this, GestureDispatchService.class); +
-        stopService(intent); +
-        finishAffinity(); +
-        System.exit(0); +
-    } +
- +
-    private void updateUI() { +
-        int[] stateLabels = {R.string.state_disconnected, R.string.state_connecting, R.string.state_connected, R.string.state_sharing, R.string.state_disconnecting}; +
-        int[] stateImages = {R.drawable.ic_disconnected, R.drawable.ic_connecting, R.drawable.ic_connected, R.drawable.ic_sharing, R.drawable.ic_connecting}; +
- +
-        int state = sharingEngine.getState(); +
-        if (state == Const.STATE_CONNECTED && adminName != null) { +
-            imageViewConnStatus.setImageDrawable(getDrawable(stateImages[Const.STATE_SHARING])); +
-            textViewConnStatus.setText(stateLabels[Const.STATE_SHARING]); +
-       } else { +
-           imageViewConnStatus.setImageDrawable(getDrawable(stateImages[state])); +
- ···········textViewConnStatus.setText(stateLabels[state]); +
-        } +
-        String serverUrl = Utils.prepareDisplayUrl(settingsHelper.getString(SettingsHelper.KEY_SERVER_URL)); +
- +
-        textViewSendLink.setVisibility(state == Const.STATE_CONNECTED ? View.VISIBLE : View.INVISIBLE); +
-       textViewConnect.setVisibility(state == Const.STATE_DISCONNECTED ? View.VISIBLE : View.INVISIBLE); +
-       switch (state) +
-           case Const.STATE_DISCONNECTED: +
-               editTextSessionId.setText(""); +
-               editTextPassword.setText(""); +
-               if (sharingEngine.getErrorReason() != null) { +
-                    textViewComment.setText(getString(R.string.hint_connection_error, serverUrl)); +
-                } +
-                break; +
-            case Const.STATE_CONNECTING: +
-                textViewComment.setText(getString(R.string.hint_connecting, serverUrl)); +
-                break; +
-            case Const.STATE_DISCONNECTING: +
-                textViewComment.setText(getString(R.string.hint_disconnecting)); +
-                break; +
-            case Const.STATE_CONNECTED: +
-                editTextSessionId.setText(sessionId); +
-                editTextPassword.setText(password); +
-                textViewComment.setText(adminName != null ? +
-                        getString(R.string.hint_sharing, adminName) : +
-                        getString(R.string.hint_connected, serverUrl) +
-                        ); +
-                break; +
-        } +
-    } +
- +
-    private void setDefaultSettings() { +
-        if (settingsHelper.getString(SettingsHelper.KEY_DEVICE_NAME) == null) { +
-            settingsHelper.setString(SettingsHelper.KEY_DEVICE_NAME, Build.MANUFACTURER + " " + Build.MODEL); +
-        } +
-        if (settingsHelper.getInt(SettingsHelper.KEY_BITRATE) == 0) { +
-            settingsHelper.setInt(SettingsHelper.KEY_BITRATE, Const.DEFAULT_BITRATE); +
-        } +
-        if (settingsHelper.getInt(SettingsHelper.KEY_FRAME_RATE) == 0) { +
-            settingsHelper.setInt(SettingsHelper.KEY_FRAME_RATE, Const.DEFAULT_FRAME_RATE); +
-        } +
-        if (settingsHelper.getInt(SettingsHelper.KEY_IDLE_TIMEOUT) == 0) { +
-            settingsHelper.setInt(SettingsHelper.KEY_IDLE_TIMEOUT, Const.DEFAULT_IDLE_TIMEOUT); +
-        } +
-        if (settingsHelper.getInt(SettingsHelper.KEY_PING_TIMEOUT) == 0) { +
-            settingsHelper.setInt(SettingsHelper.KEY_PING_TIMEOUT, Const.DEFAULT_PING_TIMEOUT); +
-        } +
-    } +
- +
-    private void sendLink() { +
-        String url = settingsHelper.getString(SettingsHelper.KEY_SERVER_URL); +
-        url += "?session=" + sessionId + "&pin=" + password; +
-        try { +
-            Intent shareIntent = new Intent(Intent.ACTION_SEND); +
-            shareIntent.setType("text/plain"); +
-            shareIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.send_link_subject)); +
-            String shareMessage= getString(R.string.send_link_message, url, settingsHelper.getString(SettingsHelper.KEY_DEVICE_NAME)); +
-            shareIntent.putExtra(Intent.EXTRA_TEXT, shareMessage); +
-            startActivity(Intent.createChooser(shareIntent, getString(R.string.send_link_chooser))); +
-        } catch(Exception e) { +
-            e.printStackTrace(); +
-            Toast.makeText(this, R.string.send_link_failed, Toast.LENGTH_LONG).show(); +
-        } +
-    } +
- +
-    private void showAbout() { +
-        ImageView imageView = new ImageView(this); +
-        imageView.setImageDrawable(getResources().getDrawable(R.mipmap.ic_launcher)); +
-        new AlertDialog.Builder(this) +
-                .setTitle(R.string.about_title) +
-                .setMessage(getString(R.string.about_message, BuildConfig.VERSION_NAME, BuildConfig.VARIANT)) +
-                .setPositiveButton(R.string.ok, (dialog, which) -> dialog.dismiss()) +
-               .create() +
- ···············.show(); +
-   } +
- +
- ···private void connect() +
-        if (sessionId == null || password == null) { +
-            sessionId = Utils.randomString(8, true); +
-            password = Utils.randomString(4, true); +
-        } +
-        sharingEngine.setUsername(settingsHelper.getString(SettingsHelper.KEY_DEVICE_NAME)); +
-        sharingEngine.connect(this, sessionId, password, (success, errorReason) -> { +
-            if (!success) { +
-                if (errorReason != null && errorReason.equals(Const.ERROR_ICE_FAILED)) { +
-                    errorReason = getString(R.string.connection_error_ice); +
-                } +
-                String message = getString(R.string.connection_error, settingsHelper.getString(SettingsHelper.KEY_SERVER_URL), errorReason); +
-                reportError(message); +
-                editTextSessionId.setText(null); +
-                editTextPassword.setText(null); +
-            } +
-        }); +
- +
-        scheduleExitOnIdle(); +
-    } +
- +
-    private void reportError(final String message) { +
-//        Toast.makeText(MainActivity.this, message, Toast.LENGTH_LONG).show(); +
-        final AlertDialog dialog = new AlertDialog.Builder(this) +
-                .setMessage(message) +
-                .setNegativeButton(R.string.copy_message, (dialog1, which) -> { +
-                    ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); +
-                    ClipData clip = ClipData.newPlainText(Const.LOG_TAG, message); +
-                    clipboard.setPrimaryClip(clip); +
-                    Toast.makeText(MainActivity.this, R.string.message_copied, Toast.LENGTH_LONG).show(); +
-                    dialog1.dismiss(); +
-                }) +
-                .setPositiveButton(R.string.close, (dialog1, which) -> dialog1.dismiss()) +
-                .create(); +
-        try { +
-            dialog.show(); +
-            handler.postDelayed(() -> { +
-                try { +
-                    dialog.dismiss(); +
-                } catch (Exception e) { +
-                } +
-            }, 10000); +
-        } catch (Exception e) { +
-        } +
-    } +
- +
-    @Override +
-    public void onStartSharing(String adminName) { +
-        // This event is raised when the admin joins the text room +
-       this.adminName = adminName; +
-       updateUI(); +
-       cancelExitOnIdle(); +
-        scheduleSharingTimeout(); +
-        ScreenSharingHelper.requestSharing(this); +
-   } +
- +
- ···@Override +
-    public void onStopSharing() +
-        // This event is raised when the admin leaves the text room +
-········notifySharingStop(); +
-       adminName = null; +
-        updateUI(); +
-        cancelSharingTimeout(); +
-        scheduleExitOnIdle(); +
-        ScreenSharingHelper.stopSharing(this, false); +
-    } +
- +
-    @Override +
-    public void onRemoteControlEvent(String event) { +
-        Intent intent = new Intent(MainActivity.this, GestureDispatchService.class); +
-        intent.setAction(Const.ACTION_GESTURE); +
-        intent.putExtra(Const.EXTRA_EVENT, event); +
-        startService(intent); +
-    } +
- +
-    @Override +
-    public void onPing() { +
-        if (adminName != null) { +
-            cancelSharingTimeout(); +
-            scheduleSharingTimeout(); +
-        } +
-    } +
- +
-    @Override +
-    public void onSharingApiStateChanged(int state) { +
-        updateUI(); +
-        if (state == Const.STATE_CONNECTED) { +
-            String rtpHost = Utils.getRtpUrl(settingsHelper.getString(SettingsHelper.KEY_SERVER_URL)); +
-            int rtpAudioPort = sharingEngine.getAudioPort(); +
-            int rtpVideoPort = sharingEngine.getVideoPort(); +
-            String testDstIp = settingsHelper.getString(SettingsHelper.KEY_TEST_DST_IP); +
-            if (testDstIp != null && !testDstIp.trim().equals("")) { +
-               rtpHost = testDstIp+
-               rtpVideoPort = Const.TEST_RTP_PORT; +
-                Toast.makeText(this, "Test mode: sending stream to " + rtpHost + ";:" + rtpVideoPort, Toast.LENGTH_LONG).show(); +
-············} +
- +
-            ScreenSharingHelper.configure(this, settingsHelper.getBoolean(SettingsHelper.KEY_TRANSLATE_AUDIO), +
-                    settingsHelper.getInt(SettingsHelper.KEY_FRAME_RATE), +
-                    settingsHelper.getInt(SettingsHelper.KEY_BITRATE), +
-                    rtpHost, +
-                    rtpAudioPort, +
-                    rtpVideoPort +
-                    ); +
-        } +
-    } +
- +
-    private void scheduleExitOnIdle() { +
-        int exitOnIdleTimeout = settingsHelper.getInt(SettingsHelper.KEY_IDLE_TIMEOUT); +
-        if (exitOnIdleTimeout > 0) { +
-            exitCounter = EXIT_PROMPT_SEC; +
-            handler.postDelayed(warningOnIdleRunnable, exitOnIdleTimeout * 1000); +
-            Log.d(Const.LOG_TAG, "Scheduling exit in " + (exitOnIdleTimeout * 1000) + " sec";); +
-        } +
-    } +
- +
-    private void cancelExitOnIdle() { +
-        Log.d(Const.LOG_TAG, "Cancelling scheduled exit";)+
-        handler.removeCallbacks(warningOnIdleRunnable); +
-        handler.removeCallbacks(exitRunnable); +
-    } +
- +
-    private Runnable exitRunnable = () -> { +
-        exitCounter--; +
-        if (exitCounter > 0) { +
-            TextView messageView = exitOnIdleDialog.findViewById(android.R.id.message); +
-           if (messageView != null) +
-               messageView.setText(MainActivity.this.getResources().getString(R.string.app_idle_warning, exitCounter)); +
-           } +
-           scheduleExitRunnable(); +
- +
-       } else +
-            gracefulExit(); +
-        } +
-    }; +
- +
-    private Runnable warningOnIdleRunnable = () -> { +
-        exitOnIdleDialog = new AlertDialog.Builder(MainActivity.this) +
- ···············.setMessage(MainActivity.this.getResources().getString(R.string.app_idle_warning, exitCounter)) +
-                .setPositiveButton(R.string.button_exit, (dialog1, which) -> { +
-····················gracefulExit(); +
-               }) +
- ···············.setNegativeButton(R.string.button_keep_idle, (dialog1, which) -> { +
-                    scheduleExitOnIdle(); +
-                    handler.removeCallbacks(exitRunnable); +
-                    dialog1.dismiss(); +
-                }) +
-                .setCancelable(false) +
-                .create(); +
-        try { +
-            exitOnIdleDialog.show(); +
-            scheduleExitRunnable(); +
-        } catch (Exception e) { +
-            gracefulExit(); +
-        } +
-    }; +
- +
-    private void scheduleExitRunnable() { +
-        handler.postDelayed(exitRunnable, 1000); +
-    } +
- +
-    private void scheduleSharingTimeout() { +
-        int pingTimeout = settingsHelper.getInt(SettingsHelper.KEY_PING_TIMEOUT); +
-        if (pingTimeout > 0) { +
-            Log.d(Const.LOG_TAG, "Scheduling sharing stop in " + (pingTimeout * 1000) + " sec"); +
-           handler.postDelayed(sharingStopByPingTimeoutRunnable, pingTimeout * 1000); +
-       } +
-   } +
- +
-    private void cancelSharingTimeout() { +
-        Log.d(Const.LOG_TAG, "Cancelling scheduled sharing stop"); +
-       handler.removeCallbacks(sharingStopByPingTimeoutRunnable); +
-   } +
- +
-   private Runnable sharingStopByPingTimeoutRunnable = new Runnable() { +
-        @Override +
-        public void run() { +
-            Toast.makeText(MainActivity.this, R.string.app_sharing_session_ping_timeout, Toast.LENGTH_LONG).show(); +
-            if (adminName != null) { +
-                notifySharingStop(); +
-                ScreenSharingHelper.stopSharing(MainActivity.this, false); +
-            } +
-            adminName = null; +
-            updateUI(); +
-            cancelSharingTimeout(); +
-            scheduleExitOnIdle(); +
-            sharingEngine.disconnect(MainActivity.this, (success, errorReason) -> connect()); +
-       } +
- ···}; +
- +
-    private Runnable overlayDotRunnable = new Runnable() { +
-        @Override +
-        public void run() { +
-            if (overlayDotDirection == 0) { +
-                return; +
-            } +
-            overlayDotAlpha += OVERLAY_DOT_ANIMATION_INCREMENT * overlayDotDirection; +
- ···········if (overlayDotAlpha > 255) +
-               overlayDotAlpha = 255; +
-                overlayDotDirection = -overlayDotDirection; +
-            } +
-            if (overlayDotAlpha < 128) { +
-                overlayDotAlpha = 128; +
-                overlayDotDirection = -overlayDotDirection; +
-            } +
-            overlayDot.setImageAlpha(overlayDotAlpha); +
- ···········handler.postDelayed(overlayDotRunnable, OVERLAY_DOT_ANIMATION_DELAY); +
-        } +
-    }; +
- +
-    private void notifySharingStart() { +
-        notifyGestureService(Const.ACTION_SCREEN_SHARING_START); +
-        if (settingsHelper.getBoolean(SettingsHelper.KEY_NOTIFY_SHARING)) { +
-            // Show a flashing dot +
-            Utils.lockDeviceRotation(this, true); +
-            overlayDot = createOverlayDot(); +
-            overlayDotAlpha = 0; +
-            overlayDotDirection = 1; +
-            handler.postDelayed(overlayDotRunnable, OVERLAY_DOT_ANIMATION_DELAY); +
- +
-        } else { +
-            // Just show some dialog to trigger the traffic +
-            final AlertDialog dialog = new AlertDialog.Builder(MainActivity.this) +
-                    .setMessage(R.string.share_start_text) +
-                    .setPositiveButton(R.string.ok, (dialog1, which) -> dialog1.dismiss()) +
-                    .create(); +
-            dialog.show(); +
-            handler.postDelayed(() -> { +
-                if (dialog != null && dialog.isShowing()) { +
-                    try { +
-                        dialog.dismiss(); +
-                    } catch (Exception e) { +
-                    } +
-                } +
-            }, 3000); +
-        } +
-    } +
- +
-    private void notifySharingStop() { +
-        notifyGestureService(Const.ACTION_SCREEN_SHARING_STOP); +
-        if (settingsHelper.getBoolean(SettingsHelper.KEY_NOTIFY_SHARING)) { +
-            overlayDotDirection = 0; +
-            if (overlayDot != null) { +
-                WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE); +
-                wm.removeView(overlayDot); +
-                overlayDot = null; +
-            } +
-            Utils.lockDeviceRotation(this, false); +
-        } +
-    } +
- +
-    private void notifyGestureService(String action) { +
-        Intent intent = new Intent(MainActivity.this, GestureDispatchService.class); +
-        intent.setAction(action); +
-        startService(intent); +
-    } +
- +
-    public ImageView createOverlayDot() { +
-        int size = getResources().getDimensionPixelOffset(R.dimen.overlay_dot_size); +
-        WindowManager.LayoutParams params = new WindowManager.LayoutParams(size, size, +
-                Utils.OverlayWindowType(), +
-                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE +
-                        |WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL +
-                        |WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, +
-                PixelFormat.TRANSLUCENT); +
-        params.gravity = Gravity.LEFT | Gravity.TOP; +
-        params.x = getResources().getDimensionPixelOffset(R.dimen.overlay_dot_offset); +
-        params.y = getResources().getDimensionPixelOffset(R.dimen.overlay_dot_offset); +
- +
-        ImageView view = new ImageView(this); +
-        view.setImageResource(R.drawable.flash_dot); +
-        view.setImageAlpha(0); +
-        WindowManager wm = (WindowManager)getSystemService(Context.WINDOW_SERVICE); +
-        wm.addView(view, params); +
-        return view; +
-    } +
-}+
 +[[history_old|Older versions]]
 +~~NOTOC~~
 +~~ARCHIVE=history_old~~

Last modified: by 188.43.235.177