Programlama

Python ile Klavye Olaylarını Okuma

Python’da engellemesiz girişle nasıl çalışılacağını gösteren bu eğitim dizisinin son bölümünde, basit bir C++ komut dosyası kullanarak klavye olayları veya fare olayları gibi giriş aygıtı verilerinin nasıl yakalanacağını öğrendik. Ayrıca bu verilerin bir Linux sisteminde depolanıp depolanmadığını ve ayrıca hangi karakter cihaz dosyalarının olduğunu öğrendik.

Bu Python eğitimini ziyaret ederek tekrar ziyaret edebilirsiniz: Python’da Engellemeyen Girişe Giriş.

Devam etmeden önce son bir not: Bu programlama eğitim serisinde gösterilen kod örnekleri Python 3.9.2’yi kullanacak ve bir Raspberry Pi 4 Model B (dahil edilen bağlantıdan satın alabilirsiniz); ancak, bu kod Python 3’ü destekleyen hemen hemen tüm Linux sistemlerine taşınabilir olmalıdır. Bu amaçla, bu kılavuz Python 3.9.12 çalıştıran Kali Linux ortamında kod gösterimlerini içerecektir. Raspberry Pi’yi birincil tanıtım cihazı olarak seçmenin nedeni, bu kodun işlevselliğini gelecekteki bir makalede genişletmekle daha fazla ilgilidir.

Yani tüm bu bilgiler elinizin altındayken, şimdi bu bilgilerin yakalanmasını engellemek için bir Python betiği yazmanın zamanı geldi. Böyle bir betiğin iki şey yapabilmesi gerekir:

  • Hangi olay dosyasının okunacağını belirleyin.
  • Bu dosyadaki olayları okuyun ve bunları yararlı bir şeye ayrıştırın.

Python ile Klavye Olay Dosyasını Belirleme

Aşağıdaki örnek Python kodu, /proc/bus/giriş/cihazlar Hangi olay dosyasının klavye olaylarını içerdiğini belirlemek için dosya. Dosyanın içeriği boş satırlarla ayrılmış bölümlere ayrılacaktır. Bunu yaptığınızda, kod terimi içeren bölümü arayacaktır. “EV=120013”. Bu bölüm bulunduğunda, uygun olay dosyasını belirlemek için bölüm daha da ayrıştırılacaktır.

Ancak devam etmeden önce, hızlı bir dönüş: Bu öğreticinin oluşturulması sırasında çok garip bir tuhaflık ortaya çıktı. Vardı iki bölümleri /proc/bus/giriş/cihazlar terimi olan dosya “EV=120013.” Bu yüzden bu bölümü arama fikri uçup gidiyor. Bu tuhaflık nedeniyle, hem yanlış hem de doğru bir yaklaşımı göstermek gerekli hale gelir, çünkü böyle bir sorun üzerinde nasıl çalışılacağını anlamak, engellemesiz girişleri işleyebilen Python kodunu başarılı bir şekilde yazabilmek için önemlidir.

Bu konuyu kapsayan diğer öğreticilerin çoğu, yalnızca bir eşleşen bir Linux ortamında cihaz EV=120013, ancak bu makale için kullanılan ortamlar bu tür iki cihaz buldu; ancak sadece bir klavye vardı, peki doğru dosyanın ne olduğunu anlamak için bu tespit nasıl yapılacaktı?

Python Kodu Örneği: Yanlış Yol

Aşağıdaki kod örneği bir tane gösterir yanlış Python kullanarak klavye olay dosyasını almanın yolu. Bu kod eklenmiştir, çünkü bir programcının ortodoksluk noktasına kadar doğru olduğunu düşünebileceği bazı temel varsayımların geçerli olmayabileceğini vurgulamak gerekir:

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

import struct
import sys
from datetime import datetime

def GetKeyboardEventFile(tokenToLookFor):
	# Any exception raised here will be processed by the calling function.
	section = ""
	line = ""
	eventName = ""

	fp = open ("/proc/bus/input/devices", "r")
	done = False
	while False == done:
		# The loop control logic is intentionally done wrong here in order to
		# illustrate what happens when there are multiple devices with the same
		# EV identifier.
		line = fp.readline()
		if line:
			#print (line.strip())
			if "" == line.strip():
				#print ("nFound Section:n" + section)
				if -1 != section.find(tokenToLookFor):
					# It is entirely possible there to be multiple devices
					# listed as a keyboard. In this case, I will look for 
					# the word "mouse" and exclude anything that contains
					# that. This section may need to be suited to taste
					print ("Found [" + tokenToLookFor + "] in:n" + section)
					# Get the last part of the "Handlers" line:
					lines = section.split('n')
					for sectionLine in lines:
						# The strip() method is needed because there may be trailing spaces
						# at the end of this line. This will confuse the split() method.
						if -1 != sectionLine.strip().find("Handlers="):
							print ("Found Handlers line: [" + sectionLine + "]")
							sectionLineParts = sectionLine.strip().split(' ')
							eventName = sectionLineParts[-1]
							print ("Found eventName [" + eventName + "]")
				section = ""
			else:
				section = section + line
		else:
			done = True
	fp.close()

	if "" == eventName:
		raise Exception("No event name was found for the token [" + tokenToLookFor + "]")

	return "/dev/input/" + eventName

def main(argv):
	# Need to add code which figures out the name of this file from 
	# /proc/bus/input/devices - Look for EV=120013
	# Per Linux docs, 120013 is a hex number indicating which types of events
	# this device supports, and this number happens to include the keyboard
	# event.
	keyboardEventFile = ""
	try:
		keyboardEventFile = GetKeyboardEventFile("EV=120013");
		print ("Keyboard Event File is [" + keyboardEventFile + "]")
	except BaseException as err:
		print ("Couldn't get the keyboard event file due to error [" + err + "]")
	return 0

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

Listing 2 - The Wrong Way to Determine the Keyboard Event File

Aşağıdaki çıktı, Ahududu Pi cihazı:

Şekil 1 – Yanlış Klavye Olay Dosyası

Benzer bir sorun Kali’de de ortaya çıkabilir. Geliştirme sürecinde bu sorunun önceden tahmin edilmesi ve hafifletilmesi gerektiğini söylemek güvenlidir:

Python ile Girdi Dosyaları Nasıl Okunur

Şekil 2 – Farklı işletim sistemi, aynı yanlış çıktı

Not: Bu yazının yazıldığı sırada Kali Linux, Python komutunu Python 2 yorumlayıcısına hâlâ eşler. Python 3’ü kullanmak için komut piton3 kullanılmalıdır.

Teorik olarak konuşursak, yalnızca bir cihaz olmalıdır. EV=120013 tanımlayıcı, ancak bu örnekte iki tane var. Ne yazık ki bu, bir sisteme bağlı her bir aygıtın kendisini Linux’a nasıl tanıtmayı seçtiğine bağlıdır. Bu durumda, hangi dosyanın okunması gerektiğini belirlemek için ekstra mantığa ihtiyaç duyulacaktır.

Bu gibi durumlarda, bu sorunu çözmenin iki yolu vardır:

  • Linux’un bunu nasıl yaptığını anlamak için Linux Belgelerini ve Kaynak Kodunu derinlemesine inceleyin.
  • Mantıklı bir tahminde bulunun.

Çıktıya bakıldığında, kendisini klavye olarak tanımlayan (ancak yine de fare olarak çalışan) farenin, kelimenin tam anlamıyla “Fare” onun adına. Bununla birlikte, bu belirlemeyi yapmanın daha iyi bir yolu, aşağıdakileri içeren herhangi bir bölümü hariç tutmak olacaktır. “Fare” başlığında, klavye için doğru giriş, adında bu değere sahip olmadığı için. Bu elbette makul bir tahmin olacaktır. Bunun gibi sorunları çözmek için bu tür geçici yaklaşımlar tasarlamak ve yürütmek zorunda kalmak, özellikle de bunu yapmanın doğru yolunu bulmak için Linux Belgelerini ve Kaynak Kodunu derinlemesine araştırmak, pratik bir seçenek değildir.

Okumak: Python: Raspberry Pi ile Temel Elektronik Kontrolü

Python Kodu Örneği: Doğru Yol

Aşağıdaki Python kodu birkaç değişiklik yapar, böylece aşağıdakileri içeren herhangi bir bölüm “Fare” hariç tutulur ve ayrıca “doğru” dosya belirlendikten sonra durdurulması için ekstra mantık ekler:

# get-keyboard-event-file.py

import struct
import sys
from datetime import datetime

def GetKeyboardEventFile(tokenToLookFor):
	# Any exception raised here will be processed by the calling function.
	section = ""
	line = ""
	eventName = ""

	fp = open ("/proc/bus/input/devices", "r")
	done = False
	while False == done:
		line = fp.readline()
		if line:
			#print (line.strip())
			if "" == line.strip():
				#print ("nFound Section:n" + section)
				if -1 != section.find(tokenToLookFor) and -1 == section.lower().find("mouse"):
					# It is entirely possible there to be multiple devices
					# listed as a keyboard. In this case, I will look for
					# the word "mouse" and exclude anything that contains
					# that. This section may need to be suited to taste
					print ("Found [" + tokenToLookFor + "] in:n" + section)
					# Get the last part of the "Handlers" line:
					lines = section.split('n')
					for sectionLine in lines:
						# The strip() method is needed because there may be trailing spaces
						# at the end of this line. This will confuse the split() method.
						if -1 != sectionLine.strip().find("Handlers=") and "" == eventName:
							print ("Found Handlers line: [" + sectionLine + "]")
							sectionLineParts = sectionLine.strip().split(' ')
							eventName = sectionLineParts[-1]
							print ("Found eventName [" + eventName + "]")
							done = True
				# Adding this section to show the extra section containing EV=120013
				elif -1 != section.find(tokenToLookFor) and -1 != section.lower().find("mouse"):
					print ("Found [" + tokenToLookFor + "] in the section below, but " +
						"it is not the keyboard event file:n" + section)
				section = ""
			else:
				section = section + line
		else:
			done = True
	fp.close()

	if "" == eventName:
		raise Exception("No event name was found for the token [" + tokenToLookFor + "]")

	return "/dev/input/" + eventName

def main(argv):
	# Need to add code which figures out the name of this file from 
	# /proc/bus/input/devices - Look for EV=120013
	# Per Linux docs, 120013 is a hex number indicating which types of events
	# this device supports, and this number happens to include the keyboard
	# event.
	keyboardEventFile = ""
	try:
		keyboardEventFile = GetKeyboardEventFile("EV=120013");
		print ("Keyboard Event File is [" + keyboardEventFile + "]")
	except BaseException as err:
		print ("Couldn't get the keyboard event file due to error [" + err + "]")
	return 0

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



Listing 2 - A Better Way to Determine the Keyboard Event File

Önceki kodumuzda yapılan tüm değişiklikler, “makul tahminin” olmasını sağlamaktır. “Fare” bölümde bulunmamak zorunludur. Ek olarak, olay dosyası bulunduğunda, içeriğin başka bir şekilde işlenmesi gerekmez. /proc/bus/giriş/cihazlar dosya gerçekleşir, bu nedenle elif… bölüm yürütülebilir veya yürütülemez. Aşağıda bu kodun çıktısı Ahududu Pi cihazı:

Python ile Klavye girişini oku

Şekil 3 – Klavye Giriş Dosyasını Doğru Belirleme, Raspberry Pi

Klavye Girişini Okumak için Python Kullanın

Şekil 4 – Klavye Giriş Dosyasını Doğru Olarak Belirleme, Kali

Şimdi bu doğru Klavye Giriş Olay Dosyası belirlendikten sonra, bir sonraki adım dosyayı okumak ve işlemek.

Karşılık gelen tüm olay dosyalarının “EV=120013” aynı anda okunamadı. Olayları okunmakta olan yalnızca bir klavye olduğunu varsayarsak, işlenecek yalnızca tek bir klavye olayı kümesi olacaktır.

Aramaktan vazgeçmek daha aşırı bir yaklaşım olacaktır. “EV=120013” içindeki tüm girdi olay dosyalarından tamamen ve basitçe okuyun. /dev/girdiyalnızca tek bir klavye olayı kümesi için filtreleme.

Okumak: Temel Raspberry Pi Elektronik Kontrolleri için Python’u Kullanma

Python ile Klavye Olayları Nasıl Okunur

Bu yüzden, aradaki tüm ado ile, gereken tek şey ham verileri okumak için bazı temel ikili veri işlemedir. Aşağıdaki kod, aşağıdakilerin belirlenmesini içerir: Klavye Giriş Olay Dosyası ve verileri yorumlamak için ona kod ekler:

# demo-keyboard.py

import struct
import sys
from datetime import datetime

def GetKeyboardEventFile(tokenToLookFor):
	# Any exception raised here will be processed by the calling function.
	section = ""
	line = ""
	eventName = ""

	fp = open ("/proc/bus/input/devices", "r")
	done = False
	while False == done:
		line = fp.readline()
		if line:
			#print (line.strip())
			if "" == line.strip():
				#print ("nFound Section:n" + section)
				if -1 != section.find(tokenToLookFor) and -1 == section.lower().find("mouse"):
					# It is entirely possible there to be multiple devices
					# listed as a keyboard. In this case, I will look for 
					# the word "mouse" and exclude anything that contains
					# that. This section may need to be suited to taste
					print ("Found [" + tokenToLookFor + "] in:n" + section)
					# Get the last part of the "Handlers" line:
					lines = section.split('n')
					for sectionLine in lines:
						# The strip() method is needed because there may be trailing spaces
						# at the end of this line. This will confuse the split() method.
						if -1 != sectionLine.strip().find("Handlers=") and "" == eventName:
							print ("Found Handlers line: [" + sectionLine + "]")
							sectionLineParts = sectionLine.strip().split(' ')
							eventName = sectionLineParts[-1]
							print ("Found eventName [" + eventName + "]")
							done = True
				section = ""
			else:
				section = section + line
		else:
			done = True
	fp.close()

	if "" == eventName:
		raise Exception("No event name was found for the token [" + tokenToLookFor + "]")

	return "/dev/input/" + eventName

def main(argv):
	# Need to add code which figures out the name of this file from 
	# /proc/bus/input/devices - Look for EV=120013
	# Per Linux docs, 120013 is a hex number indicating which types of events
	# this device supports, and this number happens to include the keyboard
	# event.

	keyboardEventFile = ""
	try:
		keyboardEventFile = GetKeyboardEventFile("EV=120013");
	except Exception as err:
		print ("Couldn't get the keyboard event file due to error [" + str(err) + "]")

	if "" != keyboardEventFile:
		try:
			k = open (keyboardEventFile, "rb");
			# The struct format reads (small L) (small L) (capital H) (capital H) (capital I)
			# Per Python, the structure format codes are as follows:
			# (small L) l - long
			# (capital H) H - unsigned short
			# (capital I) I - unsigned int
			structFormat="llHHI"
			eventSize = struct.calcsize(structFormat)

			event = k.read(eventSize)
			goingOn = True
			while goingOn and event:
				(seconds, microseconds, eventType, eventCode, value) = struct.unpack(structFormat, event)

				# Per Linux docs at https://www.kernel.org/doc/html/v4.15/input/event-codes.html
				# Constants defined in /usr/include/linux/input-event-codes.h 
				# EV_KEY (1) constant indicates a keyboard event. Values are:
				# 1 - the key is depressed.
				# 0 - the key is released.
				# 2 - the key is repeated.

				# The code corresponds to which key is being pressed/released.

				# Event codes EV_SYN (0) and EV_MSC (4) appear but are not used, although EV_MSC may 
				# appear when a state changes.

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

				if 1 == eventType:
					# It is necessary to flush the print statement or else holding multiple keys down
					# is likely to block *output*
					print ("Event Size [" + str(eventSize) + "] Type [" + str(eventType) + "], code [" +
					str (eventCode) + "], value [" + str(value) + "] at [" + friendlyDTS + "]", flush=True)
				if 1 == eventCode:
					print ("ESC Pressed - Quitting.")
					goingOn = False
				#if 4 == eventType:
				#	print ("-------------------- Separator Event 4 --------------------")
				event = k.read(eventSize)

			k.close()
		except IOError as err:
			print ("Can't open keyboard input file due to the error [" + str(err) + "]. Maybe try sudo?")
		except Exception as err:
			print ("Can't open keyboard input file due to some other error [" + str(err) + "].")
	else:
		print ("No keyboard input file could be found.")
	return 0

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



Listing 3 - Reading the keyboard Input

Bu kodu genellikle şu şekilde çalıştırmak gerekir: kök. Bunun nedeni, içindeki dosyaların /dev/girdi aittir kök. Bunu test etmenin en iyi yolu, kodu masaüstündeki bir terminal penceresinde çalıştırmak ve herhangi bir tuşa basmadan önce odağı pencereden uzaklaştırmak için fareyi kullanmaktır. Bu şekilde, tuşların çıktısı, komut dosyasının görüntü çıktısını etkilemeyecektir. Ayrıca bu kodu doğrudan masaüstünde çalıştırmanız gerekir. VNC veya SSHçünkü bu sunucular klavye olaylarını uzak bir istemciden İşletim Sistemine iletmezler.

Bu kodun yürütülmesini durdurmak için, sadece Kaçmak anahtar. Aşağıda, Raspberry Pi cihazında, anahtar kodların vurgulandığı örnek bir çıktı verilmiştir:

Python Ahududu Pi Örnekleri

Şekil 5 – Raspberry Pi’de örnek çıktı

Aşağıda Kali Linux’taki örnek çıktı bulunmaktadır. Olayın boyutunun 16 bayt değil 24 bayt olduğunu gözlemleyin. Bu, nesnenin boyutunu dinamik olarak hesaplamanın neden önemli olduğuna dair bir örnektir. Klavye Giriş Etkinliği verileri okumadan önce:

Raspberry Pi ve Python örnekleri

Şekil 6 – Kali Linux’ta örnek çıktı

Her iki örnekte de değeri 1 tuşuna basıldığını gösterir. bir değeri 0 serbest bırakıldığını gösterir. Yukarıdaki örnek çıktıda gösterilmemesine rağmen, 2 bir tuşun basılı tutulduğunu gösterir.

o kadar Çevre bir tuşa basmak ve basılı tutmak arasındaki farkı neyin oluşturduğunu belirlemek için.

boyutunu varsaymamaya ek olarak, unutmayın. Klavye Giriş Etkinliği sabitse, girdi olay dosyalarının adlarının da sabit olmadığını varsaymak önemlidir. Bunların sisteme yeni bir çevre birimi eklendiğinde veya bazı İşletim Sistemi yapılandırma değişiklikleri dosya adında bir değişikliğe neden olması durumunda değiştirilmemesi için hiçbir neden yoktur.

Python’da Klavye Olaylarını Okuma Üzerine Son Düşünceler

Bu, Python’da engellenmeyen girdilerle nasıl çalışılacağına ilişkin öğretici dizideki ikinci bölümümüzle sona eriyor. Üçüncü ve son bölümde, olay kodlarını anahtarlara nasıl eşleştireceğimize ve örnek programımızı nasıl tamamlayacağımıza bakacağız.

İlgili Makaleler

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.

Başa dön tuşu