Initial commit
This commit is contained in:
parent
0df0c6a374
commit
8f1e7570b7
1
.gitignore
vendored
1
.gitignore
vendored
@ -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
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