1 // Copyright (c) 2008-2009 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.
5 #include "base/mac_util.h"
7 #import <Cocoa/Cocoa.h>
9 #include "base/file_path.h"
10 #include "base/logging.h"
11 #include "base/message_loop.h"
12 #include "base/scoped_cftyperef.h"
13 #include "base/sys_string_conversions.h"
17 std::string PathFromFSRef(const FSRef& ref) {
18 scoped_cftyperef<CFURLRef> url(
19 CFURLCreateFromFSRef(kCFAllocatorDefault, &ref));
20 NSString *path_string = [(NSURL *)url.get() path];
21 return [path_string fileSystemRepresentation];
24 bool FSRefFromPath(const std::string& path, FSRef* ref) {
25 OSStatus status = FSPathMakeRef((const UInt8*)path.c_str(),
27 return status == noErr;
30 // Adapted from http://developer.apple.com/carbon/tipsandtricks.html#AmIBundled
32 ProcessSerialNumber psn = {0, kCurrentProcess};
35 if (GetProcessBundleLocation(&psn, &fsref) != noErr)
39 if (FSGetCatalogInfo(&fsref, kFSCatInfoNodeFlags, &info,
40 NULL, NULL, NULL) != noErr) {
44 return info.nodeFlags & kFSNodeIsDirectoryMask;
47 bool IsBackgroundOnlyProcess() {
48 // This function really does want to examine NSBundle's idea of the main
49 // bundle dictionary, and not the overriden MainAppBundle. It needs to look
50 // at the actual running .app's Info.plist to access its LSUIElement
52 NSDictionary* info_dictionary = [[NSBundle mainBundle] infoDictionary];
53 return [[info_dictionary objectForKey:@"LSUIElement"] boolValue] != NO;
56 // No threading worries since NSBundle isn't thread safe.
57 static NSBundle* g_override_app_bundle = nil;
59 NSBundle* MainAppBundle() {
60 if (g_override_app_bundle)
61 return g_override_app_bundle;
62 return [NSBundle mainBundle];
65 FilePath MainAppBundlePath() {
66 NSBundle* bundle = MainAppBundle();
67 return FilePath([[bundle bundlePath] fileSystemRepresentation]);
70 void SetOverrideAppBundle(NSBundle* bundle) {
71 if (bundle != g_override_app_bundle) {
72 [g_override_app_bundle release];
73 g_override_app_bundle = [bundle retain];
77 void SetOverrideAppBundlePath(const FilePath& file_path) {
78 NSString* path = base::SysUTF8ToNSString(file_path.value());
79 NSBundle* bundle = [NSBundle bundleWithPath:path];
80 CHECK(bundle) << "Failed to load the bundle at " << file_path.value();
82 SetOverrideAppBundle(bundle);
85 OSType CreatorCodeForCFBundleRef(CFBundleRef bundle) {
86 OSType creator = kUnknownType;
87 CFBundleGetPackageInfo(bundle, NULL, &creator);
91 OSType CreatorCodeForApplication() {
92 CFBundleRef bundle = CFBundleGetMainBundle();
96 return CreatorCodeForCFBundleRef(bundle);
99 bool GetUserDirectory(NSSearchPathDirectory directory, FilePath* result) {
102 NSSearchPathForDirectoriesInDomains(directory, NSUserDomainMask, YES);
103 if ([dirs count] < 1) {
106 NSString* path = [dirs objectAtIndex:0];
107 *result = FilePath([path fileSystemRepresentation]);
111 FilePath GetUserLibraryPath() {
112 FilePath user_library_path;
113 if (!GetUserDirectory(NSLibraryDirectory, &user_library_path)) {
114 LOG(WARNING) << "Could not get user library path";
116 return user_library_path;
119 CGColorSpaceRef GetSRGBColorSpace() {
120 // Leaked. That's OK, it's scoped to the lifetime of the application.
121 static CGColorSpaceRef g_color_space_sRGB =
122 CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
123 LOG_IF(ERROR, !g_color_space_sRGB) << "Couldn't get the sRGB color space";
124 return g_color_space_sRGB;
127 CGColorSpaceRef GetSystemColorSpace() {
128 // Leaked. That's OK, it's scoped to the lifetime of the application.
129 // Try to get the main display's color space.
130 static CGColorSpaceRef g_system_color_space =
131 CGDisplayCopyColorSpace(CGMainDisplayID());
133 if (!g_system_color_space) {
134 // Use a generic RGB color space. This is better than nothing.
135 g_system_color_space = CGColorSpaceCreateDeviceRGB();
137 if (g_system_color_space) {
139 "Couldn't get the main display's color space, using generic";
141 LOG(ERROR) << "Couldn't get any color space";
145 return g_system_color_space;
148 // a count of currently outstanding requests for full screen mode from browser
149 // windows, plugins, etc.
150 static int g_full_screen_requests = 0;
152 // Add a request for full screen mode. If the menu bar is not currently
153 // hidden, hide it. Must be called on main thread.
154 void RequestFullScreen() {
155 DCHECK_GE(g_full_screen_requests, 0);
156 if (g_full_screen_requests == 0)
157 SetSystemUIMode(kUIModeAllSuppressed, kUIOptionAutoShowMenuBar);
158 ++g_full_screen_requests;
161 // Release a request for full screen mode. If there are no other outstanding
162 // requests, show the menu bar. Must be called on main thread.
163 void ReleaseFullScreen() {
164 DCHECK_GT(g_full_screen_requests, 0);
165 --g_full_screen_requests;
166 if (g_full_screen_requests == 0)
167 SetSystemUIMode(kUIModeNormal, 0);
170 void SetCursorVisibility(bool visible) {
177 void GrabWindowSnapshot(NSWindow* window,
178 std::vector<unsigned char>* png_representation) {
179 // Make sure to grab the "window frame" view so we get current tab +
181 NSView* view = [[window contentView] superview];
182 NSBitmapImageRep* rep =
183 [view bitmapImageRepForCachingDisplayInRect:[view bounds]];
184 [view cacheDisplayInRect:[view bounds] toBitmapImageRep:rep];
185 NSData* data = [rep representationUsingType:NSPNGFileType properties:nil];
186 const unsigned char* buf = static_cast<const unsigned char*>([data bytes]);
187 NSUInteger length = [data length];
188 if (buf != NULL && length > 0){
189 png_representation->assign(buf, buf + length);
190 DCHECK(png_representation->size() > 0);
194 void ActivateProcess(pid_t pid) {
195 ProcessSerialNumber process;
196 OSStatus status = GetProcessForPID(pid, &process);
197 if (status == noErr) {
198 SetFrontProcess(&process);
200 LOG(WARNING) << "Unable to get process for pid " << pid;
204 // Takes a path to an (executable) binary and tries to provide the path to an
205 // application bundle containing it. It takes the outermost bundle that it can
206 // find (so for "/Foo/Bar.app/.../Baz.app/..." it produces "/Foo/Bar.app").
207 // |exec_name| - path to the binary
208 // returns - path to the application bundle, or empty on error
209 FilePath GetAppBundlePath(const FilePath& exec_name) {
210 const char kExt[] = ".app";
211 const size_t kExtLength = arraysize(kExt) - 1;
213 // Split the path into components.
214 std::vector<std::string> components;
215 exec_name.GetComponents(&components);
217 // It's an error if we don't get any components.
218 if (!components.size())
221 // Don't prepend '/' to the first component.
222 std::vector<std::string>::const_iterator it = components.begin();
223 std::string bundle_name = *it;
224 DCHECK(it->length() > 0);
225 // If the first component ends in ".app", we're already done.
226 if (it->length() > kExtLength &&
227 !it->compare(it->length() - kExtLength, kExtLength, kExt, kExtLength))
228 return FilePath(bundle_name);
230 // The first component may be "/" or "//", etc. Only append '/' if it doesn't
231 // already end in '/'.
232 if (bundle_name[bundle_name.length() - 1] != '/')
235 // Go through the remaining components.
236 for (++it; it != components.end(); ++it) {
237 DCHECK(it->length() > 0);
241 // If the current component ends in ".app", we're done.
242 if (it->length() > kExtLength &&
243 !it->compare(it->length() - kExtLength, kExtLength, kExt, kExtLength))
244 return FilePath(bundle_name);
246 // Separate this component from the next one.
253 bool SetFileBackupExclusion(const FilePath& file_path, bool exclude) {
255 [NSString stringWithUTF8String:file_path.value().c_str()];
257 // If being asked to exclude something in a tmp directory, just lie and say it
258 // was done. TimeMachine will already ignore tmp directories. This keeps the
259 // temporary profiles used by unittests from being added to the exclude list.
260 // Otherwise, as /Library/Preferences/com.apple.TimeMachine.plist grows the
261 // bots slow down due to reading/writing all the temporary profiles used over
264 NSString* tmpDir = NSTemporaryDirectory();
265 // Make sure the temp dir is terminated with a slash
266 if (tmpDir && ![tmpDir hasSuffix:@"/"])
267 tmpDir = [tmpDir stringByAppendingString:@"/"];
268 // '/var' is a link to '/private/var', make sure to check both forms.
269 NSString* privateTmpDir = nil;
270 if ([tmpDir hasPrefix:@"/var/"])
271 privateTmpDir = [@"/private" stringByAppendingString:tmpDir];
273 if ((tmpDir && [filePath hasPrefix:tmpDir]) ||
274 (privateTmpDir && [filePath hasPrefix:privateTmpDir]) ||
275 [filePath hasPrefix:@"/tmp/"] ||
276 [filePath hasPrefix:@"/var/tmp/"] ||
277 [filePath hasPrefix:@"/private/tmp/"] ||
278 [filePath hasPrefix:@"/private/var/tmp/"]) {
282 NSURL* url = [NSURL fileURLWithPath:filePath];
283 // Note that we always set CSBackupSetItemExcluded's excludeByPath param
284 // to true. This prevents a problem with toggling the setting: if the file
285 // is excluded with excludeByPath set to true then excludeByPath must
286 // also be true when un-excluding the file, otherwise the un-excluding
289 CSBackupSetItemExcluded((CFURLRef)url, exclude, true) == noErr;
291 LOG(WARNING) << "Failed to set backup excluson for file '"
292 << file_path.value().c_str() << "'. Continuing.";
296 CFTypeRef GetValueFromDictionary(CFDictionaryRef dict,
298 CFTypeID expected_type) {
299 CFTypeRef value = CFDictionaryGetValue(dict, key);
303 if (CFGetTypeID(value) != expected_type) {
304 scoped_cftyperef<CFStringRef> expected_type_ref(
305 CFCopyTypeIDDescription(expected_type));
306 scoped_cftyperef<CFStringRef> actual_type_ref(
307 CFCopyTypeIDDescription(CFGetTypeID(value)));
308 LOG(WARNING) << "Expected value for key "
309 << base::SysCFStringRefToUTF8(key)
311 << base::SysCFStringRefToUTF8(expected_type_ref)
313 << base::SysCFStringRefToUTF8(actual_type_ref)
321 } // namespace mac_util