Monday, July 4, 2022
HomeSoftware DevelopmentStudying Keyboard Occasions with Python

Studying Keyboard Occasions with Python


Within the final a part of this tutorial collection exhibiting work with non-blocking enter in Python, we realized seize enter machine information – resembling keyboard occasions or mouse occasions – utilizing a easy C++ script. We additionally realized whether or not that information was saved in a Linux system, in addition to what character machine information had been.

You may revisit that Python tutorial by visiting: Intro to Non-Blocking Enter in Python.

One ultimate notice earlier than we proceed: the code examples demonstrated on this programming tutorial collection will use Python 3.9.2 and run on a Raspberry Pi 4 Mannequin B (which you should purchase from the included hyperlink); nonetheless, this code ought to be transportable to nearly any Linux system which helps Python 3. To that finish, this information will embrace demonstrations of code in a Kali Linux surroundings operating Python 3.9.12. The explanation for selecting the Raspberry Pi as the first demonstration machine has extra to do with extending the performance of this code in a future article.

So with all of this data at hand, it’s now time to jot down a Python script to intercept the seize of this data. Such a script would want to have the ability to do two issues:

  • Decide which occasion file to learn.
  • Learn the occasions from that file and parse them into one thing helpful.

Figuring out the Keyboard Occasion File with Python

The instance Python code beneath will parse the /proc/bus/enter/gadgets file to find out which occasion file comprises keyboard occasions. The contents of the file will probably be cut up into sections delimited by clean strains. Upon doing so, the code will seek for the part which comprises the time period “EV=120013”. As soon as this part is discovered, the part will probably be additional parsed to find out the right occasion file.

Earlier than we transfer on, nonetheless, a fast twist: in the course of the composition of this tutorial, a really unusual quirk got here up. There have been two sections of the /proc/bus/enter/gadgets file which had the time period “EV=120013.” So the concept of looking for this part flies out the window. Due to this quirk, it turns into essential to illustrate each an incorrect and proper strategy, as a result of understanding work via such a problem is essential to with the ability to efficiently write Python code which might deal with non-blocking inputs.

Most different tutorials that cowl this subject assume that there exists solely one machine in a Linux surroundings that matches EV=120013, however the environments used for this text discovered two such gadgets; nonetheless, there was just one keyboard, so how would the dedication be made with a view to work out what the right file was?

Python Code Instance: The Mistaken Means

The code pattern beneath exhibits one fallacious method to get the keyboard occasion file utilizing Python. This code is included as a result of it’s needed to emphasise that sure core assumptions which a programmer might imagine are true, even to the purpose of orthodoxy, might not be the case:

# get-keyboard-event-file-wrong.py

import struct
import sys
from datetime import datetime

def GetKeyboardEventFile(tokenToLookFor):
	# Any exception raised right here will probably be processed by the calling perform.
	part = ""
	line = ""
	eventName = ""

	fp = open ("/proc/bus/enter/gadgets", "r")
	accomplished = False
	whereas False == accomplished:
		# The loop management logic is deliberately accomplished fallacious right here with a view to
		# illustrate what occurs when there are a number of gadgets with the identical
		# EV identifier.
		line = fp.readline()
		if line:
			#print (line.strip())
			if "" == line.strip():
				#print ("nFound Part:n" + part)
				if -1 != part.discover(tokenToLookFor):
					# It's fully doable there to be a number of gadgets
					# listed as a keyboard. On this case, I'll search for 
					# the phrase "mouse" and exclude something that comprises
					# that. This part might must be suited to style
					print ("Discovered [" + tokenToLookFor + "] in:n" + part)
					# Get the final a part of the "Handlers" line:
					strains = part.cut up('n')
					for sectionLine in strains:
						# The strip() methodology is required as a result of there could also be trailing areas
						# on the finish of this line. It will confuse the cut up() methodology.
						if -1 != sectionLine.strip().discover("Handlers="):
							print ("Discovered Handlers line: [" + sectionLine + "]")
							sectionLineParts = sectionLine.strip().cut up(' ')
							eventName = sectionLineParts[-1]
							print ("Discovered eventName [" + eventName + "]")
				part = ""
			else:
				part = part + line
		else:
			accomplished = True
	fp.shut()

	if "" == eventName:
		increase Exception("No occasion title was discovered for the token [" + tokenToLookFor + "]")

	return "/dev/enter/" + eventName

def fundamental(argv):
	# Want so as to add code which figures out the title of this file from 
	# /proc/bus/enter/gadgets - Search for EV=120013
	# Per Linux docs, 120013 is a hex quantity indicating which kinds of occasions
	# this machine helps, and this quantity occurs to incorporate the keyboard
	# occasion.
	keyboardEventFile = ""
	strive:
		keyboardEventFile = GetKeyboardEventFile("EV=120013");
		print ("Keyboard Occasion File is [" + keyboardEventFile + "]")
	besides BaseException as err:
		print ("Could not get the keyboard occasion file on account of error [" + err + "]")
	return 0

if "__main__" == __name__:
	fundamental(sys.argv[1:])

Itemizing 2 - The Mistaken Option to Decide the Keyboard Occasion File

Under is the output on the Raspberry Pi machine:

Reading Keyboard Event Files in Python

Determine 1 – The Mistaken Keyboard Occasion File

The same drawback can even happen in Kali. It’s secure to say that in the course of the course of growth, this subject have to be anticipated and mitigated:

How to Read Input Files with Python

Determine 2 – Completely different OS, identical fallacious output

Notice: On the time of this writing, Kali Linux nonetheless maps the Python command to the Python 2 interpreter. To make use of Python 3, the command python3 have to be used.

Theoretically talking, there ought to solely be one machine with the EV=120013 identifier, however on this instance, there are two. Sadly this boils all the way down to how every machine hooked up to a system chooses to establish itself to Linux. On this case, further logic goes to be wanted with a view to decide which file is the one which must be learn.

In such conditions, there are two methods to unravel this drawback:

  • Dig deep into the Linux Documentation and Supply Code to determine how Linux does this.
  • Make an affordable guess.

Trying on the output, it’s clear that the mouse, which identifies itself as a keyboard (however nonetheless works as a mouse), has the literal “Mouse” in its title. That being mentioned, a greater method to make this dedication can be to exclude any part which incorporates “Mouse” in its heading, as the right entry for the keyboard doesn’t have this worth in its title. This after all, can be an affordable guess. It isn’t unusual to have to plot and execute such ad-hoc approaches to fixing issues like this, particularly digging deep into the Linux Documentation and Supply Code to determine the right method of doing this isn’t a sensible possibility.

Learn: Python: Fundamental Electronics Management with the Raspberry Pi

Python Code Instance: The Proper Means

The Python code beneath makes just a few adjustments in order that any part which comprises “Mouse” is excluded, and it it additionally provides further logic to cease as soon as the “right” file is decided:

# get-keyboard-event-file.py

import struct
import sys
from datetime import datetime

def GetKeyboardEventFile(tokenToLookFor):
	# Any exception raised right here will probably be processed by the calling perform.
	part = ""
	line = ""
	eventName = ""

	fp = open ("/proc/bus/enter/gadgets", "r")
	accomplished = False
	whereas False == accomplished:
		line = fp.readline()
		if line:
			#print (line.strip())
			if "" == line.strip():
				#print ("nFound Part:n" + part)
				if -1 != part.discover(tokenToLookFor) and -1 == part.decrease().discover("mouse"):
					# It's fully doable there to be a number of gadgets
					# listed as a keyboard. On this case, I'll search for
					# the phrase "mouse" and exclude something that comprises
					# that. This part might must be suited to style
					print ("Discovered [" + tokenToLookFor + "] in:n" + part)
					# Get the final a part of the "Handlers" line:
					strains = part.cut up('n')
					for sectionLine in strains:
						# The strip() methodology is required as a result of there could also be trailing areas
						# on the finish of this line. It will confuse the cut up() methodology.
						if -1 != sectionLine.strip().discover("Handlers=") and "" == eventName:
							print ("Discovered Handlers line: [" + sectionLine + "]")
							sectionLineParts = sectionLine.strip().cut up(' ')
							eventName = sectionLineParts[-1]
							print ("Discovered eventName [" + eventName + "]")
							accomplished = True
				# Including this part to indicate the additional part containing EV=120013
				elif -1 != part.discover(tokenToLookFor) and -1 != part.decrease().discover("mouse"):
					print ("Discovered [" + tokenToLookFor + "] within the part beneath, however " +
						"it's not the keyboard occasion file:n" + part)
				part = ""
			else:
				part = part + line
		else:
			accomplished = True
	fp.shut()

	if "" == eventName:
		increase Exception("No occasion title was discovered for the token [" + tokenToLookFor + "]")

	return "/dev/enter/" + eventName

def fundamental(argv):
	# Want so as to add code which figures out the title of this file from 
	# /proc/bus/enter/gadgets - Search for EV=120013
	# Per Linux docs, 120013 is a hex quantity indicating which kinds of occasions
	# this machine helps, and this quantity occurs to incorporate the keyboard
	# occasion.
	keyboardEventFile = ""
	strive:
		keyboardEventFile = GetKeyboardEventFile("EV=120013");
		print ("Keyboard Occasion File is [" + keyboardEventFile + "]")
	besides BaseException as err:
		print ("Could not get the keyboard occasion file on account of error [" + err + "]")
	return 0

if "__main__" == __name__:
	fundamental(sys.argv[1:])



Itemizing 2 - A Higher Option to Decide the Keyboard Occasion File

All of the alterations to our earlier code do is make sure that the “cheap guess” of “Mouse” not being within the part is enforced. Moreover, as soon as the occasion file is discovered, no additional processing of the contents of the /proc/bus/enter/gadgets file takes place, so the elif… part might or might not be executed. Under is the output of this code on the Raspberry Pi machine:

Read Keyboard input with Python

Determine 3 – Appropriately Figuring out the Keyboard Enter File, Raspberry Pi

Use Python to Read Keyboard Input

Determine 4 – Appropriately Figuring out the Keyboard Enter File, Kali

Now that the right Keyboard Enter Occasion File has been decided, the subsequent step is to learn the file and course of it.

There is no such thing as a cause that every one occasion information that correspond to “EV=120013” couldn’t be learn concurrently. Assuming that there’s just one keyboard whose occasions are being learn, there would nonetheless solely be a single set of keyboard occasions to course of.

A extra excessive strategy can be to forego searching for “EV=120013” altogether and easily learn from all the enter occasion information in /dev/enter, filtering just for a single set of keyboard occasions.

Learn: Utilizing Python for Fundamental Raspberry Pi Digital Controls

Tips on how to Learn Keyboard Occasions with Python

So, with all of the ado out of the way in which, all that’s wanted is a few fundamental binary information processing to learn the uncooked information. The code beneath incorporates the dedication of the Keyboard Enter Occasion File and provides to it code to interpret the information:

# demo-keyboard.py

import struct
import sys
from datetime import datetime

def GetKeyboardEventFile(tokenToLookFor):
	# Any exception raised right here will probably be processed by the calling perform.
	part = ""
	line = ""
	eventName = ""

	fp = open ("/proc/bus/enter/gadgets", "r")
	accomplished = False
	whereas False == accomplished:
		line = fp.readline()
		if line:
			#print (line.strip())
			if "" == line.strip():
				#print ("nFound Part:n" + part)
				if -1 != part.discover(tokenToLookFor) and -1 == part.decrease().discover("mouse"):
					# It's fully doable there to be a number of gadgets
					# listed as a keyboard. On this case, I'll search for 
					# the phrase "mouse" and exclude something that comprises
					# that. This part might must be suited to style
					print ("Discovered [" + tokenToLookFor + "] in:n" + part)
					# Get the final a part of the "Handlers" line:
					strains = part.cut up('n')
					for sectionLine in strains:
						# The strip() methodology is required as a result of there could also be trailing areas
						# on the finish of this line. It will confuse the cut up() methodology.
						if -1 != sectionLine.strip().discover("Handlers=") and "" == eventName:
							print ("Discovered Handlers line: [" + sectionLine + "]")
							sectionLineParts = sectionLine.strip().cut up(' ')
							eventName = sectionLineParts[-1]
							print ("Discovered eventName [" + eventName + "]")
							accomplished = True
				part = ""
			else:
				part = part + line
		else:
			accomplished = True
	fp.shut()

	if "" == eventName:
		increase Exception("No occasion title was discovered for the token [" + tokenToLookFor + "]")

	return "/dev/enter/" + eventName

def fundamental(argv):
	# Want so as to add code which figures out the title of this file from 
	# /proc/bus/enter/gadgets - Search for EV=120013
	# Per Linux docs, 120013 is a hex quantity indicating which kinds of occasions
	# this machine helps, and this quantity occurs to incorporate the keyboard
	# occasion.

	keyboardEventFile = ""
	strive:
		keyboardEventFile = GetKeyboardEventFile("EV=120013");
	besides Exception as err:
		print ("Could not get the keyboard occasion file on account of error [" + str(err) + "]")

	if "" != keyboardEventFile:
		strive:
			okay = open (keyboardEventFile, "rb");
			# The struct format reads (small L) (small L) (capital H) (capital H) (capital I)
			# Per Python, the construction format codes are as follows:
			# (small L) l - lengthy
			# (capital H) H - unsigned brief
			# (capital I) I - unsigned int
			structFormat="llHHI"
			eventSize = struct.calcsize(structFormat)

			occasion = okay.learn(eventSize)
			goingOn = True
			whereas goingOn and occasion:
				(seconds, microseconds, eventType, eventCode, worth) = struct.unpack(structFormat, occasion)

				# Per Linux docs at https://www.kernel.org/doc/html/v4.15/enter/event-codes.html
				# Constants outlined in /usr/embrace/linux/input-event-codes.h 
				# EV_KEY (1) fixed signifies a keyboard occasion. Values are:
				# 1 - the secret is depressed.
				# 0 - the secret is launched.
				# 2 - the secret is repeated.

				# The code corresponds to which secret is being pressed/launched.

				# Occasion codes EV_SYN (0) and EV_MSC (4) seem however are usually not used, though EV_MSC might 
				# seem when a state adjustments.

				unixTimeStamp = float(str(seconds) + "." + str(microseconds)) 
				utsDateTimeObj = datetime.fromtimestamp(unixTimeStamp)
				friendlyDTS = utsDateTimeObj.strftime("%B %d, %Y - %H:%M:%S.%f")

				if 1 == eventType:
					# It's essential to flush the print assertion or else holding a number of keys down
					# is prone to block *output*
					print ("Occasion Dimension [" + str(eventSize) + "] Sort [" + str(eventType) + "], code [" +
					str (eventCode) + "], worth [" + str(value) + "] at [" + friendlyDTS + "]", flush=True)
				if 1 == eventCode:
					print ("ESC Pressed - Quitting.")
					goingOn = False
				#if 4 == eventType:
				#	print ("-------------------- Separator Occasion 4 --------------------")
				occasion = okay.learn(eventSize)

			okay.shut()
		besides IOError as err:
			print ("Cannot open keyboard enter file as a result of error [" + str(err) + "]. Perhaps strive sudo?")
		besides Exception as err:
			print ("Cannot open keyboard enter file on account of another error [" + str(err) + "].")
	else:
		print ("No keyboard enter file may very well be discovered.")
	return 0

if "__main__" == __name__:
	fundamental(sys.argv[1:])



Itemizing 3 - Studying the keyboard Enter

It’s often essential to run this code as root. The explanation why is as a result of the information in /dev/enter are owned by root. One of the simplest ways to check that is to execute the code in a terminal window on the desktop, and, earlier than urgent any keys, use the mouse to alter focus away from the window. This fashion the output of the keys won’t intrude with the show output of the script. It is usually essential to run this code straight on the desktop, not via VNC or SSH, as these servers don’t move keyboard occasions from a distant shopper to the Working System.

To cease execution of this code, merely press the Escape key. Under is a pattern output on the Raspberry Pi machine, with the important thing codes highlighted:

Python Raspberry Pi Examples

Determine 5 – Pattern output on the Raspberry Pi

Under is the pattern output on Kali Linux. Observe how the dimensions of the occasion is 24 bytes and never 16 bytes. That is an instance of why it is very important dynamically calculate the dimensions of the Keyboard Enter Occasion earlier than studying the information:

Raspberry Pi and Python examples

Determine 6 – Pattern output in Kali Linux

In each examples, the worth of 1 signifies that the important thing was pressed. A price of 0 signifies that it was launched. Though it’s not proven within the pattern output above, a worth of 2 signifies {that a} secret is being held down.

It’s as much as the Surroundings to find out what constitutes the distinction between merely urgent a key and holding it down.

Notice, along with not assuming that the dimensions of a Keyboard Enter Occasion is fixed, it is usually essential to imagine that the names of the enter occasion information are usually not fixed as effectively. There is no such thing as a cause why these can not change any time a brand new peripheral is added to the system, or if some Working System configuration change causes a change within the filename to happen.

Remaining Ideas on Studying Keyboard Occasions in Python

That wraps up our second half within the tutorial collection on work with non-blocking enter in Python. In our third, and ultimate half, we are going to have a look at map occasion codes to keys and wrap up our instance program.

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments