Частково клікбейт 🙂 — тільки для керування курсором миші.
Але розширивши функціонал, можна буде контролювати практично все.
Архітектура
Sender буде відправляти координати миші.
Receiver буде обробляти ці дані і застосувати дані координати у себе.
Таким чином, у нас буде симуляції контролю миші іншого пристрою.
Надсилаючи якісь дані, отримувач має знати як їх обробити.
Для цього існують протоколи - спосіб, за допомогою якого дві або більше сторони домовляються про формат обміну інформацією. Він визначає, які кроки слід виконувати, які повідомлення слід відправляти, як вони мають бути структуровані, і як системи повинні реагувати на отримані повідомлення.
Ми могли б використати вже існуючий протокол, наприклад, HTTP. Але давайте зробимо невеличкий велосипед.
Наш “протокол“ буде складатися з:
- Назви команди
- Символу нового рядка, щоб мати змогу розділити назву і дані
- Самих даних, необхідних для виконання цієї команди
[Sender]
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Sender extends Application {
private CommandSender commandSender;
@Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader(Sender.class.getResource("main.fxml"));
Scene scene = new Scene(loader.load());
commandSender = new CommandSender();
applySendingListener(scene);
primaryStage.setScene(scene);
primaryStage.show();
}
private void applySendingListener(Scene scene) {
scene.setOnMouseMoved(commandSender::sendMouseCommand);
}
}
import javafx.scene.input.MouseEvent;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
public class CommandSender {
public void sendMouseCommand(MouseEvent event) {
int sceneX = (int) event.getSceneX();
int sceneY = (int) event.getSceneY();
String msg = String.format(Constants.MOUSE_CMD_TEMPLATE, sceneX, sceneY);
sendMessage(msg);
}
private void sendMessage(String msg) {
try(var socket = new Socket(Constants.IP, Constants.port);
OutputStream outputStream = socket.getOutputStream();) {
outputStream.write(msg.getBytes());
outputStream.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public interface Constants {
String IP = "..local ip address";
int port = ..desired port;
String MOUSE_CMD_TEMPLATE = """
CursorCommand
%s,%s""";
}
Receiver
import java.awt.*;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Receiver {
private static Robot robot;
public static void main(String[] args) throws Exception {
robot = new Robot();
ServerSocket socket = new ServerSocket(8766);
while (true) {
handleConnection(socket.accept());
}
}
private static void handleConnection(Socket accept) throws IOException {
try(accept;
InputStream inputStream = accept.getInputStream()) {
byte[] bytes = inputStream.readAllBytes();
String unparsedCmd = new String(bytes);
String[] cmdNameAndData = unparsedCmd.split("\n");
String commandName = cmdNameAndData[0];
String data = cmdNameAndData[1];
System.out.println(data);
handleCommand(commandName, data);
}
}
private static void handleCommand(String commandName, String data) {
// choose command by cmd name, but we have only one command
String[] coordinates = data.split(",");
int x = Integer.parseInt(coordinates[0]);
int y = Integer.parseInt(coordinates[1]);
robot.mouseMove(x, y);
}
}
Результат (це прикріплене відео до мого незакінчего пет-проекту в гітхабі, який є аналогом цього коду, бо зараз немає вільної машини, щоб протестувати, точніше, тестував на локальній машині, але відос вийшов би не дуже ефектним)
Тут гора і трішки речей які можна покращати:
У нас захардкоджений адрес локальної машини, на яку ми надсилаємо команди. Можна використовувати звичайний пінг в межах 192.168.*.*, щоб вияснити, які машини доступні в мережі
Ми беремо координати відносно сцени, розмір якої може бути наприклад 600×500. Тобто, в нас зміна координат буде відбуватися саме в цьому діапазоні. Тому нам потрібно знати як скейлити/трансформути ці координати відносно роздільної здатності під кожен пристрій.
Нам потрібно буде з керуючої машини кидати GetInitialDataRequest, який би повертав дані про роздільну здатність.
Ми б зберігали в пам’яті інформацію про роздільну здатність відносно кожного пристрою в локальній мережі.Додати команду для кліку, управлінням клавіатури, etc.
Оскільки об’єкт події йде від ОС і дуже часто, виникає “дублікація“ координат і ми надсилаємо зайві дані, створюючи непотрібне навантаження
На кожну подію ми відкриваємо сокет. Ми можемо оптимізувати мережеву комунікацію, створюючи тільки один сокет, який буде відкритий
Рефакторинг коду
Цей довгочит — стиснута версія мого незакінченого пет-проекту.
Оскільки це лінк на гітхаб, там одразу можна взяти потикати код.