Initial commit
This commit is contained in:
parent
0df0c6a374
commit
8f1e7570b7
1
.gitignore
vendored
1
.gitignore
vendored
@ -7,6 +7,7 @@ __pycache__/
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.idea
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
|
5
remote_evdev/__init__.py
Executable file
5
remote_evdev/__init__.py
Executable file
@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from .config import get_config
|
||||
from .stream import get_streams
|
||||
from .stream import handle_client
|
75
remote_evdev/config/__init__.py
Normal file
75
remote_evdev/config/__init__.py
Normal 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
|
30
remote_evdev/stream/__init__.py
Normal file
30
remote_evdev/stream/__init__.py
Normal 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)
|
36
remote_evdev/stream/guest.py
Normal file
36
remote_evdev/stream/guest.py
Normal 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)
|
38
remote_evdev/stream/host.py
Normal file
38
remote_evdev/stream/host.py
Normal 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
2
requirements.txt
Normal file
@ -0,0 +1,2 @@
|
||||
setuptools
|
||||
evdev
|
12
setup.py
Normal file
12
setup.py
Normal 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'
|
||||
)
|
Loading…
Reference in New Issue
Block a user