From 7a267a6d9ade023b7659d854bd540f0e5f81b40d Mon Sep 17 00:00:00 2001 From: Edward Jakubowski Date: Mon, 21 Apr 2014 19:45:34 -0400 Subject: [PATCH] Added Global Keyboard Hook for refreshing using Ctrl+Shift+3 Created a Jna Global Keyboard Hook class and added hot key for refreshing the windows xml (Ctrl + Shift + 3). Fixed cancel button to dispose window properly. --- src/org/synthuse/Api.java | 1 + src/org/synthuse/KeyboardHook.java | 199 +++++++++++++++++++++ src/org/synthuse/MenuInfo.java | 7 + src/org/synthuse/SynthuseDlg.java | 22 ++- src/org/synthuse/WinPtr.java | 7 + src/org/synthuse/WindowInfo.java | 7 + src/org/synthuse/WindowsEnumeratedXml.java | 7 +- src/org/synthuse/test/WinApiTest.java | 7 - 8 files changed, 247 insertions(+), 10 deletions(-) create mode 100644 src/org/synthuse/KeyboardHook.java diff --git a/src/org/synthuse/Api.java b/src/org/synthuse/Api.java index f9d93ff..b29180f 100644 --- a/src/org/synthuse/Api.java +++ b/src/org/synthuse/Api.java @@ -115,6 +115,7 @@ public class Api { public static int VK_RMENU = 0xA5; public static int WM_COMMAND = 0x111; + public static int MN_GETHMENU = 0x01E1; public static int CWP_ALL = 0x0000; // Does not skip any child windows diff --git a/src/org/synthuse/KeyboardHook.java b/src/org/synthuse/KeyboardHook.java new file mode 100644 index 0000000..397ed09 --- /dev/null +++ b/src/org/synthuse/KeyboardHook.java @@ -0,0 +1,199 @@ +/* + * Copyright 2014, Synthuse.org + * Released under the Apache Version 2.0 License. + * + * last modified by ejakubowski7@gmail.com +*/ + + +package org.synthuse; + +//import java.awt.event.KeyEvent; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.sun.jna.*; +import com.sun.jna.platform.win32.WinUser.*; +import com.sun.jna.platform.win32.WinDef.*; +import com.sun.jna.win32.W32APIOptions; + +public class KeyboardHook implements Runnable{ + + + // Keyboard event class, interface, and array list + public static class TargetKeyPress { + int targetKeyCode; + boolean withShift, withCtrl, withAlt; + public TargetKeyPress (int targetKeyCode) { + this.targetKeyCode = targetKeyCode; + this.withShift = false; + this.withCtrl = false; + this.withAlt = false; + } + public TargetKeyPress (int targetKeyCode, boolean withShift, boolean withCtrl, boolean withAlt) { + this.targetKeyCode = targetKeyCode; + this.withShift = withShift; + this.withCtrl = withCtrl; + this.withAlt = withAlt; + } + } + + public static List targetList = Collections.synchronizedList(new ArrayList());// all keys we want to throw events on + + public static interface KeyboardEvents { + void keyPressed(TargetKeyPress target); + } + public KeyboardEvents events = new KeyboardEvents() { + public void keyPressed(TargetKeyPress target) { + //System.out.println("target key pressed: " + target.targetKeyCode); + } + }; + + // JNA constants and functions + public static final int WH_KEYBOARD_LL = 13; + //Modifier key vkCode constants + public static final int VK_SHIFT = 0x10; + public static final int VK_CONTROL = 0x11; + public static final int VK_MENU = 0x12; + public static final int VK_CAPITAL = 0x14; + + public static HHOOK hHook = null; + public static LowLevelKeyboardProc lpfn; + public static volatile boolean quit = false; + + public interface User32 extends W32APIOptions { + User32 instance = (User32) Native.loadLibrary("user32", User32.class, DEFAULT_OPTIONS); + + LRESULT LowLevelKeyboardProc(int nCode,WPARAM wParam,LPARAM lParam); + HHOOK SetWindowsHookEx(int idHook, HOOKPROC lpfn, HMODULE hMod, int dwThreadId); + LRESULT CallNextHookEx(HHOOK idHook, int nCode, WPARAM wParam, LPARAM lParam); + LRESULT CallNextHookEx(HHOOK idHook, int nCode, WPARAM wParam, Pointer lParam); + boolean PeekMessage(MSG lpMsg, HWND hWnd, int wMsgFilterMin, int wMsgFilterMax, int wRemoveMsg); + boolean UnhookWindowsHookEx(HHOOK idHook); + short GetKeyState(int nVirtKey); + + //public static interface HOOKPROC extends StdCallCallback { + // LRESULT callback(int nCode, WPARAM wParam, KBDLLHOOKSTRUCT lParam); + //} + } + public interface Kernel32 extends W32APIOptions { + Kernel32 instance = (Kernel32) Native.loadLibrary("kernel32", Kernel32.class, DEFAULT_OPTIONS); + + HMODULE GetModuleHandle(String name); + } + + // Create Global Windows Keyboard hook and wait until quit == true + public void createGlobalKeyboardHook() { + if (hHook != null) + return; //hook already running don't add anymore + HMODULE hMod = Kernel32.instance.GetModuleHandle(null); + HOOKPROC lpfn = new HOOKPROC() { + @SuppressWarnings("unused") + public LRESULT callback(int nCode, WPARAM wParam, KBDLLHOOKSTRUCT lParam) { + //System.out.println("here " + lParam.vkCode); + TargetKeyPress target = getTargetKeyPressed(lParam.vkCode); //find if this is a target key pressed + if (target != null) + events.keyPressed(target); + //if (lParam.vkCode == 87) //w + // quit = true; + return User32.instance.CallNextHookEx(hHook, nCode, wParam, lParam.getPointer()); + } + }; + + hHook = User32.instance.SetWindowsHookEx(WH_KEYBOARD_LL, lpfn, hMod, 0); + if (hHook == null) + return; + MSG msg = new MSG(); + try { + while (!quit) { + User32.instance.PeekMessage(msg, null, 0, 0, 0); + Thread.sleep(10); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + //unhook the Global Windows Keyboard hook + public void unhook() { + if (hHook == null) + return; + if (!User32.instance.UnhookWindowsHookEx(hHook)) + System.out.println("Failed to unhook"); + //System.out.println("Unhooked"); + hHook = null; + } + + //stops Keyboard hook and causes the unhook command to be called + public static void stopGlobalKeyboardHook() { + quit = true; + } + + // search target keyboard event list for a match and return it otherwise return null if no match + public TargetKeyPress getTargetKeyPressed(int keyCode) { + TargetKeyPress target = null; + for (TargetKeyPress tkp : KeyboardHook.targetList) { + if (tkp.targetKeyCode != keyCode) + continue; + if (!tkp.withShift || ((User32.instance.GetKeyState(VK_SHIFT) & 0x8000) != 0)) { + if (!tkp.withCtrl || ((User32.instance.GetKeyState(VK_CONTROL) & 0x8000) != 0)) { + if (!tkp.withAlt || ((User32.instance.GetKeyState(VK_MENU) & 0x8000) != 0)) { + return tkp; + } + } + } + } + return target; + } + + // add more target keys to watch for + public static void addKeyEvent(int targetKeyCode, boolean withShift, boolean withCtrl, boolean withAlt) { + KeyboardHook.targetList.add(new TargetKeyPress(targetKeyCode, withShift, withCtrl, withAlt)); + } + // add more target keys to watch for + public static void addKeyEvent(int targetKeyCode) { + KeyboardHook.targetList.add(new TargetKeyPress(targetKeyCode)); + } + + @Override + public void run() { + createGlobalKeyboardHook(); + unhook();//wait for quit == true then unhook + } + + public KeyboardHook() { + } + + public KeyboardHook(KeyboardEvents events) { + this.events = events; + } + + public static void StartGlobalKeyboardHookThreaded(KeyboardEvents events) { + Thread t = new Thread(new KeyboardHook(events)); + t.start(); + } + + /* + // testing + public static void main(String[] args) throws Exception { + //add target keys + KeyboardHook.addKeyEvent(KeyEvent.VK_3, true, true, false); + KeyboardHook.addKeyEvent(KeyEvent.VK_5, false, true, false); + KeyboardHook.addKeyEvent(KeyEvent.VK_Q); + + //add global hook and event + KeyboardHook.StartGlobalKeyboardHookThreaded(new KeyboardHook.KeyboardEvents() { + @Override + public void keyPressed(KeyboardHook.TargetKeyPress target) { + System.out.println("target key pressed " + target.targetKeyCode); + if (target.targetKeyCode == KeyEvent.VK_Q){ // if Q was pressed then unhook + KeyboardHook.stopGlobalKeyboardHook(); + System.out.println("unhooking"); + } + } + }); + } + */ + +} diff --git a/src/org/synthuse/MenuInfo.java b/src/org/synthuse/MenuInfo.java index ed5aeb7..8ca3217 100644 --- a/src/org/synthuse/MenuInfo.java +++ b/src/org/synthuse/MenuInfo.java @@ -1,3 +1,10 @@ +/* + * Copyright 2014, Synthuse.org + * Released under the Apache Version 2.0 License. + * + * last modified by ejakubowski7@gmail.com +*/ + package org.synthuse; import java.math.BigInteger; diff --git a/src/org/synthuse/SynthuseDlg.java b/src/org/synthuse/SynthuseDlg.java index 674bd87..180f4e7 100644 --- a/src/org/synthuse/SynthuseDlg.java +++ b/src/org/synthuse/SynthuseDlg.java @@ -321,7 +321,7 @@ public class SynthuseDlg extends JFrame { btnCancel.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent arg0) { dialogResult = ""; - SynthuseDlg.this.dispose(); + SynthuseDlg.this.disposeWindow(); } }); btnCancel.setIcon(new ImageIcon(SynthuseDlg.class.getResource(RES_STR_CANCEL_IMG))); @@ -383,12 +383,24 @@ public class SynthuseDlg extends JFrame { this.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent arg0) { - + KeyboardHook.stopGlobalKeyboardHook(); //stop keyboard hook config.save(); SynthuseDlg.this.dispose(); // force app to close } }); + KeyboardHook.addKeyEvent(KeyEvent.VK_3, true, true, false);// refresh xml when CTRL+SHIFT+3 is pressed + //add global hook and event + KeyboardHook.StartGlobalKeyboardHookThreaded(new KeyboardHook.KeyboardEvents() { + @Override + public void keyPressed(KeyboardHook.TargetKeyPress target) { + //System.out.println("target key pressed " + target.targetKeyCode); + if (target.targetKeyCode == KeyEvent.VK_3){ + btnRefresh.doClick(); + } + } + }); + btnRefresh.doClick(); refreshDatabinding(); } @@ -450,4 +462,10 @@ public class SynthuseDlg extends JFrame { XpathManager.buildXpathStatementThreaded(hwnd, runtimeId, textPane, xpathEvents); } } + + public void disposeWindow() + { + WindowEvent closingEvent = new WindowEvent(this, WindowEvent.WINDOW_CLOSING); + Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(closingEvent); + } } diff --git a/src/org/synthuse/WinPtr.java b/src/org/synthuse/WinPtr.java index 03fe5d2..c490416 100644 --- a/src/org/synthuse/WinPtr.java +++ b/src/org/synthuse/WinPtr.java @@ -1,3 +1,10 @@ +/* + * Copyright 2014, Synthuse.org + * Released under the Apache Version 2.0 License. + * + * last modified by ejakubowski7@gmail.com +*/ + package org.synthuse; import com.sun.jna.platform.win32.WinDef.HWND; diff --git a/src/org/synthuse/WindowInfo.java b/src/org/synthuse/WindowInfo.java index 3a3b635..3c47989 100644 --- a/src/org/synthuse/WindowInfo.java +++ b/src/org/synthuse/WindowInfo.java @@ -91,6 +91,7 @@ public class WindowInfo { extra = new LinkedHashMap(); extra.put("tvCount", tvCount.intValue() + ""); } + //check if window has a menu HMENU hmenu = Api.User32.instance.GetMenu(hWnd); if (hmenu != null) { //menu item count @@ -99,6 +100,12 @@ public class WindowInfo { this.menus = menuCount; this.menu = hmenu; } + else + { + LRESULT result = Api.User32.instance.PostMessage(hWnd, Api.MN_GETHMENU, new WPARAM(0), new LPARAM()); + if (result.longValue() != 1) + System.out.println("MN_GETHMENU: " + result.longValue()); + } } if (isChild) { diff --git a/src/org/synthuse/WindowsEnumeratedXml.java b/src/org/synthuse/WindowsEnumeratedXml.java index e7588d9..8d9d85e 100644 --- a/src/org/synthuse/WindowsEnumeratedXml.java +++ b/src/org/synthuse/WindowsEnumeratedXml.java @@ -16,6 +16,7 @@ import java.util.Date; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; import javax.swing.JLabel; import javax.swing.JTextPane; @@ -45,7 +46,7 @@ import com.sun.jna.platform.win32.WinDef.HWND; public class WindowsEnumeratedXml implements Runnable{ public static Exception lastException = null; - + public static AtomicBoolean enumeratingXmlFlag = new AtomicBoolean(false); public JTextPane outputPane = null; public JLabel lblStatus = null; public WindowsEnumeratedXml() { @@ -64,9 +65,13 @@ public class WindowsEnumeratedXml implements Runnable{ outputPane.setCaretPosition(0); double seconds = ((double)(System.nanoTime() - startTime) / 1000000000); lblStatus.setText("Windows Enumerated Xml loaded in " + new DecimalFormat("#.###").format(seconds) + " seconds"); + enumeratingXmlFlag.set(false); } public static void getXmlThreaded(JTextPane outputPane, JLabel lblStatus) { + if (enumeratingXmlFlag.get()) + return; //something is already running + enumeratingXmlFlag.set(true); //if we don't do this the multiple xml's could get combined on the textpane Thread t = new Thread(new WindowsEnumeratedXml(outputPane, lblStatus)); t.start(); } diff --git a/src/org/synthuse/test/WinApiTest.java b/src/org/synthuse/test/WinApiTest.java index 9384b4c..fa0c2c5 100644 --- a/src/org/synthuse/test/WinApiTest.java +++ b/src/org/synthuse/test/WinApiTest.java @@ -88,13 +88,6 @@ public class WinApiTest { String menuTxt = api.GetMenuItemText(hmenu, m); System.out.println("Menu Text: " + menuTxt); } - /* - if (menuCount == 5) { - HMENU smenu = Api.User32.instance.GetSubMenu(hmenu, 0); - boolean result = Api.User32.instance.TrackPopupMenu(smenu, 0, 1, 1, 0, hWnd, 0); - System.out.println("TrackPopupMenu: " + result); - System.out.println("last error: " + Api.Kernel32.instance.GetLastError()); - }*/ } }