10
$\begingroup$

We can use:

SetOptions[EvaluationNotebook[], NotebookEventActions -> {{"KeyDown","s"} :> Print["triggered"]}]

to make the current notebook respond to s button press, also we can do the same to Modifier Keys like Ctrl, Shift, Alt or so.

Can we do the same with Backspace, Delete or F1~F12?

Additionally, can we detect the mouse-wheel's scrolling state?

$\endgroup$
6
  • $\begingroup$ If you are Windows you probably can rig something with .NET I can't find the code but I have done this once before with some obscure keys otherwise you can't. Mac probably has no way and linux/Ubuntu probably has a way but it is quite complex. $\endgroup$
    – William
    Commented Mar 10, 2017 at 2:18
  • $\begingroup$ @Liam On the topic of .NET I was going back through a scrape for this answer and I believe I saw some internal stuff where they were adding .NET event handlers there, so it should be possible at that level. Wjx, I don't really know why WRI hasn't put in a "BackspaceKeyDown" option for EventHandler. Maybe try to contact tech support there. They'd be the final authority. $\endgroup$
    – b3m2a1
    Commented Mar 10, 2017 at 6:06
  • $\begingroup$ @Liam I'm using windows, but unfortunately I know nothing about .NET, could you show some way? Thanks! $\endgroup$
    – Wjx
    Commented Mar 10, 2017 at 10:48
  • $\begingroup$ What exactly do you mean by "scrolling state"? If you look here you'll notice we have "MouseMoved" and "MouseDragged". If you mean a mouse-wheel scroll on Windows I'm not sure how to get that. $\endgroup$
    – b3m2a1
    Commented Mar 10, 2017 at 16:12
  • $\begingroup$ I mean the mouse-wheel scroll, sorry for the confusion...... $\endgroup$
    – Wjx
    Commented Mar 10, 2017 at 16:17

4 Answers 4

14
$\begingroup$

Following detects backspace on Mac:

SetOptions[EvaluationNotebook[],NotebookEventActions->
     {{"KeyDown","\.08"}:>Print["triggered"]}]

This code can be helpful for finding out different codes for non-standard keys.

SetOptions[EvaluationNotebook[],NotebookEventActions->
     {"KeyDown":>Print[FullForm@CurrentValue["EventKey"]]}]
$\endgroup$
4
  • 1
    $\begingroup$ (+1) {"KeyDown","\.08"} works also on Windows. But {"KeyDown":>Print[FullForm@CurrentValue["EventKey"]]} doesn't catch Backspace. $\endgroup$ Commented Mar 10, 2017 at 19:08
  • $\begingroup$ @AlexeyPopkov are you saying that {"KeyDown","\.08"} , but {"KeyDown":>Print[FullForm@CurrentValue["EventKey"]]} doesn't print "\.008"??!! Does it catch ESC? $\endgroup$
    – BlacKow
    Commented Mar 10, 2017 at 19:39
  • $\begingroup$ Oh fascinating! I guess I should have found this from: here. It prints something invisible and I assumed it was a blank cell. I will have to back and figure out what all of these do. $\endgroup$
    – b3m2a1
    Commented Mar 11, 2017 at 1:09
  • 1
    $\begingroup$ @BlacKow Yes, {"KeyDown":>Print[FullForm@CurrentValue["EventKey"]]} prints None when I press Backspace or ESC. I'm on Windows 7 x64, checked with versions 8.0.4, 10.4.1 and 11.0.1. $\endgroup$ Commented Mar 11, 2017 at 4:33
7
$\begingroup$

@BlacKow's answer works well on Mac, but Mathematica cannot handle special keys (s.a. F2 or PAUSE) on Windows. Thus,

Here is a Windows workaround:

The idea is to create a dynamic link library that runs in a separate thread, as explained here. In this separate thread, the keyboard status is continuously tested. Once the specified key is pressed, an event is raised to the MMA kernel. The following code requires a working C-compiler on a Windows PC:

Needs["CCompilerDriver`GenericCCompiler`"];

keyListen[keycode_, action_] := With[{
    libMain = First@FileNames["async-examples-libmain.c", {$InstallationDirectory}, Infinity],
    src = "
      #include <stdlib.h>
      #include \"WolframLibrary.h\"
      #include \"WolframCompileLibrary.h\"
      #include \"WolframStreamsLibrary.h\"
      #include \"windows.h\"                    // for GetAsyncKeyState()

      /**********************************************************/
      typedef struct keyTrackerArgs_st
      {
          WolframIOLibrary_Functions ioLibrary;
          mint key;
      }* keyTrackerArgs;

      static void key_tracker(mint asyncObjID, void* vtarg)
      {
          keyTrackerArgs targ = (keyTrackerArgs)vtarg;
          WolframIOLibrary_Functions ioLibrary = targ->ioLibrary;
          const mint key = targ->key;
          free(targ);

          while(ioLibrary->asynchronousTaskAliveQ(asyncObjID))
          {
              if(GetAsyncKeyState(key) & 0x0001) {
                  ioLibrary->raiseAsyncEvent(asyncObjID, \"KeyPress\", NULL);
              }
          }
      }

      DLLEXPORT int start_key_tracker(WolframLibraryData libData,
          mint Argc, MArgument *Args, MArgument Res) 
      {
          mint asyncObjID;
          WolframIOLibrary_Functions ioLibrary = libData->ioLibraryFunctions;
          keyTrackerArgs threadArg = (keyTrackerArgs)malloc(sizeof(struct keyTrackerArgs_st));

          if(Argc != 1)
              return LIBRARY_FUNCTION_ERROR;

          threadArg->ioLibrary = ioLibrary;
          threadArg->key = MArgument_getInteger(Args[0]);
          asyncObjID = ioLibrary->createAsynchronousTaskWithThread(key_tracker, threadArg);
          MArgument_setInteger(Res, asyncObjID);

          return LIBRARY_NO_ERROR;
      }
      "
  },
  Module[{srcFile},
    srcFile = CreateFile["src.c"];
    WriteString[srcFile, src];
    Close[srcFile];
    lib = CreateLibrary[{srcFile, libMain}, "keyLib", "IncludeDirectories" -> {DirectoryName[libMain]}];
    DeleteFile[srcFile];
    keyListener = LibraryFunctionLoad[lib, "start_key_tracker", {Integer}, Integer];
    Internal`CreateAsynchronousTask[keyListener, {keycode}, action];
  ]
]

Let's say we want to make a sound (Beep[]) every time the "PAUSE" key is hit. Then we evaluate

keyListen[19, Beep[] &]

where 19 is the code for "VK_PAUSE". Here are all the Windows key codes, in case you need them:

virtualKeyList = {"VK_LBUTTON" -> 1, "VK_RBUTTON" -> 2, 
   "VK_CANCEL" -> 3, "VK_MBUTTON" -> 4, "VK_XBUTTON1" -> 5, 
   "VK_XBUTTON2" -> 6, "VK_BACK" -> 8, "VK_TAB" -> 9, 
   "VK_CLEAR" -> 12, "VK_RETURN" -> 13, "VK_SHIFT" -> 16, 
   "VK_CONTROL" -> 17, "VK_MENU" -> 18, "VK_PAUSE" -> 19, 
   "VK_CAPITAL" -> 20, "VK_KANA" -> 21, "VK_HANGUEL" -> 21, 
   "VK_HANGUL" -> 21, "VK_JUNJA" -> 23, "VK_FINAL" -> 24, 
   "VK_HANJA" -> 25, "VK_KANJI" -> 25, "VK_ESCAPE" -> 27, 
   "VK_CONVERT" -> 28, "VK_NONCONVERT" -> 29, "VK_ACCEPT" -> 30, 
   "VK_MODECHANGE" -> 31, "VK_SPACE" -> 32, "VK_PRIOR" -> 33, 
   "VK_NEXT" -> 34, "VK_END" -> 35, "VK_HOME" -> 36, "VK_LEFT" -> 37, 
   "VK_UP" -> 38, "VK_RIGHT" -> 39, "VK_DOWN" -> 40, 
   "VK_SELECT" -> 41, "VK_PRINT" -> 42, "VK_EXECUTE" -> 43, 
   "VK_SNAPSHOT" -> 44, "VK_INSERT" -> 45, "VK_DELETE" -> 46, 
   "VK_HELP" -> 47, "VK_LWIN" -> 91, "VK_RWIN" -> 92, "VK_APPS" -> 93,
    "VK_SLEEP" -> 95, "VK_NUMPAD0" -> 96, "VK_NUMPAD1" -> 97, 
   "VK_NUMPAD2" -> 98, "VK_NUMPAD3" -> 99, "VK_NUMPAD4" -> 100, 
   "VK_NUMPAD5" -> 101, "VK_NUMPAD6" -> 102, "VK_NUMPAD7" -> 103, 
   "VK_NUMPAD8" -> 104, "VK_NUMPAD9" -> 105, "VK_MULTIPLY" -> 106, 
   "VK_ADD" -> 107, "VK_SEPARATOR" -> 108, "VK_SUBTRACT" -> 109, 
   "VK_DECIMAL" -> 110, "VK_DIVIDE" -> 111, "VK_F1" -> 112, 
   "VK_F2" -> 113, "VK_F3" -> 114, "VK_F4" -> 115, "VK_F5" -> 116, 
   "VK_F6" -> 117, "VK_F7" -> 118, "VK_F8" -> 119, "VK_F9" -> 120, 
   "VK_F10" -> 121, "VK_F11" -> 122, "VK_F12" -> 123, "VK_F13" -> 124,
    "VK_F14" -> 125, "VK_F15" -> 126, "VK_F16" -> 127, 
   "VK_F17" -> 128, "VK_F18" -> 129, "VK_F19" -> 130, "VK_F20" -> 131,
    "VK_F21" -> 132, "VK_F22" -> 133, "VK_F23" -> 134, 
   "VK_F24" -> 135, "VK_NUMLOCK" -> 144, "VK_SCROLL" -> 145, 
   "VK_OEM_PLUS" -> 187, "VK_OEM_COMMA" -> 188, "VK_OEM_MINUS" -> 189,
    "VK_OEM_PERIOD" -> 190, "VK_OEM_2" -> 191, "VK_OEM_5" -> 220, 
   "VK_OEM_8" -> 223, "VK_PACKET" -> 231};
$\endgroup$
5
$\begingroup$

So after BlacKow's insight I found more of these by playing with this:

EventHandler[
 Panel["", ImageSize -> 100],
 "KeyDown" :>
  Print[Row@{FullForm@CurrentValue@"EventKey", " pressed"}]
 ]

A few more points of interest (this is potentially Mac specific as I am a Mac user):

"\[RawEscape]" is the escape key

"\.1c" is "LeftArrowKeyDown"

"\.1e" is "UpArrowKeyDown"

"\.1d" is "RightArrowKeyDown"

"\.1f" is "DownArrowKeyDown"

"\r" is "ReturnKeyDown"

"\[GreaterEqual]" is control-. (and various other special characters can be found as a control-key combo, but these can be easily figured out I think)

"\.10" is function-(any function key not bound)

Hopefully someone can extend this further and to Windows / Linux? Useful collection of keys to have out there.

$\endgroup$
2
  • $\begingroup$ I don't have ColoredPanel in version 11.0.1. What is this? $\endgroup$ Commented Mar 11, 2017 at 4:36
  • $\begingroup$ Oops. That's something I wrote to generate a panel with that sort of rounded background like in the docked cells of the documentation notebooks (I actually pulled the appearance from there then tweaked). I'll change that to just Panel in a bit. $\endgroup$
    – b3m2a1
    Commented Mar 11, 2017 at 5:57
4
$\begingroup$

About the issue of capturing inputs from the mouse scroll wheel: I recently found a way to do this after a suggestion by John Fultz. It's a pretty dumb hack, but it works and it seems to be the only way to do it.

The main idea is that there is one avenue for capturing something directly affected by the mouse wheel: the scroll bar in a Pane can be made dynamic with ScrollPosition -> Dynamic[...]. We can abuse this as follows:

DynamicModule[{x = {0.1, 0.}, font = 8},
 Pane[
  Pane[
   Style["Hello world", FontSize -> Dynamic[font]],
   ImageSize -> {Full, 1100}, AppearanceElements -> None, 
   Scrollbars -> None],
  ImageSize -> {Full, 1000},
  AppearanceElements -> None
  Scrollbars -> {False, True},
  ScrollPosition -> Dynamic[x,
    Function[
     If[#[[2]] >= x[[2]],
      font--,
      font++
     ];
     x[[2]] = Mod[x[[2]] + 0.001, 0.1, 0.1];
    ]
   ]
  ]
]

The inner Pane is bigger than the outer one to ensure that the outer Pane needs to show a scrollbar. The outer scrollbar is then used to capture scroll events. It's necessary keep the scrollbar away from the two most extreme positions and it also needs to move slightly to prevent outer scrollbars (like the one from the notebook) from scrolling. That's why the Function contains a line that slightly moves the position of the scrollbar and loops it around.

To hide the scroll bar, you can put another, slightly slimmer, Pane around the whole thing, or you can make the scrollbar drop out of the existing Pane with something like:

ImageSize -> {Dynamic[CurrentValue[WindowSize][[1]] - 100], 1000}

instead of ImageSize -> Full.

$\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.