项目需要在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 ListsingleInput = new ArrayList (); 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下合适的监听器,等实现了再开一篇帖子写一下。