# -*- coding: utf-8 -*-
import struct
import os
import atexit
from time import time as now
from threading import Thread
from glob import glob
try :
from queue import Queue
except ImportError :
from Queue import Queue
event_bin_format = ' llHHI '
# Taken from include/linux/input.h
# https://www.kernel.org/doc/Documentation/input/event-codes.txt
EV_SYN = 0x00
EV_KEY = 0x01
EV_REL = 0x02
EV_ABS = 0x03
EV_MSC = 0x04
def make_uinput ( ) :
if not os . path . exists ( ' /dev/uinput ' ) :
raise IOError ( ' No uinput module found. ' )
import fcntl , struct
# Requires uinput driver, but it's usually available.
uinput = open ( " /dev/uinput " , ' wb ' )
UI_SET_EVBIT = 0x40045564
fcntl . ioctl ( uinput , UI_SET_EVBIT , EV_KEY )
UI_SET_KEYBIT = 0x40045565
for i in range ( 256 ) :
fcntl . ioctl ( uinput , UI_SET_KEYBIT , i )
BUS_USB = 0x03
uinput_user_dev = " 80sHHHHi64i64i64i64i "
axis = [ 0 ] * 64 * 4
uinput . write ( struct . pack ( uinput_user_dev , b " Virtual Keyboard " , BUS_USB , 1 , 1 , 1 , 0 , * axis ) )
uinput . flush ( ) # Without this you may get Errno 22: Invalid argument.
UI_DEV_CREATE = 0x5501
fcntl . ioctl ( uinput , UI_DEV_CREATE )
UI_DEV_DESTROY = 0x5502
#fcntl.ioctl(uinput, UI_DEV_DESTROY)
return uinput
class EventDevice ( object ) :
def __init__ ( self , path ) :
self . path = path
self . _input_file = None
self . _output_file = None
@property
def input_file ( self ) :
if self . _input_file is None :
try :
self . _input_file = open ( self . path , ' rb ' )
except IOError as e :
if e . strerror == ' Permission denied ' :
print ( ' Permission denied ( {} ). You must be sudo to access global events. ' . format ( self . path ) )
exit ( )
def try_close ( ) :
try :
self . _input_file . close
except :
pass
atexit . register ( try_close )
return self . _input_file
@property
def output_file ( self ) :
if self . _output_file is None :
self . _output_file = open ( self . path , ' wb ' )
atexit . register ( self . _output_file . close )
return self . _output_file
def read_event ( self ) :
data = self . input_file . read ( struct . calcsize ( event_bin_format ) )
seconds , microseconds , type , code , value = struct . unpack ( event_bin_format , data )
return seconds + microseconds / 1e6 , type , code , value , self . path
def write_event ( self , type , code , value ) :
integer , fraction = divmod ( now ( ) , 1 )
seconds = int ( integer )
microseconds = int ( fraction * 1e6 )
data_event = struct . pack ( event_bin_format , seconds , microseconds , type , code , value )
# Send a sync event to ensure other programs update.
sync_event = struct . pack ( event_bin_format , seconds , microseconds , EV_SYN , 0 , 0 )
self . output_file . write ( data_event + sync_event )
self . output_file . flush ( )
class AggregatedEventDevice ( object ) :
def __init__ ( self , devices , output = None ) :
self . event_queue = Queue ( )
self . devices = devices
self . output = output or self . devices [ 0 ]
def start_reading ( device ) :
while True :
self . event_queue . put ( device . read_event ( ) )
for device in self . devices :
thread = Thread ( target = start_reading , args = [ device ] )
thread . setDaemon ( True )
thread . start ( )
def read_event ( self ) :
return self . event_queue . get ( block = True )
def write_event ( self , type , code , value ) :
self . output . write_event ( type , code , value )
import re
from collections import namedtuple
DeviceDescription = namedtuple ( ' DeviceDescription ' , ' event_file is_mouse is_keyboard ' )
device_pattern = r """ N: Name= " ([^ " ]+?) " .+?H: Handlers=([^ \ n]+) """
def list_devices_from_proc ( type_name ) :
try :
with open ( ' /proc/bus/input/devices ' ) as f :
description = f . read ( )
except FileNotFoundError :
return
devices = { }
for name , handlers in re . findall ( device_pattern , description , re . DOTALL ) :
path = ' /dev/input/event ' + re . search ( r ' event( \ d+) ' , handlers ) . group ( 1 )
if type_name in handlers :
yield EventDevice ( path )
def list_devices_from_by_id ( name_suffix , by_id = True ) :
for path in glob ( ' /dev/input/ {} /*-event- {} ' . format ( ' by-id ' if by_id else ' by-path ' , name_suffix ) ) :
yield EventDevice ( path )
def aggregate_devices ( type_name ) :
# Some systems have multiple keyboards with different range of allowed keys
# on each one, like a notebook with a "keyboard" device exclusive for the
# power button. Instead of figuring out which keyboard allows which key to
# send events, we create a fake device and send all events through there.
try :
uinput = make_uinput ( )
fake_device = EventDevice ( ' uinput Fake Device ' )
fake_device . _input_file = uinput
fake_device . _output_file = uinput
except IOError as e :
import warnings
warnings . warn ( ' Failed to create a device file using `uinput` module. Sending of events may be limited or unavailable depending on plugged-in devices. ' , stacklevel = 2 )
fake_device = None
# We don't aggregate devices from different sources to avoid
# duplicates.
devices_from_proc = list ( list_devices_from_proc ( type_name ) )
if devices_from_proc :
return AggregatedEventDevice ( devices_from_proc , output = fake_device )
# breaks on mouse for virtualbox
# was getting /dev/input/by-id/usb-VirtualBox_USB_Tablet-event-mouse
devices_from_by_id = list ( list_devices_from_by_id ( type_name ) ) or list ( list_devices_from_by_id ( type_name , by_id = False ) )
if devices_from_by_id :
return AggregatedEventDevice ( devices_from_by_id , output = fake_device )
# If no keyboards were found we can only use the fake device to send keys.
assert fake_device
return fake_device
def ensure_root ( ) :
if os . geteuid ( ) != 0 :
raise ImportError ( ' You must be root to use this library on linux. ' )