1// Copyright (c) 2012 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/files/file_path.h"
12#include "base/logging.h"
13#include "base/mac/mac_logging.h"
14#include "base/mac/scoped_aedesc.h"
15#include "base/strings/sys_string_conversions.h"
16#include "url/gurl.h"
17
18namespace platform_util {
19
20void ShowItemInFolder(Profile* profile, const base::FilePath& full_path) {
21  DCHECK([NSThread isMainThread]);
22  NSString* path_string = base::SysUTF8ToNSString(full_path.value());
23  if (!path_string || ![[NSWorkspace sharedWorkspace] selectFile:path_string
24                                        inFileViewerRootedAtPath:nil])
25    LOG(WARNING) << "NSWorkspace failed to select file " << full_path.value();
26}
27
28// This function opens a file.  This doesn't use LaunchServices or NSWorkspace
29// because of two bugs:
30//  1. Incorrect app activation with com.apple.quarantine:
31//     http://crbug.com/32921
32//  2. Silent no-op for unassociated file types: http://crbug.com/50263
33// Instead, an AppleEvent is constructed to tell the Finder to open the
34// document.
35void OpenItem(Profile* profile, const base::FilePath& full_path) {
36  DCHECK([NSThread isMainThread]);
37  NSString* path_string = base::SysUTF8ToNSString(full_path.value());
38  if (!path_string)
39    return;
40
41  // Create the target of this AppleEvent, the Finder.
42  base::mac::ScopedAEDesc<AEAddressDesc> address;
43  const OSType finderCreatorCode = 'MACS';
44  OSErr status = AECreateDesc(typeApplSignature,  // type
45                              &finderCreatorCode,  // data
46                              sizeof(finderCreatorCode),  // dataSize
47                              address.OutPointer());  // result
48  if (status != noErr) {
49    OSSTATUS_LOG(WARNING, status) << "Could not create OpenItem() AE target";
50    return;
51  }
52
53  // Build the AppleEvent data structure that instructs Finder to open files.
54  base::mac::ScopedAEDesc<AppleEvent> theEvent;
55  status = AECreateAppleEvent(kCoreEventClass,  // theAEEventClass
56                              kAEOpenDocuments,  // theAEEventID
57                              address,  // target
58                              kAutoGenerateReturnID,  // returnID
59                              kAnyTransactionID,  // transactionID
60                              theEvent.OutPointer());  // result
61  if (status != noErr) {
62    OSSTATUS_LOG(WARNING, status) << "Could not create OpenItem() AE event";
63    return;
64  }
65
66  // Create the list of files (only ever one) to open.
67  base::mac::ScopedAEDesc<AEDescList> fileList;
68  status = AECreateList(NULL,  // factoringPtr
69                        0,  // factoredSize
70                        false,  // isRecord
71                        fileList.OutPointer());  // resultList
72  if (status != noErr) {
73    OSSTATUS_LOG(WARNING, status) << "Could not create OpenItem() AE file list";
74    return;
75  }
76
77  // Add the single path to the file list.  C-style cast to avoid both a
78  // static_cast and a const_cast to get across the toll-free bridge.
79  CFURLRef pathURLRef = (CFURLRef)[NSURL fileURLWithPath:path_string];
80  FSRef pathRef;
81  if (CFURLGetFSRef(pathURLRef, &pathRef)) {
82    status = AEPutPtr(fileList.OutPointer(),  // theAEDescList
83                      0,  // index
84                      typeFSRef,  // typeCode
85                      &pathRef,  // dataPtr
86                      sizeof(pathRef));  // dataSize
87    if (status != noErr) {
88      OSSTATUS_LOG(WARNING, status)
89          << "Could not add file path to AE list in OpenItem()";
90      return;
91    }
92  } else {
93    LOG(WARNING) << "Could not get FSRef for path URL in OpenItem()";
94    return;
95  }
96
97  // Attach the file list to the AppleEvent.
98  status = AEPutParamDesc(theEvent.OutPointer(),  // theAppleEvent
99                          keyDirectObject,  // theAEKeyword
100                          fileList);  // theAEDesc
101  if (status != noErr) {
102    OSSTATUS_LOG(WARNING, status)
103        << "Could not put the AE file list the path in OpenItem()";
104    return;
105  }
106
107  // Send the actual event.  Do not care about the reply.
108  base::mac::ScopedAEDesc<AppleEvent> reply;
109  status = AESend(theEvent,  // theAppleEvent
110                  reply.OutPointer(),  // reply
111                  kAENoReply + kAEAlwaysInteract,  // sendMode
112                  kAENormalPriority,  // sendPriority
113                  kAEDefaultTimeout,  // timeOutInTicks
114                  NULL, // idleProc
115                  NULL);  // filterProc
116  if (status != noErr) {
117    OSSTATUS_LOG(WARNING, status)
118        << "Could not send AE to Finder in OpenItem()";
119  }
120}
121
122void OpenExternal(Profile* profile, const GURL& url) {
123  DCHECK([NSThread isMainThread]);
124  NSString* url_string = base::SysUTF8ToNSString(url.spec());
125  NSURL* ns_url = [NSURL URLWithString:url_string];
126  if (!ns_url || ![[NSWorkspace sharedWorkspace] openURL:ns_url])
127    LOG(WARNING) << "NSWorkspace failed to open URL " << url;
128}
129
130gfx::NativeWindow GetTopLevel(gfx::NativeView view) {
131  return [view window];
132}
133
134gfx::NativeView GetParent(gfx::NativeView view) {
135  return nil;
136}
137
138bool IsWindowActive(gfx::NativeWindow window) {
139  return [window isKeyWindow] || [window isMainWindow];
140}
141
142void ActivateWindow(gfx::NativeWindow window) {
143  [window makeKeyAndOrderFront:nil];
144}
145
146bool IsVisible(gfx::NativeView view) {
147  // A reasonable approximation of how you'd expect this to behave.
148  return (view &&
149          ![view isHiddenOrHasHiddenAncestor] &&
150          [view window] &&
151          [[view window] isVisible]);
152}
153
154bool IsSwipeTrackingFromScrollEventsEnabled() {
155  SEL selector = @selector(isSwipeTrackingFromScrollEventsEnabled);
156  return [NSEvent respondsToSelector:selector]
157      && [NSEvent performSelector:selector];
158}
159
160}  // namespace platform_util
161