Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  nsMacDockSupport.mm   Sprache: unbekannt

 
/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#import <Cocoa/Cocoa.h>
#include <CoreFoundation/CoreFoundation.h>
#include <signal.h>

#include "nsCocoaUtils.h"
#include "nsComponentManagerUtils.h"
#include "nsMacDockSupport.h"
#include "nsObjCExceptions.h"
#include "nsNativeThemeColors.h"
#include "nsString.h"
#include "imgLoader.h"
#include "MOZIconHelper.h"
#include "mozilla/SVGImageContext.h"
#include "nsISVGPaintContext.h"

NS_IMPL_ISUPPORTS(nsMacDockSupport, nsIMacDockSupport, nsITaskbarProgress)

// This view is used in the dock tile when we're downloading a file.
// It draws a progress bar that looks similar to the native progress bar on
// 10.12. This style of progress bar is not animated, unlike the pre-10.10
// progress bar look which had to redrawn multiple times per second.
@interface MOZProgressDockOverlayView : NSView {
  double mFractionValue;
}
@property double fractionValue;

@end

@implementation MOZProgressDockOverlayView

@synthesize fractionValue = mFractionValue;

- (void)drawRect:(NSRect)aRect {
  // Erase the background behind this view, i.e. cut a rectangle hole in the
  // icon.
  [[NSColor clearColor] set];
  NSRectFill(self.bounds);

  // Split the height of this view into four quarters. The middle two quarters
  // will be covered by the actual progress bar.
  CGFloat radius = self.bounds.size.height / 4;
  NSRect barBounds = NSInsetRect(self.bounds, 0, radius);

  NSBezierPath* path = [NSBezierPath bezierPathWithRoundedRect:barBounds
                                                       xRadius:radius
                                                       yRadius:radius];

  // Draw a grayish background first.
  [[NSColor colorWithDeviceWhite:0 alpha:0.1] setFill];
  [path fill];

  // Draw a fill in the control accent color for the progress part.
  NSRect progressFillRect = self.bounds;
  progressFillRect.size.width *= mFractionValue;
  [NSGraphicsContext saveGraphicsState];
  [NSBezierPath clipRect:progressFillRect];
  [[NSColor controlAccentColor] setFill];
  [path fill];
  [NSGraphicsContext restoreGraphicsState];

  // Add a shadowy stroke on top.
  [NSGraphicsContext saveGraphicsState];
  [path addClip];
  [[NSColor colorWithDeviceWhite:0 alpha:0.2] setStroke];
  path.lineWidth = barBounds.size.height / 10;
  [path stroke];
  [NSGraphicsContext restoreGraphicsState];
}

@end

nsMacDockSupport::nsMacDockSupport()
    : mHasBadgeImage(false),
      mDockTileWrapperView(nil),
      mDockBadgeView(nil),
      mProgressDockOverlayView(nil),
      mProgressState(STATE_NO_PROGRESS),
      mProgressFraction(0.0) {}

nsMacDockSupport::~nsMacDockSupport() {
  if (mDockTileWrapperView) {
    [mDockTileWrapperView release];
    mDockTileWrapperView = nil;
  }
  if (mDockBadgeView) {
    [mDockBadgeView release];
    mDockBadgeView = nil;
  }
  if (mProgressDockOverlayView) {
    [mProgressDockOverlayView release];
    mProgressDockOverlayView = nil;
  }
}

NS_IMETHODIMP
nsMacDockSupport::GetDockMenu(nsIStandaloneNativeMenu** aDockMenu) {
  nsCOMPtr<nsIStandaloneNativeMenu> dockMenu(mDockMenu);
  dockMenu.forget(aDockMenu);
  return NS_OK;
}

NS_IMETHODIMP
nsMacDockSupport::SetDockMenu(nsIStandaloneNativeMenu* aDockMenu) {
  mDockMenu = aDockMenu;
  return NS_OK;
}

NS_IMETHODIMP
nsMacDockSupport::ActivateApplication(bool aIgnoreOtherApplications) {
  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;

  [[NSApplication sharedApplication]
      activateIgnoringOtherApps:aIgnoreOtherApplications];
  return NS_OK;

  NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
}

NS_IMETHODIMP
nsMacDockSupport::SetBadgeText(const nsAString& aBadgeText) {
  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;

  NSDockTile* tile = [[NSApplication sharedApplication] dockTile];
  mBadgeText = aBadgeText;
  if (aBadgeText.IsEmpty()) {
    [tile setBadgeLabel:nil];
  } else {
    SetBadgeImage(nullptr, nullptr);

    [tile
        setBadgeLabel:[NSString
                          stringWithCharacters:reinterpret_cast<const unichar*>(
                                                   mBadgeText.get())
                                        length:mBadgeText.Length()]];
  }

  return NS_OK;

  NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
}

NS_IMETHODIMP
nsMacDockSupport::GetBadgeText(nsAString& aBadgeText) {
  aBadgeText = mBadgeText;
  return NS_OK;
}

NS_IMETHODIMP
nsMacDockSupport::SetBadgeImage(imgIContainer* aImage,
                                nsISVGPaintContext* aPaintContext) {
  if (!aImage) {
    mHasBadgeImage = false;
    if (mDockBadgeView) {
      mDockBadgeView.image = nullptr;
    }

    return UpdateDockTile();
  }

  if (!mBadgeText.IsEmpty()) {
    mBadgeText.Truncate();
    NSDockTile* tile = [[NSApplication sharedApplication] dockTile];
    [tile setBadgeLabel:nil];
  }

  NS_OBJC_BEGIN_TRY_BLOCK_RETURN

  mHasBadgeImage = true;
  BuildDockTile();

  mozilla::SVGImageContext svgContext;
  mozilla::SVGImageContext::MaybeStoreContextPaint(svgContext, aPaintContext,
                                                   aImage);
  NSImage* image =
      [MOZIconHelper iconImageFromImageContainer:aImage
                                        withSize:NSMakeSize(256, 256)
                                      svgContext:&svgContext
                                     scaleFactor:0.0];
  image.resizingMode = NSImageResizingModeStretch;
  mDockBadgeView.image = image;

  return UpdateDockTile();

  NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE)
}

NS_IMETHODIMP
nsMacDockSupport::SetProgressState(nsTaskbarProgressState aState,
                                   uint64_t aCurrentValue, uint64_t aMaxValue) {
  NS_ENSURE_ARG_RANGE(aState, 0, STATE_PAUSED);
  if (aState == STATE_NO_PROGRESS || aState == STATE_INDETERMINATE) {
    NS_ENSURE_TRUE(aCurrentValue == 0, NS_ERROR_INVALID_ARG);
    NS_ENSURE_TRUE(aMaxValue == 0, NS_ERROR_INVALID_ARG);
  }
  if (aCurrentValue > aMaxValue) {
    return NS_ERROR_ILLEGAL_VALUE;
  }

  mProgressState = aState;
  if (aMaxValue == 0) {
    mProgressFraction = 0;
  } else {
    mProgressFraction = (double)aCurrentValue / aMaxValue;
  }

  return UpdateDockTile();
}

void nsMacDockSupport::BuildDockTile() {
  if (!mDockTileWrapperView) {
    // Create the following NSView hierarchy:
    // * mDockTileWrapperView (NSView)
    //    * imageView (NSImageView) <- has the application icon
    //    * mDockBadgeView (NSImageView) <- has the dock badge
    //    * mProgressDockOverlayView (MOZProgressDockOverlayView) <- draws the
    //    progress bar

    mDockTileWrapperView =
        [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 32, 32)];
    mDockTileWrapperView.autoresizingMask =
        NSViewWidthSizable | NSViewHeightSizable;

    NSImageView* imageView =
        [[NSImageView alloc] initWithFrame:[mDockTileWrapperView bounds]];
    imageView.image = [NSImage imageNamed:@"NSApplicationIcon"];
    imageView.imageScaling = NSImageScaleAxesIndependently;
    imageView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
    [mDockTileWrapperView addSubview:imageView];

    mDockBadgeView =
        [[NSImageView alloc] initWithFrame:NSMakeRect(19.5, 19.5, 12, 12)];
    mDockBadgeView.imageScaling = NSImageScaleProportionallyUpOrDown;
    mDockBadgeView.autoresizingMask = NSViewMinXMargin | NSViewWidthSizable |
                                      NSViewMaxXMargin | NSViewMinYMargin |
                                      NSViewHeightSizable | NSViewMaxYMargin;
    [mDockTileWrapperView addSubview:mDockBadgeView];

    mProgressDockOverlayView = [[MOZProgressDockOverlayView alloc]
        initWithFrame:NSMakeRect(1, 3, 30, 4)];
    mProgressDockOverlayView.autoresizingMask =
        NSViewMinXMargin | NSViewWidthSizable | NSViewMaxXMargin |
        NSViewMinYMargin | NSViewHeightSizable | NSViewMaxYMargin;
    [mDockTileWrapperView addSubview:mProgressDockOverlayView];
  }
}

nsresult nsMacDockSupport::UpdateDockTile() {
  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;

  if (mProgressState == STATE_NORMAL || mProgressState == STATE_INDETERMINATE ||
      mHasBadgeImage) {
    BuildDockTile();

    if (NSApp.dockTile.contentView != mDockTileWrapperView) {
      NSApp.dockTile.contentView = mDockTileWrapperView;
    }

    mDockBadgeView.hidden = !mHasBadgeImage;

    if (mProgressState == STATE_NORMAL) {
      mProgressDockOverlayView.fractionValue = mProgressFraction;
      mProgressDockOverlayView.hidden = false;
    } else if (mProgressState == STATE_INDETERMINATE) {
      // Indeterminate states are rare. Just fill the entire progress bar in
      // that case.
      mProgressDockOverlayView.fractionValue = 1.0;
      mProgressDockOverlayView.hidden = false;
    } else {
      mProgressDockOverlayView.hidden = true;
    }
    [NSApp.dockTile display];
  } else if (NSApp.dockTile.contentView) {
    NSApp.dockTile.contentView = nil;
    [NSApp.dockTile display];
  }

  return NS_OK;

  NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
}

extern "C" {
// Private CFURL API used by the Dock.
CFPropertyListRef _CFURLCopyPropertyListRepresentation(CFURLRef url);
CFURLRef _CFURLCreateFromPropertyListRepresentation(
    CFAllocatorRef alloc, CFPropertyListRef pListRepresentation);
}  // extern "C"

namespace {

MOZ_RUNINIT const NSArray* const browserAppNames = [NSArray
    arrayWithObjects:@"Firefox.app", @"Firefox Beta.app",
                     @"Firefox Nightly.app", @"Safari.app", @"WebKit.app",
                     @"Google Chrome.app", @"Google Chrome Canary.app",
                     @"Chromium.app", @"Opera.app", nil];

constexpr NSString* const kDockDomainName = @"com.apple.dock";
// See https://developer.apple.com/documentation/devicemanagement/dock
constexpr NSString* const kDockPersistentAppsKey = @"persistent-apps";
// See
// https://developer.apple.com/documentation/devicemanagement/dock/staticitem
constexpr NSString* const kDockTileDataKey = @"tile-data";
constexpr NSString* const kDockFileDataKey = @"file-data";

NSArray* GetPersistentAppsFromDockPlist(NSDictionary* aDockPlist) {
  if (!aDockPlist) {
    return nil;
  }
  NSArray* persistentApps = [aDockPlist objectForKey:kDockPersistentAppsKey];
  if (![persistentApps isKindOfClass:[NSArray class]]) {
    return nil;
  }
  return persistentApps;
}

NSString* GetPathForApp(NSDictionary* aPersistantApp) {
  if (![aPersistantApp isKindOfClass:[NSDictionary class]]) {
    return nil;
  }
  NSDictionary* tileData = aPersistantApp[kDockTileDataKey];
  if (![tileData isKindOfClass:[NSDictionary class]]) {
    return nil;
  }
  NSDictionary* fileData = tileData[kDockFileDataKey];
  if (![fileData isKindOfClass:[NSDictionary class]]) {
    // Some special tiles may not have DockFileData but we can ignore those.
    return nil;
  }
  NSURL* url = CFBridgingRelease(
      _CFURLCreateFromPropertyListRepresentation(NULL, fileData));
  if (!url) {
    return nil;
  }
  return [url isFileURL] ? [url path] : nullptr;
}

// The only reliable way to get our changes to take effect seems to be to use
// `kill`.
void RefreshDock(NSDictionary* aDockPlist) {
  [[NSUserDefaults standardUserDefaults] setPersistentDomain:aDockPlist
                                                     forName:kDockDomainName];
  NSRunningApplication* dockApp = [[NSRunningApplication
      runningApplicationsWithBundleIdentifier:@"com.apple.dock"] firstObject];
  if (!dockApp) {
    return;
  }
  pid_t pid = [dockApp processIdentifier];
  if (pid > 0) {
    kill(pid, SIGTERM);
  }
}

}  // namespace

nsresult nsMacDockSupport::GetIsAppInDock(bool* aIsInDock) {
  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;

  *aIsInDock = false;

  NSDictionary* dockPlist = [[NSUserDefaults standardUserDefaults]
      persistentDomainForName:kDockDomainName];
  if (!dockPlist) {
    return NS_ERROR_FAILURE;
  }

  NSArray* persistentApps = GetPersistentAppsFromDockPlist(dockPlist);
  if (!persistentApps) {
    return NS_ERROR_FAILURE;
  }

  NSString* appPath = [[NSBundle mainBundle] bundlePath];

  for (id app in persistentApps) {
    NSString* persistentAppPath = GetPathForApp(app);
    if (persistentAppPath && [appPath isEqual:persistentAppPath]) {
      *aIsInDock = true;
      break;
    }
  }

  return NS_OK;

  NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
}

nsresult nsMacDockSupport::EnsureAppIsPinnedToDock(
    const nsAString& aAppPath, const nsAString& aAppToReplacePath,
    bool* aIsInDock) {
  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;

  MOZ_ASSERT(aAppPath != aAppToReplacePath || !aAppPath.IsEmpty());

  *aIsInDock = false;

  NSString* appPath = !aAppPath.IsEmpty() ? nsCocoaUtils::ToNSString(aAppPath)
                                          : [[NSBundle mainBundle] bundlePath];
  NSString* appToReplacePath = nsCocoaUtils::ToNSString(aAppToReplacePath);

  NSMutableDictionary* dockPlist = [NSMutableDictionary
      dictionaryWithDictionary:[[NSUserDefaults standardUserDefaults]
                                   persistentDomainForName:kDockDomainName]];
  if (!dockPlist) {
    return NS_ERROR_FAILURE;
  }

  NSMutableArray* persistentApps =
      [NSMutableArray arrayWithArray:GetPersistentAppsFromDockPlist(dockPlist)];
  if (!persistentApps) {
    return NS_ERROR_FAILURE;
  }

  // See the comment for this method in the .idl file for the strategy that we
  // use here to determine where to pin the app.
  NSUInteger preexistingAppIndex = NSNotFound;  // full path matches
  NSUInteger sameNameAppIndex = NSNotFound;     // app name matches only
  NSUInteger toReplaceAppIndex = NSNotFound;
  NSUInteger lastBrowserAppIndex = NSNotFound;
  for (NSUInteger index = 0; index < [persistentApps count]; ++index) {
    NSString* persistentAppPath =
        GetPathForApp([persistentApps objectAtIndex:index]);

    if ([persistentAppPath isEqualToString:appPath]) {
      preexistingAppIndex = index;
    } else if (appToReplacePath &&
               [persistentAppPath isEqualToString:appToReplacePath]) {
      toReplaceAppIndex = index;
    } else {
      NSString* appName = [appPath lastPathComponent];
      NSString* persistentAppName = [persistentAppPath lastPathComponent];

      if ([persistentAppName isEqual:appName]) {
        if ([appToReplacePath hasPrefix:@"/private/var/folders/"] &&
            [appToReplacePath containsString:@"/AppTranslocation/"] &&
            [persistentAppPath hasPrefix:@"/Volumes/"]) {
          // This is a special case when an app with the same name was
          // previously dragged and pinned from a quarantined DMG straight to
          // the Dock and an attempt is now made to pin the same named app to
          // the Dock. In this case we want to replace the currently pinned app
          // icon.
          toReplaceAppIndex = index;
        } else {
          sameNameAppIndex = index;
        }
      } else {
        if ([browserAppNames containsObject:persistentAppName]) {
          lastBrowserAppIndex = index;
        }
      }
    }
  }

  // Special cases where we're not going to add a new Dock tile:
  if (preexistingAppIndex != NSNotFound) {
    if (toReplaceAppIndex != NSNotFound) {
      [persistentApps removeObjectAtIndex:toReplaceAppIndex];
      [dockPlist setObject:persistentApps forKey:kDockPersistentAppsKey];
      RefreshDock(dockPlist);
    }
    *aIsInDock = true;
    return NS_OK;
  }

  // Create new tile:
  NSDictionary* newDockTile = nullptr;
  {
    NSURL* appUrl = [NSURL fileURLWithPath:appPath isDirectory:YES];
    NSDictionary* dict = CFBridgingRelease(
        _CFURLCopyPropertyListRepresentation((__bridge CFURLRef)appUrl));
    if (!dict) {
      return NS_ERROR_FAILURE;
    }
    NSDictionary* dockTileData =
        [NSDictionary dictionaryWithObject:dict forKey:kDockFileDataKey];
    if (dockTileData) {
      newDockTile = [NSDictionary dictionaryWithObject:dockTileData
                                                forKey:kDockTileDataKey];
    }
    if (!newDockTile) {
      return NS_ERROR_FAILURE;
    }
  }

  // Update the Dock:
  if (toReplaceAppIndex != NSNotFound) {
    [persistentApps replaceObjectAtIndex:toReplaceAppIndex
                              withObject:newDockTile];
  } else {
    NSUInteger index;
    if (sameNameAppIndex != NSNotFound) {
      index = sameNameAppIndex + 1;
    } else if (lastBrowserAppIndex != NSNotFound) {
      index = lastBrowserAppIndex + 1;
    } else {
      index = [persistentApps count];
    }
    [persistentApps insertObject:newDockTile atIndex:index];
  }
  [dockPlist setObject:persistentApps forKey:kDockPersistentAppsKey];
  RefreshDock(dockPlist);

  *aIsInDock = true;
  return NS_OK;

  NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
}

[ Dauer der Verarbeitung: 0.25 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge