Initial commit

This commit is contained in:
Surferlul 2022-07-20 06:41:09 +02:00
parent 0df0c6a374
commit 8f1e7570b7
8 changed files with 199 additions and 0 deletions

1
.gitignore vendored
View File

@ -7,6 +7,7 @@ __pycache__/
*.so *.so
# Distribution / packaging # Distribution / packaging
.idea
.Python .Python
build/ build/
develop-eggs/ develop-eggs/

5
remote_evdev/__init__.py Executable file
View File

@ -0,0 +1,5 @@
#!/usr/bin/env python3
from .config import get_config
from .stream import get_streams
from .stream import handle_client

View File

@ -0,0 +1,75 @@
from dataclasses import dataclass
import sys
from os import path
@dataclass
class DeviceInfo:
path: str
device_type: str
@dataclass
class NetConfig:
is_server: bool
is_host: bool
port: int
ip_address: str
def get_config() -> tuple[NetConfig, list[DeviceInfo]]:
args = sys.argv
cfg = NetConfig(
is_server=False,
is_host=True,
port=64654,
ip_address="auto"
)
devices_info = []
n = 1
while n < len(args):
match args[n]:
case "-s" | "--server": cfg.is_server = True
case "-c" | "--client": cfg.is_server = False
case "-h" | "--host": cfg.is_host = True
case "-g" | "--guest": cfg.is_host = False
case "-p" | "--port":
n += 1
try:
cfg.port = int(args[n])
except ValueError:
raise ValueError(f"Port must be a integer, not {args[n]}")
case "-a" | "--ip-address":
cfg.ip_address = args[n := n+1]
case "-d" | "--device":
devices_info.append(DeviceInfo(
path="",
device_type="other"
))
case _:
key = "path"
value = ""
match args[n]:
case "--id": value = "/dev/input/by-id/"
case "--path": value = "/dev/input/by-path/"
case "--event": value = "/dev/input/"
case "--full-path": pass
case "--type":
match args[n+1]:
case "pointer" | "keyboard": key = "type"
case _: raise ValueError(f"Invalid device type {args[n+1]}")
n += 1
match key:
case "path":
value = f"{value}{args[n]}"
if path.exists(value):
devices_info[-1].path = value
case "type": devices_info[-1].device_type = args[n]
n += 1
if cfg.ip_address == "auto":
raise ValueError(f"Auto IP address not implemented yet")
return cfg, devices_info

View File

@ -0,0 +1,30 @@
from socket import socket, AF_INET, SOCK_STREAM, SOL_SOCKET, SO_REUSEADDR
from typing import Generator
from . import guest
from . import host
from ..config import NetConfig, DeviceInfo
def get_streams(cfg: NetConfig) -> Generator[socket, None, None]:
while True:
with socket(AF_INET, SOCK_STREAM) as s:
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
if cfg.is_server:
s.bind((cfg.ip_address, cfg.port))
s.listen()
while True:
conn, addr = s.accept()
with conn:
print(f"Connection established to {addr}")
yield conn
else:
s.connect((cfg.ip_address, cfg.port))
yield s
def handle_client(s: socket, cfg: NetConfig, devices_info: list[DeviceInfo]):
if cfg.is_host:
host.handle_client(s, devices_info)
else:
guest.handle_client(s)

View File

@ -0,0 +1,36 @@
from evdev import UInput
from socket import socket
import json
from typing import Generator
def receive_devices(s: socket) -> Generator[tuple[int, UInput], None, None]:
while buf_size := int.from_bytes(s.recv(4), byteorder='big'):
fd = int.from_bytes(s.recv(4), byteorder='big')
cap = json.loads(s.recv(buf_size).decode())
cap = {int(key): cap[key] for key in cap}
del cap[0]
yield fd, UInput(cap, name=f"web-evdev-device-fd{fd}")
def receive_event(s: socket) -> tuple[int, tuple[int, int, int]]:
fd = int.from_bytes(s.recv(4), byteorder='big')
event_type = int.from_bytes(s.recv(4), byteorder='big')
code = int.from_bytes(s.recv(4), byteorder='big')
value = int.from_bytes(s.recv(8), byteorder='big', signed=True)
return fd, (event_type, code, value)
def handle_client(s: socket):
devices = {}
for fd, device in receive_devices(s):
devices[fd] = device
while True:
fd, event = receive_event(s)
if fd == 4294967295:
break
elif event[0] == 0:
devices[fd].syn()
else:
devices[fd].write(*event)

View File

@ -0,0 +1,38 @@
from socket import socket
from evdev import InputDevice, InputEvent
from select import select
import json
from ..config import DeviceInfo
def send_device(s: socket, device: InputDevice):
buf = json.dumps(device.capabilities()).encode()
s.sendall(len(buf).to_bytes(4, byteorder='big'))
s.sendall(device.fd.to_bytes(4, byteorder='big'))
s.sendall(buf)
def send_input_event(s: socket, fd: int, input_event: InputEvent):
s.sendall(fd.to_bytes(4, byteorder='big'))
s.sendall(input_event.type.to_bytes(4, byteorder='big'))
s.sendall(input_event.code.to_bytes(4, byteorder='big'))
s.sendall(input_event.value.to_bytes(8, byteorder='big', signed=True))
def handle_client(s: socket, devices_info: list[DeviceInfo]):
devices = []
for device_info in devices_info:
devices.append(InputDevice(device_info.path))
devices[-1].grab()
send_device(s, devices[-1])
s.sendall((0).to_bytes(4, byteorder='big'))
devices = {device.fd: device for device in devices}
while True:
r, w, x = select(devices, [], [])
for fd in r:
for event in devices[fd].read():
send_input_event(s, fd, event)
s.sendall(b'\xff'*20)
for device in devices:
device.ungrab()

2
requirements.txt Normal file
View File

@ -0,0 +1,2 @@
setuptools
evdev

12
setup.py Normal file
View File

@ -0,0 +1,12 @@
from setuptools import setup
setup(
name='remote-evdev',
version='1.0',
packages=['remote_evdev', 'remote_evdev.config', 'remote_evdev.stream'],
url='https://github.com/Surferlul/remote-evdev',
license='GNU General Public License v2.0',
author='lu',
author_email='lukasabaumann@gmail.com',
description='Share evdev devices (unix input devices) over network'
)