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 // Either we've just called System.exit(), or the app will call 161 // it when processing a WINDOW_CLOSING event. Either way, we reply 162 // to Cocoa that we don't want to exit the event loop yet. 163 nativeReplyToAppShouldTerminate(false); 164 } 165 } 166 167 /* 168 * callbacks from native delegate 169 */ 170 private static void handlePrintFiles(final List<String> filenames) { 171 instance.printFilesDispatcher.dispatch(new _NativeEvent(filenames)); 172 } 173 174 private static void handleOpenFiles(final List<String> filenames, final String searchTerm) { 175 instance.openFilesDispatcher.dispatch(new _NativeEvent(filenames, searchTerm)); 176 } 177 178 private static void handleOpenURI(final String uri) { 179 instance.openURIDispatcher.dispatch(new _NativeEvent(uri)); 180 } 181 182 // default funnel for non-complex events 183 private static void handleNativeNotification(final int code) { 184 // System.out.println(code); 185 186 switch (code) { 187 case NOTIFY_ABOUT: 188 instance.aboutDispatcher.dispatch(new _NativeEvent()); 189 break; 190 case NOTIFY_PREFS: 191 instance.preferencesDispatcher.dispatch(new _NativeEvent()); 192 break; 193 case NOTIFY_OPEN_APP: 194 instance.openAppDispatcher.dispatch(new _NativeEvent()); 195 break; 196 case NOTIFY_REOPEN_APP: 197 instance.reOpenAppDispatcher.dispatch(new _NativeEvent()); 198 break; 199 case NOTIFY_QUIT: 200 instance.quitDispatcher.dispatch(new _NativeEvent()); 201 break; 202 case NOTIFY_SHUTDOWN: 203 // do nothing for now 204 break; 205 case NOTIFY_ACTIVE_APP_GAINED: 206 instance.foregroundAppDispatcher.dispatch(new _NativeEvent(Boolean.TRUE)); 207 break; 208 case NOTIFY_ACTIVE_APP_LOST: 209 instance.foregroundAppDispatcher.dispatch(new _NativeEvent(Boolean.FALSE)); 210 break; 211 case NOTIFY_APP_HIDDEN: 212 instance.hiddenAppDispatcher.dispatch(new _NativeEvent(Boolean.TRUE)); 213 break; 214 case NOTIFY_APP_SHOWN: 215 instance.hiddenAppDispatcher.dispatch(new _NativeEvent(Boolean.FALSE)); 216 break; 217 case NOTIFY_USER_SESSION_ACTIVE: 218 instance.userSessionDispatcher.dispatch(new _NativeEvent(Boolean.TRUE)); 219 break; 220 case NOTIFY_USER_SESSION_INACTIVE: 221 instance.userSessionDispatcher.dispatch(new _NativeEvent(Boolean.FALSE)); 222 break; 223 case NOTIFY_SCREEN_SLEEP: 224 instance.screenSleepDispatcher.dispatch(new _NativeEvent(Boolean.TRUE)); 225 break; 226 case NOTIFY_SCREEN_WAKE: 227 instance.screenSleepDispatcher.dispatch(new _NativeEvent(Boolean.FALSE)); 228 break; 229 case NOTIFY_SYSTEM_SLEEP: 230 instance.systemSleepDispatcher.dispatch(new _NativeEvent(Boolean.TRUE)); 231 break; 232 case NOTIFY_SYSTEM_WAKE: 233 instance.systemSleepDispatcher.dispatch(new _NativeEvent(Boolean.FALSE)); 234 break; 235 default: 236 System.err.println("EAWT unknown native notification: " + code); 237 break; 238 } 239 } 240 241 242 class _AboutDispatcher extends _AppEventDispatcher<AboutHandler> { 243 void performDefaultAction(final _NativeEvent event) { 244 openCocoaAboutWindow(); // if the handler is null, fall back to showing the Cocoa default 245 } 246 247 void performUsing(final AboutHandler handler, final _NativeEvent event) { 248 handler.handleAbout(new AboutEvent()); 249 } 250 } 251 252 class _PreferencesDispatcher extends _AppEventDispatcher<PreferencesHandler> { 253 synchronized void setHandler(final PreferencesHandler handler) { 254 super.setHandler(handler); 255 256 _AppMenuBarHandler.getInstance().setPreferencesMenuItemVisible(handler != null); 257 _AppMenuBarHandler.getInstance().setPreferencesMenuItemEnabled(handler != null); 258 } 259 260 void performUsing(final PreferencesHandler handler, final _NativeEvent event) { 261 handler.handlePreferences(new PreferencesEvent()); 262 } 263 } 264 265 class _OpenAppDispatcher extends _QueuingAppEventDispatcher<com.apple.eawt._OpenAppHandler> { 266 void performUsing(com.apple.eawt._OpenAppHandler handler, _NativeEvent event) { 267 handler.handleOpenApp(); 268 } 269 } 270 271 class _AppReOpenedDispatcher extends _AppEventMultiplexor<AppReOpenedListener> { 272 void performOnListeners(final List<AppReOpenedListener> listeners, final _NativeEvent event) { 273 final AppReOpenedEvent e = new AppReOpenedEvent(); 274 for (final AppReOpenedListener listener : listeners) { 275 listener.appReOpened(e); 276 } 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 final List<L> _listeners = new ArrayList<L>(0); 419 boolean nativeListenerRegistered; 420 421 // called from AppKit Thread-0 422 void dispatch(final _NativeEvent event, final Object... args) { 423 // grab a local ref to the listeners 424 final List<L> localListeners; 425 synchronized (this) { 426 if (_listeners.size() == 0) return; 427 localListeners = new ArrayList<L>(_listeners); 428 } 429 430 EventQueue.invokeLater(new Runnable() { 431 public void run() { 432 performOnListeners(localListeners, event); 433 } 434 }); 435 } 436 437 synchronized void addListener(final L listener) { 438 if (!nativeListenerRegistered) { 439 registerNativeListener(); 440 nativeListenerRegistered = true; 441 } 442 _listeners.add(listener); 443 } 444 445 synchronized void removeListener(final L listener) { 446 _listeners.remove(listener); 447 } 448 449 abstract void performOnListeners(final List<L> listeners, final _NativeEvent event); 450 void registerNativeListener() { } 451 } 452 453 abstract class _BooleanAppEventMultiplexor<L, E> extends _AppEventMultiplexor<L> { 454 @Override 455 void performOnListeners(final List<L> listeners, final _NativeEvent event) { 456 final boolean isTrue = Boolean.TRUE.equals(event.get(0)); 457 final E e = createEvent(isTrue); 458 if (isTrue) { 459 for (final L listener : listeners) performTrueEventOn(listener, e); 460 } else { 461 for (final L listener : listeners) performFalseEventOn(listener, e); 462 } 463 } 464 465 abstract E createEvent(final boolean isTrue); 466 abstract void performTrueEventOn(final L listener, final E e); 467 abstract void performFalseEventOn(final L listener, final E e); 468 } 469 470 /* 471 * Ensures that setting and obtaining an app event handler is done in 472 * both a thread-safe manner, and that user code is performed on the 473 * AWT EventQueue thread. 474 * 475 * Allows native to blindly lob new events into the dispatcher, 476 * knowing that they will only be dispatched once a handler is set. 477 * 478 * User code is not (and should not be) run under any synchronized lock. 479 */ 480 abstract class _AppEventDispatcher<H> { 481 H _handler; 482 483 // called from AppKit Thread-0 484 void dispatch(final _NativeEvent event) { 485 EventQueue.invokeLater(new Runnable() { 486 public void run() { 487 // grab a local ref to the handler 488 final H localHandler; 489 synchronized (_AppEventDispatcher.this) { 490 localHandler = _handler; 491 } 492 493 // invoke the handler outside of the synchronized block 494 if (localHandler == null) { 495 performDefaultAction(event); 496 } else { 497 performUsing(localHandler, event); 498 } 499 } 500 }); 501 } 502 503 synchronized void setHandler(final H handler) { 504 this._handler = handler; 505 506 // if a new handler is installed, block addition of legacy ApplicationListeners 507 if (handler == legacyHandler) return; 508 legacyHandler.blockLegacyAPI(); 509 } 510 511 void performDefaultAction(final _NativeEvent event) { } // by default, do nothing 512 abstract void performUsing(final H handler, final _NativeEvent event); 513 } 514 515 abstract class _QueuingAppEventDispatcher<H> extends _AppEventDispatcher<H> { 516 List<_NativeEvent> queuedEvents = new LinkedList<_NativeEvent>(); 517 518 @Override 519 void dispatch(final _NativeEvent event) { 520 synchronized (this) { 521 // dispatcher hasn't started yet 522 if (queuedEvents != null) { 523 queuedEvents.add(event); 524 return; 525 } 526 } 527 528 super.dispatch(event); 529 } 530 531 synchronized void setHandler(final H handler) { 532 this._handler = handler; 533 534 // dispatch any events in the queue 535 if (queuedEvents != null) { 536 // grab a local ref to the queue, so the real one can be nulled out 537 final java.util.List<_NativeEvent> localQueuedEvents = queuedEvents; 538 queuedEvents = null; 539 if (localQueuedEvents.size() != 0) { 540 for (final _NativeEvent arg : localQueuedEvents) { 541 dispatch(arg); 542 } 543 } 544 } 545 546 // if a new handler is installed, block addition of legacy ApplicationListeners 547 if (handler == legacyHandler) return; 548 legacyHandler.blockLegacyAPI(); 549 } 550 } 551 }