michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsToolkit.h" michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: extern "C" { michael@0: #include michael@0: } michael@0: #include michael@0: #include michael@0: michael@0: #import michael@0: #import michael@0: #import michael@0: michael@0: #include "nsCocoaUtils.h" michael@0: #include "nsObjCExceptions.h" michael@0: michael@0: #include "nsGkAtoms.h" michael@0: #include "nsIRollupListener.h" michael@0: #include "nsIWidget.h" michael@0: #include "nsBaseWidget.h" michael@0: michael@0: #include "nsIObserverService.h" michael@0: #include "nsIServiceManager.h" michael@0: michael@0: #include "mozilla/Preferences.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: static io_connect_t gRootPort = MACH_PORT_NULL; michael@0: michael@0: nsToolkit* nsToolkit::gToolkit = nullptr; michael@0: michael@0: nsToolkit::nsToolkit() michael@0: : mSleepWakeNotificationRLS(nullptr) michael@0: , mEventTapPort(nullptr) michael@0: , mEventTapRLS(nullptr) michael@0: { michael@0: MOZ_COUNT_CTOR(nsToolkit); michael@0: RegisterForSleepWakeNotifcations(); michael@0: } michael@0: michael@0: nsToolkit::~nsToolkit() michael@0: { michael@0: MOZ_COUNT_DTOR(nsToolkit); michael@0: RemoveSleepWakeNotifcations(); michael@0: UnregisterAllProcessMouseEventHandlers(); michael@0: } michael@0: michael@0: void michael@0: nsToolkit::PostSleepWakeNotification(const char* aNotification) michael@0: { michael@0: nsCOMPtr observerService = do_GetService("@mozilla.org/observer-service;1"); michael@0: if (observerService) michael@0: observerService->NotifyObservers(nullptr, aNotification, nullptr); michael@0: } michael@0: michael@0: // http://developer.apple.com/documentation/DeviceDrivers/Conceptual/IOKitFundamentals/PowerMgmt/chapter_10_section_3.html michael@0: static void ToolkitSleepWakeCallback(void *refCon, io_service_t service, natural_t messageType, void * messageArgument) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: switch (messageType) michael@0: { michael@0: case kIOMessageSystemWillSleep: michael@0: // System is going to sleep now. michael@0: nsToolkit::PostSleepWakeNotification(NS_WIDGET_SLEEP_OBSERVER_TOPIC); michael@0: ::IOAllowPowerChange(gRootPort, (long)messageArgument); michael@0: break; michael@0: michael@0: case kIOMessageCanSystemSleep: michael@0: // In this case, the computer has been idle for several minutes michael@0: // and will sleep soon so you must either allow or cancel michael@0: // this notification. Important: if you don’t respond, there will michael@0: // be a 30-second timeout before the computer sleeps. michael@0: // In Mozilla's case, we always allow sleep. michael@0: ::IOAllowPowerChange(gRootPort,(long)messageArgument); michael@0: break; michael@0: michael@0: case kIOMessageSystemHasPoweredOn: michael@0: // Handle wakeup. michael@0: nsToolkit::PostSleepWakeNotification(NS_WIDGET_WAKE_OBSERVER_TOPIC); michael@0: break; michael@0: } michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: nsresult michael@0: nsToolkit::RegisterForSleepWakeNotifcations() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: IONotificationPortRef notifyPortRef; michael@0: michael@0: NS_ASSERTION(!mSleepWakeNotificationRLS, "Already registered for sleep/wake"); michael@0: michael@0: gRootPort = ::IORegisterForSystemPower(0, ¬ifyPortRef, ToolkitSleepWakeCallback, &mPowerNotifier); michael@0: if (gRootPort == MACH_PORT_NULL) { michael@0: NS_ERROR("IORegisterForSystemPower failed"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: mSleepWakeNotificationRLS = ::IONotificationPortGetRunLoopSource(notifyPortRef); michael@0: ::CFRunLoopAddSource(::CFRunLoopGetCurrent(), michael@0: mSleepWakeNotificationRLS, michael@0: kCFRunLoopDefaultMode); michael@0: michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: void michael@0: nsToolkit::RemoveSleepWakeNotifcations() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (mSleepWakeNotificationRLS) { michael@0: ::IODeregisterForSystemPower(&mPowerNotifier); michael@0: ::CFRunLoopRemoveSource(::CFRunLoopGetCurrent(), michael@0: mSleepWakeNotificationRLS, michael@0: kCFRunLoopDefaultMode); michael@0: michael@0: mSleepWakeNotificationRLS = nullptr; michael@0: } michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: // Converts aPoint from the CoreGraphics "global display coordinate" system michael@0: // (which includes all displays/screens and has a top-left origin) to its michael@0: // (presumed) Cocoa counterpart (assumed to be the same as the "screen michael@0: // coordinates" system), which has a bottom-left origin. michael@0: static NSPoint ConvertCGGlobalToCocoaScreen(CGPoint aPoint) michael@0: { michael@0: NSPoint cocoaPoint; michael@0: cocoaPoint.x = aPoint.x; michael@0: cocoaPoint.y = nsCocoaUtils::FlippedScreenY(aPoint.y); michael@0: return cocoaPoint; michael@0: } michael@0: michael@0: // Since our event tap is "listen only", events arrive here a little after michael@0: // they've already been processed. michael@0: static CGEventRef EventTapCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; michael@0: michael@0: if ((type == kCGEventTapDisabledByUserInput) || michael@0: (type == kCGEventTapDisabledByTimeout)) michael@0: return event; michael@0: if ([NSApp isActive]) michael@0: return event; michael@0: michael@0: nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener(); michael@0: NS_ENSURE_TRUE(rollupListener, event); michael@0: nsCOMPtr rollupWidget = rollupListener->GetRollupWidget(); michael@0: if (!rollupWidget) michael@0: return event; michael@0: michael@0: // Don't bother with rightMouseDown events here -- because of the delay, michael@0: // we'll end up closing browser context menus that we just opened. Since michael@0: // these events usually raise a context menu, we'll handle them by hooking michael@0: // the @"com.apple.HIToolbox.beginMenuTrackingNotification" distributed michael@0: // notification (in nsAppShell.mm's AppShellDelegate). michael@0: if (type == kCGEventRightMouseDown) michael@0: return event; michael@0: NSWindow *ctxMenuWindow = (NSWindow*) rollupWidget->GetNativeData(NS_NATIVE_WINDOW); michael@0: if (!ctxMenuWindow) michael@0: return event; michael@0: NSPoint screenLocation = ConvertCGGlobalToCocoaScreen(CGEventGetLocation(event)); michael@0: // Don't roll up the rollup widget if our mouseDown happens over it (doing michael@0: // so would break the corresponding context menu). michael@0: if (NSPointInRect(screenLocation, [ctxMenuWindow frame])) michael@0: return event; michael@0: rollupListener->Rollup(0, nullptr, nullptr); michael@0: return event; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NULL); michael@0: } michael@0: michael@0: // Cocoa Firefox's use of custom context menus requires that we explicitly michael@0: // handle mouse events from other processes that the OS handles michael@0: // "automatically" for native context menus -- mouseMoved events so that michael@0: // right-click context menus work properly when our browser doesn't have the michael@0: // focus (bmo bug 368077), and mouseDown events so that our browser can michael@0: // dismiss a context menu when a mouseDown happens in another process (bmo michael@0: // bug 339945). michael@0: void michael@0: nsToolkit::RegisterForAllProcessMouseEvents() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (getenv("MOZ_DEBUG")) michael@0: return; michael@0: michael@0: // Don't do this for apps that (like Camino) use native context menus. michael@0: #ifdef MOZ_USE_NATIVE_POPUP_WINDOWS michael@0: return; michael@0: #endif /* MOZ_USE_NATIVE_POPUP_WINDOWS */ michael@0: michael@0: if (!mEventTapRLS) { michael@0: // Using an event tap for mouseDown events (instead of installing a michael@0: // handler for them on the EventMonitor target) works around an Apple michael@0: // bug that causes OS menus (like the Clock menu) not to work properly michael@0: // on OS X 10.4.X and below (bmo bug 381448). michael@0: // We install our event tap "listen only" to get around yet another Apple michael@0: // bug -- when we install it as an event filter on any kind of mouseDown michael@0: // event, that kind of event stops working in the main menu, and usually michael@0: // mouse event processing stops working in all apps in the current login michael@0: // session (so the entire OS appears to be hung)! The downside of michael@0: // installing listen-only is that events arrive at our handler slightly michael@0: // after they've already been processed. michael@0: mEventTapPort = CGEventTapCreate(kCGSessionEventTap, michael@0: kCGHeadInsertEventTap, michael@0: kCGEventTapOptionListenOnly, michael@0: CGEventMaskBit(kCGEventLeftMouseDown) michael@0: | CGEventMaskBit(kCGEventRightMouseDown) michael@0: | CGEventMaskBit(kCGEventOtherMouseDown), michael@0: EventTapCallback, michael@0: nullptr); michael@0: if (!mEventTapPort) michael@0: return; michael@0: mEventTapRLS = CFMachPortCreateRunLoopSource(nullptr, mEventTapPort, 0); michael@0: if (!mEventTapRLS) { michael@0: CFRelease(mEventTapPort); michael@0: mEventTapPort = nullptr; michael@0: return; michael@0: } michael@0: CFRunLoopAddSource(CFRunLoopGetCurrent(), mEventTapRLS, kCFRunLoopDefaultMode); michael@0: } michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: void michael@0: nsToolkit::UnregisterAllProcessMouseEventHandlers() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (mEventTapRLS) { michael@0: CFRunLoopRemoveSource(CFRunLoopGetCurrent(), mEventTapRLS, michael@0: kCFRunLoopDefaultMode); michael@0: CFRelease(mEventTapRLS); michael@0: mEventTapRLS = nullptr; michael@0: } michael@0: if (mEventTapPort) { michael@0: // mEventTapPort must be invalidated as well as released. Otherwise the michael@0: // event tap doesn't get destroyed until the browser process ends (it michael@0: // keeps showing up in the list returned by CGGetEventTapList()). michael@0: CFMachPortInvalidate(mEventTapPort); michael@0: CFRelease(mEventTapPort); michael@0: mEventTapPort = nullptr; michael@0: } michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: // Return the nsToolkit instance. If a toolkit does not yet exist, then one michael@0: // will be created. michael@0: // static michael@0: nsToolkit* nsToolkit::GetToolkit() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; michael@0: michael@0: if (!gToolkit) { michael@0: gToolkit = new nsToolkit(); michael@0: } michael@0: michael@0: return gToolkit; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(nullptr); michael@0: } michael@0: michael@0: // An alternative to [NSObject poseAsClass:] that isn't deprecated on OS X michael@0: // Leopard and is available to 64-bit binaries on Leopard and above. Based on michael@0: // ideas and code from http://www.cocoadev.com/index.pl?MethodSwizzling. michael@0: // Since the Method type becomes an opaque type as of Objective-C 2.0, we'll michael@0: // have to switch to using accessor methods like method_exchangeImplementations() michael@0: // when we build 64-bit binaries that use Objective-C 2.0 (on and for Leopard michael@0: // and above). michael@0: // michael@0: // Be aware that, if aClass doesn't have an orgMethod selector but one of its michael@0: // superclasses does, the method substitution will (in effect) take place in michael@0: // that superclass (rather than in aClass itself). The substitution has michael@0: // effect on the class where it takes place and all of that class's michael@0: // subclasses. In order for method swizzling to work properly, posedMethod michael@0: // needs to be unique in the class where the substitution takes place and all michael@0: // of its subclasses. michael@0: nsresult nsToolkit::SwizzleMethods(Class aClass, SEL orgMethod, SEL posedMethod, michael@0: bool classMethods) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: Method original = nil; michael@0: Method posed = nil; michael@0: michael@0: if (classMethods) { michael@0: original = class_getClassMethod(aClass, orgMethod); michael@0: posed = class_getClassMethod(aClass, posedMethod); michael@0: } else { michael@0: original = class_getInstanceMethod(aClass, orgMethod); michael@0: posed = class_getInstanceMethod(aClass, posedMethod); michael@0: } michael@0: michael@0: if (!original || !posed) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: method_exchangeImplementations(original, posed); michael@0: michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: }