1 /* XEventPump.java -- Pumps events from X to AWT 2 Copyright (C) 2006 Free Software Foundation, Inc. 3 4 This file is part of GNU Classpath. 5 6 GNU Classpath is free software; you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation; either version 2, or (at your option) 9 any later version. 10 11 GNU Classpath is distributed in the hope that it will be useful, but 12 WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with GNU Classpath; see the file COPYING. If not, write to the 18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19 02110-1301 USA. 20 21 Linking this library statically or dynamically with other modules is 22 making a combined work based on this library. Thus, the terms and 23 conditions of the GNU General Public License cover the whole 24 combination. 25 26 As a special exception, the copyright holders of this library give you 27 permission to link this library with independent modules to produce an 28 executable, regardless of the license terms of these independent 29 modules, and to copy and distribute the resulting executable under 30 terms of your choice, provided that you also meet, for each linked 31 independent module, the terms and conditions of the license of that 32 module. An independent module is a module which is not derived from 33 or based on this library. If you modify this library, you may extend 34 this exception to your version of the library, but you are not 35 obligated to do so. If you do not wish to do so, delete this 36 exception statement from your version. */ 37 38 39 package gnu.java.awt.peer.x; 40 41 import java.awt.AWTEvent; 42 import java.awt.Component; 43 import java.awt.Container; 44 import java.awt.Graphics; 45 import java.awt.Insets; 46 import java.awt.Rectangle; 47 import java.awt.Toolkit; 48 import java.awt.Window; 49 import java.awt.event.ComponentEvent; 50 import java.awt.event.KeyEvent; 51 import java.awt.event.MouseEvent; 52 import java.awt.event.PaintEvent; 53 import java.awt.event.WindowEvent; 54 import java.util.HashMap; 55 56 import gnu.java.awt.ComponentReshapeEvent; 57 import gnu.x11.Atom; 58 import gnu.x11.Display; 59 import gnu.x11.event.ButtonPress; 60 import gnu.x11.event.ButtonRelease; 61 import gnu.x11.event.ClientMessage; 62 import gnu.x11.event.ConfigureNotify; 63 import gnu.x11.event.DestroyNotify; 64 import gnu.x11.event.Event; 65 import gnu.x11.event.Expose; 66 import gnu.x11.event.Input; 67 import gnu.x11.event.KeyPress; 68 import gnu.x11.event.KeyRelease; 69 import gnu.x11.event.MotionNotify; 70 import gnu.x11.event.PropertyNotify; 71 import gnu.x11.event.ResizeRequest; 72 import gnu.x11.event.UnmapNotify; 73 74 /** 75 * Fetches events from X, translates them to AWT events and pumps them up 76 * into the AWT event queue. 77 * 78 * @author Roman Kennke (kennke@aicas.com) 79 */ 80 public class XEventPump 81 implements Runnable 82 { 83 84 /** 85 * The X Display from which we fetch and pump up events. 86 */ 87 private Display display; 88 89 /** 90 * Maps X Windows to AWT Windows to be able to correctly determine the 91 * event targets. 92 */ 93 private HashMap windows; 94 95 /** 96 * Indicates if we are currently inside a drag operation. This is 97 * set to the button ID when a button is pressed and to -1 (indicating 98 * that no drag is active) when the mouse is released. 99 */ 100 private int drag; 101 102 /** 103 * Creates a new XEventPump for the specified X Display. 104 * 105 * @param d the X Display 106 */ XEventPump(Display d)107 XEventPump(Display d) 108 { 109 display = d; 110 windows = new HashMap(); 111 drag = -1; 112 Thread thread = new Thread(this, "X Event Pump"); 113 thread.setDaemon(true); 114 thread.start(); 115 } 116 117 /** 118 * The main event pump loop. This basically fetches events from the 119 * X Display and pumps them into the system event queue. 120 */ run()121 public void run() 122 { 123 while (display.connected) 124 { 125 try 126 { 127 Event xEvent = display.next_event(); 128 handleEvent(xEvent); 129 } 130 catch (ThreadDeath death) 131 { 132 // If someone wants to kill us, let them. 133 return; 134 } 135 catch (Throwable x) 136 { 137 System.err.println("Exception during event dispatch:"); 138 x.printStackTrace(System.err); 139 } 140 } 141 } 142 143 /** 144 * Adds an X Window to AWT Window mapping. This is required so that the 145 * event pump can correctly determine the event targets. 146 * 147 * @param xWindow the X Window 148 * @param awtWindow the AWT Window 149 */ registerWindow(gnu.x11.Window xWindow, Window awtWindow)150 void registerWindow(gnu.x11.Window xWindow, Window awtWindow) 151 { 152 if (XToolkit.DEBUG) 153 System.err.println("registering window id: " + xWindow.id); 154 windows.put(new Integer(xWindow.id), awtWindow); 155 } 156 unregisterWindow(gnu.x11.Window xWindow)157 void unregisterWindow(gnu.x11.Window xWindow) 158 { 159 windows.remove(new Integer(xWindow.id)); 160 } 161 handleButtonPress(ButtonPress event)162 private void handleButtonPress(ButtonPress event) 163 { 164 Integer key = new Integer(event.getEventWindowID()); 165 Window awtWindow = (Window) windows.get(key); 166 167 // Create and post the mouse event. 168 int button = event.detail(); 169 170 // AWT cannot handle more than 3 buttons and expects 0 instead. 171 if (button >= gnu.x11.Input.BUTTON3) 172 button = 0; 173 drag = button; 174 175 Component target = 176 findMouseEventTarget(awtWindow, event.getEventX(), event.getEventY()); 177 if(target == null) 178 { 179 target = awtWindow; 180 } 181 182 MouseEvent mp = new MouseEvent(target, MouseEvent.MOUSE_PRESSED, 183 System.currentTimeMillis(), 184 KeyboardMapping.mapModifiers(event.getState()) 185 | buttonToModifier(button), 186 event.getEventX(), event.getEventY(), 187 1, false, button); 188 Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(mp); 189 } 190 handleButtonRelease(ButtonRelease event)191 private void handleButtonRelease(ButtonRelease event) 192 { 193 Integer key = new Integer(event.getEventWindowID()); 194 Window awtWindow = (Window) windows.get(key); 195 196 int button = event.detail(); 197 198 // AWT cannot handle more than 3 buttons and expects 0 instead. 199 if (button >= gnu.x11.Input.BUTTON3) 200 button = 0; 201 drag = -1; 202 203 Component target = 204 findMouseEventTarget(awtWindow, event.getEventX(), event.getEventY()); 205 if(target == null) 206 { 207 target = awtWindow; 208 } 209 210 MouseEvent mr = new MouseEvent(target, MouseEvent.MOUSE_RELEASED, 211 System.currentTimeMillis(), 212 KeyboardMapping.mapModifiers(event.getState()) 213 | buttonToModifier(button), 214 event.getEventX(), event.getEventY(), 215 1, false, button); 216 Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(mr); 217 } 218 219 handleMotionNotify(MotionNotify event)220 private void handleMotionNotify(MotionNotify event) 221 { 222 Integer key = new Integer(event.getEventWindowID()); 223 Window awtWindow = (Window) windows.get(key); 224 225 int button = event.detail(); 226 227 // AWT cannot handle more than 3 buttons and expects 0 instead. 228 if (button >= gnu.x11.Input.BUTTON3) 229 button = 0; 230 231 MouseEvent mm = null; 232 if (drag == -1) 233 { 234 mm = new MouseEvent(awtWindow, MouseEvent.MOUSE_MOVED, 235 System.currentTimeMillis(), 236 KeyboardMapping.mapModifiers(event.getState()) 237 | buttonToModifier(button), 238 event.getEventX(), event.getEventY(), 239 1, false); 240 241 } 242 else 243 { 244 mm = new MouseEvent(awtWindow, MouseEvent.MOUSE_DRAGGED, 245 System.currentTimeMillis(), 246 KeyboardMapping.mapModifiers(event.getState()) 247 | buttonToModifier(drag), 248 event.getEventX(), event.getEventY(), 249 1, false); 250 } 251 Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(mm); 252 } 253 254 // FIME: refactor and make faster, maybe caching the event and handle 255 // and/or check timing (timing is generated for PropertyChange)? handleExpose(Expose event)256 private void handleExpose(Expose event) 257 { 258 Integer key = new Integer(event.window_id); 259 Window awtWindow = (Window) windows.get(key); 260 261 if (XToolkit.DEBUG) 262 System.err.println("expose request for window id: " + key); 263 264 Rectangle r = new Rectangle(event.x(), event.y(), event.width(), 265 event.height()); 266 // We need to clear the background of the exposed rectangle. 267 assert awtWindow != null : "awtWindow == null for window ID: " + key; 268 269 Graphics g = awtWindow.getGraphics(); 270 g.clearRect(r.x, r.y, r.width, r.height); 271 g.dispose(); 272 273 XWindowPeer xwindow = (XWindowPeer) awtWindow.getPeer(); 274 Insets i = xwindow.insets(); 275 if (event.width() != awtWindow.getWidth() - i.left - i.right 276 || event.height() != awtWindow.getHeight() - i.top - i.bottom) 277 { 278 int w = event.width(); 279 int h = event.height(); 280 int x = xwindow.xwindow.x; 281 int y = xwindow.xwindow.y; 282 283 if (XToolkit.DEBUG) 284 System.err.println("Setting size on AWT window: " + w 285 + ", " + h + ", " + awtWindow.getWidth() 286 + ", " + awtWindow.getHeight()); 287 288 // new width and height 289 xwindow.xwindow.width = w; 290 xwindow.xwindow.height = h; 291 292 // reshape the window 293 ComponentReshapeEvent cre = 294 new ComponentReshapeEvent(awtWindow, x, y, w, h); 295 awtWindow.dispatchEvent(cre); 296 } 297 298 ComponentEvent ce = 299 new ComponentEvent(awtWindow, ComponentEvent.COMPONENT_RESIZED); 300 awtWindow.dispatchEvent(ce); 301 302 PaintEvent pev = new PaintEvent(awtWindow, PaintEvent.UPDATE, r); 303 Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(pev); 304 } 305 handleDestroyNotify(DestroyNotify destroyNotify)306 private void handleDestroyNotify(DestroyNotify destroyNotify) 307 { 308 if (XToolkit.DEBUG) 309 System.err.println("DestroyNotify event: " + destroyNotify); 310 311 Integer key = new Integer(destroyNotify.event_window_id); 312 Window awtWindow = (Window) windows.get(key); 313 314 AWTEvent event = new WindowEvent(awtWindow, WindowEvent.WINDOW_CLOSED); 315 Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(event); 316 } 317 handleClientMessage(ClientMessage clientMessage)318 private void handleClientMessage(ClientMessage clientMessage) 319 { 320 if (XToolkit.DEBUG) 321 System.err.println("ClientMessage event: " + clientMessage); 322 323 if (clientMessage.delete_window()) 324 { 325 if (XToolkit.DEBUG) 326 System.err.println("ClientMessage is a delete_window event"); 327 328 Integer key = new Integer(clientMessage.window_id); 329 Window awtWindow = (Window) windows.get(key); 330 331 AWTEvent event = new WindowEvent(awtWindow, WindowEvent.WINDOW_CLOSING); 332 Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(event); 333 } 334 } 335 handleEvent(Event xEvent)336 private void handleEvent(Event xEvent) 337 { 338 if (XToolkit.DEBUG) 339 System.err.println("fetched event: " + xEvent); 340 341 switch (xEvent.code() & 0x7f) 342 { 343 case ButtonPress.CODE: 344 this.handleButtonPress((ButtonPress) xEvent); 345 break; 346 case ButtonRelease.CODE: 347 this.handleButtonRelease((ButtonRelease) xEvent); 348 break; 349 case MotionNotify.CODE: 350 this.handleMotionNotify((MotionNotify) xEvent); 351 break; 352 case Expose.CODE: 353 this.handleExpose((Expose) xEvent); 354 break; 355 case KeyPress.CODE: 356 case KeyRelease.CODE: 357 Integer key = new Integer(((Input) xEvent).getEventWindowID()); 358 Window awtWindow = (Window) windows.get(key); 359 handleKeyEvent(xEvent, awtWindow); 360 break; 361 case DestroyNotify.CODE: 362 this.handleDestroyNotify((DestroyNotify) xEvent); 363 break; 364 case ClientMessage.CODE: 365 this.handleClientMessage((ClientMessage) xEvent); 366 break; 367 case PropertyNotify.CODE: 368 key = new Integer (((PropertyNotify) xEvent).getWindowID()); 369 awtWindow = (Window) windows.get(key); 370 AWTEvent event = new WindowEvent(awtWindow, WindowEvent.WINDOW_STATE_CHANGED); 371 Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(event); 372 break; 373 default: 374 if (XToolkit.DEBUG) 375 System.err.println("Unhandled X event: " + xEvent); 376 } 377 } 378 379 /** 380 * Handles key events from X. 381 * 382 * @param xEvent the X event 383 * @param awtWindow the AWT window to which the event gets posted 384 */ handleKeyEvent(Event xEvent, Window awtWindow)385 private void handleKeyEvent(Event xEvent, Window awtWindow) 386 { 387 Input keyEvent = (Input) xEvent; 388 int xKeyCode = keyEvent.detail(); 389 int xMods = keyEvent.getState(); 390 int keyCode = KeyboardMapping.mapToKeyCode(xEvent.display.input, xKeyCode, 391 xMods); 392 char keyChar = KeyboardMapping.mapToKeyChar(xEvent.display.input, xKeyCode, 393 xMods); 394 if (XToolkit.DEBUG) 395 System.err.println("XEventPump.handleKeyEvent: " + xKeyCode + ", " 396 + xMods + ": " + ((int) keyChar) + ", " + keyCode); 397 int awtMods = KeyboardMapping.mapModifiers(xMods); 398 long when = System.currentTimeMillis(); 399 KeyEvent ke; 400 if (keyEvent.code() == KeyPress.CODE) 401 { 402 ke = new KeyEvent(awtWindow, KeyEvent.KEY_PRESSED, when, 403 awtMods, keyCode, 404 KeyEvent.CHAR_UNDEFINED); 405 Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(ke); 406 if (keyChar != KeyEvent.CHAR_UNDEFINED) 407 { 408 ke = new KeyEvent(awtWindow, KeyEvent.KEY_TYPED, when, 409 awtMods, KeyEvent.VK_UNDEFINED, 410 keyChar); 411 Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(ke); 412 } 413 414 } 415 else 416 { 417 ke = new KeyEvent(awtWindow, KeyEvent.KEY_RELEASED, when, 418 awtMods, keyCode, 419 KeyEvent.CHAR_UNDEFINED); 420 Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(ke); 421 } 422 423 } 424 425 /** Translates an X button identifier to the AWT's MouseEvent modifier 426 * mask. As the AWT cannot handle more than 3 buttons those return 427 * <code>0</code>. 428 */ buttonToModifier(int button)429 static int buttonToModifier(int button) 430 { 431 switch (button) 432 { 433 case gnu.x11.Input.BUTTON1: 434 return MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON1_MASK; 435 case gnu.x11.Input.BUTTON2: 436 return MouseEvent.BUTTON2_DOWN_MASK | MouseEvent.BUTTON2_MASK; 437 case gnu.x11.Input.BUTTON3: 438 return MouseEvent.BUTTON3_DOWN_MASK | MouseEvent.BUTTON3_MASK; 439 } 440 441 return 0; 442 } 443 444 /** 445 * Finds the heavyweight mouse event target. 446 * 447 * @param src the original source of the event 448 * 449 * @param pt the event coordinates 450 * 451 * @return the real mouse event target 452 */ findMouseEventTarget(Component src, int x, int y)453 private Component findMouseEventTarget(Component src, int x, int y) 454 { 455 Component found = null; 456 if (src instanceof Container) 457 { 458 Container cont = (Container) src; 459 int numChildren = cont.getComponentCount(); 460 for (int i = 0; i < numChildren && found == null; i++) 461 { 462 Component child = cont.getComponent(i); 463 if (child != null && child.isVisible() 464 && child.contains(x - child.getX(), y - child.getY())) 465 { 466 if (child instanceof Container) 467 { 468 Component deeper = findMouseEventTarget(child, 469 x - child.getX(), 470 y - child.getY()); 471 if (deeper != null) 472 found = deeper; 473 } 474 else if (! child.isLightweight()) 475 found = child; 476 } 477 } 478 } 479 480 // Consider the source itself. 481 if (found == null && src.contains(x, y) && ! src.isLightweight()) 482 found = src; 483 484 return found; 485 } 486 } 487