OLD | NEW |
| (Empty) |
1 // Copyright (c) 2010 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 "base/mac_util.h" | |
6 | |
7 #import <Cocoa/Cocoa.h> | |
8 | |
9 #include "base/file_path.h" | |
10 #include "base/logging.h" | |
11 #include "base/mac/scoped_cftyperef.h" | |
12 #include "base/message_loop.h" | |
13 #include "base/scoped_nsobject.h" | |
14 #include "base/sys_string_conversions.h" | |
15 | |
16 using base::mac::ScopedCFTypeRef; | |
17 | |
18 namespace { | |
19 | |
20 // a count of currently outstanding requests for full screen mode from browser | |
21 // windows, plugins, etc. | |
22 int g_full_screen_requests[mac_util::kNumFullScreenModes] = { 0, 0, 0}; | |
23 | |
24 // Sets the appropriate SystemUIMode based on the current full screen requests. | |
25 // Since only one SystemUIMode can be active at a given time, full screen | |
26 // requests are ordered by priority. If there are no outstanding full screen | |
27 // requests, reverts to normal mode. If the correct SystemUIMode is already | |
28 // set, does nothing. | |
29 void SetUIMode() { | |
30 // Get the current UI mode. | |
31 SystemUIMode current_mode; | |
32 GetSystemUIMode(¤t_mode, NULL); | |
33 | |
34 // Determine which mode should be active, based on which requests are | |
35 // currently outstanding. More permissive requests take precedence. For | |
36 // example, plugins request |kFullScreenModeAutoHideAll|, while browser | |
37 // windows request |kFullScreenModeHideDock| when the fullscreen overlay is | |
38 // down. Precedence goes to plugins in this case, so AutoHideAll wins over | |
39 // HideDock. | |
40 SystemUIMode desired_mode = kUIModeNormal; | |
41 SystemUIOptions desired_options = 0; | |
42 if (g_full_screen_requests[mac_util::kFullScreenModeAutoHideAll] > 0) { | |
43 desired_mode = kUIModeAllHidden; | |
44 desired_options = kUIOptionAutoShowMenuBar; | |
45 } else if (g_full_screen_requests[mac_util::kFullScreenModeHideDock] > 0) { | |
46 desired_mode = kUIModeContentHidden; | |
47 } else if (g_full_screen_requests[mac_util::kFullScreenModeHideAll] > 0) { | |
48 desired_mode = kUIModeAllHidden; | |
49 } | |
50 | |
51 if (current_mode != desired_mode) | |
52 SetSystemUIMode(desired_mode, desired_options); | |
53 } | |
54 | |
55 bool WasLaunchedAsLoginItem() { | |
56 ProcessSerialNumber psn = { 0, kCurrentProcess }; | |
57 | |
58 scoped_nsobject<NSDictionary> process_info( | |
59 mac_util::CFToNSCast(ProcessInformationCopyDictionary(&psn, | |
60 kProcessDictionaryIncludeAllInformationMask))); | |
61 | |
62 long long temp = [[process_info objectForKey:@"ParentPSN"] longLongValue]; | |
63 ProcessSerialNumber parent_psn = | |
64 { (temp >> 32) & 0x00000000FFFFFFFFLL, temp & 0x00000000FFFFFFFFLL }; | |
65 | |
66 scoped_nsobject<NSDictionary> parent_info( | |
67 mac_util::CFToNSCast(ProcessInformationCopyDictionary(&parent_psn, | |
68 kProcessDictionaryIncludeAllInformationMask))); | |
69 | |
70 // Check that creator process code is that of loginwindow. | |
71 BOOL result = | |
72 [[parent_info objectForKey:@"FileCreator"] isEqualToString:@"lgnw"]; | |
73 | |
74 return result == YES; | |
75 } | |
76 | |
77 // Looks into Shared File Lists corresponding to Login Items for the item | |
78 // representing the current application. If such an item is found, returns | |
79 // retained reference to it. Caller is responsible for releasing the reference. | |
80 LSSharedFileListItemRef GetLoginItemForApp() { | |
81 ScopedCFTypeRef<LSSharedFileListRef> login_items(LSSharedFileListCreate( | |
82 NULL, kLSSharedFileListSessionLoginItems, NULL)); | |
83 | |
84 if (!login_items.get()) { | |
85 LOG(ERROR) << "Couldn't get a Login Items list."; | |
86 return NULL; | |
87 } | |
88 | |
89 scoped_nsobject<NSArray> login_items_array( | |
90 mac_util::CFToNSCast(LSSharedFileListCopySnapshot(login_items, NULL))); | |
91 | |
92 NSURL* url = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]]; | |
93 | |
94 for(NSUInteger i = 0; i < [login_items_array count]; ++i) { | |
95 LSSharedFileListItemRef item = reinterpret_cast<LSSharedFileListItemRef>( | |
96 [login_items_array objectAtIndex:i]); | |
97 CFURLRef item_url_ref = NULL; | |
98 | |
99 if (LSSharedFileListItemResolve(item, 0, &item_url_ref, NULL) == noErr) { | |
100 ScopedCFTypeRef<CFURLRef> item_url(item_url_ref); | |
101 if (CFEqual(item_url, url)) { | |
102 CFRetain(item); | |
103 return item; | |
104 } | |
105 } | |
106 } | |
107 | |
108 return NULL; | |
109 } | |
110 | |
111 #if !defined(MAC_OS_X_VERSION_10_6) || \ | |
112 MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6 | |
113 // kLSSharedFileListLoginItemHidden is supported on | |
114 // 10.5, but missing from the 10.5 headers. | |
115 // http://openradar.appspot.com/6482251 | |
116 static NSString* kLSSharedFileListLoginItemHidden = | |
117 @"com.apple.loginitem.HideOnLaunch"; | |
118 #endif | |
119 | |
120 bool IsHiddenLoginItem(LSSharedFileListItemRef item) { | |
121 ScopedCFTypeRef<CFBooleanRef> hidden(reinterpret_cast<CFBooleanRef>( | |
122 LSSharedFileListItemCopyProperty(item, | |
123 reinterpret_cast<CFStringRef>(kLSSharedFileListLoginItemHidden)))); | |
124 | |
125 return hidden && hidden == kCFBooleanTrue; | |
126 } | |
127 | |
128 } // end namespace | |
129 | |
130 namespace mac_util { | |
131 | |
132 std::string PathFromFSRef(const FSRef& ref) { | |
133 ScopedCFTypeRef<CFURLRef> url( | |
134 CFURLCreateFromFSRef(kCFAllocatorDefault, &ref)); | |
135 NSString *path_string = [(NSURL *)url.get() path]; | |
136 return [path_string fileSystemRepresentation]; | |
137 } | |
138 | |
139 bool FSRefFromPath(const std::string& path, FSRef* ref) { | |
140 OSStatus status = FSPathMakeRef((const UInt8*)path.c_str(), | |
141 ref, nil); | |
142 return status == noErr; | |
143 } | |
144 | |
145 static bool g_override_am_i_bundled = false; | |
146 static bool g_override_am_i_bundled_value = false; | |
147 | |
148 // Adapted from http://developer.apple.com/carbon/tipsandtricks.html#AmIBundled | |
149 static bool UncachedAmIBundled() { | |
150 if (g_override_am_i_bundled) | |
151 return g_override_am_i_bundled_value; | |
152 | |
153 ProcessSerialNumber psn = {0, kCurrentProcess}; | |
154 | |
155 FSRef fsref; | |
156 OSStatus pbErr; | |
157 if ((pbErr = GetProcessBundleLocation(&psn, &fsref)) != noErr) { | |
158 LOG(ERROR) << "GetProcessBundleLocation failed: error " << pbErr; | |
159 return false; | |
160 } | |
161 | |
162 FSCatalogInfo info; | |
163 OSErr fsErr; | |
164 if ((fsErr = FSGetCatalogInfo(&fsref, kFSCatInfoNodeFlags, &info, | |
165 NULL, NULL, NULL)) != noErr) { | |
166 LOG(ERROR) << "FSGetCatalogInfo failed: error " << fsErr; | |
167 return false; | |
168 } | |
169 | |
170 return info.nodeFlags & kFSNodeIsDirectoryMask; | |
171 } | |
172 | |
173 bool AmIBundled() { | |
174 // If the return value is not cached, this function will return different | |
175 // values depending on when it's called. This confuses some client code, see | |
176 // http://crbug.com/63183 . | |
177 static bool result = UncachedAmIBundled(); | |
178 DCHECK_EQ(result, UncachedAmIBundled()) | |
179 << "The return value of AmIBundled() changed. This will confuse tests. " | |
180 << "Call SetAmIBundled() override manually if your test binary " | |
181 << "delay-loads the framework."; | |
182 return result; | |
183 } | |
184 | |
185 void SetOverrideAmIBundled(bool value) { | |
186 g_override_am_i_bundled = true; | |
187 g_override_am_i_bundled_value = value; | |
188 } | |
189 | |
190 bool IsBackgroundOnlyProcess() { | |
191 // This function really does want to examine NSBundle's idea of the main | |
192 // bundle dictionary, and not the overriden MainAppBundle. It needs to look | |
193 // at the actual running .app's Info.plist to access its LSUIElement | |
194 // property. | |
195 NSDictionary* info_dictionary = [[NSBundle mainBundle] infoDictionary]; | |
196 return [[info_dictionary objectForKey:@"LSUIElement"] boolValue] != NO; | |
197 } | |
198 | |
199 // No threading worries since NSBundle isn't thread safe. | |
200 static NSBundle* g_override_app_bundle = nil; | |
201 | |
202 NSBundle* MainAppBundle() { | |
203 if (g_override_app_bundle) | |
204 return g_override_app_bundle; | |
205 return [NSBundle mainBundle]; | |
206 } | |
207 | |
208 FilePath MainAppBundlePath() { | |
209 NSBundle* bundle = MainAppBundle(); | |
210 return FilePath([[bundle bundlePath] fileSystemRepresentation]); | |
211 } | |
212 | |
213 void SetOverrideAppBundle(NSBundle* bundle) { | |
214 if (bundle != g_override_app_bundle) { | |
215 [g_override_app_bundle release]; | |
216 g_override_app_bundle = [bundle retain]; | |
217 } | |
218 } | |
219 | |
220 void SetOverrideAppBundlePath(const FilePath& file_path) { | |
221 NSString* path = base::SysUTF8ToNSString(file_path.value()); | |
222 NSBundle* bundle = [NSBundle bundleWithPath:path]; | |
223 CHECK(bundle) << "Failed to load the bundle at " << file_path.value(); | |
224 | |
225 SetOverrideAppBundle(bundle); | |
226 } | |
227 | |
228 OSType CreatorCodeForCFBundleRef(CFBundleRef bundle) { | |
229 OSType creator = kUnknownType; | |
230 CFBundleGetPackageInfo(bundle, NULL, &creator); | |
231 return creator; | |
232 } | |
233 | |
234 OSType CreatorCodeForApplication() { | |
235 CFBundleRef bundle = CFBundleGetMainBundle(); | |
236 if (!bundle) | |
237 return kUnknownType; | |
238 | |
239 return CreatorCodeForCFBundleRef(bundle); | |
240 } | |
241 | |
242 bool GetSearchPathDirectory(NSSearchPathDirectory directory, | |
243 NSSearchPathDomainMask domain_mask, | |
244 FilePath* result) { | |
245 DCHECK(result); | |
246 NSArray* dirs = | |
247 NSSearchPathForDirectoriesInDomains(directory, domain_mask, YES); | |
248 if ([dirs count] < 1) { | |
249 return false; | |
250 } | |
251 NSString* path = [dirs objectAtIndex:0]; | |
252 *result = FilePath([path fileSystemRepresentation]); | |
253 return true; | |
254 } | |
255 | |
256 bool GetLocalDirectory(NSSearchPathDirectory directory, FilePath* result) { | |
257 return GetSearchPathDirectory(directory, NSLocalDomainMask, result); | |
258 } | |
259 | |
260 bool GetUserDirectory(NSSearchPathDirectory directory, FilePath* result) { | |
261 return GetSearchPathDirectory(directory, NSUserDomainMask, result); | |
262 } | |
263 | |
264 FilePath GetUserLibraryPath() { | |
265 FilePath user_library_path; | |
266 if (!GetUserDirectory(NSLibraryDirectory, &user_library_path)) { | |
267 LOG(WARNING) << "Could not get user library path"; | |
268 } | |
269 return user_library_path; | |
270 } | |
271 | |
272 CGColorSpaceRef GetSRGBColorSpace() { | |
273 // Leaked. That's OK, it's scoped to the lifetime of the application. | |
274 static CGColorSpaceRef g_color_space_sRGB = | |
275 CGColorSpaceCreateWithName(kCGColorSpaceSRGB); | |
276 LOG_IF(ERROR, !g_color_space_sRGB) << "Couldn't get the sRGB color space"; | |
277 return g_color_space_sRGB; | |
278 } | |
279 | |
280 CGColorSpaceRef GetSystemColorSpace() { | |
281 // Leaked. That's OK, it's scoped to the lifetime of the application. | |
282 // Try to get the main display's color space. | |
283 static CGColorSpaceRef g_system_color_space = | |
284 CGDisplayCopyColorSpace(CGMainDisplayID()); | |
285 | |
286 if (!g_system_color_space) { | |
287 // Use a generic RGB color space. This is better than nothing. | |
288 g_system_color_space = CGColorSpaceCreateDeviceRGB(); | |
289 | |
290 if (g_system_color_space) { | |
291 LOG(WARNING) << | |
292 "Couldn't get the main display's color space, using generic"; | |
293 } else { | |
294 LOG(ERROR) << "Couldn't get any color space"; | |
295 } | |
296 } | |
297 | |
298 return g_system_color_space; | |
299 } | |
300 | |
301 // Add a request for full screen mode. Must be called on the main thread. | |
302 void RequestFullScreen(FullScreenMode mode) { | |
303 DCHECK_LT(mode, kNumFullScreenModes); | |
304 if (mode >= kNumFullScreenModes) | |
305 return; | |
306 | |
307 DCHECK_GE(g_full_screen_requests[mode], 0); | |
308 g_full_screen_requests[mode] = std::max(g_full_screen_requests[mode] + 1, 1); | |
309 SetUIMode(); | |
310 } | |
311 | |
312 // Release a request for full screen mode. Must be called on the main thread. | |
313 void ReleaseFullScreen(FullScreenMode mode) { | |
314 DCHECK_LT(mode, kNumFullScreenModes); | |
315 if (mode >= kNumFullScreenModes) | |
316 return; | |
317 | |
318 DCHECK_GT(g_full_screen_requests[mode], 0); | |
319 g_full_screen_requests[mode] = std::max(g_full_screen_requests[mode] - 1, 0); | |
320 SetUIMode(); | |
321 } | |
322 | |
323 // Switches full screen modes. Releases a request for |from_mode| and adds a | |
324 // new request for |to_mode|. Must be called on the main thread. | |
325 void SwitchFullScreenModes(FullScreenMode from_mode, FullScreenMode to_mode) { | |
326 DCHECK_LT(from_mode, kNumFullScreenModes); | |
327 DCHECK_LT(to_mode, kNumFullScreenModes); | |
328 if (from_mode >= kNumFullScreenModes || to_mode >= kNumFullScreenModes) | |
329 return; | |
330 | |
331 DCHECK_GT(g_full_screen_requests[from_mode], 0); | |
332 DCHECK_GE(g_full_screen_requests[to_mode], 0); | |
333 g_full_screen_requests[from_mode] = | |
334 std::max(g_full_screen_requests[from_mode] - 1, 0); | |
335 g_full_screen_requests[to_mode] = | |
336 std::max(g_full_screen_requests[to_mode] + 1, 1); | |
337 SetUIMode(); | |
338 } | |
339 | |
340 void SetCursorVisibility(bool visible) { | |
341 if (visible) | |
342 [NSCursor unhide]; | |
343 else | |
344 [NSCursor hide]; | |
345 } | |
346 | |
347 bool ShouldWindowsMiniaturizeOnDoubleClick() { | |
348 // We use an undocumented method in Cocoa; if it doesn't exist, default to | |
349 // |true|. If it ever goes away, we can do (using an undocumented pref key): | |
350 // NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; | |
351 // return ![defaults objectForKey:@"AppleMiniaturizeOnDoubleClick"] || | |
352 // [defaults boolForKey:@"AppleMiniaturizeOnDoubleClick"]; | |
353 BOOL methodImplemented = | |
354 [NSWindow respondsToSelector:@selector(_shouldMiniaturizeOnDoubleClick)]; | |
355 DCHECK(methodImplemented); | |
356 return !methodImplemented || | |
357 [NSWindow performSelector:@selector(_shouldMiniaturizeOnDoubleClick)]; | |
358 } | |
359 | |
360 void GrabWindowSnapshot(NSWindow* window, | |
361 std::vector<unsigned char>* png_representation, | |
362 int* width, int* height) { | |
363 // Make sure to grab the "window frame" view so we get current tab + | |
364 // tabstrip. | |
365 NSView* view = [[window contentView] superview]; | |
366 NSBitmapImageRep* rep = | |
367 [view bitmapImageRepForCachingDisplayInRect:[view bounds]]; | |
368 [view cacheDisplayInRect:[view bounds] toBitmapImageRep:rep]; | |
369 NSData* data = [rep representationUsingType:NSPNGFileType properties:nil]; | |
370 const unsigned char* buf = static_cast<const unsigned char*>([data bytes]); | |
371 NSUInteger length = [data length]; | |
372 if (buf != NULL && length > 0){ | |
373 *width = static_cast<int>([rep pixelsWide]); | |
374 *height = static_cast<int>([rep pixelsHigh]); | |
375 png_representation->assign(buf, buf + length); | |
376 DCHECK(png_representation->size() > 0); | |
377 } | |
378 } | |
379 | |
380 void ActivateProcess(pid_t pid) { | |
381 ProcessSerialNumber process; | |
382 OSStatus status = GetProcessForPID(pid, &process); | |
383 if (status == noErr) { | |
384 SetFrontProcess(&process); | |
385 } else { | |
386 LOG(WARNING) << "Unable to get process for pid " << pid; | |
387 } | |
388 } | |
389 | |
390 // Takes a path to an (executable) binary and tries to provide the path to an | |
391 // application bundle containing it. It takes the outermost bundle that it can | |
392 // find (so for "/Foo/Bar.app/.../Baz.app/..." it produces "/Foo/Bar.app"). | |
393 // |exec_name| - path to the binary | |
394 // returns - path to the application bundle, or empty on error | |
395 FilePath GetAppBundlePath(const FilePath& exec_name) { | |
396 const char kExt[] = ".app"; | |
397 const size_t kExtLength = arraysize(kExt) - 1; | |
398 | |
399 // Split the path into components. | |
400 std::vector<std::string> components; | |
401 exec_name.GetComponents(&components); | |
402 | |
403 // It's an error if we don't get any components. | |
404 if (!components.size()) | |
405 return FilePath(); | |
406 | |
407 // Don't prepend '/' to the first component. | |
408 std::vector<std::string>::const_iterator it = components.begin(); | |
409 std::string bundle_name = *it; | |
410 DCHECK(it->length() > 0); | |
411 // If the first component ends in ".app", we're already done. | |
412 if (it->length() > kExtLength && | |
413 !it->compare(it->length() - kExtLength, kExtLength, kExt, kExtLength)) | |
414 return FilePath(bundle_name); | |
415 | |
416 // The first component may be "/" or "//", etc. Only append '/' if it doesn't | |
417 // already end in '/'. | |
418 if (bundle_name[bundle_name.length() - 1] != '/') | |
419 bundle_name += '/'; | |
420 | |
421 // Go through the remaining components. | |
422 for (++it; it != components.end(); ++it) { | |
423 DCHECK(it->length() > 0); | |
424 | |
425 bundle_name += *it; | |
426 | |
427 // If the current component ends in ".app", we're done. | |
428 if (it->length() > kExtLength && | |
429 !it->compare(it->length() - kExtLength, kExtLength, kExt, kExtLength)) | |
430 return FilePath(bundle_name); | |
431 | |
432 // Separate this component from the next one. | |
433 bundle_name += '/'; | |
434 } | |
435 | |
436 return FilePath(); | |
437 } | |
438 | |
439 bool SetFileBackupExclusion(const FilePath& file_path, bool exclude) { | |
440 NSString* filePath = | |
441 [NSString stringWithUTF8String:file_path.value().c_str()]; | |
442 | |
443 // If being asked to exclude something in a tmp directory, just lie and say it | |
444 // was done. TimeMachine will already ignore tmp directories. This keeps the | |
445 // temporary profiles used by unittests from being added to the exclude list. | |
446 // Otherwise, as /Library/Preferences/com.apple.TimeMachine.plist grows the | |
447 // bots slow down due to reading/writing all the temporary profiles used over | |
448 // time. | |
449 | |
450 NSString* tmpDir = NSTemporaryDirectory(); | |
451 // Make sure the temp dir is terminated with a slash | |
452 if (tmpDir && ![tmpDir hasSuffix:@"/"]) | |
453 tmpDir = [tmpDir stringByAppendingString:@"/"]; | |
454 // '/var' is a link to '/private/var', make sure to check both forms. | |
455 NSString* privateTmpDir = nil; | |
456 if ([tmpDir hasPrefix:@"/var/"]) | |
457 privateTmpDir = [@"/private" stringByAppendingString:tmpDir]; | |
458 | |
459 if ((tmpDir && [filePath hasPrefix:tmpDir]) || | |
460 (privateTmpDir && [filePath hasPrefix:privateTmpDir]) || | |
461 [filePath hasPrefix:@"/tmp/"] || | |
462 [filePath hasPrefix:@"/var/tmp/"] || | |
463 [filePath hasPrefix:@"/private/tmp/"] || | |
464 [filePath hasPrefix:@"/private/var/tmp/"]) { | |
465 return true; | |
466 } | |
467 | |
468 NSURL* url = [NSURL fileURLWithPath:filePath]; | |
469 // Note that we always set CSBackupSetItemExcluded's excludeByPath param | |
470 // to true. This prevents a problem with toggling the setting: if the file | |
471 // is excluded with excludeByPath set to true then excludeByPath must | |
472 // also be true when un-excluding the file, otherwise the un-excluding | |
473 // will be ignored. | |
474 bool success = | |
475 CSBackupSetItemExcluded((CFURLRef)url, exclude, true) == noErr; | |
476 if (!success) | |
477 LOG(WARNING) << "Failed to set backup excluson for file '" | |
478 << file_path.value().c_str() << "'. Continuing."; | |
479 return success; | |
480 } | |
481 | |
482 CFTypeRef GetValueFromDictionary(CFDictionaryRef dict, | |
483 CFStringRef key, | |
484 CFTypeID expected_type) { | |
485 CFTypeRef value = CFDictionaryGetValue(dict, key); | |
486 if (!value) | |
487 return value; | |
488 | |
489 if (CFGetTypeID(value) != expected_type) { | |
490 ScopedCFTypeRef<CFStringRef> expected_type_ref( | |
491 CFCopyTypeIDDescription(expected_type)); | |
492 ScopedCFTypeRef<CFStringRef> actual_type_ref( | |
493 CFCopyTypeIDDescription(CFGetTypeID(value))); | |
494 LOG(WARNING) << "Expected value for key " | |
495 << base::SysCFStringRefToUTF8(key) | |
496 << " to be " | |
497 << base::SysCFStringRefToUTF8(expected_type_ref) | |
498 << " but it was " | |
499 << base::SysCFStringRefToUTF8(actual_type_ref) | |
500 << " instead"; | |
501 return NULL; | |
502 } | |
503 | |
504 return value; | |
505 } | |
506 | |
507 void SetProcessName(CFStringRef process_name) { | |
508 if (!process_name || CFStringGetLength(process_name) == 0) { | |
509 NOTREACHED() << "SetProcessName given bad name."; | |
510 return; | |
511 } | |
512 | |
513 if (![NSThread isMainThread]) { | |
514 NOTREACHED() << "Should only set process name from main thread."; | |
515 return; | |
516 } | |
517 | |
518 // Warning: here be dragons! This is SPI reverse-engineered from WebKit's | |
519 // plugin host, and could break at any time (although realistically it's only | |
520 // likely to break in a new major release). | |
521 // When 10.7 is available, check that this still works, and update this | |
522 // comment for 10.8. | |
523 | |
524 // Private CFType used in these LaunchServices calls. | |
525 typedef CFTypeRef PrivateLSASN; | |
526 typedef PrivateLSASN (*LSGetCurrentApplicationASNType)(); | |
527 typedef OSStatus (*LSSetApplicationInformationItemType)(int, PrivateLSASN, | |
528 CFStringRef, | |
529 CFStringRef, | |
530 CFDictionaryRef*); | |
531 | |
532 static LSGetCurrentApplicationASNType ls_get_current_application_asn_func = | |
533 NULL; | |
534 static LSSetApplicationInformationItemType | |
535 ls_set_application_information_item_func = NULL; | |
536 static CFStringRef ls_display_name_key = NULL; | |
537 | |
538 static bool did_symbol_lookup = false; | |
539 if (!did_symbol_lookup) { | |
540 did_symbol_lookup = true; | |
541 CFBundleRef launch_services_bundle = | |
542 CFBundleGetBundleWithIdentifier(CFSTR("com.apple.LaunchServices")); | |
543 if (!launch_services_bundle) { | |
544 LOG(ERROR) << "Failed to look up LaunchServices bundle"; | |
545 return; | |
546 } | |
547 | |
548 ls_get_current_application_asn_func = | |
549 reinterpret_cast<LSGetCurrentApplicationASNType>( | |
550 CFBundleGetFunctionPointerForName( | |
551 launch_services_bundle, CFSTR("_LSGetCurrentApplicationASN"))); | |
552 if (!ls_get_current_application_asn_func) | |
553 LOG(ERROR) << "Could not find _LSGetCurrentApplicationASN"; | |
554 | |
555 ls_set_application_information_item_func = | |
556 reinterpret_cast<LSSetApplicationInformationItemType>( | |
557 CFBundleGetFunctionPointerForName( | |
558 launch_services_bundle, | |
559 CFSTR("_LSSetApplicationInformationItem"))); | |
560 if (!ls_set_application_information_item_func) | |
561 LOG(ERROR) << "Could not find _LSSetApplicationInformationItem"; | |
562 | |
563 CFStringRef* key_pointer = reinterpret_cast<CFStringRef*>( | |
564 CFBundleGetDataPointerForName(launch_services_bundle, | |
565 CFSTR("_kLSDisplayNameKey"))); | |
566 ls_display_name_key = key_pointer ? *key_pointer : NULL; | |
567 if (!ls_display_name_key) | |
568 LOG(ERROR) << "Could not find _kLSDisplayNameKey"; | |
569 | |
570 // Internally, this call relies on the Mach ports that are started up by the | |
571 // Carbon Process Manager. In debug builds this usually happens due to how | |
572 // the logging layers are started up; but in release, it isn't started in as | |
573 // much of a defined order. So if the symbols had to be loaded, go ahead | |
574 // and force a call to make sure the manager has been initialized and hence | |
575 // the ports are opened. | |
576 ProcessSerialNumber psn; | |
577 GetCurrentProcess(&psn); | |
578 } | |
579 if (!ls_get_current_application_asn_func || | |
580 !ls_set_application_information_item_func || | |
581 !ls_display_name_key) { | |
582 return; | |
583 } | |
584 | |
585 PrivateLSASN asn = ls_get_current_application_asn_func(); | |
586 // Constant used by WebKit; what exactly it means is unknown. | |
587 const int magic_session_constant = -2; | |
588 OSErr err = | |
589 ls_set_application_information_item_func(magic_session_constant, asn, | |
590 ls_display_name_key, | |
591 process_name, | |
592 NULL /* optional out param */); | |
593 LOG_IF(ERROR, err) << "Call to set process name failed, err " << err; | |
594 } | |
595 | |
596 // Converts a NSImage to a CGImageRef. Normally, the system frameworks can do | |
597 // this fine, especially on 10.6. On 10.5, however, CGImage cannot handle | |
598 // converting a PDF-backed NSImage into a CGImageRef. This function will | |
599 // rasterize the PDF into a bitmap CGImage. The caller is responsible for | |
600 // releasing the return value. | |
601 CGImageRef CopyNSImageToCGImage(NSImage* image) { | |
602 // This is based loosely on http://www.cocoadev.com/index.pl?CGImageRef . | |
603 NSSize size = [image size]; | |
604 ScopedCFTypeRef<CGContextRef> context( | |
605 CGBitmapContextCreate(NULL, // Allow CG to allocate memory. | |
606 size.width, | |
607 size.height, | |
608 8, // bitsPerComponent | |
609 0, // bytesPerRow - CG will calculate by default. | |
610 [[NSColorSpace genericRGBColorSpace] CGColorSpace], | |
611 kCGBitmapByteOrder32Host | | |
612 kCGImageAlphaPremultipliedFirst)); | |
613 if (!context.get()) | |
614 return NULL; | |
615 | |
616 [NSGraphicsContext saveGraphicsState]; | |
617 [NSGraphicsContext setCurrentContext: | |
618 [NSGraphicsContext graphicsContextWithGraphicsPort:context.get() | |
619 flipped:NO]]; | |
620 [image drawInRect:NSMakeRect(0,0, size.width, size.height) | |
621 fromRect:NSZeroRect | |
622 operation:NSCompositeCopy | |
623 fraction:1.0]; | |
624 [NSGraphicsContext restoreGraphicsState]; | |
625 | |
626 return CGBitmapContextCreateImage(context); | |
627 } | |
628 | |
629 bool CheckLoginItemStatus(bool* is_hidden) { | |
630 ScopedCFTypeRef<LSSharedFileListItemRef> item(GetLoginItemForApp()); | |
631 if (!item.get()) | |
632 return false; | |
633 | |
634 if (is_hidden) | |
635 *is_hidden = IsHiddenLoginItem(item); | |
636 | |
637 return true; | |
638 } | |
639 | |
640 void AddToLoginItems(bool hide_on_startup) { | |
641 ScopedCFTypeRef<LSSharedFileListItemRef> item(GetLoginItemForApp()); | |
642 if (item.get() && (IsHiddenLoginItem(item) == hide_on_startup)) { | |
643 return; // Already is a login item with required hide flag. | |
644 } | |
645 | |
646 ScopedCFTypeRef<LSSharedFileListRef> login_items(LSSharedFileListCreate( | |
647 NULL, kLSSharedFileListSessionLoginItems, NULL)); | |
648 | |
649 if (!login_items.get()) { | |
650 LOG(ERROR) << "Couldn't get a Login Items list."; | |
651 return; | |
652 } | |
653 | |
654 // Remove the old item, it has wrong hide flag, we'll create a new one. | |
655 if (item.get()) { | |
656 LSSharedFileListItemRemove(login_items, item); | |
657 } | |
658 | |
659 NSURL* url = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]]; | |
660 | |
661 BOOL hide = hide_on_startup ? YES : NO; | |
662 NSDictionary* properties = | |
663 [NSDictionary | |
664 dictionaryWithObject:[NSNumber numberWithBool:hide] | |
665 forKey:(NSString*)kLSSharedFileListLoginItemHidden]; | |
666 | |
667 ScopedCFTypeRef<LSSharedFileListItemRef> new_item; | |
668 new_item.reset(LSSharedFileListInsertItemURL( | |
669 login_items, kLSSharedFileListItemLast, NULL, NULL, | |
670 reinterpret_cast<CFURLRef>(url), | |
671 reinterpret_cast<CFDictionaryRef>(properties), NULL)); | |
672 | |
673 if (!new_item.get()) { | |
674 LOG(ERROR) << "Couldn't insert current app into Login Items list."; | |
675 } | |
676 } | |
677 | |
678 void RemoveFromLoginItems() { | |
679 ScopedCFTypeRef<LSSharedFileListItemRef> item(GetLoginItemForApp()); | |
680 if (!item.get()) | |
681 return; | |
682 | |
683 ScopedCFTypeRef<LSSharedFileListRef> login_items(LSSharedFileListCreate( | |
684 NULL, kLSSharedFileListSessionLoginItems, NULL)); | |
685 | |
686 if (!login_items.get()) { | |
687 LOG(ERROR) << "Couldn't get a Login Items list."; | |
688 return; | |
689 } | |
690 | |
691 LSSharedFileListItemRemove(login_items, item); | |
692 } | |
693 | |
694 bool WasLaunchedAsHiddenLoginItem() { | |
695 if (!WasLaunchedAsLoginItem()) | |
696 return false; | |
697 | |
698 ScopedCFTypeRef<LSSharedFileListItemRef> item(GetLoginItemForApp()); | |
699 if (!item.get()) { | |
700 LOG(ERROR) << "Process launched at Login but can't access Login Item List."; | |
701 return false; | |
702 } | |
703 return IsHiddenLoginItem(item); | |
704 } | |
705 | |
706 void NSObjectRetain(void* obj) { | |
707 id<NSObject> nsobj = static_cast<id<NSObject> >(obj); | |
708 [nsobj retain]; | |
709 } | |
710 | |
711 void NSObjectRelease(void* obj) { | |
712 id<NSObject> nsobj = static_cast<id<NSObject> >(obj); | |
713 [nsobj release]; | |
714 } | |
715 | |
716 } // namespace mac_util | |
OLD | NEW |