diff --git a/README.md b/README.md index 07b0d6af..28b89221 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ With his permission I moved the code base to GitHub, where it can be maintained ## Releases -The ![latest version](https://github.com/RetGal/Dayon/releases/latest) is 1.4.2 - released more than eight years after the latest release of the original author. +The ![latest version](https://github.com/RetGal/Dayon/releases/latest) is 1.4.3 - released more than eight years after the latest release of the original author. This version uses updated libraries, can handle Windows UAC-dialogs and encrypts all its communication. diff --git a/build.xml b/build.xml index 3129b8d2..ed4dbac0 100644 --- a/build.xml +++ b/build.xml @@ -40,7 +40,7 @@ - + diff --git a/src/mpo/dayon/assistant/gui/Assistant.java b/src/mpo/dayon/assistant/gui/Assistant.java index ec2eb20d..8e24fcd1 100644 --- a/src/mpo/dayon/assistant/gui/Assistant.java +++ b/src/mpo/dayon/assistant/gui/Assistant.java @@ -164,6 +164,7 @@ public void start() { FatalErrorHandler.attachFrame(frame); + frame.addListener(control); frame.setVisible(true); } diff --git a/src/mpo/dayon/assisted/control/RobotNetworkControlMessageHandler.java b/src/mpo/dayon/assisted/control/RobotNetworkControlMessageHandler.java index 817d6664..b76ff7cd 100644 --- a/src/mpo/dayon/assisted/control/RobotNetworkControlMessageHandler.java +++ b/src/mpo/dayon/assisted/control/RobotNetworkControlMessageHandler.java @@ -4,6 +4,7 @@ import java.awt.Robot; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; +import java.io.File; import java.util.ArrayList; import java.util.List; @@ -17,27 +18,27 @@ public class RobotNetworkControlMessageHandler implements NetworkControlMessageHandler { private final Robot robot; + + private List subscribers = new ArrayList<>(); - private List subscribers = new ArrayList(); - + public RobotNetworkControlMessageHandler() { + try { + robot = new Robot(); + } catch (AWTException ex) { + throw new RuntimeException(ex); + } + } + public void subscribe(Subscriber subscriber) { subscribers.add(subscriber); } - + public void shout(char bogusChar) { for (Subscriber subscriber : subscribers) { subscriber.digest(String.valueOf(bogusChar)); } } - public RobotNetworkControlMessageHandler() { - try { - robot = new Robot(); - } catch (AWTException ex) { - throw new RuntimeException(ex); - } - } - /** * Should not block as called from the network incoming message thread (!) */ @@ -61,7 +62,6 @@ public void handleMessage(NetworkEngine engine, NetworkMouseControlMessage messa } else if (message.isWheel()) { robot.mouseWheel(message.getRotations()); } - robot.mouseMove(message.getX(), message.getY()); } @@ -74,12 +74,14 @@ public void handleMessage(NetworkEngine engine, NetworkKeyControlMessage message robot.keyPress(message.getKeyCode()); } catch (IllegalArgumentException ex) { Log.warn(message.toString() +" contained an invalid keyCode for "+message.getKeyChar()); - shout(message.getKeyChar()); if (message.getKeyChar() != KeyEvent.CHAR_UNDEFINED) { - KeyStroke key = KeyStroke.getKeyStroke(message.getKeyChar()); + KeyStroke key = KeyStroke.getKeyStroke(message.getKeyChar(), 0); + // plan b if (key.getKeyCode() != Character.MIN_VALUE) { Log.warn("retrying with keyCode "+key.getKeyCode()); - robot.keyPress(key.getKeyCode()); + typeUnicode(key.getKeyCode()); + } else { + shout(message.getKeyChar()); } } } @@ -88,16 +90,93 @@ public void handleMessage(NetworkEngine engine, NetworkKeyControlMessage message robot.keyRelease(message.getKeyCode()); } catch (IllegalArgumentException ex) { Log.warn(message.toString() +" contained an invalid keyCode for "+message.getKeyChar()); - shout(message.getKeyChar()); - if (message.getKeyChar() != KeyEvent.CHAR_UNDEFINED) { - KeyStroke key = KeyStroke.getKeyStroke(message.getKeyChar()); - if (key.getKeyCode() != Character.MIN_VALUE) { - Log.warn("retrying with keyCode "+key.getKeyCode()); - robot.keyRelease(key.getKeyCode()); - } - } } - + } + } + + /** + * Q&D OS detection + */ + public void typeUnicode(int keyCode) + { + if (File.separatorChar == '/') { + typeLinuxUnicode(keyCode); + } else { + typeWindowsUnicode(keyCode); + } + } + + /** + * Unicode characters are typed in decimal on Windows ä => 228 + */ + private void typeWindowsUnicode(int keyCode) { + robot.keyPress(KeyEvent.VK_ALT); + // simulate a numpad key press for each digit + for(int i = 3; i >= 0; --i) + { + int code = keyCode / (int) (Math.pow(10, i)) % 10 + KeyEvent.VK_NUMPAD0; + robot.keyPress(code); + robot.keyRelease(code); + } + robot.keyRelease(KeyEvent.VK_ALT); + } + + /** + * Unicode characters are typed in hex on Linux ä => e4 + */ + private void typeLinuxUnicode(int keyCode) { + robot.keyPress(KeyEvent.VK_CONTROL); + robot.keyPress(KeyEvent.VK_SHIFT); + robot.keyPress(KeyEvent.VK_U); + robot.keyRelease(KeyEvent.VK_U); + char[] charArray = Integer.toHexString(keyCode).toCharArray(); + // simulate a key press for each char + for (char c : charArray) { + int code = getKeyCode(c); + robot.keyPress(code); + robot.keyRelease(code); + } + robot.keyRelease(KeyEvent.VK_SHIFT); + robot.keyRelease(KeyEvent.VK_CONTROL); + } + + /** + * Maps a hex char to its corresponding virtual key code + */ + private int getKeyCode(char c) { + switch(c) { + case '0': + return 48; + case '1': + return 49; + case '2': + return 50; + case '3': + return 51; + case '4': + return 52; + case '5': + return 53; + case '6': + return 54; + case '7': + return 55; + case '8': + return 56; + case '9': + return 57; + case 'a': + return 65; + case 'b': + return 66; + case 'c': + return 67; + case 'd': + return 68; + case 'e': + return 69; + default: + return 70; } } } diff --git a/src/mpo/dayon/assisted/gui/Assisted.java b/src/mpo/dayon/assisted/gui/Assisted.java index 045fb5dd..4cad0276 100644 --- a/src/mpo/dayon/assisted/gui/Assisted.java +++ b/src/mpo/dayon/assisted/gui/Assisted.java @@ -71,6 +71,7 @@ public void start() throws IOException { frame = new AssistedFrame(new AssistedFrameConfiguration()); FatalErrorHandler.attachFrame(frame); + SeriousErrorHandler.attachFrame(frame); frame.setVisible(true); @@ -158,8 +159,6 @@ public void handleConfiguration(NetworkEngine engine, NetworkCompressorConfigura final NetworkControlMessageHandler controlHandler = new RobotNetworkControlMessageHandler(); - SeriousErrorHandler.attachFrame(frame); - controlHandler.subscribe(this); frame.onConnecting(configuration); diff --git a/src/mpo/dayon/common/error/FatalErrorHandler.java b/src/mpo/dayon/common/error/FatalErrorHandler.java index fb80d7c6..eed3c18f 100644 --- a/src/mpo/dayon/common/error/FatalErrorHandler.java +++ b/src/mpo/dayon/common/error/FatalErrorHandler.java @@ -12,6 +12,9 @@ public abstract class FatalErrorHandler { @Nullable private static JFrame frame; + /** + * Displays a translated error message and terminates + */ public static void bye(String message, Throwable error) { Log.fatal(message, error); Log.fatal("Bye!"); diff --git a/src/mpo/dayon/common/error/SeriousErrorHandler.java b/src/mpo/dayon/common/error/SeriousErrorHandler.java index dc947ce1..7b1d43ed 100644 --- a/src/mpo/dayon/common/error/SeriousErrorHandler.java +++ b/src/mpo/dayon/common/error/SeriousErrorHandler.java @@ -1,7 +1,14 @@ package mpo.dayon.common.error; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + import javax.swing.JFrame; +import javax.swing.JLabel; import javax.swing.JOptionPane; +import javax.swing.SwingUtilities; +import javax.swing.Timer; import org.jetbrains.annotations.Nullable; @@ -9,16 +16,48 @@ import mpo.dayon.common.log.Log; public abstract class SeriousErrorHandler { + @Nullable private static JFrame frame; - public static void warn(String message) { + /** + * Displays a self closing translated warning message + */ + public static void warn(final String message) { if (frame != null) { - JOptionPane.showMessageDialog(frame, Babylon.translate("serious.error.msg1") + "\n" + Babylon.translate("serious.error.msg2", message) + "\n" + Babylon.translate("serious.error.msg3") , - Babylon.translate("serious.error"), JOptionPane.ERROR_MESSAGE); + final JLabel label = new JLabel(); + int timerDelay = 1000; + new Timer(timerDelay, new ActionListener() { + int timeLeft = 4; + + @Override + public void actionPerformed(ActionEvent e) { + if (timeLeft > 0) { + StringBuilder sb = new StringBuilder(""); + sb.append(Babylon.translate("serious.error.msg1")).append("
"); + sb.append(Babylon.translate("serious.error.msg2", message)).append("
"); + sb.append(Babylon.translate("serious.error.msg3", message)).append(""); + label.setText(sb.toString()); + --timeLeft; + } else { + ((Timer) e.getSource()).stop(); + Window win = SwingUtilities.getWindowAncestor(label); + win.setVisible(false); + } + } + }) { + private static final long serialVersionUID = -3451080808563481433L; + + { + setInitialDelay(0); + } + }.start(); + + JOptionPane.showMessageDialog(frame, label, Babylon.translate("serious.error"), JOptionPane.WARNING_MESSAGE); + } else { - Log.error("Unable to display error message "+message); + Log.error("Unable to display error message " + message); } }