28
$\begingroup$

Is there a way to detect when a key is released, similar to

EventHandler[InputField[], {"KeyDown", "k"} :> Print["k pressed"]]

It seems such detection isn't available at first glance, perhaps it can be achieved some other way?

Alternatively is there a way to detect which keys are currently pressed? I know that;

CurrentValue["ModifierKeys"] 

Works for shift, ctrl and such, but I can't seem to figure out how to get a list of regular keys being pressed.

Edit: CurrentValue["EventKey"] does not seem to work for this purpose as it only detects keydown events, thus you cannot do something like the following which detects shift-up

shiftDown = False;
Dynamic[
If[MemberQ[CurrentValue["ModifierKeys"], "Shift"], (shiftDown = True;), 
      If[shiftDown, (shiftDown = False; Print["Shift up code"])]
    ]
]

link to MathGroup post

$\endgroup$
5
  • 3
    $\begingroup$ paging Yu-Sung Chang... $\endgroup$
    – rm -rf
    May 6, 2012 at 19:12
  • 5
    $\begingroup$ The fact that the KeyDown event fires continuously while the key is pressed seems coherent with a timed scan of the keyboard, and not a real (interrupt driven) event handler. If that is the case, you'll never get a KeyUp event. $\endgroup$ May 6, 2012 at 19:21
  • $\begingroup$ If anyone knows for sure if this is the case, it would be nice with confirmation of this. $\endgroup$
    – jVincent
    May 6, 2012 at 20:01
  • $\begingroup$ Thanks for the edit. The reason is to keep things together for readers of both threads. $\endgroup$
    – Szabolcs
    May 7, 2012 at 21:20
  • 2
    $\begingroup$ This isn't finished but the follow code works for registering key up events. I am either going to link it in using MathKernel or convert it completely over to M code. code here $\endgroup$
    – William
    Sep 25, 2014 at 1:42

5 Answers 5

6
$\begingroup$

To answer the second part of your question, you can use CurrentValue["EventKey"] to get the current key that is being pressed. Modifying your example above:

EventHandler[InputField[], "KeyDown" :> Print[CurrentValue["EventKey"]]]
$\endgroup$
2
  • $\begingroup$ Thank you for your answer. I realize that you can detect which key triggered the event inside an event handler, however for the second part of my question I was looking for a way to cercumvent the lack of a keyup event, by having something similar to Dynamic[CurrentValue["ModifierKeys"]] which can be used to along with tricks to for example detect when shift is lifted. $\endgroup$
    – jVincent
    May 6, 2012 at 19:25
  • $\begingroup$ CurrentValue["EventKey"] however does not detect all keys, only alphanumeric and punctuation keys, but no Control, Shift, Enter, etc. $\endgroup$ Aug 31, 2013 at 8:04
6
$\begingroup$

Edit: Updated to register any key presses

Whenever an EventHandler does not handle something continuously, one can set up a listener manually using scheduled tasks. Here, the task task is initiated when the InputField is displayed, that scans the state of the Shift key. You can put whatever code you want to be evaluated on keypress in the update function.

DynamicModule[{task, pressed = {}, on = False, last, time, modNew, modOld, event = {}, 
  mods = {"AltKey", "CommandKey", "ControlKey", "OptionKey", "ShiftKey"}},
 Column@{
   Dynamic@pressed,
   EventHandler[
    InputField[], {"KeyDown" :> (If[! on, on = True]; last = SessionTime[];
                                 event = CurrentValue@"EventKey")}, 
    PassEventsDown -> True]
   },
 Initialization :> (
   modOld = False /@ mods;
   If[FreeQ[ScheduledTasks[], task], task = RunScheduledTask[
      time = SessionTime[];
      modNew = CurrentValue /@ mods;
      If[modNew =!= modOld, pressed = Pick[mods, modOld]];
      If[on && time - last > .55, on = False; pressed = {event}];
      modOld = modNew,
      .05]];
   ),
 Deinitialization :> If[MemberQ[ScheduledTasks[], task], RemoveScheduledTask@task]
 ]

Image shows the update due to successive key presses of any key (that can be registered):

enter image description here

For control keys, the method is simply checking CurrentValue-s while the task is running. The hard part is to get the letter/digit/punctuation keys to be registered. For those, the code measures the time between successive updates (time and last) which are calculated while the (non-control-)key is held pressed. Since Windows uses a 0.5 sec time delay before starting to flood-type when a key is pressed, one has to wait at least the same amount of time before being able to tell whether the key was released or it is still pressed and continuous typing is about to start. Thus registering the release of letter-keys can only happen when enough time (.55 sec) has been spent since the last update.

Since the scheduled task should run in the background as long as the InputField is on screen, there should be a method to destroy it only when the InputField is destroyed, hence the Deinitialization code.

$\endgroup$
9
  • $\begingroup$ @Mr.Wizard I've experimented with this kind of update, with no avail. If you could make it update i in "discrete steps" it would be a useful approach, but this still does not work: old = False; i = 0; Dynamic[If[old =!= shift && shift, i++]; {shift, i}, old = shift; shift := CurrentValue@"ShiftKey"]. $\endgroup$ Aug 31, 2013 at 10:49
  • $\begingroup$ @IstvánZachar Try old = False; i = 0; Dynamic[If[old =!= shift && shift, i++]; old = shift; shift := CurrentValue@"ShiftKey"; {shift, i}] $\endgroup$
    – Hector
    Aug 31, 2013 at 11:11
  • $\begingroup$ @Hector Well that's certainly a lot cleaner! $\endgroup$
    – Mr.Wizard
    Aug 31, 2013 at 11:14
  • $\begingroup$ @Hector I just realized similar function is already in the question, and this is about other keys, not modifiers. Oh well, it was fun playing with it. $\endgroup$
    – Mr.Wizard
    Aug 31, 2013 at 11:23
  • $\begingroup$ I'm afraid without extension to letters etc., not just modifiers, this answer brings nothing new. Do you think that's possible? $\endgroup$
    – Mr.Wizard
    Aug 31, 2013 at 11:26
3
$\begingroup$

This has been bugging non stop so I wanted to create a somewhat viable solution for Windows using autohotkey.

Run the following as an Administrator in autohotkey on Windows.

down=0

e::
    if(down=0){
      FileDelete, C:\Test.txt
      FileAppend, Down.`n, C:\Test.txt
      down=1
    }
return

e Up::
      Send e
      FileDelete, C:\Test.txt
      FileAppend, Up.`n, C:\Test.txt
      down=0
return

Then create the following

Needs["NETLink`"];
InstallNET[];

ShowNETConsole["stdout"];
LoadNETType["System.Console"];
Console`Out@WriteLine["Hello from .NET"];

WatchFile[file_String, fun_] := 
  Module[{time, time2, w, e}, 
   w = NETNew["System.IO.FileSystemWatcher"];
   LoadNETType["System.IO.NotifyFilters"];
   LoadNETType["System.IO.File"];
   w@Path = FileNameJoin@Drop[FileNameSplit[file], -1];
   w@NotifyFilter = NotifyFilters`LastWrite;
   w@Filter = Last@FileNameSplit[file];
   e = AddEventHandler[w@Changed, myHandler];
   content = Import[file, "Plaintext"]; 
   myHandler[source_, e_] := (Catch[
      content2 = Import[file, "Plaintext"];
      If[content != content2, fun[source, e, content2];];
      content = content2;
      ]
     );
   w@EnableRaisingEvents = True;
   Return[{w, e}];];
WatchFile["C:\\Test.txt", Function[{w, e, content2},
  var = content2
  ]]
EventHandler[Dynamic@var, {"KeyDown" :> (Null;)}]

It will essentially allow the e key to detect when it is up and down.

$\endgroup$
1
$\begingroup$

You can try using NotebookEventActions see the given example in an other subject.

displaying = True;

SetOptions[EvaluationNotebook[], 
 NotebookEventActions :> {{"KeyDown", "k"} :> (If[! displaying, 
      Print["k press"]]), 
   "DownArrowKeyDown" :> (If[! displaying, Print["down press"]]), 
   "MouseClicked" :> (If[displaying, displaying = False])}]

the related thread Make EventHandler work for clicks and keys in a Dynamic display

$\endgroup$
2
  • 2
    $\begingroup$ I fear this has the exact same problem, you cannot detect keyUp, since events only trigger on keyDown. $\endgroup$
    – jVincent
    May 6, 2012 at 19:50
  • 4
    $\begingroup$ If it is really so crucial you may use JLink to call java API in mathematica. There defined key up events. $\endgroup$
    – s.s.o
    May 6, 2012 at 20:04
1
$\begingroup$

One rather drastic solution is to use LibraryLink to just get the keyboard state directly from the operating system, and then deploy a high-frequency ScheduledTask to poll the keyboard state. I do this on Windows using C.

Here's a minimal working example, showing how to get the keyboard state into the kernel as a ByteArray, and then using EventHandler to change the color of a graphic when a certain key is pressed down, and to change it back as soon as the key is released.

In a separate file, library.c:

#include "WolframLibrary.h" 
#include "WolframNumericArrayLibrary.h"
#include <Windows.h>
#include <string.h> 

DLLEXPORT int getKeyboardState(WolframLibraryData libData, mint Argc, MArgument *Args, MArgument Res){
    GetKeyState(0);
    BYTE keyboardState[256] = {0};
    if (GetKeyboardState(keyboardState)){
        MNumericArray keyboardByteArray;
        const mint length = 256;
        
        if (libData->numericarrayLibraryFunctions->MNumericArray_new(MNumericArray_Type_UBit8, 1, &length, &keyboardByteArray)) {
            return LIBRARY_FUNCTION_ERROR;
        }
       
        memcpy((uint8_t*) libData->numericarrayLibraryFunctions->MNumericArray_getData(keyboardByteArray), keyboardState, 256);
        MArgument_setMNumericArray(Res, keyboardByteArray);
        return LIBRARY_NO_ERROR;

    } else {
        return LIBRARY_FUNCTION_ERROR;
    }
}

I use CCompilerDriver to compile this into a DLL, and then I load the function into the kernel and make some appropriate definitions.

<<CCompilerDriver`

CreateLibrary[
 File@"library.c", 
 "library.dll",
 "Libraries" -> {"User32.lib"},
 "Language" -> "C"
]

With[
 {func =
   LibraryFunctionLoad[
    File@"library.dll",
    "getKeyboardState",
    {}, ByteArray
    ]
  },
 keyboard[] := func[];
 keyboard[vk_Integer] := func[][[vk + 1]]
]

Now you can use this function in a ScheduledTask to poll the state a particular key.

color=Black;
w$pressed=.

EventHandler[
 Graphics[
  {Dynamic@color, Rectangle[],
   White, Arrow@{
     {.5, .5},
     Dynamic@MousePosition["Graphics", {.5, .5}]}
   }],
 {{"KeyDown", "w"} :>
   If[! TrueQ@w$pressed, (w$pressed = True;
     color = Purple;
     task = SessionSubmit@ScheduledTask[
        If[(keyboard[87]~Quotient~128) == 0,
         (TaskRemove@task;
          w$pressed = False;
          color = Black
          )], 1/100])]
  }
 ]

Select the resulting Graphics cell, and you'll see that the background color changes to purple as soon as the W key is pressed, and changes back again as soon as it's let go. Here's a GIF demonstrating: https://i.stack.imgur.com/xQx5B.jpg

Unfortunately, this solution is specific to Windows, but I believe it should be easy to generalize this example.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.