By Alvin Alexander. Last updated: July 10, 2017
If you ever wanted to get access to global operating system keystrokes from a Java or Scala Swing application, you can do it with the jnativehook library. Here’s a short demo:
Here’s the full Scala source code for the demo. I put a few comments in the code to highlight the important areas:
package com.alvinalexander.mackeystrokes import scala.swing._ import scala.swing.event.EditDone import scala.swing.event.WindowActivated import org.jnativehook.GlobalScreen import org.jnativehook.NativeHookException import org.jnativehook.keyboard.NativeKeyEvent import org.jnativehook.keyboard.NativeKeyListener import javax.swing.SwingUtilities import java.awt.Graphics import javax.swing.JPanel import java.awt.Color // (1) extend NativeKeyListener object MacKeystrokes extends SimpleSwingApplication with NativeKeyListener { val charPanel = new CharPanel { setPreferredSize(new Dimension(400, 300)) } def top = new MainFrame { title = "Mac Keystrokes" peer.getContentPane.add(charPanel) peer.setLocationRelativeTo(null) peer.setBackground(Color.BLACK) } // (2) GlobalScreen.registerNativeHook try { GlobalScreen.registerNativeHook } catch { case e: NativeHookException => System.err.println("There was a problem registering the native hook.") System.err.println(e.getMessage) System.exit(1) } // (3) implement nativeKeyPressed, nativeKeyReleased, and nativeKeyTyped def nativeKeyPressed(e: NativeKeyEvent) { val key = NativeKeyEvent.getKeyText(e.getKeyCode) println("Key Pressed: " + NativeKeyEvent.getKeyText(e.getKeyCode)) SwingUtilities.invokeLater(new Runnable { def run { charPanel.setChar(key.toCharArray) } }) if (e.getKeyCode == NativeKeyEvent.VK_ESCAPE) { GlobalScreen.unregisterNativeHook } } def nativeKeyReleased(e: NativeKeyEvent) {} def nativeKeyTyped(e: NativeKeyEvent) {} // (4) do this GlobalScreen.getInstance.addNativeKeyListener(this) } import scala.util.Random import java.awt.Font class CharPanel extends JPanel { private val colors = Array(Color.BLUE, Color.CYAN, Color.DARK_GRAY, Color.GREEN, Color.LIGHT_GRAY, Color.MAGENTA, Color.ORANGE, Color.RED, Color.YELLOW) private var chars = Array('a') private val r = new Random var i = 10 def setChar(charArray: Array[Char]) { this.chars = charArray i += 15 repaint() } override def paintComponent(g: Graphics) { val font = new Font(Font.SANS_SERIF, Font.PLAIN, 60) val color = colors(r.nextInt(colors.length)) g.setFont(font) g.setColor(color) g.clearRect(0, 0, 400, 300) g.drawChars(chars, 0, 1, r.nextInt(320), r.nextInt(220)+20) } }
I needed to be able to do something like this for SARAH, so I dug around until I found this solution, which works great.
To get this to work, you have to enable “access for assistive devices”, as explained in this apple.com article.
Again, I did remarkably little here; the secret sauce is in the jnativehook library. See their examples and docs for more information.