moses-palmer / pynput

Sends virtual input commands
GNU Lesser General Public License v3.0
1.76k stars 244 forks source link

Restart a listener after a combination is pressed. #226

Open ppel123 opened 4 years ago

ppel123 commented 4 years ago

Hi, I want to restart the listener after a specific combination or a specific rule is fulfilled, but I am struggling a bit. I have the below code :

from pynput.keyboard import Key, Listener
from langdetect import detect
from pynput import keyboard

def listener_manager():
    #the idea is to return a new listener when needed
    listener = keyboard.Listener(on_press=on_press)
    return listener

def on_press(key):
    global string
    global listener
    global controller

    if key == keyboard.Key.esc: #if button escape is pressed close the program
        listener.stop()
    elif key == keyboard.Key.space:
        print(string)
        string=""
    elif key == keyboard.Key.shift:
        listener.stop()
        controller = keyboard.Controller()
        listener = listener_manager()
        listener.start()
        print("New listener started")
    elif key== keyboard.Key.alt_l:
        listener.stop()
        controller = keyboard.Controller()
        listener = listener_manager()
        listener.start()
        print("Left alt pressed")
    else:
        string = ''.join([string,str(key).replace("'","")])

string=""

"""This is the beginning"""
controller = keyboard.Controller()
# Collect events until released

listener = keyboard.Listener(on_press=on_press)
listener.start()

Can I somehow stop and restart it or should I stop it and create a new one (and how can I do this) ? Thanks in advance.

moses-palmer commented 4 years ago

Sorry for the late reply.

Can you please expand on why you want to restart the listener? Is it to work around a bug in the listener that you have found? Mutating global state like that is often not the best way to solve problems.

ppel123 commented 4 years ago

Hi @moses-palmer , I found that when I change the input mode from one language to another(english -> greek and vice versa), the key pressed is shown in the previous mode, not the current changed one. Please see at my example in order to understand better: 1)having as input mode english and pressing "a" shows "a" as key pressed 2)changing input mode to greek and pressing "α" shows "a" as key pressed

I found that closing the program and restarting it shows the correct key pressed, so I thought that restarting the listener, if possible will do the job. Moreover I found a detour but it is not pynput-specific. Thanks for the reply.

nadav-kaempfer commented 4 years ago

Hi @ppel123, I'm also having the same problem, can you send the not pynput-specific detour solution you've found? thanks, nadav

ppel123 commented 4 years ago

Hi, sorry for the late reply, I will try to explain it as better as I can.

import win32event, \
    win32api, winerror  # for disallowing multiple instances
import win32console  # for getting the console window
import win32gui  # for getting window titles and hiding the console window
import ctypes  # for getting window titles, current keyboard layout and capslock state

# Languages codes, taken from http://atpad.sourceforge.net/languages-ids.txt
lcid_dict = {'0x436': 'Afrikaans - South Africa', '0x041c': 'Albanian - Albania', '0x045e': 'Amharic - Ethiopia',
             '0x401': 'Arabic - Saudi Arabia', '0x1401': 'Arabic - Algeria', '0x3c01': 'Arabic - Bahrain',
             '0x0c01': 'Arabic - Egypt', '0x801': 'Arabic - Iraq', '0x2c01': 'Arabic - Jordan',
             '0x3401': 'Arabic - Kuwait', '0x3001': 'Arabic - Lebanon', '0x1001': 'Arabic - Libya',
             '0x1801': 'Arabic - Morocco', '0x2001': 'Arabic - Oman', '0x4001': 'Arabic - Qatar',
             '0x2801': 'Arabic - Syria', '0x1c01': 'Arabic - Tunisia', '0x3801': 'Arabic - U.A.E.',
             '0x2401': 'Arabic - Yemen', '0x042b': 'Armenian - Armenia', '0x044d': 'Assamese',
             '0x082c': 'Azeri (Cyrillic)', '0x042c': 'Azeri (Latin)', '0x042d': 'Basque', '0x423': 'Belarusian',
             '0x445': 'Bengali (India)', '0x845': 'Bengali (Bangladesh)', '0x141A': 'Bosnian (Bosnia/Herzegovina)',
             '0x402': 'Bulgarian', '0x455': 'Burmese', '0x403': 'Catalan', '0x045c': 'Cherokee - United States',
             '0x804': "Chinese - People's Republic of China", '0x1004': 'Chinese - Singapore',
             '0x404': 'Chinese - Taiwan', '0x0c04': 'Chinese - Hong Kong SAR', '0x1404': 'Chinese - Macao SAR',
             '0x041a': 'Croatian', '0x101a': 'Croatian (Bosnia/Herzegovina)', '0x405': 'Czech', '0x406': 'Danish',
             '0x465': 'Divehi', '0x413': 'Dutch - Netherlands', '0x813': 'Dutch - Belgium', '0x466': 'Edo',
             '0x409': 'English - United States', '0x809': 'English - United Kingdom', '0x0c09': 'English - Australia',
             '0x2809': 'English - Belize', '0x1009': 'English - Canada', '0x2409': 'English - Caribbean',
             '0x3c09': 'English - Hong Kong SAR', '0x4009': 'English - India', '0x3809': 'English - Indonesia',
             '0x1809': 'English - Ireland', '0x2009': 'English - Jamaica', '0x4409': 'English - Malaysia',
             '0x1409': 'English - New Zealand', '0x3409': 'English - Philippines', '0x4809': 'English - Singapore',
             '0x1c09': 'English - South Africa', '0x2c09': 'English - Trinidad', '0x3009': 'English - Zimbabwe',
             '0x425': 'Estonian', '0x438': 'Faroese', '0x429': 'Farsi', '0x464': 'Filipino', '0x040b': 'Finnish',
             '0x040c': 'French - France', '0x080c': 'French - Belgium', '0x2c0c': 'French - Cameroon',
             '0x0c0c': 'French - Canada', '0x240c': 'French - Democratic Rep. of Congo', '0x300c':
                 "French - Cote d'Ivoire", '0x3c0c': 'French - Haiti', '0x140c': 'French - Luxembourg',
             '0x340c': 'French - Mali', '0x180c': 'French - Monaco', '0x380c': 'French - Morocco',
             '0xe40c': 'French - North Africa', '0x200c': 'French - Reunion', '0x280c': 'French - Senegal',
             '0x100c': 'French - Switzerland', '0x1c0c': 'French - West Indies', '0x462': 'Frisian - Netherlands',
             '0x467': 'Fulfulde - Nigeria', '0x042f': 'FYRO Macedonian', '0x083c': 'Gaelic (Ireland)',
             '0x043c': 'Gaelic (Scotland)', '0x456': 'Galician', '0x437': 'Georgian', '0x407': 'German - Germany',
             '0x0c07': 'German - Austria', '0x1407': 'German - Liechtenstein', '0x1007': 'German - Luxembourg',
             '0x807': 'German - Switzerland', '0x408': 'Greek', '0x474': 'Guarani - Paraguay', '0x447': 'Gujarati',
             '0x468': 'Hausa - Nigeria', '0x475': 'Hawaiian - United States', '0x040d': 'Hebrew', '0x439': 'Hindi',
             '0x040e': 'Hungarian', '0x469': 'Ibibio - Nigeria', '0x040f': 'Icelandic', '0x470': 'Igbo - Nigeria',
             '0x421': 'Indonesian', '0x045d': 'Inuktitut', '0x410': 'Italian - Italy',
             '0x810': 'Italian - Switzerland', '0x411': 'Japanese', '0x044b': 'Kannada', '0x471': 'Kanuri - Nigeria',
             '0x860': 'Kashmiri', '0x460': 'Kashmiri (Arabic)', '0x043f': 'Kazakh', '0x453': 'Khmer',
             '0x457': 'Konkani', '0x412': 'Korean', '0x440': 'Kyrgyz (Cyrillic)', '0x454': 'Lao', '0x476': 'Latin',
             '0x426': 'Latvian', '0x427': 'Lithuanian', '0x043e': 'Malay - Malaysia',
             '0x083e': 'Malay - Brunei Darussalam', '0x044c': 'Malayalam', '0x043a': 'Maltese', '0x458': 'Manipuri',
             '0x481': 'Maori - New Zealand', '0x044e': 'Marathi', '0x450': 'Mongolian (Cyrillic)',
             '0x850': 'Mongolian (Mongolian)', '0x461': 'Nepali', '0x861': 'Nepali - India',
             '0x414': 'Norwegian (Bokmål)', '0x814': 'Norwegian (Nynorsk)', '0x448': 'Oriya', '0x472': 'Oromo',
             '0x479': 'Papiamentu', '0x463': 'Pashto', '0x415': 'Polish', '0x416': 'Portuguese - Brazil',
             '0x816': 'Portuguese - Portugal', '0x446': 'Punjabi', '0x846': 'Punjabi (Pakistan)',
             '0x046B': 'Quecha - Bolivia', '0x086B': 'Quecha - Ecuador', '0x0C6B': 'Quecha - Peru',
             '0x417': 'Rhaeto-Romanic', '0x418': 'Romanian', '0x818': 'Romanian - Moldava', '0x419': 'Russian',
             '0x819': 'Russian - Moldava', '0x043b': 'Sami (Lappish)', '0x044f': 'Sanskrit', '0x046c': 'Sepedi',
             '0x0c1a': 'Serbian (Cyrillic)', '0x081a': 'Serbian (Latin)', '0x459': 'Sindhi - India',
             '0x859': 'Sindhi - Pakistan', '0x045b': 'Sinhalese - Sri Lanka', '0x041b': 'Slovak',
             '0x424': 'Slovenian', '0x477': 'Somali', '0x042e': 'Sorbian', '0x0c0a': 'Spanish - Spain (Modern Sort)',
             '0x040a': 'Spanish - Spain (Traditional Sort)', '0x2c0a': 'Spanish - Argentina',
             '0x400a': 'Spanish - Bolivia', '0x340a': 'Spanish - Chile', '0x240a': 'Spanish - Colombia',
             '0x140a': 'Spanish - Costa Rica', '0x1c0a': 'Spanish - Dominican Republic',
             '0x300a': 'Spanish - Ecuador', '0x440a': 'Spanish - El Salvador', '0x100a': 'Spanish - Guatemala',
             '0x480a': 'Spanish - Honduras', '0xe40a': 'Spanish - Latin America', '0x080a': 'Spanish - Mexico',
             '0x4c0a': 'Spanish - Nicaragua', '0x180a': 'Spanish - Panama', '0x3c0a': 'Spanish - Paraguay',
             '0x280a': 'Spanish - Peru', '0x500a': 'Spanish - Puerto Rico', '0x540a': 'Spanish - United States',
             '0x380a': 'Spanish - Uruguay', '0x200a': 'Spanish - Venezuela', '0x430': 'Sutu', '0x441': 'Swahili',
             '0x041d': 'Swedish', '0x081d': 'Swedish - Finland', '0x045a': 'Syriac', '0x428': 'Tajik',
             '0x045f': 'Tamazight (Arabic)', '0x085f': 'Tamazight (Latin)', '0x449': 'Tamil', '0x444': 'Tatar',
             '0x044a': 'Telugu', '0x041e': 'Thai', '0x851': 'Tibetan - Bhutan',
             '0x451': "Tibetan - People's Republic of China", '0x873': 'Tigrigna - Eritrea',
             '0x473': 'Tigrigna - Ethiopia', '0x431': 'Tsonga', '0x432': 'Tswana', '0x041f': 'Turkish',
             '0x442': 'Turkmen', '0x480': 'Uighur - China', '0x422': 'Ukrainian', '0x420': 'Urdu',
             '0x820': 'Urdu - India', '0x843': 'Uzbek (Cyrillic)', '0x443': 'Uzbek (Latin)', '0x433': 'Venda',
             '0x042a': 'Vietnamese', '0x452': 'Welsh', '0x434': 'Xhosa', '0x478': 'Yi', '0x043d': 'Yiddish',
             '0x046a': 'Yoruba', '0x435': 'Zulu', '0x04ff': 'HID (Human Interface Device)'}

latin_into_greek = (u"QWERTYUIOP[]ASDFGHJKL;'ZXCVBNM,./" +
                    u"qwertyuiop[]asdfghjkl;'zxcvbnm,./",
                    u";ςΕΡΤΥΘΙΟΠ[]ΑΣΔΦΓΗΞΚΛ΄'ΖΧΨΩΒΝΜ,./" +
                    u";ςερτυθιοπ[]ασδφγηξκλ΄'ζχψωβνμ,./")

greek_into_latin = (latin_into_greek[1], latin_into_greek[0])
latin_into_greek_trantab = dict([(ord(a), ord(b)) for (a, b) in zip(*latin_into_greek)])
greek_into_latin_trantab = dict([(ord(a), ord(b)) for (a, b) in zip(*greek_into_latin)])

greek_layouts = ['Greek']

def detect_key_layout():
    global lcid_dict
    user32 = ctypes.WinDLL('user32', use_last_error=True)
    curr_window = user32.GetForegroundWindow()
    thread_id = user32.GetWindowThreadProcessId(curr_window, 0)
    klid = user32.GetKeyboardLayout(thread_id)
    # made up of 0xAAABBBB, AAA = HKL (handle object) & BBBB = language ID
    # Language ID -> low 10 bits, Sub-language ID -> high 6 bits
    # Extract language ID from KLID
    lid = klid & (2 ** 16 - 1)
    # Convert language ID from decimal to hexadecimal
    lid_hex = hex(lid)
    try:
        language = lcid_dict[str(lid_hex)]
    except KeyError:
        language = lcid_dict['0x409']  # English - United States
    return language

#######YOU START MONITORING AND CATCHING THE DIFFERENT KEYS THE USERS ENTERS
#######AND WHEN YOU SWITCH TO THE OTHER MODE (INPUT LANGUAGE)
#######YOU DO THE FOLLOWING CHANGE

language = detect_key_layout()
####### HERE WE GET THE LANGUAGE MODE (INPUT LANGUAGE) OF THE KEYBOARD

####### monitoring the keyboard
"""key_pressed = the_key_user_pressed """

global latin_into_greek_trantab, greek_layouts
if ('English' in language and 'English' not in initial_language):
    # greek -> latin reverse translation is required
    print("greek -> latin reverse translation is required")
    if ord(key_pressed) in greek_into_latin_trantab:
        key_pressed = chr(greek_into_latin_trantab[ord(key_pressed)])
        print("The KEY PRESSED is {}".format(key_pressed))
elif (language in greek_layouts and initial_language not in greek_layouts):
    # latin -> greek translation is required
    print("latin -> greek translation is required")
    if ord(key_pressed) in latin_into_greek_trantab:
        key_pressed = chr(latin_into_greek_trantab[ord(key_pressed)]Σ
        print("The KEY PRESSED is {}".format(key_pressed))

After every key press we have to check if the key must be changed to the correct mode-input language e.g. lets say that the input mode or the windows language (supposing that you are working on windows) is english and the user presses 'a' then the monitor reads correct 'a', but if you change to greek lets say and press 'a' then the monitor continues reading 'a' as the pressed key. We don't restart the listener but map the key pressed to the corresponding key in the other mode. Hope I explained it well.