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