platform_util_mac.mm revision ddb351dbec246cf1fab5ec20d2d5520909041de1
1// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/platform_util.h"
6
7#include <Carbon/Carbon.h>
8#import <Cocoa/Cocoa.h>
9#include <CoreServices/CoreServices.h>
10
11#include "base/file_path.h"
12#include "base/logging.h"
13#include "base/mac/scoped_aedesc.h"
14#include "base/sys_string_conversions.h"
15#include "googleurl/src/gurl.h"
16#include "grit/generated_resources.h"
17#include "ui/base/l10n/l10n_util.h"
18#include "ui/base/l10n/l10n_util_mac.h"
19
20namespace platform_util {
21
22void ShowItemInFolder(const FilePath& full_path) {
23  DCHECK_EQ([NSThread currentThread], [NSThread mainThread]);
24  NSString* path_string = base::SysUTF8ToNSString(full_path.value());
25  if (!path_string || ![[NSWorkspace sharedWorkspace] selectFile:path_string
26                                        inFileViewerRootedAtPath:nil])
27    LOG(WARNING) << "NSWorkspace failed to select file " << full_path.value();
28}
29
30// This function opens a file.  This doesn't use LaunchServices or NSWorkspace
31// because of two bugs:
32//  1. Incorrect app activation with com.apple.quarantine:
33//     http://crbug.com/32921
34//  2. Silent no-op for unassociated file types: http://crbug.com/50263
35// Instead, an AppleEvent is constructed to tell the Finder to open the
36// document.
37void OpenItem(const FilePath& full_path) {
38  DCHECK_EQ([NSThread currentThread], [NSThread mainThread]);
39  NSString* path_string = base::SysUTF8ToNSString(full_path.value());
40  if (!path_string)
41    return;
42
43  OSErr status;
44
45  // Create the target of this AppleEvent, the Finder.
46  base::mac::ScopedAEDesc<AEAddressDesc> address;
47  const OSType finderCreatorCode = 'MACS';
48  status = AECreateDesc(typeApplSignature,  // type
49                        &finderCreatorCode,  // data
50                        sizeof(finderCreatorCode),  // dataSize
51                        address.OutPointer());  // result
52  if (status != noErr) {
53    LOG(WARNING) << "Could not create OpenItem() AE target";
54    return;
55  }
56
57  // Build the AppleEvent data structure that instructs Finder to open files.
58  base::mac::ScopedAEDesc<AppleEvent> theEvent;
59  status = AECreateAppleEvent(kCoreEventClass,  // theAEEventClass
60                              kAEOpenDocuments,  // theAEEventID
61                              address,  // target
62                              kAutoGenerateReturnID,  // returnID
63                              kAnyTransactionID,  // transactionID
64                              theEvent.OutPointer());  // result
65  if (status != noErr) {
66    LOG(WARNING) << "Could not create OpenItem() AE event";
67    return;
68  }
69
70  // Create the list of files (only ever one) to open.
71  base::mac::ScopedAEDesc<AEDescList> fileList;
72  status = AECreateList(NULL,  // factoringPtr
73                        0,  // factoredSize
74                        false,  // isRecord
75                        fileList.OutPointer());  // resultList
76  if (status != noErr) {
77    LOG(WARNING) << "Could not create OpenItem() AE file list";
78    return;
79  }
80
81  // Add the single path to the file list.  C-style cast to avoid both a
82  // static_cast and a const_cast to get across the toll-free bridge.
83  CFURLRef pathURLRef = (CFURLRef)[NSURL fileURLWithPath:path_string];
84  FSRef pathRef;
85  if (CFURLGetFSRef(pathURLRef, &pathRef)) {
86    status = AEPutPtr(fileList.OutPointer(),  // theAEDescList
87                      0,  // index
88                      typeFSRef,  // typeCode
89                      &pathRef,  // dataPtr
90                      sizeof(pathRef));  // dataSize
91    if (status != noErr) {
92      LOG(WARNING) << "Could not add file path to AE list in OpenItem()";
93      return;
94    }
95  } else {
96    LOG(WARNING) << "Could not get FSRef for path URL in OpenItem()";
97    return;
98  }
99
100  // Attach the file list to the AppleEvent.
101  status = AEPutParamDesc(theEvent.OutPointer(),  // theAppleEvent
102                          keyDirectObject,  // theAEKeyword
103                          fileList);  // theAEDesc
104  if (status != noErr) {
105    LOG(WARNING) << "Could not put the AE file list the path in OpenItem()";
106    return;
107  }
108
109  // Send the actual event.  Do not care about the reply.
110  base::mac::ScopedAEDesc<AppleEvent> reply;
111  status = AESend(theEvent,  // theAppleEvent
112                  reply.OutPointer(),  // reply
113                  kAENoReply + kAEAlwaysInteract,  // sendMode
114                  kAENormalPriority,  // sendPriority
115                  kAEDefaultTimeout,  // timeOutInTicks
116                  NULL, // idleProc
117                  NULL);  // filterProc
118  if (status != noErr) {
119    LOG(WARNING) << "Could not send AE to Finder in OpenItem()";
120  }
121}
122
123void OpenExternal(const GURL& url) {
124  DCHECK_EQ([NSThread currentThread], [NSThread mainThread]);
125  NSString* url_string = base::SysUTF8ToNSString(url.spec());
126  NSURL* ns_url = [NSURL URLWithString:url_string];
127  if (!ns_url || ![[NSWorkspace sharedWorkspace] openURL:ns_url])
128    LOG(WARNING) << "NSWorkspace failed to open URL " << url;
129}
130
131gfx::NativeWindow GetTopLevel(gfx::NativeView view) {
132  return [view window];
133}
134
135gfx::NativeView GetParent(gfx::NativeView view) {
136  return nil;
137}
138
139bool IsWindowActive(gfx::NativeWindow window) {
140  return [window isKeyWindow] || [window isMainWindow];
141}
142
143void ActivateWindow(gfx::NativeWindow window) {
144  [window makeKeyAndOrderFront:nil];
145}
146
147bool IsVisible(gfx::NativeView view) {
148  // A reasonable approximation of how you'd expect this to behave.
149  return (view &&
150          ![view isHiddenOrHasHiddenAncestor] &&
151          [view window] &&
152          [[view window] isVisible]);
153}
154
155void SimpleErrorBox(gfx::NativeWindow parent,
156                    const string16& title,
157                    const string16& message) {
158  // Ignore the title; it's the window title on other platforms and ignorable.
159  NSAlert* alert = [[[NSAlert alloc] init] autorelease];
160  [alert addButtonWithTitle:l10n_util::GetNSString(IDS_OK)];
161  [alert setMessageText:base::SysUTF16ToNSString(message)];
162  [alert setAlertStyle:NSWarningAlertStyle];
163  [alert runModal];
164}
165
166bool SimpleYesNoBox(gfx::NativeWindow parent,
167                    const string16& title,
168                    const string16& message) {
169  // Ignore the title; it's the window title on other platforms and ignorable.
170  NSAlert* alert = [[[NSAlert alloc] init] autorelease];
171  [alert setMessageText:base::SysUTF16ToNSString(message)];
172  [alert setAlertStyle:NSWarningAlertStyle];
173
174  [alert addButtonWithTitle:
175      l10n_util::GetNSString(IDS_CONFIRM_MESSAGEBOX_YES_BUTTON_LABEL)];
176  [alert addButtonWithTitle:
177      l10n_util::GetNSString(IDS_CONFIRM_MESSAGEBOX_NO_BUTTON_LABEL)];
178
179  NSInteger result = [alert runModal];
180  return result == NSAlertFirstButtonReturn;
181}
182
183std::string GetVersionStringModifier() {
184#if defined(GOOGLE_CHROME_BUILD)
185  // Use the main application bundle and not the framework bundle. Keystone
186  // keys don't live in the framework.
187  NSBundle* bundle = [NSBundle mainBundle];
188  NSString* channel = [bundle objectForInfoDictionaryKey:@"KSChannelID"];
189
190  // Only ever return "", "unknown", "beta", "dev", or "canary" in a branded
191  // build.
192  if (![bundle objectForInfoDictionaryKey:@"KSProductID"]) {
193    // This build is not Keystone-enabled, it can't have a channel.
194    channel = @"unknown";
195  } else if (!channel) {
196    // For the stable channel, KSChannelID is not set.
197    channel = @"";
198  } else if ([channel isEqual:@"beta"] ||
199             [channel isEqual:@"dev"] ||
200             [channel isEqual:@"canary"]) {
201    // do nothing.
202  } else {
203    channel = @"unknown";
204  }
205
206  return base::SysNSStringToUTF8(channel);
207#else
208  return std::string();
209#endif
210}
211
212bool CanSetAsDefaultBrowser() {
213  return GetVersionStringModifier().compare("canary") != 0;
214}
215
216}  // namespace platform_util
217