項目需要在Web項目中獲取掃描槍掃描的內容,項目是Java Web項目,最后部署在Linux系統中的。
拿到掃描槍后,連接在自己的Windows系統上試了下,插上后,不需要裝任何驅動,只要有個文本框,就能將掃描到的內容輸入到文本框里。反復測試后發現,當前窗口的焦點在哪里,掃描到的內容就顯示在哪里。
那么現在遇到一個問題,項目以后要跑起來,是沒有任何窗口的,是運行在后台的,那怎么拿到掃描槍輸入的內容呢?
先按照盒子上的廠家名稱找到官網,在官網上查到了技術支持電話,結果人家說人家也不知道,他只是硬件層面的技術支持,如何用編程語言拿到掃描到的東西,他不清楚。但是他說他們還有串口類型的掃描槍,可支持軟件編程。掛完電話看了下我們的工控機,是沒有串口的,只有USB接口。但是網上搜了一下,有用Java掃描系統的串口,然后根據串口號獲得串口輸入進來的東西,應該不難。既然我們沒有串口,我也沒深入研究。
然后就以Java 掃描槍為關鍵字搜索相關資料,以前還真有人做過這個,在開源中國找到一個前輩做的項目,是一個條形碼掃描槍,人家實現了。代碼那過來研究了一番,大致明白了。
掃描儀其實說白了對電腦來說就是個鍵盤, 掃描槍將掃描得到的內容解析,然后模擬鍵盤,一個一個敲入到電腦中,最后按一下回車鍵!怪不得焦點在哪個窗口就輸入到哪個窗口呢。
那就又遇到一個問題,Java代碼運行在Jvm虛擬機內,掃描槍或鍵盤輸入的東西,只有操作系統知道,Jvm虛擬機如何知道呢?那就是JNI編程,通過寫C/C++代碼,監聽操作系統的的輸入流,然后通過JNI調用。雖然我不會JNI,也不會C/C++,
但幸運的是,SUN公司已經實現了這個代碼,弄出一個叫JNA的東西(Java Native Access),給Java提供了訪問操作系統鍵盤鼠標的能力。
然后將人家的代碼完整拷貝,想跑一下,結果沒jar包,一直報錯,根據包名百度,在maven倉庫中找相關jar包,(想找官方的jar包和一些文檔,無奈,因為被收購的原因,有些鏈接已經掛了,找不到哇)找到幾個,放進去,編譯不報錯了,運行一直報錯,換了好幾個jar包,還是不行,真是可郁悶了。
最后在一個國內的倉庫網站找到一個清晰的分類,下載里面的大分類下面的一組jar包,運行成功了。網址是www.mvnjar.com。
運行的時候,人家的是條形碼掃描槍,只有0到9是個數字,我們的是二維碼掃描槍,輸入的文字中有字母數字符合,不夠用啊,只能自己再開發了。
當自己要實現字母鍵的時候,才發現,字母不是那么好實現的,因為有大小寫區分,還有!@#$%^這些字符需要按住shift鍵輸入。JNA提供的鈎子函數,我們能拿到的只有鍵盤的鍵控代碼,頓時覺得頭大了。
分析鍵控代碼值發現,監控代碼值跟ASCII代碼值中的字母鍵完全匹配,數字鍵差一些,字符只有極個別有點規律,於是自己按照鍵控代碼和ASCII碼對照了一遍,完整實現了所有字母和數字和字符的輸入。
注意,因為二維碼掃描槍只能輸入大小寫字母、數字、特殊字符,所以其他的鍵我沒管,類似於Ctrl、FN、Alt、F快捷鍵等。
下面貼上代碼,感覺只是為了實現功能,代碼寫得很Low。
運行需要的jar包在倉庫下,網址是:https://www.mvnjar.com/net.java.dev.jna/list.html,下載jna-5.2.0.jar和jna-platform-5.2.0.jar這兩個就可以。
import java.util.ArrayList; import java.util.List; import com.sun.jna.Pointer; import com.sun.jna.platform.win32.Kernel32; import com.sun.jna.platform.win32.User32; import com.sun.jna.platform.win32.WinDef.HMODULE; import com.sun.jna.platform.win32.WinDef.LPARAM; import com.sun.jna.platform.win32.WinDef.LRESULT; import com.sun.jna.platform.win32.WinDef.WPARAM; import com.sun.jna.platform.win32.WinUser; import com.sun.jna.platform.win32.WinUser.HHOOK; import com.sun.jna.platform.win32.WinUser.KBDLLHOOKSTRUCT; import com.sun.jna.platform.win32.WinUser.LowLevelKeyboardProc; import com.sun.jna.platform.win32.WinUser.MSG; public class WindowsKeybordListener { private static HHOOK hhk; private static LowLevelKeyboardProc keyboardHook; static List<Character> singleInput = new ArrayList<Character>(); private static String caseCode() { StringBuffer buffer = new StringBuffer(); for (Character i : singleInput) { buffer.append(i); } return buffer.toString(); } public static void main(String[] args) { final User32 lib = User32.INSTANCE; HMODULE hMod = Kernel32.INSTANCE.GetModuleHandle(null); keyboardHook = new LowLevelKeyboardProc() { boolean isShiftUp = false; @Override public LRESULT callback(int nCode, WPARAM wParam, KBDLLHOOKSTRUCT info) { if (nCode >= 0) { switch (wParam.intValue()) { case WinUser.WM_KEYDOWN:// 只監聽鍵盤按下 //按下回車鍵,生成完整的字符串,並清空list if(info.vkCode==13) { String text = caseCode(); System.out.println(text); singleInput.clear(); break; } //按下的是shift鍵時,標記一下 if (info.vkCode == 160) { isShiftUp = true; } if (!isShiftUp) { if (info.vkCode >= 65 && info.vkCode <= 90) {//字母鍵 singleInput.add((char) (info.vkCode + 32)); } else if (info.vkCode >= 219 && info.vkCode <= 221) {//[\] singleInput.add((char) (info.vkCode - 128)); } else if (info.vkCode >= 188 && info.vkCode <= 191) {//,-./ singleInput.add((char) (info.vkCode - 144)); } else if (info.vkCode >= 48 && info.vkCode <= 57) {//數字鍵 singleInput.add((char) info.vkCode); } if (info.vkCode == 186) { singleInput.add(';'); } if (info.vkCode == 187) { singleInput.add('='); } if (info.vkCode == 192) { singleInput.add('`'); } if (info.vkCode == 222) { singleInput.add('\''); } } else { //大寫字母 if (info.vkCode >= 65 && info.vkCode <= 90) { singleInput.add((char) info.vkCode ); } switch (info.vkCode) { case 186: singleInput.add(':'); break; case 187: singleInput.add('+'); break; case 188: singleInput.add('<'); break; case 189: singleInput.add('_'); break; case 190: singleInput.add('>'); break; case 191: singleInput.add('?'); break; case 192: singleInput.add('~'); break; case 219: singleInput.add('{'); break; case 220: singleInput.add('|'); break; case 221: singleInput.add('}'); break; case 222: singleInput.add('\"'); break; case 48: singleInput.add('!'); break; case 49: singleInput.add('@'); break; case 50: singleInput.add('#'); break; case 51: singleInput.add('$'); break; case 52: singleInput.add('%'); break; case 53: singleInput.add('^'); break; case 54: singleInput.add('&'); break; case 55: singleInput.add('*'); break; case 56: singleInput.add('('); break; case 57: singleInput.add(')'); break; } } break; case WinUser.WM_KEYUP:// 按鍵起來 if (info.vkCode == 160) { isShiftUp = false; } break; } } Pointer ptr = info.getPointer(); long peer = Pointer.nativeValue(ptr); return lib.CallNextHookEx(hhk, nCode, wParam, new LPARAM(peer)); } };hhk=lib.SetWindowsHookEx(WinUser.WH_KEYBOARD_LL,keyboardHook,hMod,0); // This bit never returns from GetMessage int result; MSG msg = new MSG();while((result=lib.GetMessage(msg,null,0,0))!=0) { if (result == -1) { // System.err.println("error in get message"); break; } else { // System.err.println("got message"); lib.TranslateMessage(msg); lib.DispatchMessage(msg); } }lib.UnhookWindowsHookEx(hhk); } }
完整實現后,想在Linux下運行,需要改代碼,缺發現jar包中的Unix下面沒有像Windows一樣有很多類,想找官方文檔也找不到,苦逼啊,按理來說有Unix和Mac相關的類,應該是全平台支持的,就是不知道咋調用。
又在網上各種搜,最后整理了一下,有如下結論:
有個jintellitype項目,這個項目比較簡單,只能注冊熱鍵,比如設定一個Ctrl+S可以完成的功能,項目運行在后台,前台按這個組合鍵也可以執行自己設定的功能,這個項目只能在Windows上運行。
然后,有外國人,從上面那個項目中過得靈感,開發了Linux下的一個項目jxgrabkey,能注冊一些熱鍵在X11窗口中,也是使用C++的鈎子函數,通過JNI調用。
不過上面兩個項目都是只為注冊快捷鍵使用的,不適合用於我們這種需要輸入大量文字的模塊中。
現在還在找Linux下合適的監聽器,等實現了再開一篇帖子寫一下。