Allow starting and stopping connections to the assistant

This commit is contained in:
retgal 2021-02-15 12:26:34 +01:00
parent 7f8319856a
commit a3d6496622
2 changed files with 280 additions and 200 deletions

View File

@ -6,12 +6,9 @@ import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.SocketException; import java.net.Socket;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.List; import java.util.List;
import javax.net.ssl.*;
import javax.swing.*; import javax.swing.*;
import mpo.dayon.assisted.capture.CaptureEngine; import mpo.dayon.assisted.capture.CaptureEngine;
@ -24,6 +21,7 @@ import mpo.dayon.assisted.control.RobotNetworkControlMessageHandler;
import mpo.dayon.assisted.mouse.MouseEngine; import mpo.dayon.assisted.mouse.MouseEngine;
import mpo.dayon.assisted.network.NetworkAssistedEngine; import mpo.dayon.assisted.network.NetworkAssistedEngine;
import mpo.dayon.assisted.network.NetworkAssistedEngineConfiguration; import mpo.dayon.assisted.network.NetworkAssistedEngineConfiguration;
import mpo.dayon.assisted.network.NetworkAssistedEngineListener;
import mpo.dayon.common.babylon.Babylon; import mpo.dayon.common.babylon.Babylon;
import mpo.dayon.common.error.FatalErrorHandler; import mpo.dayon.common.error.FatalErrorHandler;
import mpo.dayon.common.error.KeyboardErrorHandler; import mpo.dayon.common.error.KeyboardErrorHandler;
@ -32,127 +30,112 @@ import mpo.dayon.common.gui.common.DialogFactory;
import mpo.dayon.common.log.Log; import mpo.dayon.common.log.Log;
import mpo.dayon.common.network.NetworkEngine; import mpo.dayon.common.network.NetworkEngine;
import mpo.dayon.common.network.message.*; import mpo.dayon.common.network.message.*;
import mpo.dayon.common.security.CustomTrustManager;
import mpo.dayon.common.utils.FileUtilities; import mpo.dayon.common.utils.FileUtilities;
import mpo.dayon.common.utils.SystemUtilities; import mpo.dayon.common.utils.SystemUtilities;
public class Assisted implements Subscriber, ClipboardOwner { public class Assisted implements Subscriber, ClipboardOwner {
private AssistedFrame frame; private AssistedFrame frame;
private NetworkAssistedEngineConfiguration configuration; private NetworkAssistedEngineConfiguration configuration;
private CaptureEngine captureEngine; private CaptureEngine captureEngine;
private CompressorEngine compressorEngine; private CompressorEngine compressorEngine;
public void configure() { private NetworkAssistedEngine networkEngine;
final String lnf = SystemUtilities.getDefaultLookAndFeel();
try {
UIManager.setLookAndFeel(lnf);
} catch (Exception ex) {
Log.warn("Could not set the [" + lnf + "] L&F!", ex);
}
}
public void start(String serverName, String portNumber) { private boolean coldStart = true;
frame = new AssistedFrame();
FatalErrorHandler.attachFrame(frame); public void configure() {
KeyboardErrorHandler.attachFrame(frame); final String lnf = SystemUtilities.getDefaultLookAndFeel();
try {
UIManager.setLookAndFeel(lnf);
} catch (Exception ex) {
Log.warn("Could not set the [" + lnf + "] L&F!", ex);
}
}
frame.setVisible(true); /**
* Returns true if we have a valid configuration
*/
public boolean start(String serverName, String portNumber) {
Log.info("Assisted start");
// accept own cert, avoid PKIX path building exception // these should not block as they are called from the network incoming message thread (!)
SSLContext sc = null; final NetworkCaptureConfigurationMessageHandler captureConfigurationHandler = this::onCaptureEngineConfigured;
try { final NetworkCompressorConfigurationMessageHandler compressorConfigurationHandler = this::onCompressorEngineConfigured;
sc = SSLContext.getInstance("TLS"); final NetworkClipboardRequestMessageHandler clipboardRequestHandler = this::onClipboardRequested;
sc.init(null, new TrustManager[] { new CustomTrustManager() }, null);
} catch (NoSuchAlgorithmException | KeyManagementException e) {
Log.error(e.getMessage());
System.exit(1);
}
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
// these should not block as they are called from the network incoming message thread (!) final NetworkControlMessageHandler controlHandler = new RobotNetworkControlMessageHandler();
final NetworkCaptureConfigurationMessageHandler captureConfigurationHandler = this::onCaptureEngineConfigured; controlHandler.subscribe(this);
final NetworkCompressorConfigurationMessageHandler compressorConfigurationHandler = this::onCompressorEngineConfigured;
final NetworkClipboardRequestMessageHandler clipboardRequestHandler = this::onClipboardRequested;
final NetworkControlMessageHandler controlHandler = new RobotNetworkControlMessageHandler();
controlHandler.subscribe(this); networkEngine = new NetworkAssistedEngine(captureConfigurationHandler, compressorConfigurationHandler, controlHandler, clipboardRequestHandler, this);
networkEngine.addListener(new MyNetworkAssistedEngineListener());
final NetworkAssistedEngine networkEngine = new NetworkAssistedEngine(captureConfigurationHandler, compressorConfigurationHandler, controlHandler, clipboardRequestHandler, this); if (frame == null) {
frame = new AssistedFrame(new AssistedStartAction(this), new AssistedStopAction(this));
FatalErrorHandler.attachFrame(frame);
KeyboardErrorHandler.attachFrame(frame);
frame.setVisible(true);
}
return configureConnection(serverName, portNumber);
}
boolean connected = false; private boolean configureConnection(String serverName, String portNumber) {
if (SystemUtilities.isValidIpAddressOrHostName(serverName) && SystemUtilities.isValidPortNumber(portNumber)) {
coldStart = false;
configuration = new NetworkAssistedEngineConfiguration(serverName, Integer.parseInt(portNumber));
Log.info("Configuration from cli params" + configuration);
networkEngine.configure(configuration);
networkEngine.connect();
return true;
}
while (!connected) { final String ip = SystemUtilities.getStringProperty(null, "dayon.assistant.ipAddress", null);
configureConnection(serverName, portNumber); final int port = SystemUtilities.getIntProperty(null, "dayon.assistant.portNumber", -1);
frame.onConnecting(configuration); if (ip != null && port > -1) {
networkEngine.configure(configuration); configuration = new NetworkAssistedEngineConfiguration(ip, port);
try { } else {
networkEngine.start(); configuration = new NetworkAssistedEngineConfiguration();
connected = true; }
} catch (SocketException e) {
frame.onRefused(configuration);
serverName = null;
portNumber = null;
} catch (NoSuchAlgorithmException | IOException | KeyManagementException e) {
FatalErrorHandler.bye(e.getMessage(), e);
}
}
networkEngine.sendHello();
frame.onConnected();
}
private void configureConnection(String serverName, String portNumber) { // no network settings dialogue
if (SystemUtilities.isValidIpAddressOrHostName(serverName) && SystemUtilities.isValidPortNumber(portNumber)) { if (coldStart) {
configuration = new NetworkAssistedEngineConfiguration(serverName, Integer.parseInt(portNumber)); coldStart = false;
} else { return true;
configuration = new NetworkAssistedEngineConfiguration(); }
final String ip = SystemUtilities.getStringProperty(null, "dayon.assistant.ipAddress", null); coldStart = false;
final int port = SystemUtilities.getIntProperty(null, "dayon.assistant.portNumber", -1); return requestConnectionSettings();
}
if ((ip == null || port == -1) && !requestConnectionSettings()) { private boolean requestConnectionSettings() {
Log.info("Bye!"); JPanel connectionSettingsDialog = new JPanel();
System.exit(0);
}
}
Log.info("Configuration " + configuration);
}
@Override connectionSettingsDialog.setLayout(new GridLayout(3, 2, 10, 10));
public void lostOwnership(Clipboard clipboard, Transferable transferable) {
Log.error("Lost clipboard ownership");
}
private boolean requestConnectionSettings() { final JLabel assistantIpAddress = new JLabel(Babylon.translate("connection.settings.assistantIpAddress"));
JPanel connectionSettingsDialog = new JPanel(); final JTextField assistantIpAddressTextField = new JTextField();
assistantIpAddressTextField.setText(configuration.getServerName());
assistantIpAddressTextField.addMouseListener(clearTextOnDoubleClick(assistantIpAddressTextField));
connectionSettingsDialog.setLayout(new GridLayout(3, 2, 10, 10)); connectionSettingsDialog.add(assistantIpAddress);
connectionSettingsDialog.add(assistantIpAddressTextField);
final JLabel assistantIpAddress = new JLabel(Babylon.translate("connection.settings.assistantIpAddress")); final JLabel assistantPortNumberLbl = new JLabel(Babylon.translate("connection.settings.assistantPortNumber"));
final JTextField assistantIpAddressTextField = new JTextField(); final JTextField assistantPortNumberTextField = new JTextField();
assistantIpAddressTextField.setText(configuration.getServerName()); assistantPortNumberTextField.setText(String.valueOf(configuration.getServerPort()));
assistantIpAddressTextField.addMouseListener(clearTextOnDoubleClick(assistantIpAddressTextField)); assistantPortNumberTextField.addMouseListener(clearTextOnDoubleClick(assistantPortNumberTextField));
connectionSettingsDialog.add(assistantIpAddress); connectionSettingsDialog.add(assistantPortNumberLbl);
connectionSettingsDialog.add(assistantIpAddressTextField); connectionSettingsDialog.add(assistantPortNumberTextField);
final JLabel assistantPortNumberLbl = new JLabel(Babylon.translate("connection.settings.assistantPortNumber")); final boolean ok = DialogFactory.showOkCancel(frame, Babylon.translate("connection.settings"), connectionSettingsDialog, () -> {
final JTextField assistantPortNumberTextField = new JTextField();
assistantPortNumberTextField.setText(String.valueOf(configuration.getServerPort()));
assistantPortNumberTextField.addMouseListener(clearTextOnDoubleClick(assistantPortNumberTextField));
connectionSettingsDialog.add(assistantPortNumberLbl);
connectionSettingsDialog.add(assistantPortNumberTextField);
final boolean ok = DialogFactory.showOkCancel(frame, Babylon.translate("connection.settings"), connectionSettingsDialog, () -> {
final String ipAddress = assistantIpAddressTextField.getText(); final String ipAddress = assistantIpAddressTextField.getText();
if (ipAddress.isEmpty()) { if (ipAddress.isEmpty()) {
return Babylon.translate("connection.settings.emptyIpAddress"); return Babylon.translate("connection.settings.emptyIpAddress");
} else if (!SystemUtilities.isValidIpAddressOrHostName(ipAddress.trim())) { } else if (!SystemUtilities.isValidIpAddressOrHostName(ipAddress.trim())) {
return Babylon.translate("connection.settings.invalidIpAddress"); return Babylon.translate("connection.settings.invalidIpAddress");
} }
final String portNumber = assistantPortNumberTextField.getText(); final String portNumber = assistantPortNumberTextField.getText();
@ -162,115 +145,187 @@ public class Assisted implements Subscriber, ClipboardOwner {
return SystemUtilities.isValidPortNumber(portNumber.trim()) ? null : Babylon.translate("connection.settings.invalidPortNumber"); return SystemUtilities.isValidPortNumber(portNumber.trim()) ? null : Babylon.translate("connection.settings.invalidPortNumber");
}); });
if (ok) { if (ok) {
final NetworkAssistedEngineConfiguration xconfiguration = new NetworkAssistedEngineConfiguration(assistantIpAddressTextField.getText().trim(), final NetworkAssistedEngineConfiguration xconfiguration = new NetworkAssistedEngineConfiguration(assistantIpAddressTextField.getText().trim(),
Integer.parseInt(assistantPortNumberTextField.getText().trim())); Integer.parseInt(assistantPortNumberTextField.getText().trim()));
if (!xconfiguration.equals(configuration)) { if (!xconfiguration.equals(configuration)) {
configuration = xconfiguration; configuration = xconfiguration;
configuration.persist(); configuration.persist();
} }
} Log.info("Configuration " + configuration);
return ok; } else {
} // cancel
frame.onReady();
}
return ok;
}
private MouseAdapter clearTextOnDoubleClick(JTextField textField) { boolean start() {
return new MouseAdapter() { // triggers network settings dialogue
@Override return start(null, null);
public void mouseClicked(MouseEvent e) { }
if (e.getClickCount() == 2) {
textField.setText(null);
}
}
};
}
/** void connect() {
* Should not block as called from the network incoming message thread (!) frame.onConnecting(configuration.getServerName(), configuration.getServerPort());
*/ networkEngine.configure(configuration);
private void onCaptureEngineConfigured(NetworkEngine engine, NetworkCaptureConfigurationMessage configuration) { networkEngine.connect();
final CaptureEngineConfiguration captureEngineConfiguration = configuration.getConfiguration(); }
Log.info("Capture configuration received " + captureEngineConfiguration); void stop() {
Log.info("Assisted stop");
if (captureEngine != null) {
captureEngine.stop();
captureEngine = null;
}
if (compressorEngine != null) {
compressorEngine.stop();
compressorEngine = null;
}
if (networkEngine != null) {
networkEngine.cancel();
networkEngine = null;
}
frame.onDisconnecting();
}
if (captureEngine != null) { @Override
captureEngine.reconfigure(captureEngineConfiguration); public void lostOwnership(Clipboard clipboard, Transferable transferable) {
return; Log.error("Lost clipboard ownership");
} }
// First time we receive a configuration from the assistant (!) private MouseAdapter clearTextOnDoubleClick(JTextField textField) {
return new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
textField.setText(null);
}
}
};
}
// Setup the mouse engine (no need before I guess) /**
final MouseEngine mouseEngine = new MouseEngine(); * Should not block as called from the network incoming message thread (!)
mouseEngine.addListener((NetworkAssistedEngine) engine); */
mouseEngine.start(); private void onCaptureEngineConfigured(NetworkEngine engine, NetworkCaptureConfigurationMessage configuration) {
final CaptureEngineConfiguration captureEngineConfiguration = configuration.getConfiguration();
captureEngine = new CaptureEngine(new RobotCaptureFactory()); if (captureEngine != null) {
captureEngine.configure(captureEngineConfiguration); Log.info("Capture configuration received " + captureEngineConfiguration);
captureEngine.reconfigure(captureEngineConfiguration);
return;
}
if (compressorEngine != null) { // Setup the mouse engine (no need before I guess)
captureEngine.addListener(compressorEngine); final MouseEngine mouseEngine = new MouseEngine();
} mouseEngine.addListener((NetworkAssistedEngine) engine);
mouseEngine.start();
captureEngine.start(); captureEngine = new CaptureEngine(new RobotCaptureFactory());
} captureEngine.configure(captureEngineConfiguration);
if (compressorEngine != null) {
captureEngine.addListener(compressorEngine);
}
captureEngine.start();
}
/** /**
* Should not block as called from the network incoming message thread (!) * Should not block as called from the network incoming message thread (!)
*/ */
private void onCompressorEngineConfigured(NetworkEngine engine, NetworkCompressorConfigurationMessage configuration) { private void onCompressorEngineConfigured(NetworkEngine engine, NetworkCompressorConfigurationMessage configuration) {
final CompressorEngineConfiguration compressorEngineConfiguration = configuration.getConfiguration(); final CompressorEngineConfiguration compressorEngineConfiguration = configuration.getConfiguration();
Log.info("Compressor configuration received " + compressorEngineConfiguration); if (compressorEngine != null) {
Log.info("Compressor configuration received " + compressorEngineConfiguration);
compressorEngine.reconfigure(compressorEngineConfiguration);
return;
}
if (compressorEngine != null) { compressorEngine = new CompressorEngine();
compressorEngine.reconfigure(compressorEngineConfiguration); compressorEngine.configure(compressorEngineConfiguration);
return; compressorEngine.addListener((NetworkAssistedEngine) engine);
} compressorEngine.start(1);
if (captureEngine != null) {
captureEngine.addListener(compressorEngine);
}
}
compressorEngine = new CompressorEngine(); /**
compressorEngine.configure(compressorEngineConfiguration); * Should not block as called from the network incoming message thread (!)
compressorEngine.addListener((NetworkAssistedEngine) engine); */
compressorEngine.start(1); private void onClipboardRequested(NetworkAssistedEngine engine) {
if (captureEngine != null) { Log.info("Clipboard transfer request received");
captureEngine.addListener(compressorEngine); Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
} Transferable transferable = clipboard.getContents(this);
}
/** if (transferable == null) return;
* Should not block as called from the network incoming message thread (!)
*/
private void onClipboardRequested(NetworkAssistedEngine engine) {
Log.info("Clipboard transfer request received"); try {
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); if (transferable.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
Transferable transferable = clipboard.getContents(this); // noinspection unchecked
List<File> files = (List<File>) clipboard.getData(DataFlavor.javaFileListFlavor);
if (!files.isEmpty()) {
final long totalFilesSize = FileUtilities.calculateTotalFileSize(files);
Log.debug("Clipboard contains files with size: " + totalFilesSize);
engine.sendClipboardFiles(files, totalFilesSize, files.get(0).getParent());
}
} else if (transferable.isDataFlavorSupported(DataFlavor.stringFlavor)) {
// noinspection unchecked
String text = (String) clipboard.getData(DataFlavor.stringFlavor);
Log.debug("Clipboard contains text: " + text);
engine.sendClipboardText(text, text.getBytes().length);
}
} catch (IOException | UnsupportedFlavorException ex) {
Log.error("Clipboard error " + ex.getMessage());
}
}
if (transferable == null) return; @Override
public void digest(String message) {
KeyboardErrorHandler.warn(String.valueOf(message));
}
try { public void onReady() {
if (transferable.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { frame.onReady();
// noinspection unchecked }
List<File> files = (List<File>) clipboard.getData(DataFlavor.javaFileListFlavor);
if (!files.isEmpty()) {
final long totalFilesSize = FileUtilities.calculateTotalFileSize(files);
Log.debug("Clipboard contains files with size: " + totalFilesSize );
engine.sendClipboardFiles(files, totalFilesSize, files.get(0).getParent());
}
} else if (transferable.isDataFlavorSupported(DataFlavor.stringFlavor)) {
// noinspection unchecked
String text = (String) clipboard.getData(DataFlavor.stringFlavor);
Log.debug("Clipboard contains text: " + text);
engine.sendClipboardText(text, text.getBytes().length);
}
} catch (IOException | UnsupportedFlavorException ex) {
Log.error("Clipboard error " + ex.getMessage());
}
}
@Override private class MyNetworkAssistedEngineListener implements NetworkAssistedEngineListener {
public void digest(String message) {
KeyboardErrorHandler.warn(String.valueOf(message));
}
@Override
public void onConnecting(String serverName, int serverPort) {
frame.onConnecting(serverName, serverPort);
}
@Override
public void onHostNotFound(String serverName) {
frame.onHostNotFound(serverName);
}
@Override
public void onConnectionTimeout(String serverName, int serverPort) {
frame.onConnectionTimeout(serverName, serverPort);
}
@Override
public void onRefused(String serverName, int serverPort) {
frame.onRefused(serverName, serverPort);
}
@Override
public void onConnected(Socket connection) {
frame.onConnected();
}
@Override
public void onDisconnecting(Socket connection) {
frame.onDisconnecting();
}
@Override
public void onIOError(IOException error) {
stop();
frame.onDisconnecting();
}
}
} }

View File

@ -1,8 +1,7 @@
package mpo.dayon.assisted.gui; package mpo.dayon.assisted.gui;
import javax.swing.Box; import javax.swing.*;
import mpo.dayon.assisted.network.NetworkAssistedEngineConfiguration;
import mpo.dayon.common.babylon.Babylon; import mpo.dayon.common.babylon.Babylon;
import mpo.dayon.common.gui.common.BaseFrame; import mpo.dayon.common.gui.common.BaseFrame;
import mpo.dayon.common.gui.common.FrameType; import mpo.dayon.common.gui.common.FrameType;
@ -11,53 +10,79 @@ import mpo.dayon.common.gui.toolbar.ToolBar;
import mpo.dayon.common.version.Version; import mpo.dayon.common.version.Version;
class AssistedFrame extends BaseFrame { class AssistedFrame extends BaseFrame {
private Action startAction;
private Action stopAction;
public AssistedFrame() { public AssistedFrame(AssistedStartAction startAction, AssistedStopAction stopAction) {
super.setFrameType(FrameType.ASSISTED); super.setFrameType(FrameType.ASSISTED);
setTitle("Dayon! (" + Babylon.translate("assisted") + ") " + Version.get()); setTitle("Dayon! (" + Babylon.translate("assisted") + ") " + Version.get());
this.stopAction = stopAction;
this.startAction = startAction;
setupToolBar(createToolBar()); setupToolBar(createToolBar());
setupStatusBar(createStatusBar()); setupStatusBar(createStatusBar());
onReady(); onReady();
} }
private ToolBar createToolBar() { private ToolBar createToolBar() {
final ToolBar toolbar = new ToolBar(); final ToolBar toolbar = new ToolBar();
toolbar.addAction(startAction);
toolbar.addSeparator();
toolbar.addAction(stopAction);
toolbar.addSeparator();
toolbar.addAction(createShowInfoAction()); toolbar.addAction(createShowInfoAction());
toolbar.addSeparator(); toolbar.addSeparator();
toolbar.addAction(createShowHelpAction()); toolbar.addAction(createShowHelpAction());
toolbar.addGlue(); toolbar.addGlue();
toolbar.addAction(createExitAction()); toolbar.addAction(createExitAction());
return toolbar; return toolbar;
} }
private StatusBar createStatusBar() { private StatusBar createStatusBar() {
final StatusBar statusBar = new StatusBar(); final StatusBar statusBar = new StatusBar();
statusBar.addSeparator(); statusBar.addSeparator();
statusBar.addRamInfo(); statusBar.addRamInfo();
statusBar.add(Box.createHorizontalStrut(10)); statusBar.add(Box.createHorizontalStrut(10));
return statusBar; return statusBar;
} }
private void onReady() { void onReady() {
startAction.setEnabled(true);
stopAction.setEnabled(false);
statusBar.setMessage(Babylon.translate("ready")); statusBar.setMessage(Babylon.translate("ready"));
} }
void onConnecting(NetworkAssistedEngineConfiguration configuration) { void onConnecting(String serverName, int serverPort) {
statusBar.setMessage(Babylon.translate("connecting", configuration.getServerName(), configuration.getServerPort())); startAction.setEnabled(false);
stopAction.setEnabled(false);
statusBar.setMessage(Babylon.translate("connecting", serverName, serverPort));
} }
void onConnected() { void onConnected() {
startAction.setEnabled(false);
stopAction.setEnabled(true);
statusBar.setMessage(Babylon.translate("connected")); statusBar.setMessage(Babylon.translate("connected"));
} }
void onRefused(NetworkAssistedEngineConfiguration configuration) { void onHostNotFound(String serverName) {
statusBar.setMessage(Babylon.translate("refused", configuration.getServerName(), configuration.getServerPort())); startAction.setEnabled(true);
stopAction.setEnabled(false);
statusBar.setMessage(Babylon.translate("serverNotFound", serverName));
}
void onConnectionTimeout(String serverName, int serverPort) {
stopAction.setEnabled(false);
startAction.setEnabled(true);
statusBar.setMessage(Babylon.translate("connectionTimeout", serverName, serverPort));
}
void onRefused(String serverName, int serverPort) {
startAction.setEnabled(true);
stopAction.setEnabled(false);
statusBar.setMessage(Babylon.translate("refused", serverName, serverPort));
}
void onDisconnecting() {
onReady();
} }
} }