1 /* 2 * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.apple.eawt; 27 28 import java.awt.*; 29 import java.awt.event.WindowEvent; 30 import java.io.File; 31 import java.net.*; 32 import java.util.*; 33 import java.util.List; 34 35 import com.apple.eawt.AppEvent.*; 36 37 class _AppEventHandler { 38 private static final int NOTIFY_ABOUT = 1; 39 private static final int NOTIFY_PREFS = 2; 40 private static final int NOTIFY_OPEN_APP = 3; 41 private static final int NOTIFY_REOPEN_APP = 4; 42 private static final int NOTIFY_QUIT = 5; 43 private static final int NOTIFY_SHUTDOWN = 6; 44 private static final int NOTIFY_ACTIVE_APP_GAINED = 7; 45 private static final int NOTIFY_ACTIVE_APP_LOST = 8; 46 private static final int NOTIFY_APP_HIDDEN = 9; 47 private static final int NOTIFY_APP_SHOWN = 10; 48 private static final int NOTIFY_USER_SESSION_ACTIVE = 11; 49 private static final int NOTIFY_USER_SESSION_INACTIVE = 12; 50 private static final int NOTIFY_SCREEN_SLEEP = 13; 51 private static final int NOTIFY_SCREEN_WAKE = 14; 52 private static final int NOTIFY_SYSTEM_SLEEP = 15; 53 private static final int NOTIFY_SYSTEM_WAKE = 16; 54 55 private static final int REGISTER_USER_SESSION = 1; 56 private static final int REGISTER_SCREEN_SLEEP = 2; 57 private static final int REGISTER_SYSTEM_SLEEP = 3; 58 59 private static native void nativeOpenCocoaAboutWindow(); 60 private static native void nativeReplyToAppShouldTerminate(final boolean shouldTerminate); 61 private static native void nativeRegisterForNotification(final int notification); 62 63 final static _AppEventHandler instance = new _AppEventHandler(); 64 static _AppEventHandler getInstance() { 65 return instance; 66 } 67 68 // single shot dispatchers (some queuing, others not) 69 final _AboutDispatcher aboutDispatcher = new _AboutDispatcher(); 70 final _PreferencesDispatcher preferencesDispatcher = new _PreferencesDispatcher(); 71 final _OpenFileDispatcher openFilesDispatcher = new _OpenFileDispatcher(); 72 final _PrintFileDispatcher printFilesDispatcher = new _PrintFileDispatcher(); 73 final _OpenURIDispatcher openURIDispatcher = new _OpenURIDispatcher(); 74 final _QuitDispatcher quitDispatcher = new _QuitDispatcher(); 75 final _OpenAppDispatcher openAppDispatcher = new _OpenAppDispatcher(); 76 77 // multiplexing dispatchers (contains listener lists) 78 final _AppReOpenedDispatcher reOpenAppDispatcher = new _AppReOpenedDispatcher(); 79 final _AppForegroundDispatcher foregroundAppDispatcher = new _AppForegroundDispatcher(); 80 final _HiddenAppDispatcher hiddenAppDispatcher = new _HiddenAppDispatcher(); 81 final _UserSessionDispatcher userSessionDispatcher = new _UserSessionDispatcher(); 82 final _ScreenSleepDispatcher screenSleepDispatcher = new _ScreenSleepDispatcher(); 83 final _SystemSleepDispatcher systemSleepDispatcher = new _SystemSleepDispatcher(); 84 85 final _AppEventLegacyHandler legacyHandler = new _AppEventLegacyHandler(this); 86 87 QuitStrategy defaultQuitAction = QuitStrategy.SYSTEM_EXIT_0; 88 89 _AppEventHandler() { 90 final String strategyProp = System.getProperty("apple.eawt.quitStrategy"); 91 if (strategyProp == null) return; 92 93 if ("CLOSE_ALL_WINDOWS".equals(strategyProp)) { 94 setDefaultQuitStrategy(QuitStrategy.CLOSE_ALL_WINDOWS); 95 } else if ("SYSTEM_EXIT_O".equals(strategyProp)) { 96 setDefaultQuitStrategy(QuitStrategy.SYSTEM_EXIT_0); 97 } else { 98 System.err.println("unrecognized apple.eawt.quitStrategy: " + strategyProp); 99 } 100 } 101 102 void addListener(final AppEventListener listener) { 103 if (listener instanceof AppReOpenedListener) reOpenAppDispatcher.addListener((AppReOpenedListener)listener); 104 if (listener instanceof AppForegroundListener) foregroundAppDispatcher.addListener((AppForegroundListener)listener); 105 if (listener instanceof AppHiddenListener) hiddenAppDispatcher.addListener((AppHiddenListener)listener); 106 if (listener instanceof UserSessionListener) userSessionDispatcher.addListener((UserSessionListener)listener); 107 if (listener instanceof ScreenSleepListener) screenSleepDispatcher.addListener((ScreenSleepListener)listener); 108 if (listener instanceof SystemSleepListener) systemSleepDispatcher.addListener((SystemSleepListener)listener); 109 } 110 111 void removeListener(final AppEventListener listener) { 112 if (listener instanceof AppReOpenedListener) reOpenAppDispatcher.removeListener((AppReOpenedListener)listener); 113 if (listener instanceof AppForegroundListener) foregroundAppDispatcher.removeListener((AppForegroundListener)listener); 114 if (listener instanceof AppHiddenListener) hiddenAppDispatcher.removeListener((AppHiddenListener)listener); 115 if (listener instanceof UserSessionListener) userSessionDispatcher.removeListener((UserSessionListener)listener); 116 if (listener instanceof ScreenSleepListener) screenSleepDispatcher.removeListener((ScreenSleepListener)listener); 117 if (listener instanceof SystemSleepListener) systemSleepDispatcher.removeListener((SystemSleepListener)listener); 118 } 119 120 void openCocoaAboutWindow() { 121 nativeOpenCocoaAboutWindow(); 122 } 123 124 void setDefaultQuitStrategy(final QuitStrategy defaultQuitAction) { 125 this.defaultQuitAction = defaultQuitAction; 126 } 127 128 QuitResponse currentQuitResponse; 129 synchronized QuitResponse obtainQuitResponse() { 130 if (currentQuitResponse != null) return currentQuitResponse; 131 return currentQuitResponse = new QuitResponse(this); 132 } 133 134 synchronized void cancelQuit() { 135 currentQuitResponse = null; 136 nativeReplyToAppShouldTerminate(false); 137 } 138 139 synchronized void performQuit() { 140 currentQuitResponse = null; 141 142 try { 143 if (defaultQuitAction == QuitStrategy.SYSTEM_EXIT_0) System.exit(0); 144 145 if (defaultQuitAction != QuitStrategy.CLOSE_ALL_WINDOWS) { 146 throw new RuntimeException("Unknown quit action"); 147 } 148 149 EventQueue.invokeLater(new Runnable() { 150 public void run() { 151 // walk frames from back to front 152 final Frame[] allFrames = Frame.getFrames(); 153 for (int i = allFrames.length - 1; i >= 0; i--) { 154 final Frame frame = allFrames[i]; 155 frame.dispatchEvent(new WindowEvent(frame, WindowEvent.WINDOW_CLOSING)); 156 } 157 } 158 }); 159 } finally { 160 nativeReplyToAppShouldTerminate(true); 161 } 162 } 163 164 /* 165 * callbacks from native delegate 166 */ 167 private static void handlePrintFiles(final List<String> filenames) { 168 instance.printFilesDispatcher.dispatch(new _NativeEvent(filenames)); 169 } 170 171 private static void handleOpenFiles(final List<String> filenames, final String searchTerm) { 172 instance.openFilesDispatcher.dispatch(new _NativeEvent(filenames, searchTerm)); 173 } 174 175 private static void handleOpenURI(final String uri) { 176 instance.openURIDispatcher.dispatch(new _NativeEvent(uri)); 177 } 178 179 // default funnel for non-complex events 180 private static void handleNativeNotification(final int code) { 181 // System.out.println(code); 182 183 switch (code) { 184 case NOTIFY_ABOUT: 185 instance.aboutDispatcher.dispatch(new _NativeEvent()); 186 break; 187 case NOTIFY_PREFS: 188 instance.preferencesDispatcher.dispatch(new _NativeEvent()); 189 break; 190 case NOTIFY_OPEN_APP: 191 instance.openAppDispatcher.dispatch(new _NativeEvent()); 192 break; 193 case NOTIFY_REOPEN_APP: 194 instance.reOpenAppDispatcher.dispatch(new _NativeEvent()); 195 break; 196 case NOTIFY_QUIT: 197 instance.quitDispatcher.dispatch(new _NativeEvent()); 198 break; 199 case NOTIFY_SHUTDOWN: 200 // do nothing for now 201 break; 202 case NOTIFY_ACTIVE_APP_GAINED: 203 instance.foregroundAppDispatcher.dispatch(new _NativeEvent(Boolean.TRUE)); 204 break; 205 case NOTIFY_ACTIVE_APP_LOST: 206 instance.foregroundAppDispatcher.dispatch(new _NativeEvent(Boolean.FALSE)); 207 break; 208 case NOTIFY_APP_HIDDEN: 209 instance.hiddenAppDispatcher.dispatch(new _NativeEvent(Boolean.TRUE)); 210 break; 211 case NOTIFY_APP_SHOWN: 212 instance.hiddenAppDispatcher.dispatch(new _NativeEvent(Boolean.FALSE)); 213 break; 214 case NOTIFY_USER_SESSION_ACTIVE: 215 instance.userSessionDispatcher.dispatch(new _NativeEvent(Boolean.TRUE)); 216 break; 217 case NOTIFY_USER_SESSION_INACTIVE: 218 instance.userSessionDispatcher.dispatch(new _NativeEvent(Boolean.FALSE)); 219 break; 220 case NOTIFY_SCREEN_SLEEP: 221 instance.screenSleepDispatcher.dispatch(new _NativeEvent(Boolean.TRUE)); 222 break; 223 case NOTIFY_SCREEN_WAKE: 224 instance.screenSleepDispatcher.dispatch(new _NativeEvent(Boolean.FALSE)); 225 break; 226 case NOTIFY_SYSTEM_SLEEP: 227 instance.systemSleepDispatcher.dispatch(new _NativeEvent(Boolean.TRUE)); 228 break; 229 case NOTIFY_SYSTEM_WAKE: 230 instance.systemSleepDispatcher.dispatch(new _NativeEvent(Boolean.FALSE)); 231 break; 232 default: 233 System.err.println("EAWT unknown native notification: " + code); 234 break; 235 } 236 } 237 238 239 class _AboutDispatcher extends _AppEventDispatcher<AboutHandler> { 240 void performDefaultAction(final _NativeEvent event) { 241 openCocoaAboutWindow(); // if the handler is null, fall back to showing the Cocoa default 242 } 243 244 void performUsing(final AboutHandler handler, final _NativeEvent event) { 245 handler.handleAbout(new AboutEvent()); 246 } 247 } 248 249 class _PreferencesDispatcher extends _AppEventDispatcher<PreferencesHandler> { 250 synchronized void setHandler(final PreferencesHandler handler) { 251 super.setHandler(handler); 252 253 _AppMenuBarHandler.getInstance().setPreferencesMenuItemVisible(handler != null); 254 _AppMenuBarHandler.getInstance().setPreferencesMenuItemEnabled(handler != null); 255 } 256 257 void performUsing(final PreferencesHandler handler, final _NativeEvent event) { 258 handler.handlePreferences(new PreferencesEvent()); 259 } 260 } 261 262 class _OpenAppDispatcher extends _QueuingAppEventDispatcher<com.apple.eawt._OpenAppHandler> { 263 void performUsing(com.apple.eawt._OpenAppHandler handler, _NativeEvent event) { 264 handler.handleOpenApp(); 265 } 266 } 267 268 class _AppReOpenedDispatcher extends _AppEventMultiplexor<AppReOpenedListener> { 269 void performOnListeners(final List<AppReOpenedListener> listeners, final _NativeEvent event) { 270 final AppReOpenedEvent e = new AppReOpenedEvent(); 271 for (final AppReOpenedListener listener : listeners) { 272 listener.appReOpened(e); 273 } 274 } 275 } 276 277 class _AppForegroundDispatcher extends _BooleanAppEventMultiplexor<AppForegroundListener, AppForegroundEvent> { 278 AppForegroundEvent createEvent(final boolean isTrue) { return new AppForegroundEvent(); } 279 280 void performFalseEventOn(final AppForegroundListener listener, final AppForegroundEvent e) { 281 listener.appMovedToBackground(e); 282 } 283 284 void performTrueEventOn(final AppForegroundListener listener, final AppForegroundEvent e) { 285 listener.appRaisedToForeground(e); 286 } 287 } 288 289 class _HiddenAppDispatcher extends _BooleanAppEventMultiplexor<AppHiddenListener, AppHiddenEvent> { 290 AppHiddenEvent createEvent(final boolean isTrue) { return new AppHiddenEvent(); } 291 292 void performFalseEventOn(final AppHiddenListener listener, final AppHiddenEvent e) { 293 listener.appUnhidden(e); 294 } 295 296 void performTrueEventOn(final AppHiddenListener listener, final AppHiddenEvent e) { 297 listener.appHidden(e); 298 } 299 } 300 301 class _UserSessionDispatcher extends _BooleanAppEventMultiplexor<UserSessionListener, UserSessionEvent> { 302 UserSessionEvent createEvent(final boolean isTrue) { return new UserSessionEvent(); } 303 304 void performFalseEventOn(final UserSessionListener listener, final UserSessionEvent e) { 305 listener.userSessionDeactivated(e); 306 } 307 308 void performTrueEventOn(final UserSessionListener listener, final UserSessionEvent e) { 309 listener.userSessionActivated(e); 310 } 311 312 void registerNativeListener() { 313 nativeRegisterForNotification(REGISTER_USER_SESSION); 314 } 315 } 316 317 class _ScreenSleepDispatcher extends _BooleanAppEventMultiplexor<ScreenSleepListener, ScreenSleepEvent> { 318 ScreenSleepEvent createEvent(final boolean isTrue) { return new ScreenSleepEvent(); } 319 320 void performFalseEventOn(final ScreenSleepListener listener, final ScreenSleepEvent e) { 321 listener.screenAwoke(e); 322 } 323 324 void performTrueEventOn(final ScreenSleepListener listener, final ScreenSleepEvent e) { 325 listener.screenAboutToSleep(e); 326 } 327 328 void registerNativeListener() { 329 nativeRegisterForNotification(REGISTER_SCREEN_SLEEP); 330 } 331 } 332 333 class _SystemSleepDispatcher extends _BooleanAppEventMultiplexor<SystemSleepListener, SystemSleepEvent> { 334 SystemSleepEvent createEvent(final boolean isTrue) { return new SystemSleepEvent(); } 335 336 void performFalseEventOn(final SystemSleepListener listener, final SystemSleepEvent e) { 337 listener.systemAwoke(e); 338 } 339 340 void performTrueEventOn(final SystemSleepListener listener, final SystemSleepEvent e) { 341 listener.systemAboutToSleep(e); 342 } 343 344 void registerNativeListener() { 345 nativeRegisterForNotification(REGISTER_SYSTEM_SLEEP); 346 } 347 } 348 349 class _OpenFileDispatcher extends _QueuingAppEventDispatcher<OpenFilesHandler> { 350 void performUsing(final OpenFilesHandler handler, final _NativeEvent event) { 351 // create file list from fileNames 352 final List<String> fileNameList = event.get(0); 353 final ArrayList<File> files = new ArrayList<File>(fileNameList.size()); 354 for (final String fileName : fileNameList) files.add(new File(fileName)); 355 356 // populate the properties map 357 final String searchTerm = event.get(1); 358 handler.openFiles(new OpenFilesEvent(files, searchTerm)); 359 } 360 } 361 362 class _PrintFileDispatcher extends _QueuingAppEventDispatcher<PrintFilesHandler> { 363 void performUsing(final PrintFilesHandler handler, final _NativeEvent event) { 364 // create file list from fileNames 365 final List<String> fileNameList = event.get(0); 366 final ArrayList<File> files = new ArrayList<File>(fileNameList.size()); 367 for (final String fileName : fileNameList) files.add(new File(fileName)); 368 369 handler.printFiles(new PrintFilesEvent(files)); 370 } 371 } 372 373 // Java URLs can't handle unknown protocol types, which is why we use URIs 374 class _OpenURIDispatcher extends _QueuingAppEventDispatcher<OpenURIHandler> { 375 void performUsing(final OpenURIHandler handler, final _NativeEvent event) { 376 final String urlString = event.get(0); 377 try { 378 handler.openURI(new OpenURIEvent(new URI(urlString))); 379 } catch (final URISyntaxException e) { 380 throw new RuntimeException(e); 381 } 382 } 383 } 384 385 class _QuitDispatcher extends _AppEventDispatcher<QuitHandler> { 386 void performDefaultAction(final _NativeEvent event) { 387 obtainQuitResponse().performQuit(); 388 } 389 390 void performUsing(final QuitHandler handler, final _NativeEvent event) { 391 final QuitResponse response = obtainQuitResponse(); // obtains the "current" quit response 392 handler.handleQuitRequestWith(new QuitEvent(), response); 393 } 394 } 395 396 397 // -- ABSTRACT QUEUE/EVENT/LISTENER HELPERS -- 398 399 // generic little "raw event" that's constructed easily from the native callbacks 400 static class _NativeEvent { 401 Object[] args; 402 403 public _NativeEvent(final Object... args) { 404 this.args = args; 405 } 406 407 @SuppressWarnings("unchecked") 408 <T> T get(final int i) { 409 if (args == null) return null; 410 return (T)args[i]; 411 } 412 } 413 414 abstract class _AppEventMultiplexor<L> { 415 final List<L> _listeners = new ArrayList<L>(0); 416 boolean nativeListenerRegistered; 417 418 // called from AppKit Thread-0 419 void dispatch(final _NativeEvent event, final Object... args) { 420 // grab a local ref to the listeners 421 final List<L> localListeners; 422 synchronized (this) { 423 if (_listeners.size() == 0) return; 424 localListeners = new ArrayList<L>(_listeners); 425 } 426 427 EventQueue.invokeLater(new Runnable() { 428 public void run() { 429 performOnListeners(localListeners, event); 430 } 431 }); 432 } 433 434 synchronized void addListener(final L listener) { 435 if (!nativeListenerRegistered) { 436 registerNativeListener(); 437 nativeListenerRegistered = true; 438 } 439 _listeners.add(listener); 440 } 441 442 synchronized void removeListener(final L listener) { 443 _listeners.remove(listener); 444 } 445 446 abstract void performOnListeners(final List<L> listeners, final _NativeEvent event); 447 void registerNativeListener() { } 448 } 449 450 abstract class _BooleanAppEventMultiplexor<L, E> extends _AppEventMultiplexor<L> { 451 @Override 452 void performOnListeners(final List<L> listeners, final _NativeEvent event) { 453 final boolean isTrue = Boolean.TRUE.equals(event.get(0)); 454 final E e = createEvent(isTrue); 455 if (isTrue) { 456 for (final L listener : listeners) performTrueEventOn(listener, e); 457 } else { 458 for (final L listener : listeners) performFalseEventOn(listener, e); 459 } 460 } 461 462 abstract E createEvent(final boolean isTrue); 463 abstract void performTrueEventOn(final L listener, final E e); 464 abstract void performFalseEventOn(final L listener, final E e); 465 } 466 467 /* 468 * Ensures that setting and obtaining an app event handler is done in 469 * both a thread-safe manner, and that user code is performed on the 470 * AWT EventQueue thread. 471 * 472 * Allows native to blindly lob new events into the dispatcher, 473 * knowing that they will only be dispatched once a handler is set. 474 * 475 * User code is not (and should not be) run under any synchronized lock. 476 */ 477 abstract class _AppEventDispatcher<H> { 478 H _handler; 479 480 // called from AppKit Thread-0 481 void dispatch(final _NativeEvent event) { 482 EventQueue.invokeLater(new Runnable() { 483 public void run() { 484 // grab a local ref to the handler 485 final H localHandler; 486 synchronized (_AppEventDispatcher.this) { 487 localHandler = _handler; 488 } 489 490 // invoke the handler outside of the synchronized block 491 if (localHandler == null) { 492 performDefaultAction(event); 493 } else { 494 performUsing(localHandler, event); 495 } 496 } 497 }); 498 } 499 500 synchronized void setHandler(final H handler) { 501 this._handler = handler; 502 503 // if a new handler is installed, block addition of legacy ApplicationListeners 504 if (handler == legacyHandler) return; 505 legacyHandler.blockLegacyAPI(); 506 } 507 508 void performDefaultAction(final _NativeEvent event) { } // by default, do nothing 509 abstract void performUsing(final H handler, final _NativeEvent event); 510 } 511 512 abstract class _QueuingAppEventDispatcher<H> extends _AppEventDispatcher<H> { 513 List<_NativeEvent> queuedEvents = new LinkedList<_NativeEvent>(); 514 515 @Override 516 void dispatch(final _NativeEvent event) { 517 synchronized (this) { 518 // dispatcher hasn't started yet 519 if (queuedEvents != null) { 520 queuedEvents.add(event); 521 return; 522 } 523 } 524 525 super.dispatch(event); 526 } 527 528 synchronized void setHandler(final H handler) { 529 this._handler = handler; 530 531 // dispatch any events in the queue 532 if (queuedEvents != null) { 533 // grab a local ref to the queue, so the real one can be nulled out 534 final java.util.List<_NativeEvent> localQueuedEvents = queuedEvents; 535 queuedEvents = null; 536 if (localQueuedEvents.size() != 0) { 537 for (final _NativeEvent arg : localQueuedEvents) { 538 dispatch(arg); 539 } 540 } 541 } 542 543 // if a new handler is installed, block addition of legacy ApplicationListeners 544 if (handler == legacyHandler) return; 545 legacyHandler.blockLegacyAPI(); 546 } 547 } 548 }