Source for gnu.java.awt.peer.x.XEventPump

   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:    */
 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:    */
 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:    */
 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: 
 157:   void unregisterWindow(gnu.x11.Window xWindow)
 158:   {
 159:     windows.remove(new Integer(xWindow.id));
 160:   }
 161: 
 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: 
 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: 
 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)?
 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: 
 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: 
 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: 
 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:    */
 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:    */
 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:    */
 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: }