How do I program a double key press to make a non-modifier key behave like a modifier key within my program?

1.3k views Asked by At

I am writing a typing program that includes many more characters than are available on a standard keyboard. In order to achieve this I need to transform some of the alphabet keys into modifier keys CTRL+A. For example f+j would output a. Typing f then j is slow for the user, I need them to be able to press f and j at the same time and receive one output. It's fine (preferable even) if some of the keyboard's normal functionality is stopped while the program is running.

I have looked into pygame Keydown, but it only seems to have functions for increasing key repeat and not stopping key output. Pyglet is also a possibility, but it doesn't have exact documentation on how I could make additional modifier keys. The only way I can figure out is to be constantly scanning the whole keyboard to see if any keys are pressed, but that won't determine the order the keys are pressed in and will create errors for the user, as the user pressing f then j would be read the same as the user pressing j then f and I need only the f then j combo to be understood as a keystroke by the system.

2

There are 2 answers

2
Douglas On

Here is some simple code to print keys pressed in quick succession, written in Python 2. It should be able to easily be modified to suit your needs:

import pygame, sys
pygame.init()
screen = pygame.display.set_mode([500,500])
clock = pygame.time.Clock()

combokeys = []
timer = 0
ACCEPTABLE_DELAY = 30 #0.5 seconds

while 1:
    clock.tick(60)
    timer += 1
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
        if event.type == pygame.KEYDOWN:
            if timer <= ACCEPTABLE_DELAY:
                combokeys.append(event.unicode)
            else:
                combokeys = [event.unicode]
            timer = 0
            print combokeys

I have not been able to test this code (working at school computer), so please notify me in the comments if something did not work so I can fix it.

You can change the value given for ACCEPTABLE_DELAY to change the delay before something is considered a different key combination. The delay should be (ACCEPTABLE_DELAY/60) seconds.

0
Torxed On

Here's a Pyglet version of how you could do it.
I based it on common GUI class that I use often here on SO because it's modular and easier to build on without the code getting messy after 40 lines.

import pyglet
from pyglet.gl import *

key = pyglet.window.key

class main(pyglet.window.Window):
    def __init__ (self):
        super(main, self).__init__(800, 800, fullscreen = False)
        self.x, self.y = 0, 0

        #self.bg = Spr('background.jpg')
        self.output = pyglet.text.Label('',
                          font_size=14,
                          x=self.width//2, y=self.height//2,
                          anchor_x='center', anchor_y='center')

        self.alive = 1
        self.pressed = []
        self.key_table = {213 : 'a'}

    def on_draw(self):
        self.render()

    def on_close(self):
        self.alive = 0

    def on_key_release(self, symbol, modifiers):
        if symbol == key.LCTRL:
            pass # Again, here's how you modify based on Left CTRL for instance

        ## All key presses represents a integer, a=97, b=98 etc.
        ## What we do here is have a custom key_table, representing combinations.
        ## If the sum of two pressed matches to our table, we add that to our label.
        ## - If no match was found, we add the character representing each press instead. 
        ##   This way we support multiple presses but joined ones still takes priority.

        key_values = sum(self.pressed)
        if key_values in self.key_table:
            self.output.text += self.key_table[key_values]
        else:
            for i in self.pressed:
                self.output.text += chr(i)
        self.pressed = []

    def on_key_press(self, symbol, modifiers):
        if symbol == key.ESCAPE: # [ESC]
            self.alive = 0
        elif symbol == key.LCTRL:
            pass # Modify based on left control for instance
        else:
            self.pressed.append(symbol)

    def render(self):
        self.clear()
        #self.bg.draw()

        self.output.draw()

        self.flip()

    def run(self):
        while self.alive == 1:
            self.render()

            # -----------> This is key <----------
            # This is what replaces pyglet.app.run()
            # but is required for the GUI to not freeze
            #
            event = self.dispatch_events()

x = main()
x.run()

It might look like a lot of code, especially to the Pygame answer. But you could condense this down to ~15 lines as well, but again, the code would get messy if you tried to build on it any further.

Hope this works. Now I haven't thought the math through on this one.. It might be possible that two duplicate key combinations will produce the same value as another key representation, simply replace the dictionary keys 213 for instance with a tuple key such as self.key_table = {(107, 106) : 'a'} which would represent k+j

Few benefits:

  • No need to keep track of delay's
  • Fast and responsive
  • Any key could be turned into a modifier or map against custom keyboard layouts, meaning you could turn QWERTY into DWORAK for this application alone.. Not sure why you would want that, but hey.. None of my business :D
  • Overrides default keyboard inputs, so you can intercept them and do whatever you want with them.

Edit: One cool feature would be to register each key down but replace the last character with the joined combination.. Again this is all manual works since a keyboard isn't meant to do double-key-representations, and it's more of a graphical idea.. But would be cool :)