/* * Copyright (c) 2011 Denis Tulskiy * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * version 3 along with this work. If not, see <http://www.gnu.org/licenses/>. */ package com.qksnap.www.snap.hotkeys.x11; import com.qksnap.www.snap.hotkeys.common.HotKey; import com.qksnap.www.snap.hotkeys.common.HotKeyListener; import com.qksnap.www.snap.hotkeys.common.MediaKey; import com.qksnap.www.snap.hotkeys.common.Provider; import com.sun.jna.NativeLong; import com.sun.jna.Pointer; import javax.swing.*; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Queue; import static com.qksnap.www.snap.hotkeys.x11.X11.*; /** * Author: Denis Tulskiy * Date: 6/13/11 */ public class X11Provider extends Provider { private Pointer display; private NativeLong window; private boolean listening; private Thread thread; private boolean reset; private ErrorHandler errorHandler; private final Object lock = new Object(); private Queue<X11HotKey> registerQueue = new LinkedList<X11HotKey>(); private List<X11HotKey> hotKeys = new ArrayList<X11HotKey>(); public void init() { Runnable runnable = new Runnable() { public void run() { //logger.info("Starting X11 global hotkey provider"); display = Lib.XOpenDisplay(null); errorHandler = new ErrorHandler(); Lib.XSetErrorHandler(errorHandler); window = Lib.XDefaultRootWindow(display); listening = true; XEvent event = new XEvent(); while (listening) { while (Lib.XPending(display) > 0) { Lib.XNextEvent(display, event); if (event.type == KeyPress) { XKeyEvent xkey = (XKeyEvent) event.readField("xkey"); for (X11HotKey hotKey : hotKeys) { int state = xkey.state & (ShiftMask | ControlMask | Mod1Mask | Mod4Mask); if (hotKey.code == (byte) xkey.keycode && hotKey.modifiers == state) { //logger.info("Received event for hotkey: " + hotKey); fireEvent(hotKey); break; } } } } synchronized (lock) { if (reset) { //logger.info("Reset hotkeys"); resetAll(); reset = false; lock.notify(); } while (!registerQueue.isEmpty()) { X11HotKey hotKey = registerQueue.poll(); //logger.info("Registering hotkey: " + hotKey); if (hotKey.isMedia()) { registerMedia(hotKey); } else { register(hotKey); } hotKeys.add(hotKey); } try { lock.wait(300); } catch (InterruptedException e) { e.printStackTrace(); } } } //logger.info("Thread - stop listening"); } }; thread = new Thread(runnable); thread.start(); } private void register(X11HotKey hotKey) { byte code = KeyMap.getCode(hotKey.keyStroke, display); if (code == 0) { return; } int modifiers = KeyMap.getModifiers(hotKey.keyStroke); hotKey.code = code; hotKey.modifiers = modifiers; for (int i = 0; i < 16; i++) { int flags = correctModifiers(modifiers, i); Lib.XGrabKey(display, code, flags, window, 1, GrabModeAsync, GrabModeAsync); } } private void registerMedia(X11HotKey hotKey) { byte keyCode = KeyMap.getMediaCode(hotKey.mediaKey, display); hotKey.modifiers = 0; hotKey.code = keyCode; Lib.XGrabKey(display, keyCode, 0, window, 1, GrabModeAsync, GrabModeAsync); } private void resetAll() { for (X11HotKey hotKey : hotKeys) { if (!hotKey.isMedia()) { int modifiers = hotKey.modifiers; for (int i = 0; i < 16; i++) { int flags = correctModifiers(modifiers, i); Lib.XUngrabKey(display, hotKey.code, flags, window); } } else { Lib.XUngrabKey(display, hotKey.code, 0, window); } } hotKeys.clear(); } private int correctModifiers(int modifiers, int flags) { int ret = modifiers; if ((flags & 1) != 0) ret |= LockMask; if ((flags & 2) != 0) ret |= Mod2Mask; if ((flags & 4) != 0) ret |= Mod3Mask; if ((flags & 8) != 0) ret |= Mod5Mask; return ret; } @Override public void stop() { if (thread != null) { listening = false; try { thread.join(); Lib.XCloseDisplay(display); } catch (InterruptedException e) { e.printStackTrace(); } } super.stop(); } public void register(KeyStroke keyCode, HotKeyListener listener) { synchronized (lock) { registerQueue.add(new X11HotKey(keyCode, listener)); } } public void register(MediaKey mediaKey, HotKeyListener listener) { synchronized (lock) { registerQueue.add(new X11HotKey(mediaKey, listener)); } } public void reset() { synchronized (lock) { reset = true; try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } class ErrorHandler implements XErrorHandler { public int apply(Pointer display, XErrorEvent errorEvent) { byte[] buf = new byte[1024]; Lib.XGetErrorText(display, errorEvent.error_code, buf, buf.length); int len = 0; while (buf[len] != 0) len++; //logger.warning("Error: " + new String(buf, 0, len)); return 0; } } class X11HotKey extends HotKey { byte code; int modifiers; X11HotKey(KeyStroke keyStroke, HotKeyListener listener) { super(keyStroke, listener); } X11HotKey(MediaKey mediaKey, HotKeyListener listener) { super(mediaKey, listener); } } }