How to Detect Key Presses

How do I detect keypresses in Javascript?

With plain Javascript, the simplest is:

document.onkeypress = function (e) {
e = e || window.event;
// use e.keyCode
};

But with this, you can only bind one handler for the event.

In addition, you could use the following to be able to potentially bind multiple handlers to the same event:

addEvent(document, "keypress", function (e) {
e = e || window.event;
// use e.keyCode
});

function addEvent(element, eventName, callback) {
if (element.addEventListener) {
element.addEventListener(eventName, callback, false);
} else if (element.attachEvent) {
element.attachEvent("on" + eventName, callback);
} else {
element["on" + eventName] = callback;
}
}

In either case, keyCode isn't consistent across browsers, so there's more to check for and figure out. Notice the e = e || window.event - that's a normal problem with Internet Explorer, putting the event in window.event instead of passing it to the callback.

References:

  • https://developer.mozilla.org/en-US/docs/DOM/Mozilla_event_reference/keypress
  • https://developer.mozilla.org/en-US/docs/DOM/EventTarget.addEventListener

With jQuery:

$(document).on("keypress", function (e) {
// use e.which
});

Reference:

  • http://api.jquery.com/on/

Other than jQuery being a "large" library, jQuery really helps with inconsistencies between browsers, especially with window events...and that can't be denied. Hopefully it's obvious that the jQuery code I provided for your example is much more elegant and shorter, yet accomplishes what you want in a consistent way. You should be able to trust that e (the event) and e.which (the key code, for knowing which key was pressed) are accurate. In plain Javascript, it's a little harder to know unless you do everything that the jQuery library internally does.

Note there is a keydown event, that is different than keypress. You can learn more about them here: onKeyPress Vs. onKeyUp and onKeyDown

As for suggesting what to use, I would definitely suggest using jQuery if you're up for learning the framework. At the same time, I would say that you should learn Javascript's syntax, methods, features, and how to interact with the DOM. Once you understand how it works and what's happening, you should be more comfortable working with jQuery. To me, jQuery makes things more consistent and is more concise. In the end, it's Javascript, and wraps the language.

Another example of jQuery being very useful is with AJAX. Browsers are inconsistent with how AJAX requests are handled, so jQuery abstracts that so you don't have to worry.

Here's something that might help decide:

  • http://www.jscripters.com/jquery-disadvantages-and-advantages/

How do I detect multiple keypresses in python all at the same time?

The best approach to do it is to use pygame module. pygame.key.get_pressed() returns the list of all the keys pressed at one time:

e.g. for multiple key press

if keys[pygame.K_w] and keys[pygame.K_a]:
#Do something

More details in documentation.

How to detect key presses in flutter without a RawKeyboardListener

You can use the following from dart_html:

    window.onKeyPress.listen((KeyboardEvent e) {
print(e.charCode.toString() + " " + new String.fromCharCode(e.charCode));
});

How to detect Fn key press in Swift?

You need to check the NSEvent's modifierFlags:

if event.modifierFlags.contains(.function) {
print("fn key pressed")
}

Is there any way to detect key press in Python?

Assuming you want a command line application
you should be able to do it with

import sys
import termios
import tty
custom_messages = {'a': 'some other message'}
custom_messages['b'] = 'what?';
stdin = sys.stdin.fileno()
tattr = termios.tcgetattr(stdin)
try:
tty.setcbreak(stdin, termios.TCSANOW)
while True:
char = sys.stdin.read(1)
print(custom_messages.get(char, 'You pressed ' + char))
except KeyboardInterrupt:
print('You interrupted')
sys.exit() ## ?
finally:
termios.tcsetattr(stdin, termios.TCSANOW, tattr)

I based my answer on disabling buffering in stdin

How to detect key press and release in swiftUI (macOS)

Unfortunately keyboard event handling is one of those areas where it's painfully obvious that SwiftUI was designed first and foremost for iOS, with macOS being an afterthought.

If the key you're trying to detect is a modifier to a mouse click, such as cmd, option, or shift, you can use the .modifiers with onTapGesture to distinguish it from an unmodified onTapGesture. In that case, my experience with it is that you want the .onTapGesture call that uses .modifiers to precede the unmodified one.

Handling general key events for arbitrary views requires going outside of SwiftUI.

If you just need it for one View, one possibility is to implement that view with AppKit so you can receive the keyboard events via the ordinary Cocoa firstResponder mechanism, and then wrap that view in SwiftUI's NSViewRepresentable. In that case your wrapped NSView would update some @State property in NSViewRespresentable. A lot of developers using SwiftUI for macOS do it this way. While this is fine for a small number of views, if it turns out that you have to implement a lot of views in AppKit to make them usable in SwiftUI, then you're kind of defeating the point of using SwiftUI anyway. In that case, just make it an ordinary Cocoa app.

But there is another way...

You could use another thread that uses CGEventSource to poll the keyboard state actively in conjunction with a SwiftUI @EnvironmentObject or @StateObject to communicate keyboard state changes to the SwiftUI Views that are interested in them.

Let's say you want to detect when the up-arrow is pressed. To detect the key, I use an extension on CGKeyCode.

import CoreGraphics

extension CGKeyCode
{
// Define whatever key codes you want to detect here
static let kVK_UpArrow: CGKeyCode = 0x7E

var isPressed: Bool {
CGEventSource.keyState(.combinedSessionState, key: self)
}
}

Of course, you have to use the right key codes. I have a gist containing all of the old key codes. Rename them to be more Swifty if you like. The names listed go back to classic MacOS and were defined in Inside Macintosh.

With that extension defined, you can test if a key is pressed anytime you like:

if CGKeyCode.kVK_UpArrow.isPressed { 
// Do something in response to the key press.
}

Note these are not key-up or key-down events. It's simply a boolean detecting if the key is pressed when you perform the check. To behave more like events, you'll need to do that part yourself by keeping track of key state changes.

There are multiple ways of doing this, and the following code is not meant to imply that this is the "best" way. It is simply a way. In any case, something like the following code would go (or be called from) wherever you do global initialization when you app starts.

// These will handle sending the "event" and will be fleshed 
// out further down
func dispatchKeyDown(_ key: CGKeyCode) {...}
func dispatchKeyUp(_ key: CGKeyCode) {...}

fileprivate var keyStates: [CGKeyCode: Bool] =
[
.kVK_UpArrow: false,
// populate with other key codes you're interested in
]

fileprivate let sleepSem = DispatchSemaphore(value: 0)
fileprivate let someConcurrentQueue = DispatchQueue(label: "polling", attributes: .concurrent)

someConcurrentQueue.async
{
while true
{
for (code, wasPressed) in keyStates
{
if code.isPressed
{
if !wasPressed
{
dispatchKeyDown(code)
keyStates[code] = true
}
}
else if wasPressed
{
dispatchKeyUp(code)
keyStates[code] = false
}
}

// Sleep long enough to avoid wasting CPU cycles, but
// not so long that you miss key presses. You may
// need to experiment with the .milliseconds value.
let_ = sleepSem.wait(timeout: .now() + .milliseconds(50))
}
}

The idea is just to have some code that periodically polls key states, compares them with previous states, dispatches an appropriate "event" when they change, and updates the previous states. The code above does that by running an infinite loop in a concurrent task. It requires creating a DispatchQueue with the .concurrent attribute. You can't use it on DispatchQueue.main because that queue is serial not concurrent, so the infinite loop would block the main thread, and the program would become unresponsive. If you already have a concurrent DispatchQueue you use for other reasons, you can just use that one instead of creating one just for polling.

However, any code that accomplishes the basic goal of periodic polling will do, so if you don't already have a concurrent DispatchQueue and would prefer not to create one just to poll for keyboard states, which would be a reasonable objection, here's an alternate version that uses DispatchQueue.main with a technique called "async chaining" to avoid blocking/sleeping:

fileprivate var keyStates: [CGKeyCode: Bool] =
[
.kVK_UpArrow: false,
// populate with other key codes you're interested in
]

fileprivate func pollKeyStates()
{
for (code, wasPressed) in keyStates
{
if code.isPressed
{
if !wasPressed
{
dispatchKeyDown(code)
keyStates[code] = true
}
}
else if wasPressed
{
dispatchKeyUp(code)
keyStates[code] = false
}
}

DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(50))
{
// The infinite loop from previous code is replaced by
// infinite chaining.
pollKeyStates()
}
}

// Start up key state polling
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(50)) {
pollKeyStates()
}

With code in place to detect when keys are pressed, you now need a way to communicate that to your SwiftUI Views. Again, there's more than one way to skin that cat. Here's an overly simplistic one that will update a View whenever the up-arrow is pressed, but you'll probably want to implement something a bit more sophisticated... probably something that allows views to specify what keys they're interested in responding to.

class UpArrowDetector: ObservableObject
{
@Published var isPressed: Bool = false
}

let upArrowDetector = UpArrowDetector()

func dispatchKeyDown(_ key: CGKeyCode)
{
if key == .kVK_UpArrow {
upArrowDetector.isPressed = true
}
}

func dispatchKeyUp(_ key: CGKeyCode) {
if key == .kVK_UpArrow {
upArrowDetector.isPressed = false
}
}

// Now we hook it into SwiftUI
struct UpArrowDetectorView: View
{
@StateObject var detector: UpArrowDetector

var body: some View
{
Text(
detector.isPressed
? "Up-Arrow is pressed"
: "Up-Arrow is NOT pressed"
)
}
}

// Use the .environmentObject() method of `View` to inject the
// `upArrowDetector`
struct ContentView: View
{
var body: some View
{
UpArrowDetectorView()
.environmentObject(upArrowDetector)
}
}

I've put a full, compilable, and working example at this gist patterned on code you linked to in comments. It's slightly refactored from the above code, but all the parts are there, including starting up the polling code.

I hope this points you in a useful direction.



Related Topics



Leave a reply



Submit