9
$\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$
5
  • $\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 Mar 10 '17 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 Mar 10 '17 at 6:06
  • $\begingroup$ @Liam I'm using windows, but unfortunately I know nothing about .NET, could you show some way? Thanks! $\endgroup$ – Wjx Mar 10 '17 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 Mar 10 '17 at 16:12
  • $\begingroup$ I mean the mouse-wheel scroll, sorry for the confusion...... $\endgroup$ – Wjx Mar 10 '17 at 16:17
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$ – Alexey Popkov Mar 10 '17 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 Mar 10 '17 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 Mar 11 '17 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$ – Alexey Popkov Mar 11 '17 at 4:33
6
$\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$ – Alexey Popkov Mar 11 '17 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 Mar 11 '17 at 5:57
3
$\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, privacy policy and cookie policy

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