java监听全局组合键

发布时间 2023-12-11 09:32:48作者: Codorld

1. jintellitype

  • pom
    <!-- 不能注册多个组合键比如alt+abc -->
    <!-- https://mvnrepository.com/artifact/com.melloware/jintellitype -->
    <dependency>
    	<groupId>com.melloware</groupId>
    	<artifactId>jintellitype</artifactId>
    	<version>1.4.1</version>
    </dependency>
    
  • 代码
    package com.hotkey.app;
    
    import cn.hutool.core.date.DateUtil;
    import com.melloware.jintellitype.JIntellitype;
    
    public class IntelApplication {
    	public static void main(String[] args) {
    
    		// 添加全局热键
    		JIntellitype instance = JIntellitype.getInstance();
    		instance.registerHotKey(0, JIntellitype.MOD_ALT + JIntellitype.MOD_SHIFT, 'B');
    		instance.registerHotKey(2, 20, 'B');
    		instance.registerHotKey(1, "CTRL+SHIFT+X+V");
    		instance.registerHotKey(3, "CTRL+SHIFT+ALT+WIN+DELETE+B");
    
    		// 添加热键监听器
    		instance.addHotKeyListener(hotkey -> {
    			System.out.println(DateUtil.now() + " Hotkey pressed: " + hotkey);
    			// 在这里处理你的逻辑
    		});
    
    	}
    }
    
  • 缺点: 对于ctrl a b 按ctrl b也可以触发

2. jkeymaster

  • pom
    <!-- https://mvnrepository.com/artifact/com.github.tulskiy/jkeymaster -->
    <dependency>
    	<groupId>com.github.tulskiy</groupId>
    	<artifactId>jkeymaster</artifactId>
    	<version>1.3</version>
    </dependency>
    
    <!-- https://mvnrepository.com/artifact/net.java.dev.jna/jna -->
    <dependency>
    	<groupId>net.java.dev.jna</groupId>
    	<artifactId>jna</artifactId>
    	<version>5.12.1</version>
    </dependency>
    
  • 代码
    import com.tulskiy.keymaster.common.Provider;
    
    import javax.swing.*;
    import java.awt.event.InputEvent;
    import java.awt.event.KeyEvent;
    
    public class KeyApplication {
    
    	public static void main(String[] args) {
    		Provider provider = Provider.getCurrentProvider(true);
    
    		provider.register(
    				KeyStroke.getKeyStroke(KeyEvent.CTRL_MASK,
    						InputEvent.ALT_MASK),
    				x -> System.err.println(x));
    	}
    }
    
  • 缺点: 对于+ = [ ]特殊按键识别不到; 不能注册类似ctrl a b会报错

3. jnativehook(推荐)

  • pom
    <!-- https://mvnrepository.com/artifact/com.1stleg/jnativehook -->
    <dependency>
    	<groupId>com.1stleg</groupId>
    	<artifactId>jnativehook</artifactId>
    	<version>2.1.0</version>
    </dependency>
    
  • 代码
    import cn.hutool.core.collection.CollUtil;
    import org.jnativehook.GlobalScreen;
    import org.jnativehook.NativeHookException;
    import org.jnativehook.keyboard.NativeKeyEvent;
    import org.jnativehook.keyboard.NativeKeyListener;
    
    import javax.swing.*;
    import java.awt.*;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.LinkedHashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.concurrent.atomic.AtomicInteger;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    
    /**
     * 监听键盘和鼠标按键, 这个是强制顺序和不支持重复的
     * 如果要支持重复就得使用List保存快捷键, 并给每个编键去配置标志位, 或者快捷键数组使用对象数组
     * 如果要不要求顺序, 就按下时候直接标志位变成true即可
     *
     * @author codor
     * @date 2023/12/08 16:23
     * @see org.jnativehook.example.NativeHookDemo
     */
    public class NativeApplication extends JFrame implements NativeKeyListener {
    
    	// ----------------------------------------------------------------------------------------------------------------- 逻辑使用
    
    	/**
    	 * 快捷键组合
    	 * 名字也可以不写上去, 使用NativeKeyEvent.getKeyText(event.getKeyCode())获取
    	 */
    	private Map<Integer, String> hotKeys;
    
    	/**
    	 * 键编码的列表, 因为要保证顺序
    	 */
    	private List<Integer> codes;
    
    
    	/**
    	 * 快捷键是否按下标标志位
    	 */
    	private Map<Integer, Boolean> flags;
    
    	/**
    	 * 窗口是否展示出来了
    	 */
    	private boolean isWindowShow = false;
    
    	// ---------------------------------------------------------------------------------------------------------------- 界面使用
    
    	/**
    	 * 倒计时
    	 */
    	private Timer countdownTimer;
    
    	/**
    	 * 倒计时事件
    	 */
    	private final static Integer COUNT_DOWN_SECONDS = 10;
    
    
    	/**
    	 * 初始化标志位
    	 *
    	 * @see org.jnativehook.keyboard.NativeKeyEvent
    	 */
    	public void initHotKeys() {
    		this.hotKeys = new LinkedHashMap<>();
    		this.hotKeys.put(29, "Ctrl");
    		this.hotKeys.put(46, "C");
    		this.hotKeys.put(45, "X");
    		this.hotKeys.put(47, "V");
    
    		// 初始化键编码
    		this.codes = new ArrayList<>();
    		this.hotKeys.forEach((k, v) -> this.codes.add(k));
    
    		// 初始化标指位
    		this.initFlags();
    	}
    
    	/**
    	 * 初始化标志位
    	 */
    	public void initFlags() {
    		if (this.hotKeys == null) {
    			this.initHotKeys();
    		}
    		this.flags = new HashMap<>();
    		for (Integer code : this.codes) {
    			this.flags.put(code, false);
    		}
    	}
    
    	/**
    	 * 按下键时候, 强制要求顺序
    	 *
    	 * @param code 键编码
    	 * @return 是否全部按下了, true是, 反之false
    	 */
    	public boolean pressedKey(int code) {
    		// 如果未在内, 重置标志位
    		if (!this.codes.contains(code)) {
    			this.initFlags();
    			return false;
    		}
    
    		for (Integer k : this.codes) {
    			// 循环到 第一个非按下状态的就跳出循环或退出方法
    			if (!this.flags.get(k)) {
    				// 并且等于按下的键, 设置按下成功
    				if (k.equals(code)) {
    					this.flags.put(code, true);
    					break;
    				} else { // 不是不等于按下的, 说明按了后面的,
    					this.initFlags();
    					return false;
    				}
    			}
    		}
    
    		// 只有break时候到这里, 判断是否循环完, 也即code是否最后一个
    		return code == this.codes.get(this.codes.size() - 1);
    	}
    
    	/**
    	 * 松开键
    	 */
    	public void releaseKey(int code) {
    		this.flags.put(code, false);
    	}
    
    	public String getKeyTexts() {
    		if (CollUtil.isEmpty(this.codes)) {
    			return "";
    		}
    		StringBuilder resB = new StringBuilder(" ");
    		for (Integer code : this.codes) {
    			resB.append(NativeKeyEvent.getKeyText(code)).append(" ");
    		}
    		return resB.toString();
    	}
    
    	public void showWindow(String hotKeyTexts) {
    
    		setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
    		setUndecorated(true); // 窗口无装饰, 但是标题也会干掉
    
    		setTitle("倒计时窗口");
    		setLayout(new BorderLayout());
    
    		JLabel countdownLabel = new JLabel("倒计时: " + COUNT_DOWN_SECONDS + " 秒", JLabel.CENTER);
    		JButton cancelButton = new JButton("取消");
    
    		cancelButton.addActionListener(e -> {
    			// 点击取消按钮时的处理
    			if (countdownTimer != null) {
    				// 重置标志位
    				this.initFlags();
    				// 关闭窗口
    				this.closeWindow();
    			}
    		});
    
    		add(countdownLabel, BorderLayout.CENTER);
    		add(cancelButton, BorderLayout.SOUTH);
    
    		setSize(300, 150);
    		setLocationRelativeTo(null); // 将窗口置于屏幕中央
    		setVisible(true);
    
    		AtomicInteger second = new AtomicInteger(COUNT_DOWN_SECONDS);
    		countdownTimer = new Timer(1000, e -> {
    			second.getAndDecrement();
    
    			if (second.get() >= 0) {
    				countdownLabel.setText("倒计时: " + second.get() + " 秒");
    			} else {
    				// 关闭窗口
    				this.closeWindow();
    				// 触发成功后发送指令
    				// 指令发送成功后重置标志位
    				this.initFlags();
    			}
    		});
    
    		countdownTimer.start();
    	}
    
    	public void closeWindow() {
    		countdownTimer.stop();
    		dispose(); // 关闭窗口
    		setVisible(false); // 设置窗口不可见
    		getContentPane().removeAll(); // 移除所有组件
    		revalidate(); // 重新验证布局
    		repaint(); // 重绘窗口
    
    		// 关闭窗口时候重置
    		isWindowShow = false;
    	}
    
    	// ---------------------------------------------------------------------------------------------------------------- Override Method
    
    	@Override
    	public void nativeKeyTyped(NativeKeyEvent nativeKeyEvent) {
    	}
    
    	@Override
    	public void nativeKeyPressed(NativeKeyEvent event) {
    		boolean isHotKey = this.pressedKey(event.getKeyCode());
    		// 成功并且窗口未展示时候
    		if (isHotKey && !isWindowShow) {
    			System.err.println("触发快捷组合键成功...");
    			// 窗口标志位设置成功
    			isWindowShow = true;
    			this.showWindow(this.getKeyTexts());
    		}
    	}
    
    	@Override
    	public void nativeKeyReleased(NativeKeyEvent event) {
    		this.releaseKey(event.getKeyCode());
    	}
    
    	public static void main(String[] args) {
    		try {
    			// 设置日志级别,避免 JNativeHook 的日志输出
    			Logger logger = Logger.getLogger(GlobalScreen.class.getPackage().getName());
    			logger.setLevel(Level.OFF);
    
    			// 注册全局键盘事件
    			GlobalScreen.registerNativeHook();
    		} catch (NativeHookException ex) {
    			ex.printStackTrace();
    			System.err.println("Failed to initialize native hook. Exiting.");
    			System.exit(1);
    		}
    
    		// 添加 NativeKeyListener
    		NativeApplication application = new NativeApplication();
    		application.initFlags();
    		GlobalScreen.addNativeKeyListener(application);
    	}
    }