From 2f4f0483f462a8b5275f951218df205a540713f6 Mon Sep 17 00:00:00 2001 From: Ajey Bhat <114414284+Ajeybhat47@users.noreply.github.com> Date: Fri, 25 Jul 2025 17:56:12 +0530 Subject: [PATCH] Use JSON messages and remove unused modules --- client.py | 268 ++++++++++++++++++++++++++++------------------------- reciver.py | 1 - sender.py | 1 - server.py | 176 ++++++++++++++++++----------------- 4 files changed, 230 insertions(+), 216 deletions(-) delete mode 100644 reciver.py delete mode 100644 sender.py diff --git a/client.py b/client.py index 8059f40..05fc75b 100644 --- a/client.py +++ b/client.py @@ -1,95 +1,88 @@ -import re +"""Client for controlling a remote machine via mouse and screen share.""" + +from __future__ import annotations + +import argparse +import json +import logging import os -from pynput.mouse import Listener, Button +import re import socket -import cv2 -import numpy as np import threading import time -import pyautogui - +import cv2 +import numpy as np import psutil +import pyautogui +from pynput.mouse import Button, Listener from scapy.all import ARP, Ether, srp -import time -# define the MAC address of the device you're looking for -target_mac = "00:00:00:00:00:00" -target_mac2 = "00:00:00:00:00:00" -HOST = 'localhost' +logging.basicConfig( + level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s" +) -with os.popen('arp -a') as f: - Arp_data = f.read() -Arp_table = [] -for line in re.findall('([-.0-9]+)\s+([-0-9a-f]{17})', Arp_data): - Arp_table.append(line) - if (target_mac2 == line[1]): - print("target's ip address found in ARP table") - HOST = line[0] -if (HOST == 'localhost'): - print("Could not find in ARP table") - # get the IP address and netmask of the WiFi interface that's currently in use - addrs = psutil.net_if_addrs() - # print(addrs) - interface = [i for i in addrs if i.startswith( - 'Wi-Fi')][0] # assuming WiFi interface - ip_address = addrs[interface][1].address - netmask = addrs[interface][1].netmask +def _discover_host(target_mac: str, alt_mac: str) -> str: + """Attempt to discover the host IP using the ARP table or network scan.""" - # calculate the network address from the IP address and netmask - ip_address_bytes = [int(x) for x in ip_address.split('.')] - netmask_bytes = [int(x) for x in netmask.split('.')] - network_address_bytes = [ip_address_bytes[i] - & netmask_bytes[i] for i in range(4)] - network_address = '.'.join([str(x) for x in network_address_bytes]) + host = "localhost" + with os.popen("arp -a") as f: + arp_data = f.read() - print("network=", network_address) + for ip_addr, mac in re.findall(r"([-.0-9]+)\s+([-0-9a-f]{17})", arp_data): + if mac.lower() == alt_mac.lower(): + logging.info("Found %s in ARP table", mac) + return ip_addr - # ans, unans = srp(Ether(dst="ff:ff:ff:ff:ff:ff") / - # ARP(pdst="192.168.1.0/24"), timeout=2) + logging.info("Could not find device in ARP table; scanning network") - # create an ARP request packet to send to the local network - arp = ARP(op=1, pdst=f"{network_address}/24", hwdst="ff:ff:ff:ff:ff:ff") - ether = Ether(dst="ff:ff:ff:ff:ff:ff") - packet = ether/arp + addrs = psutil.net_if_addrs() + interface = [i for i in addrs if i.startswith("Wi-Fi")] + if not interface: + logging.warning("Wi-Fi interface not found; using localhost") + return host - # send the packet and capture the response - result = srp(packet, timeout=5, verbose=0)[0] + iface = interface[0] + ip_address = addrs[iface][1].address + netmask = addrs[iface][1].netmask + + ip_bytes = [int(x) for x in ip_address.split(".")] + mask_bytes = [int(x) for x in netmask.split(".")] + network_bytes = [ip_bytes[i] & mask_bytes[i] for i in range(4)] + network_address = ".".join(str(x) for x in network_bytes) - # iterate over the responses and check if any of them match the target MAC address - for sent, received in result: - print(received.hwsrc, received.psrc) - if received.hwsrc == target_mac: - print("IP address of MAC address {} is {}".format( - target_mac, received.psrc)) - HOST = str(received.psrc) + arp = ARP(op=1, pdst=f"{network_address}/24", hwdst="ff:ff:ff:ff:ff:ff") + packet = Ether(dst="ff:ff:ff:ff:ff:ff") / arp + result = srp(packet, timeout=5, verbose=0)[0] + for _, received in result: + logging.debug("%s %s", received.hwsrc, received.psrc) + if received.hwsrc.lower() == target_mac.lower(): + logging.info("Found host %s for MAC %s", received.psrc, target_mac) + return str(received.psrc) -print("server IP=", HOST) + logging.warning("Device not found; falling back to localhost") + return host -print("proceeding to connect to server") -# HOST = '192.168.106.46' +TARGET_MAC = "00:00:00:00:00:00" +ALT_MAC = "00:00:00:00:00:00" -# HOST = 'localhost' # IP address of the server -PORT = 8000 # Port to connect to -# Create a TCP socket -s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +def _get_connection(host: str, port: int) -> socket.socket: + """Establish a TCP connection to the server.""" -# Connect to the server -s.connect((HOST, PORT)) -print('Connected to server') + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect((host, port)) + logging.info("Connected to %s:%s", host, port) + return sock STOP_BUTTON = 0 Button_state = 0 # 0-no click 1-left click 2-right click - -mouse_refresh_time = 0.075 - -# Function to send mouse coordinates to the server +MOUSE_REFRESH_TIME = 0.075 win_x = 0 win_y = 0 @@ -99,14 +92,6 @@ def send_mouse_coords(conn): - global STOP_BUTTON - global Button_state - global win_x - global win_y - global win_width - global win_height - - global mouse_refresh_time while True: # Get the current mouse position if STOP_BUTTON: @@ -115,61 +100,60 @@ def send_mouse_coords(conn): x, y = pyautogui.position() try: - msg = "|"+str((x-win_x)/(win_width/1920))+":" + \ - str((y-win_y)/(win_height/1080))+":"+str(Button_state)+"|" - # print(msg) - except: - pass - # print(msg.encode()) - s.sendall(msg.encode()) + msg = json.dumps( + { + "x": (x - win_x) / (win_width / 1920), + "y": (y - win_y) / (win_height / 1080), + "button": Button_state, + } + ) + except Exception: # noqa: BLE001 + continue + conn.sendall((msg + "\n").encode()) # Wait for some time before sending the next mouse coordinates - time.sleep(mouse_refresh_time) + time.sleep(MOUSE_REFRESH_TIME) + # Function to receive video data from the server def clicking(conn): - global STOP_BUTTON - global Button_state if STOP_BUTTON: return def on_click(x, y, button, pressed): - global STOP_BUTTON global Button_state if STOP_BUTTON: return - global win_x - global win_y - global win_width - global win_height - - global mouse_refresh_time if pressed: - # print(button == Button.left) - print(button) - if (button == Button.left): + logging.debug("mouse button: %s", button) + if button == Button.left: Button_state = 1 - elif (button == Button.right): + elif button == Button.right: Button_state = 2 else: Button_state = 0 try: - msg = "|"+str((x-win_x)/(win_width/1920))+":" + \ - str((y-win_y)/(win_height/1080))+":"+str(Button_state)+"|" - print(msg) - except: - pass - # print(msg.encode()) - s.sendall(msg.encode()) + msg = json.dumps( + { + "x": (x - win_x) / (win_width / 1920), + "y": (y - win_y) / (win_height / 1080), + "button": Button_state, + } + ) + logging.debug(msg) + except Exception: # noqa: BLE001 + return + conn.sendall((msg + "\n").encode()) # Wait for some time before sending the next mouse coordinates - time.sleep(mouse_refresh_time) + time.sleep(MOUSE_REFRESH_TIME) else: Button_state = 0 + with Listener(on_click=on_click) as listener: listener.join() @@ -182,14 +166,14 @@ def receive_video_data(conn): global win_y global win_width global win_height - cv2.namedWindow('Screen', cv2.WINDOW_NORMAL) + cv2.namedWindow("Screen", cv2.WINDOW_NORMAL) win_x = cv2.getWindowImageRect("Screen")[0] win_y = cv2.getWindowImageRect("Screen")[1] win_width = cv2.getWindowImageRect("Screen")[2] win_height = cv2.getWindowImageRect("Screen")[3] - buffer = b'' + buffer = b"" while True: @@ -202,8 +186,8 @@ def receive_video_data(conn): size_bytes = conn.recv(4) if not size_bytes: break - size = int.from_bytes(size_bytes, byteorder='big') - print(size) + size = int.from_bytes(size_bytes, byteorder="big") + logging.debug("frame size: %s", size) # Receive the JPEG buffer from the server while len(buffer) < size: data = conn.recv(min(1024, size - len(buffer))) @@ -212,39 +196,69 @@ def receive_video_data(conn): buffer += data # Convert the JPEG buffer to a numpy array - img = cv2.imdecode(np.frombuffer( - buffer, dtype=np.uint8), cv2.IMREAD_COLOR) + img = cv2.imdecode( + np.frombuffer(buffer, dtype=np.uint8), + cv2.IMREAD_COLOR, + ) # Display the image - cv2.imshow('Screen', img) + cv2.imshow("Screen", img) # cv2.waitKey(1) - if cv2.waitKey(1) & 0xFF == ord('q'): + if cv2.waitKey(1) & 0xFF == ord("q"): STOP_BUTTON = 1 break # Reset the buffer for the next image - buffer = b'' + buffer = b"" + + +def run_client(host: str, port: int) -> None: + """Run the remote client against the given host.""" + + conn = _get_connection(host, port) + + send_mouse_coords_thread = threading.Thread( + target=send_mouse_coords, args=(conn,), daemon=True + ) + receive_video_data_thread = threading.Thread( + target=receive_video_data, args=(conn,), daemon=True + ) + detect_mouse_click_thread = threading.Thread( + target=clicking, args=(conn,), daemon=True + ) + + send_mouse_coords_thread.start() + receive_video_data_thread.start() + detect_mouse_click_thread.start() + + send_mouse_coords_thread.join() + receive_video_data_thread.join() + detect_mouse_click_thread.join() + + conn.close() -# Create threads for sending mouse coordinates and receiving video data -send_mouse_coords_thread = threading.Thread( - target=send_mouse_coords, args=(s,)) -receive_video_data_thread = threading.Thread( - target=receive_video_data, args=(s,)) -detect_mouse_click_thread = threading.Thread( - target=clicking, args=(s,)) +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description=("Remote device controller client") + ) # noqa: E501 + parser.add_argument("--host", help="Server IP address") + parser.add_argument("--port", type=int, default=8000, help="Server port") + parser.add_argument("--mac", default=TARGET_MAC, help="Target MAC address") + parser.add_argument( + "--alt-mac", + default=ALT_MAC, + help="Alternate MAC address used in ARP table lookup", + ) + return parser.parse_args() -# Start threads -send_mouse_coords_thread.start() -receive_video_data_thread.start() -detect_mouse_click_thread.start() -# Wait for threads to finish -send_mouse_coords_thread.join() -receive_video_data_thread.join() -detect_mouse_click_thread.join() -# Close the socket +def main() -> None: + args = parse_args() + host = args.host or _discover_host(args.mac, args.alt_mac) + run_client(host, args.port) -s.close() +if __name__ == "__main__": + main() diff --git a/reciver.py b/reciver.py deleted file mode 100644 index 8b13789..0000000 --- a/reciver.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/sender.py b/sender.py deleted file mode 100644 index 8b13789..0000000 --- a/sender.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/server.py b/server.py index fef972c..1dcdad3 100644 --- a/server.py +++ b/server.py @@ -1,115 +1,117 @@ +"""Server for streaming screen and receiving mouse events.""" + +from __future__ import annotations + +import argparse +import json +import logging import socket -import numpy as np -import mss -import cv2 import threading -import pyautogui -# import time - -HOST = '0.0.0.0' # IP address of the server -PORT = 8000 # Port to listen on +from typing import Tuple +import cv2 +import mss +import numpy as np +import pyautogui -# pyautogui.FAILSAFE = False pyautogui.PAUSE = 0 -# Create a TCP socket -s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - -# Bind the socket to the IP address and port -s.bind((HOST, PORT)) - -# mouse = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -# mouse.bind((HOST ,PORT+1)) -# mouse.listen(10) -# conn2, addr2 = mouse.accept() - - -# Listen for incoming connections -s.listen(10) - -# Accept a client connection -conn, addr = s.accept() -print(f'Connected by {addr}') - -# Create a monitor instance for screen recording - - -def send_screen(conn): +def send_screen(conn: socket.socket, stop_event: threading.Event) -> None: + """Continuously capture the screen and stream it to the client.""" with mss.mss() as monitor: - - while True: - # Capture a screenshot of the entire screen + while not stop_event.is_set(): screenshot = monitor.grab(monitor.monitors[1]) - - # Convert the screenshot to a numpy array img = np.array(screenshot) - - # Convert the image to JPEG format for compression - encoded, buffer = cv2.imencode('.jpg', img) - - # Send the size of the JPEG buffer to the client - size = len(buffer) - conn.sendall(size.to_bytes(4, byteorder='big')) - - # Send the JPEG buffer to the client + _, buffer = cv2.imencode(".jpg", img) + conn.sendall(len(buffer).to_bytes(4, byteorder="big")) conn.sendall(buffer) -def receive_mouse_input(conn): - while True: - # receive mouse input coordinates from client +def receive_mouse_input( + conn: socket.socket, + stop_event: threading.Event, +) -> None: + """Handle mouse events received from the client.""" + buffer = "" + while not stop_event.is_set(): data = conn.recv(1024).decode() if not data: break - print(data) - data = data.split("|") - data = data[1] - try: - x, y, z = data.split(':') - x = float(x) - y = float(y) - z = int(z) - if x > 0 and x < 1920: - if y > 0 and y < 1080: - pass - print("moved to", x, y) + buffer += data + while "\n" in buffer: + line, buffer = buffer.split("\n", 1) + if not line: + continue + try: + payload = json.loads(line) + x = float(payload.get("x", 0)) + y = float(payload.get("y", 0)) + click = int(payload.get("button", 0)) + if 0 < x < 1920 and 0 < y < 1080: pyautogui.moveTo(round(x), round(y)) - if (z == 0): + if click == 0: pyautogui.mouseUp(button="left") - # pyautogui.mouseUp(button="right") - elif (z == 1): - # print("click") + elif click == 1: pyautogui.mouseDown(button="left") - elif (z == 2): + elif click == 2: pyautogui.click( - button="right", clicks=1, interval=0.25) - except: - print("error in coordinates") + button="right", clicks=1, interval=0.25 + ) + except Exception: # noqa: BLE001 + logging.exception("error decoding coordinates") + + +def handle_client(conn: socket.socket, addr: Tuple[str, int]) -> None: + """Serve a single client connection.""" + logging.info("Client connected from %s:%s", addr[0], addr[1]) + stop = threading.Event() + sender = threading.Thread( + target=send_screen, + args=(conn, stop), + daemon=True, + ) + receiver = threading.Thread( + target=receive_mouse_input, args=(conn, stop), daemon=True + ) + + sender.start() + receiver.start() + sender.join() + receiver.join() + conn.close() + logging.info("Client disconnected from %s:%s", addr[0], addr[1]) -# function to handle client connections -def handle_client(conn, addr): - print('Client connected from {}:{}'.format(addr[0], addr[1])) +def start_server(host: str, port: int) -> None: + """Start listening for client connections.""" + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind((host, port)) + s.listen(10) + logging.info("Server listening on %s:%s", host, port) + while True: + conn, addr = s.accept() + threading.Thread( + target=handle_client, args=(conn, addr), daemon=True + ).start() - # create threads for sending screen and receiving mouse input - send_screen_thread = threading.Thread(target=send_screen, args=(conn,)) - receive_mouse_input_thread = threading.Thread( - target=receive_mouse_input, args=(conn,)) - # start threads - send_screen_thread.start() - receive_mouse_input_thread.start() +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Remote device controller server" + ) # noqa: E501 + parser.add_argument("--host", default="0.0.0.0", help="Host to bind") + parser.add_argument("--port", type=int, default=8000, help="Port to bind") + return parser.parse_args() - # wait for threads to finish - send_screen_thread.join() - receive_mouse_input_thread.join() - # close connection - conn.close() - print('Client disconnected from {}:{}'.format(addr[0], addr[1])) +def main() -> None: + args = parse_args() + logging.basicConfig( + level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s" + ) + start_server(args.host, args.port) -client_thread = threading.Thread(target=handle_client, args=(conn, addr)) -client_thread.start() +if __name__ == "__main__": + main()