1/** <title>NSWorkspace</title> 2 3 <abstract>Workspace class</abstract> 4 5 Copyright (C) 1996-2016 Free Software Foundation, Inc. 6 7 Author: Scott Christley <scottc@net-community.com> 8 Date: 1996 9 Implementation by: Richard Frith-Macdonald <richard@brainstorm.co.uk> 10 Date: 1998 11 Implementation by: Fred Kiefer <FredKiefer@gmx.de> 12 Date: 2001 13 14 This file is part of the GNUstep GUI Library. 15 16 This library is free software; you can redistribute it and/or 17 modify it under the terms of the GNU Lesser General Public 18 License as published by the Free Software Foundation; either 19 version 2 of the License, or (at your option) any later version. 20 21 This library is distributed in the hope that it will be useful, 22 but WITHOUT ANY WARRANTY; without even the implied warranty of 23 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 24 Lesser General Public License for more details. 25 26 You should have received a copy of the GNU Lesser General Public 27 License along with this library; see the file COPYING.LIB. 28 If not, see <http://www.gnu.org/licenses/> or write to the 29 Free Software Foundation, 51 Franklin Street, Fifth Floor, 30 Boston, MA 02110-1301, USA. 31*/ 32 33#import "config.h" 34 35#include <unistd.h> 36#include <sys/types.h> 37 38#if defined(HAVE_GETMNTINFO) 39#include <sys/param.h> 40#include <sys/mount.h> 41#endif 42 43#if defined(HAVE_GETMNTENT) && defined (MNT_MEMB) 44 #if defined(HAVE_MNTENT_H) 45 #include <mntent.h> 46 #elif defined(HAVE_SYS_MNTENT_H) 47 #include <sys/mntent.h> 48 #else 49 #undef HAVE_GETMNTENT 50 #endif 51#endif /* HAVE_GETMNTENT */ 52 53#if defined (HAVE_SYS_STATVFS_H) 54#include <sys/statvfs.h> 55#endif 56#if defined (HAVE_SYS_VFS_H) 57#include <sys/vfs.h> 58 #ifdef __linux__ 59 #include <linux/magic.h> 60 #endif 61#endif 62 63/* FIXME Solaris uses /etc/mnttab instead of /etc/mtab, but defines 64 * MNTTAB to that path. 65 * FIXME We won't get here on Solaris at all because it defines the 66 * mntent struct in sys/mnttab.h instead of sys/mntent.h. 67 */ 68# ifdef _PATH_MOUNTED 69# define MOUNTED_PATH _PATH_MOUNTED 70# elif defined(MOUNTED) 71# define MOUNTED_PATH MOUNTED 72# else 73# define MNTTAB "/etc/mtab" 74# warning "Mounted path file for you OS guessed to /etc/mtab"; 75# endif 76 77#import <Foundation/NSBundle.h> 78#import <Foundation/NSData.h> 79#import <Foundation/NSDictionary.h> 80#import <Foundation/NSHost.h> 81#import <Foundation/NSLock.h> 82#import <Foundation/NSDistributedLock.h> 83#import <Foundation/NSPathUtilities.h> 84#import <Foundation/NSUserDefaults.h> 85#import <Foundation/NSTask.h> 86#import <GNUstepBase/NSTask+GNUstepBase.h> 87#import <Foundation/NSException.h> 88#import <Foundation/NSFileManager.h> 89#import <Foundation/NSNotificationQueue.h> 90#import <Foundation/NSDistributedNotificationCenter.h> 91#import <Foundation/NSConnection.h> 92#import <Foundation/NSDebug.h> 93#import <Foundation/NSProcessInfo.h> 94#import <Foundation/NSThread.h> 95#import <Foundation/NSURL.h> 96#import <Foundation/NSValue.h> 97#import "AppKit/NSWorkspace.h" 98#import "AppKit/NSApplication.h" 99#import "AppKit/NSImage.h" 100#import "AppKit/NSPasteboard.h" 101#import "AppKit/NSView.h" 102#import "AppKit/NSPanel.h" 103#import "AppKit/NSWindow.h" 104#import "AppKit/NSScreen.h" 105#import "GNUstepGUI/GSServicesManager.h" 106#import "GNUstepGUI/GSDisplayServer.h" 107#import "GSGuiPrivate.h" 108 109/* Informal protocol for method to ask an app to open a URL. 110 */ 111@interface NSObject (OpenURL) 112- (BOOL) application: (NSApplication*)a openURL: (NSURL*)u; 113@end 114 115/* Private method to check that a process exists. 116 */ 117@interface NSProcessInfo (Private) 118+ (BOOL)_exists: (int)pid; 119@end 120 121#define PosixExecutePermission (0111) 122 123static NSMutableDictionary *folderPathIconDict = nil; 124static NSMutableDictionary *folderIconCache = nil; 125 126static NSImage *folderImage = nil; 127static NSImage *multipleFiles = nil; 128static NSImage *unknownApplication = nil; 129static NSImage *unknownTool = nil; 130 131static NSLock *mlock = nil; 132 133static NSString *GSWorkspaceNotification = @"GSWorkspaceNotification"; 134static NSString *GSWorkspacePreferencesChanged = 135 @"GSWorkspacePreferencesChanged"; 136 137/* 138 * Depending on the 'active' flag this returns either the currently 139 * active application or an array containing all launched apps.<br /> 140 * The 'notification' argument is either nil (simply query on disk 141 * database) or a notification containing information to be used to 142 * update the database. 143 */ 144static id GSLaunched(NSNotification *notification, BOOL active) 145{ 146 static NSString *path = nil; 147 static NSDistributedLock *lock = nil; 148 NSDictionary *info = [notification userInfo]; 149 NSString *mode = [notification name]; 150 NSMutableDictionary *file = nil; 151 NSString *name; 152 NSDictionary *apps = nil; 153 BOOL modified = NO; 154 unsigned sleeps = 0; 155 156 [mlock lock]; // start critical section 157 if (path == nil) 158 { 159 path = [NSTemporaryDirectory() 160 stringByAppendingPathComponent: @"GSLaunchedApplications"]; 161 RETAIN(path); 162 lock = [[NSDistributedLock alloc] initWithPath: 163 [path stringByAppendingPathExtension: @"lock"]]; 164 } 165 if ([lock tryLock] == NO) 166 { 167 /* 168 * If the lock is really old ... assume the app has died and break it. 169 */ 170 if ([[lock lockDate] timeIntervalSinceNow] < -20.0) 171 { 172 NS_DURING 173 { 174 [lock breakLock]; 175 } 176 NS_HANDLER 177 { 178 NSLog(@"Unable to break lock %@ ... %@", lock, localException); 179 } 180 NS_ENDHANDLER 181 } 182 /* 183 * Retry locking several times if necessary before giving up. 184 */ 185 for (sleeps = 0; sleeps < 10; sleeps++) 186 { 187 if ([lock tryLock] == YES) 188 { 189 break; 190 } 191 sleeps++; 192 [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.1]]; 193 } 194 if (sleeps >= 10) 195 { 196 [mlock unlock]; 197 NSLog(@"Unable to obtain lock %@", lock); 198 return nil; 199 } 200 } 201 202 if ([[NSFileManager defaultManager] isReadableFileAtPath: path] == YES) 203 { 204 file = [NSMutableDictionary dictionaryWithContentsOfFile: path]; 205 } 206 if (file == nil) 207 { 208 file = [NSMutableDictionary dictionaryWithCapacity: 2]; 209 } 210 apps = [file objectForKey: @"GSLaunched"]; 211 if (apps == nil) 212 { 213 apps = [NSDictionary new]; 214 [file setObject: apps forKey: @"GSLaunched"]; 215 RELEASE(apps); 216 } 217 218 if (info != nil 219 && (name = [info objectForKey: @"NSApplicationName"]) != nil) 220 { 221 NSDictionary *oldInfo = [apps objectForKey: name]; 222 223 if ([mode isEqualToString: 224 NSApplicationDidResignActiveNotification] == YES 225 || [mode isEqualToString: 226 NSWorkspaceDidTerminateApplicationNotification] == YES) 227 { 228 if ([name isEqual: [file objectForKey: @"GSActive"]] == YES) 229 { 230 [file removeObjectForKey: @"GSActive"]; 231 modified = YES; 232 } 233 } 234 else if ([mode isEqualToString: 235 NSApplicationDidBecomeActiveNotification] == YES) 236 { 237 if ([name isEqual: [file objectForKey: @"GSActive"]] == NO) 238 { 239 [file setObject: name forKey: @"GSActive"]; 240 modified = YES; 241 } 242 } 243 244 if ([mode isEqualToString: 245 NSWorkspaceDidTerminateApplicationNotification] == YES) 246 { 247 if (oldInfo != nil) 248 { 249 NSMutableDictionary *m = [apps mutableCopy]; 250 251 [m removeObjectForKey: name]; 252 [file setObject: m forKey: @"GSLaunched"]; 253 apps = m; 254 RELEASE(m); 255 modified = YES; 256 } 257 } 258 else if ([mode isEqualToString: 259 NSApplicationDidResignActiveNotification] == NO) 260 { 261 if ([info isEqual: oldInfo] == NO) 262 { 263 NSMutableDictionary *m = [apps mutableCopy]; 264 265 [m setObject: info forKey: name]; 266 [file setObject: m forKey: @"GSLaunched"]; 267 apps = m; 268 RELEASE(m); 269 modified = YES; 270 } 271 } 272 } 273 274 if (modified == YES) 275 { 276 [file writeToFile: path atomically: YES]; 277 } 278 279 NS_DURING 280 { 281 sleeps = 0; 282 [lock unlock]; 283 } 284 NS_HANDLER 285 { 286 for (sleeps = 0; sleeps < 10; sleeps++) 287 { 288 NS_DURING 289 { 290 [lock unlock]; 291 NSLog(@"Unlocked %@", lock); 292 break; 293 } 294 NS_HANDLER 295 { 296 sleeps++; 297 if (sleeps >= 10) 298 { 299 NSLog(@"Unable to unlock %@", lock); 300 break; 301 } 302 [NSThread sleepForTimeInterval: 0.1]; 303 continue; 304 } 305 NS_ENDHANDLER; 306 } 307 } 308 NS_ENDHANDLER; 309 [mlock unlock]; // end critical section 310 311 if (active == YES) 312 { 313 NSString *activeName = [file objectForKey: @"GSActive"]; 314 315 if (activeName == nil) 316 { 317 return nil; 318 } 319 return [apps objectForKey: activeName]; 320 } 321 else 322 { 323 return [[file objectForKey: @"GSLaunched"] allValues]; 324 } 325} 326 327@interface _GSWorkspaceCenter: NSNotificationCenter 328{ 329 NSNotificationCenter *remote; 330} 331- (void) _handleRemoteNotification: (NSNotification*)aNotification; 332- (void) _postLocal: (NSString*)name userInfo: (NSDictionary*)info; 333@end 334 335@implementation _GSWorkspaceCenter 336 337- (void) dealloc 338{ 339 [remote removeObserver: self name: nil object: GSWorkspaceNotification]; 340 RELEASE(remote); 341 [super dealloc]; 342} 343 344- (id) init 345{ 346 self = [super init]; 347 if (self != nil) 348 { 349 remote = RETAIN([NSDistributedNotificationCenter defaultCenter]); 350 NS_DURING 351 { 352 [remote addObserver: self 353 selector: @selector(_handleRemoteNotification:) 354 name: nil 355 object: GSWorkspaceNotification]; 356 } 357 NS_HANDLER 358 { 359 NSUserDefaults *defs = [NSUserDefaults standardUserDefaults]; 360 361 if ([defs boolForKey: @"GSLogWorkspaceTimeout"]) 362 { 363 NSLog(@"NSWorkspace caught exception %@: %@", 364 [localException name], [localException reason]); 365 } 366 else 367 { 368 [localException raise]; 369 } 370 } 371 NS_ENDHANDLER 372 } 373 return self; 374} 375 376/* 377 * Post notification remotely - since we are listening for distributed 378 * notifications, we will observe the notification arriving from the 379 * distributed notification center, and it will get sent out locally too. 380 */ 381- (void) postNotification: (NSNotification*)aNotification 382{ 383 NSNotification *rem; 384 NSString *name = [aNotification name]; 385 NSDictionary *info = [aNotification userInfo]; 386 387 if ([name isEqual: NSWorkspaceDidTerminateApplicationNotification] == YES 388 || [name isEqual: NSWorkspaceDidLaunchApplicationNotification] == YES 389 || [name isEqualToString: NSApplicationDidBecomeActiveNotification] == YES 390 || [name isEqualToString: NSApplicationDidResignActiveNotification] == YES) 391 { 392 GSLaunched(aNotification, YES); 393 } 394 395 rem = [NSNotification notificationWithName: name 396 object: GSWorkspaceNotification 397 userInfo: info]; 398 NS_DURING 399 { 400 [remote postNotification: rem]; 401 } 402 NS_HANDLER 403 { 404 NSUserDefaults *defs = [NSUserDefaults standardUserDefaults]; 405 406 if ([defs boolForKey: @"GSLogWorkspaceTimeout"]) 407 { 408 NSLog(@"NSWorkspace caught exception %@: %@", 409 [localException name], [localException reason]); 410 } 411 else 412 { 413 [localException raise]; 414 } 415 } 416 NS_ENDHANDLER 417} 418 419- (void) postNotificationName: (NSString*)name 420 object: (id)object 421{ 422 [self postNotification: [NSNotification notificationWithName: name 423 object: object]]; 424} 425 426- (void) postNotificationName: (NSString*)name 427 object: (id)object 428 userInfo: (NSDictionary*)info 429{ 430 [self postNotification: [NSNotification notificationWithName: name 431 object: object 432 userInfo: info]]; 433} 434 435/* 436 * Forward a notification from a remote application to observers in this 437 * application. 438 */ 439- (void) _handleRemoteNotification: (NSNotification*)aNotification 440{ 441 [self _postLocal: [aNotification name] 442 userInfo: [aNotification userInfo]]; 443} 444 445/* 446 * Method allowing a notification to be posted locally. 447 */ 448- (void) _postLocal: (NSString*)name userInfo: (NSDictionary*)info 449{ 450 NSNotification *aNotification; 451 452 aNotification = [NSNotification notificationWithName: name 453 object: self 454 userInfo: info]; 455 [super postNotification: aNotification]; 456} 457@end 458 459 460@interface NSWorkspace (Private) 461 462// Icon handling 463- (NSImage*) _extIconForApp: (NSString*)appName info: (NSDictionary*)extInfo; 464- (NSImage*) unknownFiletypeImage; 465- (NSImage*) _saveImageFor: (NSString*)iconPath; 466- (NSString*) thumbnailForFile: (NSString *)file; 467- (NSImage*) _iconForExtension: (NSString*)ext; 468- (BOOL) _extension: (NSString*)ext 469 role: (NSString*)role 470 app: (NSString**)app; 471- (BOOL) _scheme: (NSString*)scheme 472 role: (NSString*)role 473 app: (NSString**)app; 474- (void) _workspacePreferencesChanged: (NSNotification *)aNotification; 475 476// application communication 477- (BOOL) _launchApplication: (NSString*)appName 478 arguments: (NSArray*)args; 479- (id) _connectApplication: (NSString*)appName; 480- (id) _workspaceApplication; 481 482@end 483 484 485/** 486 * <p>The NSWorkspace class gathers together a large number of capabilities 487 * needed for workspace management. 488 * </p> 489 * <p>The make_services tool examines all applications (anything with a 490 * .app, .debug, or .profile suffix) in the system, local, and user Apps 491 * directories, and caches information about the services each app 492 * provides (extracted from the Info-gnustep.plist file in each application). 493 * </p> 494 * <p>In addition to the cache of services information, it builds a cache of 495 * information about all known applications (including information about file 496 * types they handle). 497 * </p> 498 * <p>NSWorkspace reads the cache and uses it to determine which application 499 * to use to open a document and which icon to use to represent that document. 500 * </p> 501 * <p>The NSWorkspace API has been extended to provide methods for 502 * finding/setting the preferred icon/application for a particular file 503 * type. NSWorkspace will use the 'best' icon/application available. 504 * </p> 505 * <p>To determine the executable to launch, if there was an 506 * Info-gnustep.plist/Info.plist in the app wrapper and it had an 507 * NSExecutable field - it uses that name. Otherwise, it tries to use 508 * the name of the app - eg. foo.app/foo <br /> 509 * The executable is launched by NSTask, which handles the addition 510 * of machine/os/library path components as necessary. 511 * </p> 512 * <p>To determine the icon for a file, it uses the value from the 513 * cache of icons for the file extension, or use an 'unknown' icon. 514 * </p> 515 * <p>To determine the icon for a folder, if the folder has a '.app', 516 * '.debug' or '.profile' extension - the Info-gnustep.plist file 517 * is examined for an 'NSIcon' value and NSWorkspace tries to use that. 518 * If there is no value specified - it tries 'foo.app/foo.png' 519 * or 'foo.app/foo.tiff' or 'foo.app/.dir.png' or 'foo.app/.dir.tiff' 520 * </p> 521 * <p>If the folder was not an application wrapper, it just tries 522 * the .dir.png and .dir.tiff file. 523 * </p> 524 * <p>If no icon was available, it uses a default folder icon or a 525 * special icon for the root directory. 526 * </p> 527 * <p>The information about what file types an app can handle needs 528 * to be stored in Info-gnustep.plist in an array keyed on the name 529 * <em>NSTypes</em>, within which each value is a dictionary.<br /> 530 * </p> 531 * <p>In the NSTypes fields, NSWorkspace uses NSIcon (the icon to use 532 * for the type) NSUnixExtensions (a list of file extensions 533 * corresponding to the type) and NSRole (what the app can do with 534 * documents of this type <em>Editor</em>, <em>Viewer</em>, 535 * or <em>None</em>). In the AppList cache, make_services 536 * generates a dictionary, keyed by file extension, whose values are 537 * the dictionaries containing the NSTypes dictionaries of each 538 * of the apps that handle the extension. The NSWorkspace class 539 * makes use of this cache at runtime. 540 * </p> 541 * <p>If the Info-gnustep.plist of an application says that it 542 * can open files with a particular extension, then when NSWorkspace 543 * is asked to open such a file it will attempt to send an 544 * -application:openFile: message to the application (which must be 545 * handled by the applications delegate). If the application is not 546 * running, NSWorkspace will instead attempt to launch the application 547 * passing the filename to open after a '-GSFilePath' flag 548 * in the command line arguments. For a GNUstep application, the 549 * application will recognize this and invoke the -application:openFile: 550 * method passing it the file name. 551 * </p> 552 * <p>This command line argument mechanism provides a way for non-gnustep 553 * applications to be used to open files simply by provideing a wrapper 554 * for them containing the appropriate Info-gnustep.plist.<br /> 555 * For instance - you could set up xv.app to contain a shellscript 'xv' 556 * that would start the real xv binary passing it a file to open if the 557 * '-GSFilePath' argument was given. The Info-gnustep.plist file could look 558 * like this: 559 * </p> 560 * <example> 561 * 562 * { 563 * NSExecutable = "xv"; 564 * NSIcon = "xv.png"; 565 * NSTypes = ( 566 * { 567 * NSIcon = "tiff.tiff"; 568 * NSUnixExtensions = (tiff, tif); 569 * }, 570 * { 571 * NSIcon = "xbm.tiff"; 572 * NSUnixExtensions = (xbm); 573 * } 574 *); 575 * } 576 * </example> 577 */ 578@implementation NSWorkspace 579 580static NSWorkspace *sharedWorkspace = nil; 581 582static NSString *appListPath = nil; 583static NSDictionary *applications = nil; 584 585static NSString *extPrefPath = nil; 586static NSDictionary *extPreferences = nil; 587 588static NSString *urlPrefPath = nil; 589static NSDictionary *urlPreferences = nil; 590 591/* 592 * Class methods 593 */ 594+ (void) initialize 595{ 596 if (self == [NSWorkspace class]) 597 { 598 static BOOL beenHere = NO; 599 NSFileManager *mgr = [NSFileManager defaultManager]; 600 NSString *service; 601 NSData *data; 602 NSDictionary *dict; 603 604 [self setVersion: 1]; 605 606 [gnustep_global_lock lock]; 607 if (beenHere == YES) 608 { 609 [gnustep_global_lock unlock]; 610 return; 611 } 612 613 beenHere = YES; 614 mlock = [NSLock new]; 615 616 NS_DURING 617 { 618 service = [[NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, 619 NSUserDomainMask, YES) objectAtIndex: 0] 620 stringByAppendingPathComponent: @"Services"]; 621 622 /* 623 * Load file extension preferences. 624 */ 625 extPrefPath = [service 626 stringByAppendingPathComponent: @".GNUstepExtPrefs"]; 627 RETAIN(extPrefPath); 628 if ([mgr isReadableFileAtPath: extPrefPath] == YES) 629 { 630 data = [NSData dataWithContentsOfFile: extPrefPath]; 631 if (data) 632 { 633 dict = [NSDeserializer deserializePropertyListFromData: data 634 mutableContainers: NO]; 635 extPreferences = RETAIN(dict); 636 } 637 } 638 639 /* 640 * Load URL scheme preferences. 641 */ 642 urlPrefPath = [service 643 stringByAppendingPathComponent: @".GNUstepURLPrefs"]; 644 RETAIN(urlPrefPath); 645 if ([mgr isReadableFileAtPath: urlPrefPath] == YES) 646 { 647 data = [NSData dataWithContentsOfFile: urlPrefPath]; 648 if (data) 649 { 650 dict = [NSDeserializer deserializePropertyListFromData: data 651 mutableContainers: NO]; 652 urlPreferences = RETAIN(dict); 653 } 654 } 655 656 /* 657 * Load cached application information. 658 */ 659 appListPath = [service 660 stringByAppendingPathComponent: @".GNUstepAppList"]; 661 RETAIN(appListPath); 662 if ([mgr isReadableFileAtPath: appListPath] == YES) 663 { 664 data = [NSData dataWithContentsOfFile: appListPath]; 665 if (data) 666 { 667 dict = [NSDeserializer deserializePropertyListFromData: data 668 mutableContainers: NO]; 669 applications = RETAIN(dict); 670 } 671 } 672 } 673 NS_HANDLER 674 { 675 [gnustep_global_lock unlock]; 676 [localException raise]; 677 } 678 NS_ENDHANDLER 679 680 [gnustep_global_lock unlock]; 681 } 682} 683 684+ (id) allocWithZone: (NSZone*)zone 685{ 686 [NSException raise: NSInvalidArgumentException 687 format: @"You may not allocate a workspace directly"]; 688 return nil; 689} 690 691/* 692 * Creating a Workspace 693 */ 694+ (NSWorkspace*) sharedWorkspace 695{ 696 if (sharedWorkspace == nil) 697 { 698 [gnustep_global_lock lock]; 699 if (sharedWorkspace == nil) 700 { 701 sharedWorkspace = 702 (NSWorkspace*)NSAllocateObject(self, 0, NSDefaultMallocZone()); 703 [sharedWorkspace init]; 704 } 705 [gnustep_global_lock unlock]; 706 } 707 return sharedWorkspace; 708} 709 710/* 711 * Instance methods 712 */ 713- (void) dealloc 714{ 715 [NSException raise: NSInvalidArgumentException 716 format: @"Attempt to call dealloc for shared worksapace"]; 717 GSNOSUPERDEALLOC; 718} 719 720- (id) init 721{ 722 NSArray *documentDir; 723 NSArray *libraryDirs; 724 NSArray *sysAppDir; 725 NSArray *appDirs; 726 NSArray *downloadDir; 727 NSArray *desktopDir; 728 NSArray *imgDir; 729 NSArray *musicDir; 730 NSArray *videoDir; 731 NSString *sysDir; 732 NSUInteger i; 733 734 if (sharedWorkspace != self) 735 { 736 RELEASE(self); 737 return RETAIN(sharedWorkspace); 738 } 739 740 [[NSNotificationCenter defaultCenter] 741 addObserver: self 742 selector: @selector(noteUserDefaultsChanged) 743 name: NSUserDefaultsDidChangeNotification 744 object: nil]; 745 746 /* There's currently no way of knowing if things have changed due to 747 * apps being installed etc ... so we actually poll regularly. 748 */ 749 [[NSNotificationCenter defaultCenter] 750 addObserver: self 751 selector: @selector(_workspacePreferencesChanged:) 752 name: @"GSHousekeeping" 753 object: nil]; 754 755 _workspaceCenter = [_GSWorkspaceCenter new]; 756 _iconMap = [NSMutableDictionary new]; 757 _launched = [NSMutableDictionary new]; 758 if (applications == nil) 759 { 760 [self findApplications]; 761 } 762 [_workspaceCenter 763 addObserver: self 764 selector: @selector(_workspacePreferencesChanged:) 765 name: GSWorkspacePreferencesChanged 766 object: nil]; 767 768 /* icon association and caching */ 769 folderPathIconDict = [[NSMutableDictionary alloc] initWithCapacity:5]; 770 771 documentDir = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, 772 NSUserDomainMask, YES); 773 downloadDir = NSSearchPathForDirectoriesInDomains(NSDownloadsDirectory, 774 NSUserDomainMask, YES); 775 desktopDir = NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, 776 NSUserDomainMask, YES); 777 libraryDirs = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, 778 NSAllDomainsMask, YES); 779 sysAppDir = NSSearchPathForDirectoriesInDomains(NSApplicationDirectory, 780 NSSystemDomainMask, YES); 781 appDirs = NSSearchPathForDirectoriesInDomains(NSApplicationDirectory, 782 NSAllDomainsMask, YES); 783 imgDir = NSSearchPathForDirectoriesInDomains(NSPicturesDirectory, 784 NSUserDomainMask, YES); 785 musicDir = NSSearchPathForDirectoriesInDomains(NSMusicDirectory, 786 NSUserDomainMask, YES); 787 videoDir = NSSearchPathForDirectoriesInDomains(NSMoviesDirectory, 788 NSUserDomainMask, YES); 789 790 /* we try to guess a System directory and check if looks like one */ 791 sysDir = nil; 792 if ([sysAppDir count] > 0) 793 { 794 sysDir = [[sysAppDir objectAtIndex: 0] stringByDeletingLastPathComponent]; 795 if (![[sysDir lastPathComponent] isEqualToString: @"System"]) 796 sysDir = nil; 797 } 798 799 if (sysDir != nil) 800 [folderPathIconDict setObject: @"GSFolder" forKey: sysDir]; 801 802 [folderPathIconDict setObject: @"HomeDirectory" 803 forKey: NSHomeDirectory()]; 804 805 /* it would be nice to use different root icons... */ 806 [folderPathIconDict setObject: @"Root_PC" forKey: NSOpenStepRootDirectory()]; 807 808 for (i = 0; i < [libraryDirs count]; i++) 809 { 810 [folderPathIconDict setObject: @"LibraryFolder" 811 forKey: [libraryDirs objectAtIndex: i]]; 812 } 813 for (i = 0; i < [appDirs count]; i++) 814 { 815 [folderPathIconDict setObject: @"ApplicationFolder" 816 forKey: [appDirs objectAtIndex: i]]; 817 } 818 for (i = 0; i < [documentDir count]; i++) 819 { 820 [folderPathIconDict setObject: @"DocsFolder" 821 forKey: [documentDir objectAtIndex: i]]; 822 } 823 for (i = 0; i < [downloadDir count]; i++) 824 { 825 [folderPathIconDict setObject: @"DownloadFolder" 826 forKey: [downloadDir objectAtIndex: i]]; 827 } 828 for (i = 0; i < [desktopDir count]; i++) 829 { 830 [folderPathIconDict setObject: @"Desktop" 831 forKey: [desktopDir objectAtIndex: i]]; 832 } 833 for (i = 0; i < [imgDir count]; i++) 834 { 835 [folderPathIconDict setObject: @"ImageFolder" 836 forKey: [imgDir objectAtIndex: i]]; 837 } 838 for (i = 0; i < [musicDir count]; i++) 839 { 840 [folderPathIconDict setObject: @"MusicFolder" 841 forKey: [musicDir objectAtIndex: i]]; 842 } 843 for (i = 0; i < [videoDir count]; i++) 844 { 845 [folderPathIconDict setObject: @"VideoFolder" 846 forKey: [videoDir objectAtIndex: i]]; 847 } 848 folderIconCache = [[NSMutableDictionary alloc] init]; 849 850 return self; 851} 852 853/* 854 * Opening Files 855 */ 856- (BOOL) _openUnknown: (NSString*)fullPath 857{ 858 NSString *tool = [[NSUserDefaults standardUserDefaults] objectForKey: @"GSUnknownFileTool"]; 859 NSString *launchPath; 860 861 if ((tool == nil) || (launchPath = [NSTask launchPathForTool: tool]) == nil) 862 { 863#ifdef __MINGW32__ 864 // Maybe we should rather use "Explorer.exe /e, " as the tool name 865 unichar *buffer = (unichar *)calloc(1, ([fullPath length] + 1) * sizeof(unichar)); 866 [fullPath getCharacters: buffer range: NSMakeRange(0, [fullPath length])]; 867 buffer[[fullPath length]] = 0; 868 BOOL success = ((int)ShellExecuteW(GetDesktopWindow(), L"open", buffer, NULL, 869 NULL, SW_SHOWNORMAL) > 32); 870 free(buffer); 871 return success; 872#else 873 // Fall back to xdg-open 874 launchPath = [NSTask launchPathForTool: @"xdg-open"]; 875#endif 876 } 877 878 if (launchPath) 879 { 880 NSTask * task = [NSTask launchedTaskWithLaunchPath: launchPath 881 arguments: [NSArray arrayWithObject: fullPath]]; 882 if (task != nil) 883 { 884 [task waitUntilExit]; 885 if ([task terminationStatus] == 0) 886 return YES; 887 } 888 } 889 890 return NO; 891} 892 893- (BOOL) openFile: (NSString*)fullPath 894{ 895 return [self openFile: fullPath withApplication: nil]; 896} 897 898- (BOOL) openFile: (NSString*)fullPath 899 fromImage: (NSImage*)anImage 900 at: (NSPoint)point 901 inView: (NSView*)aView 902{ 903 NSWindow *win = [aView window]; 904 NSPoint screenLoc = [win convertBaseToScreen: 905 [aView convertPoint: point toView: nil]]; 906 NSSize screenSize = [[win screen] frame].size; 907 NSPoint screenCenter = NSMakePoint(screenSize.width / 2, 908 screenSize.height / 2); 909 910 [self slideImage: anImage from: screenLoc to: screenCenter]; 911 return [self openFile: fullPath]; 912} 913 914- (BOOL) openFile: (NSString*)fullPath 915 withApplication: (NSString*)appName 916{ 917 return [self openFile: fullPath withApplication: appName andDeactivate: YES]; 918} 919 920- (BOOL) openFile: (NSString*)fullPath 921 withApplication: (NSString*)appName 922 andDeactivate: (BOOL)flag 923{ 924 id app; 925 926 NS_DURING 927 { 928 if ((app = [self _workspaceApplication]) != nil) 929 { 930 BOOL result; 931 932 result = [app openFile: fullPath 933 withApplication: appName 934 andDeactivate: flag]; 935 NS_VALRETURN(result); 936 } 937 } 938 NS_HANDLER 939 // workspace manager problem ... fall through to default code 940 NS_ENDHANDLER 941 942 if (appName == nil) 943 { 944 NSString *ext = [fullPath pathExtension]; 945 946 if ([self _extension: ext role: nil app: &appName] == NO) 947 { 948 if ([self _openUnknown: fullPath]) 949 { 950 return YES; 951 } 952 else 953 { 954 NSWarnLog(@"No known applications for file extension '%@'", ext); 955 return NO; 956 } 957 } 958 } 959 960 app = [self _connectApplication: appName]; 961 if (app == nil) 962 { 963 NSArray *args; 964 965 args = [NSArray arrayWithObjects: @"-GSFilePath", fullPath, nil]; 966 return [self _launchApplication: appName arguments: args]; 967 } 968 else 969 { 970 NS_DURING 971 { 972 if (flag == NO) 973 { 974 [app application: NSApp openFileWithoutUI: fullPath]; 975 } 976 else 977 { 978 [app application: NSApp openFile: fullPath]; 979 } 980 } 981 NS_HANDLER 982 { 983 NSWarnLog(@"Failed to contact '%@' to open file", appName); 984 return NO; 985 } 986 NS_ENDHANDLER 987 } 988 if (flag) 989 { 990 [NSApp deactivate]; 991 } 992 return YES; 993} 994 995- (BOOL) openTempFile: (NSString*)fullPath 996{ 997 id app; 998 NSString *appName; 999 NSString *ext; 1000 1001 NS_DURING 1002 { 1003 if ((app = [self _workspaceApplication]) != nil) 1004 { 1005 BOOL result; 1006 1007 result = [app openTempFile: fullPath]; 1008 NS_VALRETURN(result); 1009 } 1010 } 1011 NS_HANDLER 1012 // workspace manager problem ... fall through to default code 1013 NS_ENDHANDLER 1014 1015 ext = [fullPath pathExtension]; 1016 if ([self _extension: ext role: nil app: &appName] == NO) 1017 { 1018 if ([self _openUnknown: fullPath]) 1019 { 1020 return YES; 1021 } 1022 else 1023 { 1024 NSWarnLog(@"No known applications for file extension '%@'", ext); 1025 return NO; 1026 } 1027 } 1028 1029 app = [self _connectApplication: appName]; 1030 if (app == nil) 1031 { 1032 NSArray *args; 1033 1034 args = [NSArray arrayWithObjects: @"-GSTempPath", fullPath, nil]; 1035 return [self _launchApplication: appName arguments: args]; 1036 } 1037 else 1038 { 1039 NS_DURING 1040 { 1041 [app application: NSApp openTempFile: fullPath]; 1042 } 1043 NS_HANDLER 1044 { 1045 NSWarnLog(@"Failed to contact '%@' to open temp file", appName); 1046 return NO; 1047 } 1048 NS_ENDHANDLER 1049 } 1050 1051 [NSApp deactivate]; 1052 1053 return YES; 1054} 1055 1056- (BOOL) openURL: (NSURL*)url 1057{ 1058 if ([url isFileURL]) 1059 { 1060 return [self openFile: [url path]]; 1061 } 1062 else 1063 { 1064 NSString *appName; 1065 NSPasteboard *pb; 1066 1067 appName = [self getBestAppInRole: nil forScheme: [url scheme]]; 1068 if (appName != nil) 1069 { 1070 id app; 1071 1072 /* Now try to get the application to open the URL. 1073 */ 1074 app = GSContactApplication(appName, nil, nil); 1075 if (app != nil) 1076 { 1077 NS_DURING 1078 { 1079 [app application: NSApp openURL: url]; 1080 } 1081 NS_HANDLER 1082 { 1083 NSWarnLog(@"Failed to contact '%@' to open file", appName); 1084 return NO; 1085 } 1086 NS_ENDHANDLER 1087 [NSApp deactivate]; 1088 return YES; 1089 } 1090 } 1091 /* No application found to open the URL. 1092 * Try any OpenURL service available. 1093 */ 1094 pb = [NSPasteboard pasteboardWithUniqueName]; 1095 [pb declareTypes: [NSArray arrayWithObject: NSURLPboardType] 1096 owner: nil]; 1097 [url writeToPasteboard: pb]; 1098 if (NSPerformService(@"OpenURL", pb)) 1099 { 1100 return YES; 1101 } 1102 else 1103 { 1104 return [self _openUnknown: [url absoluteString]]; 1105 } 1106 } 1107} 1108 1109/* 1110 * Manipulating Files 1111 */ 1112- (BOOL) performFileOperation: (NSString*)operation 1113 source: (NSString*)source 1114 destination: (NSString*)destination 1115 files: (NSArray*)files 1116 tag: (NSInteger*)tag 1117{ 1118 id app; 1119 1120 NS_DURING 1121 { 1122 if ((app = [self _workspaceApplication]) != nil) 1123 { 1124 BOOL result; 1125 1126 result = [app performFileOperation: operation 1127 source: source 1128 destination: destination 1129 files: files 1130 tag: tag]; 1131 NS_VALRETURN(result); 1132 } 1133 } 1134 NS_HANDLER 1135 // workspace manager problem ... fall through to default code 1136 NS_ENDHANDLER 1137 1138 return NO; 1139} 1140 1141- (BOOL) selectFile: (NSString*)fullPath 1142inFileViewerRootedAtPath: (NSString*)rootFullpath 1143{ 1144 id app; 1145 1146 NS_DURING 1147 { 1148 if ((app = [self _workspaceApplication]) != nil) 1149 { 1150 BOOL result; 1151 1152 result = [app selectFile: fullPath 1153 inFileViewerRootedAtPath: rootFullpath]; 1154 NS_VALRETURN(result); 1155 } 1156 } 1157 NS_HANDLER 1158 // workspace manager problem ... fall through to default code 1159 NS_ENDHANDLER 1160 1161 return NO; 1162} 1163 1164/** 1165 * Given an application name, return the full path for that application.<br /> 1166 * This method looks for the application in standard locations, and if not 1167 * found there, according to MacOS-X documentation, returns nil.<br /> 1168 * If the supplied application name is an absolute path, returns that path 1169 * irrespective of whether such an application exists or not. This is 1170 * <em>not</em> the docmented debavior in the MacOS-X documentation, but is 1171 * the MacOS-X implemented behavior.<br /> 1172 * If the appName has an extension, it is used, otherwise in GNUstep 1173 * the standard app, debug, and profile extensions * are tried.<br /> 1174 */ 1175- (NSString*) fullPathForApplication: (NSString*)appName 1176{ 1177 NSString *base; 1178 NSString *path; 1179 NSString *ext; 1180 1181 if ([appName length] == 0) 1182 { 1183 return nil; 1184 } 1185 if ([[appName lastPathComponent] isEqual: appName] == NO) 1186 { 1187 if ([appName isAbsolutePath] == YES) 1188 { 1189 return appName; // MacOS-X implementation behavior. 1190 } 1191 /* 1192 * Relative path ... get standarized absolute path 1193 */ 1194 path = [[NSFileManager defaultManager] currentDirectoryPath]; 1195 appName = [path stringByAppendingPathComponent: appName]; 1196 appName = [appName stringByStandardizingPath]; 1197 } 1198 base = [appName stringByDeletingLastPathComponent]; 1199 appName = [appName lastPathComponent]; 1200 ext = [appName pathExtension]; 1201 if ([ext length] == 0) // no extension, let's find one 1202 { 1203 path = [appName stringByAppendingPathExtension: @"app"]; 1204 path = [applications objectForKey: path]; 1205 if (path == nil) 1206 { 1207 path = [appName stringByAppendingPathExtension: @"debug"]; 1208 path = [applications objectForKey: path]; 1209 } 1210 if (path == nil) 1211 { 1212 path = [appName stringByAppendingPathExtension: @"profile"]; 1213 path = [applications objectForKey: path]; 1214 } 1215 } 1216 else 1217 { 1218 path = [applications objectForKey: appName]; 1219 } 1220 1221 /* 1222 * If the original name included a path, check that the located name 1223 * matches it. If it doesn't we return nil as MacOS-X does. 1224 */ 1225 if ([base length] > 0 1226 && [base isEqual: [path stringByDeletingLastPathComponent]] == NO) 1227 { 1228 path = nil; 1229 } 1230 return path; 1231} 1232 1233- (BOOL) getFileSystemInfoForPath: (NSString*)fullPath 1234 isRemovable: (BOOL*)removableFlag 1235 isWritable: (BOOL*)writableFlag 1236 isUnmountable: (BOOL*)unmountableFlag 1237 description: (NSString **)description 1238 type: (NSString **)fileSystemType 1239{ 1240 NSArray *removables; 1241 NSString *fsName; 1242 1243 /* since we might not be able to get information about removable volumes 1244 we use the information from the preferences which can be set in SystemPreferences 1245 */ 1246 removables = [[[NSUserDefaults standardUserDefaults] persistentDomainForName: NSGlobalDomain] objectForKey: @"GSRemovableMediaPaths"]; 1247 1248 *removableFlag = NO; 1249 if ([removables containsObject: fullPath]) 1250 *removableFlag = YES; 1251 1252 fsName = nil; 1253#if defined(HAVE_GETMNTENT) && defined (MNT_MEMB) 1254 // if this is called from mountedLocalVolumePaths, the getmntent is searched again for each item 1255 FILE *fptr = setmntent(MOUNTED_PATH, "r"); 1256 struct mntent *me; 1257 1258 while ((me = getmntent(fptr)) != 0) 1259 { 1260 if (strcmp(me->MNT_MEMB, [fullPath fileSystemRepresentation]) == 0) 1261 { 1262 fsName = [NSString stringWithCString:me->MNT_FSNAME]; 1263 } 1264 } 1265 endmntent(fptr); 1266#endif /* HAVE_GETMINTENT */ 1267 if (fsName && [fsName hasPrefix:@"/dev"]) 1268 { 1269 NSString *devName; 1270 NSString *devInfoPath; 1271 BOOL r; 1272 NSString *removableString; 1273 1274 r = NO; 1275 devName = [fsName lastPathComponent]; 1276 // This is a very crude way of removing the partition number 1277 if ([devName length] > 3) 1278 devName = [devName substringToIndex: 3]; 1279 1280 devInfoPath = [@"/sys/block" stringByAppendingPathComponent:devName]; 1281 devInfoPath = [devInfoPath stringByAppendingPathComponent:@"removable"]; 1282 1283 removableString = [[NSString alloc] initWithContentsOfFile:devInfoPath]; 1284 1285 if ([removableString hasPrefix:@"1"]) 1286 r = YES; 1287 [removableString release]; 1288 1289 // we go in OR against the informatoin derived from declared removables 1290 // so we enrich, but don't mark removables as not 1291 *removableFlag |= r; 1292 } 1293 1294 1295#if defined (HAVE_SYS_STATVFS_H) || defined (HAVE_SYS_VFS_H) 1296 /* We use statvfs() if available to get information but statfs() 1297 will provide more information on different systems, but in a non 1298 standard way. Thus e.g. on Linux two calls are needed. 1299 The NetBSD statvfs is a statfs in disguise, i.e., it provides all 1300 information available in the 4.4BSD statfs call. 1301 Other BSDs and Linuxes have statvfs as well, but this returns less 1302 information than the 4.4BSD statfs call. 1303 Note that the POSIX statvfs is not really helpful for us here. The 1304 only information that could be extracted from the data returned by 1305 that syscall is the ST_RDONLY flag. There is no owner field nor a 1306 typename. 1307 The statvfs call on Solaris returns a structure that includes a 1308 non-standard f_basetype field, which provides the name of the 1309 underlying file system type. 1310 */ 1311 BOOL isRootFS; 1312 BOOL hasOwnership; 1313 1314#if defined(HAVE_STATVFS) 1315 #define USING_STATVFS 1 1316 struct statvfs m; 1317 if (statvfs([fullPath fileSystemRepresentation], &m)) 1318 return NO; 1319#elif defined (HAVE_STATFS) 1320 #define USING_STATFS 1 1321 struct statfs m; 1322 if (statfs([fullPath fileSystemRepresentation], &m)) 1323 return NO; 1324#endif 1325 1326 *writableFlag = 1; 1327#if defined(HAVE_STRUCT_STATVFS_F_FLAG) 1328 *writableFlag = (m.f_flag & ST_RDONLY) == 0; 1329#elif defined(HAVE_STRUCT_STATFS_F_FLAGS) 1330 *writableFlag = (m.f_flags & ST_RDONLY) == 0; 1331#endif 1332 1333 1334 isRootFS = NO; 1335#if defined(ST_ROOTFS) 1336 isRootFS = (m.f_flag & ST_ROOTFS); 1337#elif defined (MNT_ROOTFS) 1338 isRootFS = (m.f_flag & MNT_ROOTFS); 1339#endif 1340 1341 hasOwnership = NO; 1342#if (defined(USING_STATFS) && defined(HAVE_STRUCT_STATFS_F_OWNER)) || (defined(USING_STATVFS) && defined(HAVE_STRUCT_STATVFS_F_OWNER)) 1343 uid_t uid = geteuid(); 1344 if (uid == 0 || uid == m.f_owner) 1345 hasOwnership = YES; 1346#elif (defined(USING_STATVFS) && !defined(USING_STATFS) && defined (HAVE_STATFS) && defined(HAVE_STRUCT_STATFS_F_OWNER)) 1347 uid_t uid = geteuid(); 1348 // FreeBSD only? 1349 struct statfs m2; 1350 statfs([fullPath fileSystemRepresentation], &m2); 1351 if (uid == 0 || uid == m2.f_owner) 1352 hasOwnership = YES; 1353#endif 1354 1355 *unmountableFlag = !isRootFS && hasOwnership; 1356 1357 *description = @"filesystem"; // FIXME 1358 1359 *fileSystemType = nil; 1360#if defined (__linux__) 1361 struct statfs m2; 1362 1363 statfs([fullPath fileSystemRepresentation], &m2); 1364 if (m2.f_type == EXT2_SUPER_MAGIC) 1365 *fileSystemType = @"EXT2"; 1366 else if (m2.f_type == EXT3_SUPER_MAGIC) 1367 *fileSystemType = @"EXT3"; 1368 else if (m2.f_type == EXT4_SUPER_MAGIC) 1369 *fileSystemType = @"EXT4"; 1370 else if (m2.f_type == ISOFS_SUPER_MAGIC) 1371 *fileSystemType = @"ISO9660"; 1372#ifdef JFS_SUPER_MAGIC 1373 else if (m2.f_type == JFS_SUPER_MAGIC) 1374 *fileSystemType = @"JFS"; 1375#endif 1376 else if (m2.f_type == MSDOS_SUPER_MAGIC) 1377 *fileSystemType = @"MSDOS"; 1378 else if (m2.f_type == NFS_SUPER_MAGIC) 1379 *fileSystemType = @"NFS"; 1380 else 1381 *fileSystemType = @"Other"; 1382#elif defined(__sun__) 1383 *fileSystemType = 1384 [[NSString alloc] initWithCString: m.f_basetype encoding: [NSString defaultCStringEncoding]]; 1385#elif !defined(__GNU__) 1386 // FIXME we disable this for HURD, but we need to check for struct member in configure 1387 // *fileSystemType = [[NSString alloc] initWithCString: m.f_fstypename encoding: [NSString defaultCStringEncoding]]; 1388#endif 1389 1390#else /* no statfs() nor statvfs() */ 1391 NSLog(@"getFileSystemInfoForPath not supported on your OS"); 1392#endif 1393 1394 return YES; 1395} 1396 1397/** 1398 * This method gets information about the file at fullPath and 1399 * returns YES on success, NO if the named file could not be 1400 * found.<br /> 1401 * On success, the name of the preferred application for opening 1402 * the file is returned in *appName, or nil if there is no known 1403 * application to open it.<br /> 1404 * The returned value in *type describes the file using one of 1405 * the following constants. 1406 * <deflist> 1407 * <term>NSPlainFileType</term> 1408 * <desc> 1409 * A plain file or a directory that some application 1410 * claims to be able to open like a file. 1411 * </desc> 1412 * <term>NSDirectoryFileType</term> 1413 * <desc>An untyped directory</desc> 1414 * <term>NSApplicationFileType</term> 1415 * <desc>A GNUstep application</desc> 1416 * <term>NSFilesystemFileType</term> 1417 * <desc>A file system mount point</desc> 1418 * <term>NSShellCommandFileType</term> 1419 * <desc>Executable shell command</desc> 1420 * </deflist> 1421 */ 1422- (BOOL) getInfoForFile: (NSString*)fullPath 1423 application: (NSString **)appName 1424 type: (NSString **)type 1425{ 1426 NSFileManager *fm = [NSFileManager defaultManager]; 1427 NSDictionary *attributes; 1428 NSString *fileType; 1429 NSString *extension = [fullPath pathExtension]; 1430 1431 attributes = [fm fileAttributesAtPath: fullPath traverseLink: YES]; 1432 1433 if (attributes != nil) 1434 { 1435 *appName = [self getBestAppInRole: nil forExtension: extension]; 1436 fileType = [attributes fileType]; 1437 if ([fileType isEqualToString: NSFileTypeRegular]) 1438 { 1439 if ([attributes filePosixPermissions] & PosixExecutePermission) 1440 { 1441 *type = NSShellCommandFileType; 1442 } 1443 else 1444 { 1445 *type = NSPlainFileType; 1446 } 1447 } 1448 else if ([fileType isEqualToString: NSFileTypeDirectory]) 1449 { 1450 if ([extension isEqualToString: @"app"] 1451 || [extension isEqualToString: @"debug"] 1452 || [extension isEqualToString: @"profile"]) 1453 { 1454 *type = NSApplicationFileType; 1455 } 1456 else if ([extension isEqualToString: @"bundle"]) 1457 { 1458 *type = NSPlainFileType; 1459 } 1460 else if (*appName != nil && [extension length] > 0) 1461 { 1462 *type = NSPlainFileType; 1463 } 1464 /* 1465 * The idea here is that if the parent directory's 1466 * fileSystemNumber differs, this must be a filesystem 1467 * mount point. 1468 */ 1469 else if ([[fm fileAttributesAtPath: 1470 [fullPath stringByDeletingLastPathComponent] 1471 traverseLink: YES] fileSystemNumber] 1472 != [attributes fileSystemNumber]) 1473 { 1474 *type = NSFilesystemFileType; 1475 } 1476 else 1477 { 1478 *type = NSDirectoryFileType; 1479 } 1480 } 1481 else 1482 { 1483 /* 1484 * This catches sockets, character special, block special, 1485 * and unknown file types 1486 */ 1487 *type = NSPlainFileType; 1488 } 1489 return YES; 1490 } 1491 else 1492 { 1493 *appName = nil; 1494 return NO; 1495 } 1496} 1497 1498- (NSImage*) iconForFile: (NSString*)fullPath 1499{ 1500 NSImage *image = nil; 1501 NSString *pathExtension = [[fullPath pathExtension] lowercaseString]; 1502 NSFileManager *mgr = [NSFileManager defaultManager]; 1503 NSDictionary *attributes; 1504 NSString *fileType; 1505 1506 /* 1507 If we have a symobolic link, get not only the original path attributes, 1508 but also the original path, to resolve the correct icon. 1509 mac resolves the original icon 1510 */ 1511 fullPath = [fullPath stringByResolvingSymlinksInPath]; 1512 1513 /* now we get the target attributes of the traversed link */ 1514 attributes = [mgr fileAttributesAtPath: fullPath traverseLink: NO]; 1515 fileType = [attributes fileType]; 1516 1517 if ([fileType isEqual: NSFileTypeDirectory] == YES) 1518 { 1519 NSString *iconPath = nil; 1520 1521 if ([pathExtension isEqualToString: @"app"] 1522 || [pathExtension isEqualToString: @"debug"] 1523 || [pathExtension isEqualToString: @"profile"]) 1524 { 1525 image = [self appIconForApp: fullPath]; 1526 1527 if (image == nil) 1528 { 1529 /* 1530 * Just use the appropriate icon for the path extension 1531 */ 1532 return [self _iconForExtension: pathExtension]; 1533 } 1534 } 1535 1536 /* 1537 * If we have no iconPath, try 'dir/.dir.png' as a 1538 * possible locations for the directory icon. 1539 */ 1540 if (iconPath == nil) 1541 { 1542 iconPath = [fullPath stringByAppendingPathComponent: @".dir.png"]; 1543 if ([mgr isReadableFileAtPath: iconPath] == NO) 1544 { 1545 iconPath 1546 = [fullPath stringByAppendingPathComponent: @".dir.tiff"]; 1547 if ([mgr isReadableFileAtPath: iconPath] == NO) 1548 { 1549 iconPath = nil; 1550 } 1551 } 1552 } 1553 1554 if (iconPath != nil) 1555 { 1556 image = [self _saveImageFor: iconPath]; 1557 } 1558 1559 if (image == nil) 1560 { 1561 image = [self _iconForExtension: pathExtension]; 1562 if (image == nil || image == [self unknownFiletypeImage]) 1563 { 1564 NSString *iconName; 1565 1566 iconName = [folderPathIconDict objectForKey: fullPath]; 1567 if (iconName != nil) 1568 { 1569 NSImage *iconImage; 1570 1571 iconImage = [folderIconCache objectForKey: iconName]; 1572 if (iconImage == nil) 1573 { 1574 iconImage = [NSImage _standardImageWithName: iconName]; 1575 if (!iconImage) 1576 { 1577 /* no specific image found in theme, fall-back to folder */ 1578 NSLog(@"no image found for %@", iconName); 1579 iconImage = [NSImage _standardImageWithName: @"Folder"]; 1580 } 1581 /* the dictionary retains the image */ 1582 [folderIconCache setObject: iconImage forKey: iconName]; 1583 } 1584 image = iconImage; 1585 } 1586 else 1587 { 1588 if (folderImage == nil) 1589 { 1590 folderImage = RETAIN([NSImage _standardImageWithName: 1591 @"Folder"]); 1592 } 1593 image = folderImage; 1594 } 1595 } 1596 1597 } 1598 } 1599 else 1600 { 1601 NSDebugLog(@"pathExtension is '%@'", pathExtension); 1602 1603 if ([[NSUserDefaults standardUserDefaults] boolForKey: 1604 @"GSUseFreedesktopThumbnails"]) 1605 { 1606 /* This image will be 128x128 pixels as oposed to the 48x48 1607 of other GNUstep icons or the 32x32 of the specification */ 1608 image = [self _saveImageFor: [self thumbnailForFile: fullPath]]; 1609 if (image != nil) 1610 { 1611 return image; 1612 } 1613 } 1614 1615 image = [self _iconForExtension: pathExtension]; 1616 if (image == nil || image == [self unknownFiletypeImage]) 1617 { 1618 NSFileManager *mgr; 1619 1620 mgr = [NSFileManager defaultManager]; 1621 if ([mgr isExecutableFileAtPath: fullPath] == YES) 1622 { 1623 NSDictionary *attributes; 1624 NSString *fileType; 1625 1626 attributes = [mgr fileAttributesAtPath: fullPath 1627 traverseLink: YES]; 1628 fileType = [attributes objectForKey: NSFileType]; 1629 if ([fileType isEqual: NSFileTypeRegular] == YES) 1630 { 1631 if (unknownTool == nil) 1632 { 1633 unknownTool = RETAIN([NSImage _standardImageWithName: 1634 @"UnknownTool"]); 1635 } 1636 image = unknownTool; 1637 } 1638 } 1639 } 1640 } 1641 1642 if (image == nil) 1643 { 1644 image = [self unknownFiletypeImage]; 1645 } 1646 1647 return image; 1648} 1649 1650- (NSImage*) iconForFiles: (NSArray*)pathArray 1651{ 1652 if ([pathArray count] == 1) 1653 { 1654 return [self iconForFile: [pathArray objectAtIndex: 0]]; 1655 } 1656 if (multipleFiles == nil) 1657 { 1658 // FIXME: Icon does not exist 1659 multipleFiles = [NSImage imageNamed: @"FileIcon_multi"]; 1660 } 1661 1662 return multipleFiles; 1663} 1664 1665- (NSImage*) iconForFileType: (NSString*)fileType 1666{ 1667 return [self _iconForExtension: fileType]; 1668} 1669 1670- (BOOL) isFilePackageAtPath: (NSString*)fullPath 1671{ 1672 NSFileManager *mgr = [NSFileManager defaultManager]; 1673 NSDictionary *attributes; 1674 NSString *fileType, *extension; 1675 1676 attributes = [mgr fileAttributesAtPath: fullPath traverseLink: YES]; 1677 fileType = [attributes objectForKey: NSFileType]; 1678 if ([fileType isEqual: NSFileTypeDirectory] == YES) 1679 { 1680 /* 1681 * We return YES here exactly when getInfoForFile:application:type: 1682 * considers the directory an application or a plain file 1683 */ 1684 extension = [fullPath pathExtension]; 1685 if ([extension isEqualToString: @"app"] 1686 || [extension isEqualToString: @"debug"] 1687 || [extension isEqualToString: @"profile"] 1688 || [extension isEqualToString: @"bundle"]) 1689 { 1690 return YES; 1691 } 1692 else if ([extension length] > 0 1693 && [self getBestAppInRole: nil forExtension: extension] != nil) 1694 { 1695 return YES; 1696 } 1697 } 1698 return NO; 1699} 1700 1701- (BOOL) setIcon: (NSImage *)image 1702 forFile: (NSString *)fullPath 1703 options: (NSWorkspaceIconCreationOptions)options 1704{ 1705 // FIXME 1706 return NO; 1707} 1708 1709/** 1710 * Tracking Changes to the File System 1711 */ 1712- (BOOL) fileSystemChanged 1713{ 1714 BOOL flag = _fileSystemChanged; 1715 1716 _fileSystemChanged = NO; 1717 return flag; 1718} 1719 1720- (void) noteFileSystemChanged 1721{ 1722 _fileSystemChanged = YES; 1723} 1724 1725- (void) noteFileSystemChanged: (NSString*)path 1726{ 1727 _fileSystemChanged = YES; 1728} 1729 1730/** 1731 * Updates Registered Services, File Types, and other information about any 1732 * applications installed in the standard locations. 1733 */ 1734- (void) findApplications 1735{ 1736 static NSString *path = nil; 1737 NSTask *task; 1738 1739 /* 1740 * Try to locate and run an executable copy of 'make_services' 1741 */ 1742 if (path == nil) 1743 { 1744 path = [[NSTask launchPathForTool: @"make_services"] retain]; 1745 } 1746 task = [NSTask launchedTaskWithLaunchPath: path 1747 arguments: nil]; 1748 if (task != nil) 1749 { 1750 [task waitUntilExit]; 1751 } 1752 [self _workspacePreferencesChanged: 1753 [NSNotification notificationWithName: GSWorkspacePreferencesChanged 1754 object: self]]; 1755} 1756 1757/** 1758 * Instructs all the other running applications to hide themselves. 1759 * <em>not yet implemented</em> 1760 */ 1761- (void) hideOtherApplications 1762{ 1763 // FIXME 1764} 1765 1766/** 1767 * Calls -launchApplication:showIcon:autolaunch: with arguments set to 1768 * show the icon but not set it up as an autolaunch. 1769 */ 1770- (BOOL) launchApplication: (NSString*)appName 1771{ 1772 return [self launchApplication: appName 1773 showIcon: YES 1774 autolaunch: NO]; 1775} 1776 1777/** 1778 * <p>Launches the specified application (unless it is already running).<br /> 1779 * If the autolaunch flag is yes, sets the autolaunch user default for the 1780 * newly launched application, so that applications which understand the 1781 * concept of being autolaunched at system startup time can modify their 1782 * behavior appropriately. 1783 * </p> 1784 * <p>Sends an NSWorkspaceWillLaunchApplicationNotification before it 1785 * actually attempts to launch the application (this is not sent if the 1786 * application is already running). 1787 * </p> 1788 * <p>The application sends an NSWorkspaceDidlLaunchApplicationNotification 1789 * on completion of launching. This is not sent if the application is already 1790 * running, or if it fails to complete its startup. 1791 * </p> 1792 * <p>Returns NO if the application cannot be launched (eg. it does not exist 1793 * or the binary is not executable). 1794 * </p> 1795 * <p>Returns YES if the application was already running or of it was launched 1796 * (this does not necessarily mean that the application succeeded in starting 1797 * up fully). 1798 * </p> 1799 * <p>Once an application has fully started up, you should be able to connect 1800 * to it using [NSConnection+rootProxyForConnectionWithRegisteredName:host:] 1801 * passing the application name (normally the filesystem name excluding path 1802 * and file extension) and an empty host name. This will let you communicate 1803 * with the the [NSApplication-delegate] of the launched application, and you 1804 * can generally use this as a test of whether an application is running 1805 * correctly. 1806 * </p> 1807 */ 1808- (BOOL) launchApplication: (NSString*)appName 1809 showIcon: (BOOL)showIcon 1810 autolaunch: (BOOL)autolaunch 1811{ 1812 id app; 1813 1814 NS_DURING 1815 { 1816 if ((app = [self _workspaceApplication]) != nil) 1817 { 1818 BOOL result; 1819 1820 result = [app launchApplication: appName 1821 showIcon: showIcon 1822 autolaunch: autolaunch]; 1823 NS_VALRETURN(result); 1824 } 1825 } 1826 NS_HANDLER 1827 // workspace manager problem ... fall through to default code 1828 NS_ENDHANDLER 1829 1830 app = [self _connectApplication: appName]; 1831 if (app == nil) 1832 { 1833 NSArray *args = nil; 1834 1835 if (autolaunch == YES) 1836 { 1837 args = [NSArray arrayWithObjects: @"-autolaunch", @"YES", nil]; 1838 } 1839 return [self _launchApplication: appName arguments: args]; 1840 } 1841 else 1842 { 1843 [app activateIgnoringOtherApps:YES]; 1844 } 1845 1846 return YES; 1847} 1848 1849- (NSString *) absolutePathForAppBundleWithIdentifier: (NSString *)bundleIdentifier 1850{ 1851 // TODO: full implementation 1852 return [self fullPathForApplication: bundleIdentifier]; 1853} 1854 1855- (BOOL) launchAppWithBundleIdentifier: (NSString *)bundleIdentifier 1856 options: (NSWorkspaceLaunchOptions)options 1857 additionalEventParamDescriptor: (NSAppleEventDescriptor *)descriptor 1858 launchIdentifier: (NSNumber **)identifier 1859{ 1860 // TODO: full implementation 1861 return [self launchApplication: bundleIdentifier 1862 showIcon: YES 1863 autolaunch: NO]; 1864} 1865 1866- (BOOL) openURLs: (NSArray *)urls 1867withAppBundleIdentifier: (NSString *)bundleIdentifier 1868 options: (NSWorkspaceLaunchOptions)options 1869additionalEventParamDescriptor: (NSAppleEventDescriptor *)descriptor 1870launchIdentifiers: (NSArray **)identifiers 1871{ 1872 // FIXME 1873 return NO; 1874} 1875 1876/** 1877 * Returns a description of the currently active application, containing 1878 * the name (NSApplicationName), path (NSApplicationPath) and process 1879 * identifier (NSApplicationProcessIdentifier).<br /> 1880 * Returns nil if there is no known active application. 1881 */ 1882- (NSDictionary*) activeApplication 1883{ 1884 id app; 1885 1886 NS_DURING 1887 { 1888 if ((app = [self _workspaceApplication]) != nil) 1889 { 1890 NSDictionary *result; 1891 1892 result = [app activeApplication]; 1893 NS_VALRETURN(result); 1894 } 1895 } 1896 NS_HANDLER 1897 // workspace manager problem ... fall through to default code 1898 NS_ENDHANDLER 1899 1900 return GSLaunched(nil, YES); 1901} 1902 1903/** 1904 * Returns an array listing all the applications known to have been 1905 * launched. Each entry in the array is a dictionary providing 1906 * the name, path and process identfier of an application. 1907 */ 1908- (NSArray*) launchedApplications 1909{ 1910 NSArray *apps = nil; 1911 1912 NS_DURING 1913 { 1914 id app; 1915 1916 if ((app = [self _workspaceApplication]) != nil) 1917 { 1918 apps = [app launchedApplications]; 1919 } 1920 } 1921 NS_HANDLER 1922 { 1923 // workspace manager problem ... fall through to default code 1924 } 1925 NS_ENDHANDLER 1926 1927 if (apps == nil) 1928 { 1929 NSMutableArray *m; 1930 unsigned count; 1931 1932 apps = GSLaunched(nil, NO); 1933 apps = m = AUTORELEASE([apps mutableCopy]); 1934 if ((count = [apps count]) > 0) 1935 { 1936 if ([NSProcessInfo respondsToSelector: @selector(_exists:)] == YES) 1937 { 1938 /* Check and remove apps whose pid no longer exists 1939 */ 1940 while (count-- > 0) 1941 { 1942 int pid; 1943 NSString *name; 1944 1945 name = [[apps objectAtIndex: count] 1946 objectForKey: @"NSApplicationName"]; 1947 pid = [[[apps objectAtIndex: count] 1948 objectForKey: @"NSApplicationProcessIdentifier"] intValue]; 1949 if (pid > 0 && [name length] > 0) 1950 { 1951 if ([NSProcessInfo _exists: pid] == NO) 1952 { 1953 GSLaunched([NSNotification notificationWithName: 1954 NSWorkspaceDidTerminateApplicationNotification 1955 object: self 1956 userInfo: [NSDictionary dictionaryWithObject: name 1957 forKey: @"NSApplicationName"]], NO); 1958 [m removeObjectAtIndex: count]; 1959 } 1960 } 1961 } 1962 } 1963 } 1964 } 1965 return apps; 1966} 1967 1968/* 1969 * Unmounting a Device and eject if possible 1970 */ 1971- (BOOL) unmountAndEjectDeviceAtPath: (NSString*)path 1972{ 1973 NSUInteger systype = [[NSProcessInfo processInfo] operatingSystem]; 1974 NSDictionary *userinfo; 1975 NSTask *task; 1976 1977 /* let's check if it is a local volume we may unmount */ 1978 if (![[self mountedLocalVolumePaths] containsObject:path]) 1979 { 1980 NSLog(@"unmountAndEjectDeviceAtPath: Path %@ not mounted", path); 1981 return NO; 1982 } 1983 1984 userinfo = [NSDictionary dictionaryWithObject: path 1985 forKey: @"NSDevicePath"]; 1986 [_workspaceCenter postNotificationName: NSWorkspaceWillUnmountNotification 1987 object: self 1988 userInfo: userinfo]; 1989 task = [NSTask launchedTaskWithLaunchPath: @"umount" 1990 arguments: [NSArray arrayWithObject: path]]; 1991 1992 if (task) 1993 { 1994 [task waitUntilExit]; 1995 if ([task terminationStatus] != 0) 1996 { 1997 return NO; 1998 } 1999 } 2000 else 2001 { 2002 return NO; 2003 } 2004 2005 [[self notificationCenter] postNotificationName: NSWorkspaceDidUnmountNotification 2006 object: self 2007 userInfo: userinfo]; 2008 2009 /* this is system specific and we try our best 2010 and the failure of eject doesn't mean unmount failed */ 2011 task = nil; 2012 if (systype == NSGNULinuxOperatingSystem) 2013 { 2014 task = [NSTask launchedTaskWithLaunchPath: @"eject" 2015 arguments: [NSArray arrayWithObject: path]]; 2016 } 2017 else if (systype == NSBSDOperatingSystem || systype == NSSolarisOperatingSystem) 2018 { 2019 NSString *mountDir; 2020 2021 // Note: it would be better to check the device, not the mount point 2022 mountDir = [path lastPathComponent]; 2023 if ([mountDir rangeOfString:@"cd"].location != NSNotFound || 2024 [mountDir rangeOfString:@"dvd"].location != NSNotFound) 2025 { 2026 task = [NSTask launchedTaskWithLaunchPath: @"eject" 2027 arguments: [NSArray arrayWithObject: @"cdrom"]]; 2028 } 2029 else if ([mountDir rangeOfString:@"fd"].location != NSNotFound || 2030 [mountDir rangeOfString:@"floppy"].location != NSNotFound) 2031 { 2032 task = [NSTask launchedTaskWithLaunchPath: @"eject" 2033 arguments: [NSArray arrayWithObject: @"floppy"]]; 2034 } 2035 } 2036 else 2037 { 2038 NSLog(@"Don't know how to eject"); 2039 } 2040 if (task != nil) 2041 { 2042 [task waitUntilExit]; 2043 if ([task terminationStatus] != 0) 2044 { 2045 NSLog(@"eject failed"); 2046 } 2047 } 2048 2049 return YES; 2050} 2051 2052/* 2053 * Tracking Status Changes for Devices 2054 */ 2055- (void) checkForRemovableMedia 2056{ 2057 // FIXME 2058} 2059 2060- (NSArray*) mountNewRemovableMedia 2061{ 2062 NSArray *removables; 2063 NSArray *mountedMedia = [self mountedRemovableMedia]; 2064 NSMutableArray *willMountMedia = [NSMutableArray array]; 2065 NSMutableArray *newlyMountedMedia = [NSMutableArray array]; 2066 NSUInteger i; 2067 2068 /* we use the system preferences to know which ones to mount */ 2069 removables = [[[NSUserDefaults standardUserDefaults] persistentDomainForName: NSGlobalDomain] objectForKey: @"GSRemovableMediaPaths"]; 2070 2071 for (i = 0; i < [removables count]; i++) 2072 { 2073 NSString *removable = [removables objectAtIndex: i]; 2074 2075 if ([mountedMedia containsObject: removable] == NO) 2076 { 2077 [willMountMedia addObject: removable]; 2078 } 2079 } 2080 2081 for (i = 0; i < [willMountMedia count]; i++) 2082 { 2083 NSString *media = [willMountMedia objectAtIndex: i]; 2084 NSTask *task = [NSTask launchedTaskWithLaunchPath: @"mount" 2085 arguments: [NSArray arrayWithObject: media]]; 2086 2087 if (task) 2088 { 2089 [task waitUntilExit]; 2090 2091 if ([task terminationStatus] == 0) 2092 { 2093 NSDictionary *userinfo = [NSDictionary dictionaryWithObject: media 2094 forKey: @"NSDevicePath"]; 2095 2096 [[self notificationCenter] postNotificationName: NSWorkspaceDidMountNotification 2097 object: self 2098 userInfo: userinfo]; 2099 2100 [newlyMountedMedia addObject: media]; 2101 } 2102 } 2103 } 2104 2105 return newlyMountedMedia; 2106} 2107 2108- (NSArray*) mountedRemovableMedia 2109{ 2110 NSArray *volumes; 2111 NSMutableArray *names; 2112 NSUInteger count; 2113 NSUInteger i; 2114 2115 volumes = [self mountedLocalVolumePaths]; 2116 count = [volumes count]; 2117 names = [NSMutableArray arrayWithCapacity: count]; 2118 for (i = 0; i < count; i++) 2119 { 2120 BOOL removableFlag; 2121 BOOL writableFlag; 2122 BOOL unmountableFlag; 2123 NSString *description; 2124 NSString *fileSystemType; 2125 NSString *name = [volumes objectAtIndex: i]; 2126 2127 if ([self getFileSystemInfoForPath: name 2128 isRemovable: &removableFlag 2129 isWritable: &writableFlag 2130 isUnmountable: &unmountableFlag 2131 description: &description 2132 type: &fileSystemType] && removableFlag) 2133 { 2134 [names addObject: name]; 2135 } 2136 } 2137 NSDebugLog(@"mountedRemovableMedia returning names: %@", names); 2138 return names; 2139} 2140 2141- (NSArray*) mountedLocalVolumePaths 2142{ 2143 NSMutableArray *names; 2144 NSArray *reservedMountNames; 2145 2146 // get reserved names.... 2147 reservedMountNames = [[NSUserDefaults standardUserDefaults] objectForKey: @"GSReservedMountNames"]; 2148 if (reservedMountNames == nil) 2149 { 2150 reservedMountNames = [NSArray arrayWithObjects: 2151 @"proc",@"devpts", 2152 @"shm",@"usbdevfs", 2153 @"devtmpfs", @"devpts",@"sysfs", 2154 @"tmpfs",@"procbususb", 2155 @"udev", @"pstore", 2156 @"cgroup", nil]; 2157 [[NSUserDefaults standardUserDefaults] setObject: reservedMountNames 2158 forKey: @"GSReservedMountNames"]; 2159 } 2160 2161#if defined(__MINGW32__) 2162 NSFileManager *mgr = [NSFileManager defaultManager]; 2163 unsigned max = BUFSIZ; 2164 unichar buf[max]; 2165 unichar *base = buf; 2166 unichar *ptr; 2167 unichar *end; 2168 unsigned len; 2169 2170 names = [NSMutableArray arrayWithCapacity: 8]; 2171 len = GetLogicalDriveStringsW(max-1, base); 2172 while (len >= max) 2173 { 2174 base = NSZoneMalloc(NSDefaultMallocZone(), (len+1) * sizeof(unichar)); 2175 max = len; 2176 len = GetLogicalDriveStringsW(max-1, base); 2177 } 2178 for (ptr = base; *ptr != 0; ptr = end + 1) 2179 { 2180 NSString *path; 2181 2182 end = ptr; 2183 while (*end != 0) 2184 { 2185 end++; 2186 } 2187 len = (end - ptr); 2188 path = [mgr stringWithFileSystemRepresentation: ptr length: len]; 2189 [names addObject: path]; 2190 } 2191 if (base != buf) 2192 { 2193 NSZoneFree(NSDefaultMallocZone(), base); 2194 } 2195 2196#elif defined (HAVE_GETMNTINFO) 2197 NSFileManager *mgr = [NSFileManager defaultManager]; 2198 unsigned int i, n; 2199#if defined(HAVE_STATVFS) && defined (__NetBSD__) 2200 struct statvfs *m; 2201#else 2202 struct statfs *m; 2203#endif 2204 2205 n = getmntinfo(&m, MNT_NOWAIT); 2206 names = [NSMutableArray arrayWithCapacity: n]; 2207 for (i = 0; i < n; i++) 2208 { 2209 /* NB For now assume that all local volumes are mounted from a device 2210 with an entry /dev and this is not the case for any pseudo 2211 filesystems. 2212 */ 2213 if (strncmp(m[i].f_mntfromname, "/dev/", 5) == 0) 2214 { 2215 [names addObject: 2216 [mgr stringWithFileSystemRepresentation: m[i].f_mntonname 2217 length: strlen(m[i].f_mntonname)]]; 2218 } 2219 } 2220#elif defined(HAVE_GETMNTENT) && defined (MNT_MEMB) 2221 2222 NSFileManager *mgr = [NSFileManager defaultManager]; 2223 FILE *fptr = setmntent(MOUNTED_PATH, "r"); 2224 struct mntent *m; 2225 2226 names = [NSMutableArray arrayWithCapacity: 8]; 2227 while ((m = getmntent(fptr)) != 0) 2228 { 2229 NSString *path; 2230 NSString *type; 2231 2232 path = [mgr stringWithFileSystemRepresentation: m->MNT_MEMB 2233 length: strlen(m->MNT_MEMB)]; 2234 type = [NSString stringWithCString:m->mnt_type]; 2235 if ([reservedMountNames containsObject: type] == NO) 2236 { 2237 [names addObject: path]; 2238 } 2239 } 2240 endmntent(fptr); 2241#else 2242 /* we resort in parsing mtab manually and removing then reserved mount names 2243 defined in preferences GSReservedMountNames (SystemPreferences) */ 2244 NSString *mtabPath; 2245 NSString *mtab; 2246 NSArray *mounts; 2247 unsigned int i; 2248 2249 // get mount table... 2250 mtabPath = [[NSUserDefaults standardUserDefaults] objectForKey:@"GSMtabPath"]; 2251 if (mtabPath == nil) 2252 { 2253 mtabPath = @"/etc/mtab"; 2254 } 2255 2256 mtab = [NSString stringWithContentsOfFile: mtabPath]; 2257 mounts = [mtab componentsSeparatedByString: @"\n"]; 2258 2259 names = [NSMutableArray arrayWithCapacity: [mounts count]]; 2260 for (i = 0; i < [mounts count]; i++) 2261 { 2262 NSString *mount = [mounts objectAtIndex: i]; 2263 2264 if ([mount length]) 2265 { 2266 NSArray *parts = [mount componentsSeparatedByString: @" "]; 2267 2268 if ([parts count] >= 2) 2269 { 2270 NSString *type = [parts objectAtIndex: 2]; 2271 if ([reservedMountNames containsObject: type] == NO) 2272 { 2273 [names addObject: [parts objectAtIndex: 1]]; 2274 } 2275 } 2276 } 2277 } 2278#endif 2279 NSDebugLog(@"mountedLocalVolumePaths returning names: %@", names); 2280 return names; 2281} 2282 2283/** 2284 * Returns the workspace notification center 2285 */ 2286- (NSNotificationCenter*) notificationCenter 2287{ 2288 return _workspaceCenter; 2289} 2290 2291/** 2292 * Simply makes a note that the user defaults database has changed. 2293 */ 2294- (void) noteUserDefaultsChanged 2295{ 2296 _userDefaultsChanged = YES; 2297} 2298 2299/** 2300 * Returns a flag to say if the defaults database has changed since 2301 * the last time this method was called. 2302 */ 2303- (BOOL) userDefaultsChanged 2304{ 2305 BOOL hasChanged = _userDefaultsChanged; 2306 2307 _userDefaultsChanged = NO; 2308 return hasChanged; 2309} 2310 2311/** 2312 * Animating an Image- slides it from one point on the screen to another. 2313 */ 2314- (void) slideImage: (NSImage*)image 2315 from: (NSPoint)fromPoint 2316 to: (NSPoint)toPoint 2317{ 2318 [GSCurrentServer() slideImage: image from: fromPoint to: toPoint]; 2319} 2320 2321/* 2322 * Requesting Additional Time before Power Off or Logout<br /> 2323 * Returns the amount of time actually granted (which may be less than 2324 * requested).<br /> 2325 * Times are measured in milliseconds. 2326 */ 2327- (int) extendPowerOffBy: (int)requested 2328{ 2329 id app; 2330 2331 NS_DURING 2332 { 2333 if ((app = [self _workspaceApplication]) != nil) 2334 { 2335 int result; 2336 2337 result = [app extendPowerOffBy: requested]; 2338 NS_VALRETURN(result); 2339 } 2340 } 2341 NS_HANDLER 2342 // workspace manager problem ... fall through to default code 2343 NS_ENDHANDLER 2344 2345 return 0; 2346} 2347 2348- (BOOL) filenameExtension: (NSString *)filenameExtension 2349 isValidForType: (NSString*)typeName 2350{ 2351 // FIXME 2352 return [filenameExtension isEqualToString: typeName]; 2353} 2354 2355- (NSString *) localizedDescriptionForType: (NSString *)typeName 2356{ 2357 // FIXME 2358 return typeName; 2359} 2360 2361- (NSString *) preferredFilenameExtensionForType: (NSString *)typeName 2362{ 2363 // FIXME 2364 return typeName; 2365} 2366 2367- (BOOL) type: (NSString *)firstTypeName conformsToType: (NSString *)secondTypeName 2368{ 2369 // FIXME 2370 return [firstTypeName isEqualToString: secondTypeName]; 2371} 2372 2373- (NSString *) typeOfFile: (NSString *)absoluteFilePath error: (NSError **)outError 2374{ 2375 // FIXME 2376 return [absoluteFilePath pathExtension]; 2377} 2378 2379@end 2380 2381@implementation NSWorkspace (GNUstep) 2382 2383/** 2384 * Returns the 'best' application to open a file with the specified extension 2385 * using the given role. If the role is nil then apps which can edit are 2386 * preferred but viewers are also acceptable. Uses a user preferred app 2387 * or picks any good match. 2388 */ 2389- (NSString*) getBestAppInRole: (NSString*)role 2390 forExtension: (NSString*)ext 2391{ 2392 NSString *appName = nil; 2393 2394 if ([self _extension: ext role: role app: &appName] == NO) 2395 { 2396 appName = nil; 2397 } 2398 return appName; 2399} 2400 2401/** 2402 * Returns the path set for the icon matching the image by 2403 * -setBestIcon:forExtension: 2404 */ 2405- (NSString*) getBestIconForExtension: (NSString*)ext 2406{ 2407 NSString *iconPath = nil; 2408 2409 if (extPreferences != nil) 2410 { 2411 NSDictionary *inf; 2412 2413 inf = [extPreferences objectForKey: [ext lowercaseString]]; 2414 if (inf != nil) 2415 { 2416 iconPath = [inf objectForKey: @"Icon"]; 2417 } 2418 } 2419 return iconPath; 2420} 2421 2422/** 2423 * Gets the applications cache (generated by the make_services tool) 2424 * and looks up the special entry that contains a dictionary of all 2425 * file extensions recognised by GNUstep applications. Then finds 2426 * the dictionary of applications that can handle our file and 2427 * returns it. 2428 */ 2429- (NSDictionary*) infoForExtension: (NSString*)ext 2430{ 2431 NSDictionary *map; 2432 2433 ext = [ext lowercaseString]; 2434 map = [applications objectForKey: @"GSExtensionsMap"]; 2435 return [map objectForKey: ext]; 2436} 2437 2438/** 2439 * Returns the application bundle for the named application. Accepts 2440 * either a full path to an app or just the name. The extension (.app, 2441 * .debug, .profile) is optional, but if provided it will be used.<br /> 2442 * Returns nil if the specified app does not exist as requested. 2443 */ 2444- (NSBundle*) bundleForApp: (NSString*)appName 2445{ 2446 if ([appName length] == 0) 2447 { 2448 return nil; 2449 } 2450 if ([[appName lastPathComponent] isEqual: appName]) // it's a name 2451 { 2452 appName = [self fullPathForApplication: appName]; 2453 } 2454 else 2455 { 2456 NSFileManager *fm; 2457 NSString *ext; 2458 BOOL flag; 2459 2460 fm = [NSFileManager defaultManager]; 2461 ext = [appName pathExtension]; 2462 if ([ext length] == 0) // no extension, let's find one 2463 { 2464 NSString *path; 2465 2466 path = [appName stringByAppendingPathExtension: @"app"]; 2467 if ([fm fileExistsAtPath: path isDirectory: &flag] == NO 2468 || flag == NO) 2469 { 2470 path = [appName stringByAppendingPathExtension: @"debug"]; 2471 if ([fm fileExistsAtPath: path isDirectory: &flag] == NO 2472 || flag == NO) 2473 { 2474 path = [appName stringByAppendingPathExtension: @"profile"]; 2475 } 2476 } 2477 appName = path; 2478 } 2479 if ([fm fileExistsAtPath: appName isDirectory: &flag] == NO 2480 || flag == NO) 2481 { 2482 appName = nil; 2483 } 2484 } 2485 if (appName == nil) 2486 { 2487 return nil; 2488 } 2489 return [NSBundle bundleWithPath: appName]; 2490} 2491 2492/** 2493 * Returns the application icon for the given app. 2494 * Or null if none defined or appName is not a valid application name. 2495 */ 2496- (NSImage*) appIconForApp: (NSString*)appName 2497{ 2498 NSBundle *bundle; 2499 NSImage *image = nil; 2500 NSFileManager *mgr = [NSFileManager defaultManager]; 2501 NSString *iconPath = nil; 2502 NSString *fullPath; 2503 2504 fullPath = [self fullPathForApplication: appName]; 2505 bundle = [self bundleForApp: fullPath]; 2506 if (bundle == nil) 2507 { 2508 return nil; 2509 } 2510 2511 iconPath = [[bundle infoDictionary] objectForKey: @"NSIcon"]; 2512 if (iconPath == nil) 2513 { 2514 /* 2515 * Try the CFBundleIconFile property. 2516 */ 2517 iconPath = [[bundle infoDictionary] objectForKey: @"CFBundleIconFile"]; 2518 } 2519 2520 if (iconPath && [iconPath isAbsolutePath] == NO) 2521 { 2522 NSString *file = iconPath; 2523 2524 iconPath = [bundle pathForImageResource: file]; 2525 2526 /* 2527 * If there is no icon in the Resources of the app, try 2528 * looking directly in the app wrapper. 2529 */ 2530 if (iconPath == nil) 2531 { 2532 iconPath = [fullPath stringByAppendingPathComponent: file]; 2533 if ([mgr isReadableFileAtPath: iconPath] == NO) 2534 { 2535 iconPath = nil; 2536 } 2537 } 2538 } 2539 2540 /* 2541 * If there is no icon specified in the Info.plist for app 2542 * try 'wrapper/app.png' 2543 */ 2544 if (iconPath == nil) 2545 { 2546 NSString *str; 2547 2548 str = [fullPath lastPathComponent]; 2549 str = [str stringByDeletingPathExtension]; 2550 iconPath = [fullPath stringByAppendingPathComponent: str]; 2551 iconPath = [iconPath stringByAppendingPathExtension: @"png"]; 2552 if ([mgr isReadableFileAtPath: iconPath] == NO) 2553 { 2554 iconPath = [iconPath stringByAppendingPathExtension: @"tiff"]; 2555 if ([mgr isReadableFileAtPath: iconPath] == NO) 2556 { 2557 iconPath = [iconPath stringByAppendingPathExtension: @"icns"]; 2558 if ([mgr isReadableFileAtPath: iconPath] == NO) 2559 { 2560 iconPath = nil; 2561 } 2562 } 2563 } 2564 } 2565 2566 if (iconPath != nil) 2567 { 2568 image = [self _saveImageFor: iconPath]; 2569 } 2570 2571 return image; 2572} 2573 2574/** 2575 * Requires the path to an application wrapper as an argument, and returns 2576 * the full path to the executable. 2577 */ 2578- (NSString*) locateApplicationBinary: (NSString*)appName 2579{ 2580 NSString *path; 2581 NSString *file; 2582 NSBundle *bundle = [self bundleForApp: appName]; 2583 2584 if (bundle == nil) 2585 { 2586 return nil; 2587 } 2588 path = [bundle bundlePath]; 2589 file = [[bundle infoDictionary] objectForKey: @"NSExecutable"]; 2590 2591 if (file == nil) 2592 { 2593 /* 2594 * If there is no executable specified in the info property-list, then 2595 * we expect the executable to reside within the app wrapper and to 2596 * have the same name as the app wrapper but without the extension. 2597 */ 2598 file = [path lastPathComponent]; 2599 file = [file stringByDeletingPathExtension]; 2600 path = [path stringByAppendingPathComponent: file]; 2601 } 2602 else 2603 { 2604 /* 2605 * If there is an executable specified in the info property-list, then 2606 * it can be either an absolute path, or a path relative to the app 2607 * wrapper, so we make sure we end up with an absolute path to return. 2608 */ 2609 if ([file isAbsolutePath] == YES) 2610 { 2611 path = file; 2612 } 2613 else 2614 { 2615 path = [path stringByAppendingPathComponent: file]; 2616 } 2617 } 2618 2619 return path; 2620} 2621 2622/** 2623 * Sets up a user preference for which app should be used to open files 2624 * of the specified extension. 2625 */ 2626- (void) setBestApp: (NSString*)appName 2627 inRole: (NSString*)role 2628 forExtension: (NSString*)ext 2629{ 2630 NSMutableDictionary *map; 2631 NSMutableDictionary *inf; 2632 NSData *data; 2633 2634 ext = [ext lowercaseString]; 2635 if (extPreferences != nil) 2636 map = [extPreferences mutableCopy]; 2637 else 2638 map = [NSMutableDictionary new]; 2639 2640 inf = [[map objectForKey: ext] mutableCopy]; 2641 if (inf == nil) 2642 { 2643 inf = [NSMutableDictionary new]; 2644 } 2645 if (appName == nil) 2646 { 2647 if (role == nil) 2648 { 2649 NSString *iconPath = [inf objectForKey: @"Icon"]; 2650 2651 RETAIN(iconPath); 2652 [inf removeAllObjects]; 2653 if (iconPath) 2654 { 2655 [inf setObject: iconPath forKey: @"Icon"]; 2656 RELEASE(iconPath); 2657 } 2658 } 2659 else 2660 { 2661 [inf removeObjectForKey: role]; 2662 } 2663 } 2664 else 2665 { 2666 [inf setObject: appName forKey: (role ? (id)role : (id)@"Editor")]; 2667 } 2668 [map setObject: inf forKey: ext]; 2669 RELEASE(inf); 2670 RELEASE(extPreferences); 2671 extPreferences = map; 2672 data = [NSSerializer serializePropertyList: extPreferences]; 2673 if ([data writeToFile: extPrefPath atomically: YES]) 2674 { 2675 [_workspaceCenter postNotificationName: GSWorkspacePreferencesChanged 2676 object: self]; 2677 } 2678 else 2679 { 2680 NSLog(@"Update %@ of failed", extPrefPath); 2681 } 2682} 2683 2684/** 2685 * Sets up a user preference for which icon should be used to 2686 * represent the specified file extension. 2687 */ 2688- (void) setBestIcon: (NSString*)iconPath forExtension: (NSString*)ext 2689{ 2690 NSMutableDictionary *map; 2691 NSMutableDictionary *inf; 2692 NSData *data; 2693 2694 ext = [ext lowercaseString]; 2695 if (extPreferences != nil) 2696 map = [extPreferences mutableCopy]; 2697 else 2698 map = [NSMutableDictionary new]; 2699 2700 inf = [[map objectForKey: ext] mutableCopy]; 2701 if (inf == nil) 2702 inf = [NSMutableDictionary new]; 2703 if (iconPath) 2704 [inf setObject: iconPath forKey: @"Icon"]; 2705 else 2706 [inf removeObjectForKey: @"Icon"]; 2707 [map setObject: inf forKey: ext]; 2708 RELEASE(inf); 2709 RELEASE(extPreferences); 2710 extPreferences = map; 2711 data = [NSSerializer serializePropertyList: extPreferences]; 2712 if ([data writeToFile: extPrefPath atomically: YES]) 2713 { 2714 [_workspaceCenter postNotificationName: GSWorkspacePreferencesChanged 2715 object: self]; 2716 } 2717 else 2718 { 2719 NSLog(@"Update %@ of failed", extPrefPath); 2720 } 2721} 2722 2723/** 2724 * Gets the applications cache (generated by the make_services tool) 2725 * and looks up the special entry that contains a dictionary of all 2726 * URL schemes recognised by GNUstep applications. Then finds the 2727 * dictionary of applications that can handle our scheme and returns 2728 * it. 2729 */ 2730- (NSDictionary*) infoForScheme: (NSString*)scheme 2731{ 2732 NSDictionary *map; 2733 2734 scheme = [scheme lowercaseString]; 2735 map = [applications objectForKey: @"GSSchemesMap"]; 2736 return [map objectForKey: scheme]; 2737} 2738 2739/** 2740 * Returns the 'best' application to open a file with the specified URL 2741 * scheme using the given role. If the role is nil then apps which can 2742 * edit are preferred but viewers are also acceptable. Uses a user preferred 2743 * app or picks any good match. 2744 */ 2745- (NSString*) getBestAppInRole: (NSString*)role 2746 forScheme: (NSString*)scheme 2747{ 2748 NSString *appName = nil; 2749 2750 if ([self _scheme: scheme role: role app: &appName] == NO) 2751 { 2752 appName = nil; 2753 } 2754 return appName; 2755} 2756 2757/** 2758 * Sets up a user preference for which app should be used to open files 2759 * of the specified URL scheme 2760 */ 2761- (void) setBestApp: (NSString*)appName 2762 inRole: (NSString*)role 2763 forScheme: (NSString*)scheme 2764{ 2765 NSMutableDictionary *map; 2766 NSMutableDictionary *inf; 2767 NSData *data; 2768 2769 scheme = [scheme lowercaseString]; 2770 if (urlPreferences != nil) 2771 map = [urlPreferences mutableCopy]; 2772 else 2773 map = [NSMutableDictionary new]; 2774 2775 inf = [[map objectForKey: scheme] mutableCopy]; 2776 if (inf == nil) 2777 { 2778 inf = [NSMutableDictionary new]; 2779 } 2780 if (appName == nil) 2781 { 2782 if (role == nil) 2783 { 2784 NSString *iconPath = [inf objectForKey: @"Icon"]; 2785 2786 RETAIN(iconPath); 2787 [inf removeAllObjects]; 2788 if (iconPath) 2789 { 2790 [inf setObject: iconPath forKey: @"Icon"]; 2791 RELEASE(iconPath); 2792 } 2793 } 2794 else 2795 { 2796 [inf removeObjectForKey: role]; 2797 } 2798 } 2799 else 2800 { 2801 [inf setObject: appName forKey: (role ? (id)role : (id)@"Editor")]; 2802 } 2803 [map setObject: inf forKey: scheme]; 2804 RELEASE(inf); 2805 RELEASE(urlPreferences); 2806 urlPreferences = map; 2807 data = [NSSerializer serializePropertyList: urlPreferences]; 2808 if ([data writeToFile: urlPrefPath atomically: YES]) 2809 { 2810 [_workspaceCenter postNotificationName: GSWorkspacePreferencesChanged 2811 object: self]; 2812 } 2813 else 2814 { 2815 NSLog(@"Update %@ of failed", urlPrefPath); 2816 } 2817} 2818 2819@end 2820 2821@implementation NSWorkspace (Private) 2822 2823- (NSImage*) _extIconForApp: (NSString*)appName info: (NSDictionary*)extInfo 2824{ 2825 NSDictionary *typeInfo = [extInfo objectForKey: appName]; 2826 NSString *file = [typeInfo objectForKey: @"NSIcon"]; 2827 2828 /* 2829 * If the NSIcon entry isn't there and the CFBundle entries are, 2830 * get the first icon in the list if it's an array, or assign 2831 * the icon to file if it's a string. 2832 * 2833 * FIXME: CFBundleTypeExtensions/IconFile can be arrays which assign 2834 * multiple types to icons. This needs to be handled eventually. 2835 */ 2836 if (file == nil) 2837 { 2838 id icon = [typeInfo objectForKey: @"CFBundleTypeIconFile"]; 2839 if ([icon isKindOfClass: [NSArray class]]) 2840 { 2841 if ([icon count]) 2842 { 2843 file = [icon objectAtIndex: 0]; 2844 } 2845 } 2846 else 2847 { 2848 file = icon; 2849 } 2850 } 2851 2852 if (file && [file length] != 0) 2853 { 2854 if ([file isAbsolutePath] == NO) 2855 { 2856 NSString *iconPath; 2857 NSBundle *bundle; 2858 2859 bundle = [self bundleForApp: appName]; 2860 iconPath = [bundle pathForImageResource: file]; 2861 /* 2862 * If the icon is not in the Resources of the app, try looking 2863 * directly in the app wrapper. 2864 */ 2865 if (iconPath == nil) 2866 { 2867 iconPath = [[bundle bundlePath] 2868 stringByAppendingPathComponent: file]; 2869 } 2870 file = iconPath; 2871 } 2872 if ([[NSFileManager defaultManager] isReadableFileAtPath: file] == YES) 2873 { 2874 return [self _saveImageFor: file]; 2875 } 2876 } 2877 return nil; 2878} 2879 2880/** Returns the default icon to display for a file */ 2881- (NSImage*) unknownFiletypeImage 2882{ 2883 static NSImage *image = nil; 2884 2885 if (image == nil) 2886 { 2887 image = RETAIN([NSImage _standardImageWithName: @"Unknown"]); 2888 } 2889 2890 return image; 2891} 2892 2893/** Try to create the image in an exception handling context */ 2894- (NSImage*) _saveImageFor: (NSString*)iconPath 2895{ 2896 NSImage *tmp = nil; 2897 2898 NS_DURING 2899 { 2900 tmp = [[NSImage alloc] initWithContentsOfFile: iconPath]; 2901 if (tmp != nil) 2902 { 2903 AUTORELEASE(tmp); 2904 } 2905 } 2906 NS_HANDLER 2907 { 2908 NSLog(@"BAD TIFF FILE '%@'", iconPath); 2909 } 2910 NS_ENDHANDLER 2911 2912 return tmp; 2913} 2914 2915/** Returns the freedesktop thumbnail file name for a given file name */ 2916- (NSString*) thumbnailForFile: (NSString *)file 2917{ 2918 NSString *absolute; 2919 NSString *digest; 2920 NSString *thumbnail; 2921 2922 absolute = [[NSURL fileURLWithPath: [file stringByStandardizingPath]] 2923 absoluteString]; 2924 /* This compensates for a feature we have in NSURL, that is there to have 2925 * MacOSX compatibility. 2926 */ 2927 if ([absolute hasPrefix: @"file://localhost/"]) 2928 { 2929 absolute = [@"file:///" stringByAppendingString: 2930 [absolute substringWithRange: 2931 NSMakeRange(17, [absolute length] - 17)]]; 2932 } 2933 2934 // FIXME: Not sure which encoding to use here. 2935 digest = [[[[absolute dataUsingEncoding: NSASCIIStringEncoding] 2936 md5Digest] hexadecimalRepresentation] lowercaseString]; 2937 thumbnail = [@"~/.thumbnails/normal" stringByAppendingPathComponent: 2938 [digest stringByAppendingPathExtension: @"png"]]; 2939 2940 return [thumbnail stringByStandardizingPath]; 2941} 2942 2943- (NSImage*) _iconForExtension: (NSString*)ext 2944{ 2945 NSImage *icon = nil; 2946 2947 if (ext == nil || [ext isEqualToString: @""]) 2948 { 2949 return nil; 2950 } 2951 /* 2952 * extensions are case-insensitive - convert to lowercase. 2953 */ 2954 ext = [ext lowercaseString]; 2955 if ((icon = [_iconMap objectForKey: ext]) == nil) 2956 { 2957 NSDictionary *prefs; 2958 NSDictionary *extInfo; 2959 NSString *iconPath; 2960 2961 /* 2962 * If there is a user-specified preference for an image - 2963 * try to use that one. 2964 */ 2965 prefs = [extPreferences objectForKey: ext]; 2966 iconPath = [prefs objectForKey: @"Icon"]; 2967 if (iconPath) 2968 { 2969 icon = [self _saveImageFor: iconPath]; 2970 } 2971 2972 if (icon == nil && (extInfo = [self infoForExtension: ext]) != nil) 2973 { 2974 NSString *appName; 2975 2976 /* 2977 * If there are any application preferences given, try to use the 2978 * icon for this file that is used by the preferred app. 2979 */ 2980 if (prefs) 2981 { 2982 if ((appName = [prefs objectForKey: @"Editor"]) != nil) 2983 { 2984 icon = [self _extIconForApp: appName info: extInfo]; 2985 } 2986 if (icon == nil 2987 && (appName = [prefs objectForKey: @"Viewer"]) != nil) 2988 { 2989 icon = [self _extIconForApp: appName info: extInfo]; 2990 } 2991 } 2992 2993 if (icon == nil) 2994 { 2995 NSEnumerator *enumerator; 2996 2997 /* 2998 * Still no icon - try all the apps that handle this file 2999 * extension. 3000 */ 3001 enumerator = [extInfo keyEnumerator]; 3002 while (icon == nil && (appName = [enumerator nextObject]) != nil) 3003 { 3004 icon = [self _extIconForApp: appName info: extInfo]; 3005 } 3006 } 3007 } 3008 3009 /* 3010 * Nothing found at all - use the unknowntype icon. 3011 */ 3012 if (icon == nil) 3013 { 3014 if ([ext isEqualToString: @"app"] == YES 3015 || [ext isEqualToString: @"debug"] == YES 3016 || [ext isEqualToString: @"profile"] == YES) 3017 { 3018 if (unknownApplication == nil) 3019 { 3020 unknownApplication = RETAIN([NSImage _standardImageWithName: 3021 @"UnknownApplication"]); 3022 } 3023 icon = unknownApplication; 3024 } 3025 else 3026 { 3027 icon = [self unknownFiletypeImage]; 3028 } 3029 } 3030 3031 /* 3032 * Set the icon in the cache for next time. 3033 */ 3034 if (icon != nil) 3035 { 3036 [_iconMap setObject: icon forKey: ext]; 3037 } 3038 } 3039 return icon; 3040} 3041 3042- (BOOL) _extension: (NSString*)ext 3043 role: (NSString*)role 3044 app: (NSString**)app 3045{ 3046 NSEnumerator *enumerator; 3047 NSString *appName = nil; 3048 NSDictionary *apps = [self infoForExtension: ext]; 3049 NSDictionary *prefs; 3050 NSDictionary *info; 3051 3052 ext = [ext lowercaseString]; 3053 3054 /* 3055 * Look for the name of the preferred app in this role. 3056 * A 'nil' roll is a wildcard - find the preferred Editor or Viewer. 3057 */ 3058 prefs = [extPreferences objectForKey: ext]; 3059 if (role == nil || [role isEqualToString: @"Editor"]) 3060 { 3061 appName = [prefs objectForKey: @"Editor"]; 3062 if (appName != nil) 3063 { 3064 info = [apps objectForKey: appName]; 3065 if (info != nil) 3066 { 3067 if (app != 0) 3068 { 3069 *app = appName; 3070 } 3071 return YES; 3072 } 3073 else if ([self locateApplicationBinary: appName] != nil) 3074 { 3075 /* 3076 * Return the preferred application even though it doesn't 3077 * say it opens this type of file ... preferences overrule. 3078 */ 3079 if (app != 0) 3080 { 3081 *app = appName; 3082 } 3083 return YES; 3084 } 3085 } 3086 } 3087 if (role == nil || [role isEqualToString: @"Viewer"]) 3088 { 3089 appName = [prefs objectForKey: @"Viewer"]; 3090 if (appName != nil) 3091 { 3092 info = [apps objectForKey: appName]; 3093 if (info != nil) 3094 { 3095 if (app != 0) 3096 { 3097 *app = appName; 3098 } 3099 return YES; 3100 } 3101 else if ([self locateApplicationBinary: appName] != nil) 3102 { 3103 /* 3104 * Return the preferred application even though it doesn't 3105 * say it opens this type of file ... preferences overrule. 3106 */ 3107 if (app != 0) 3108 { 3109 *app = appName; 3110 } 3111 return YES; 3112 } 3113 } 3114 } 3115 3116 /* 3117 * Go through the dictionary of apps that know about this file type and 3118 * determine the best application to open the file by examining the 3119 * type information for each app. 3120 * The 'NSRole' field specifies what the app can do with the file - if it 3121 * is missing, we assume an 'Editor' role. 3122 */ 3123 if (apps == nil || [apps count] == 0) 3124 { 3125 return NO; 3126 } 3127 enumerator = [apps keyEnumerator]; 3128 3129 if (role == nil) 3130 { 3131 BOOL found = NO; 3132 3133 /* 3134 * If the requested role is 'nil', we can accept an app that is either 3135 * an Editor (preferred) or a Viewer, or unknown. 3136 */ 3137 while ((appName = [enumerator nextObject]) != nil) 3138 { 3139 NSString *str; 3140 3141 info = [apps objectForKey: appName]; 3142 str = [info objectForKey: @"NSRole"]; 3143 /* NB. If str is nil or an empty string, there is no role set, 3144 * and we treat this as an Editor since the role is unrestricted. 3145 */ 3146 if ([str length] == 0 || [str isEqualToString: @"Editor"]) 3147 { 3148 if (app != 0) 3149 { 3150 *app = appName; 3151 } 3152 return YES; 3153 } 3154 if ([str isEqualToString: @"Viewer"]) 3155 { 3156 if (app != 0) 3157 { 3158 *app = appName; 3159 } 3160 found = YES; 3161 } 3162 } 3163 return found; 3164 } 3165 else 3166 { 3167 while ((appName = [enumerator nextObject]) != nil) 3168 { 3169 NSString *str; 3170 3171 info = [apps objectForKey: appName]; 3172 str = [info objectForKey: @"NSRole"]; 3173 if ((str == nil && [role isEqualToString: @"Editor"]) 3174 || [str isEqualToString: role]) 3175 { 3176 if (app != 0) 3177 { 3178 *app = appName; 3179 } 3180 return YES; 3181 } 3182 } 3183 return NO; 3184 } 3185} 3186 3187- (BOOL) _scheme: (NSString*)scheme 3188 role: (NSString*)role 3189 app: (NSString**)app 3190{ 3191 NSEnumerator *enumerator; 3192 NSString *appName = nil; 3193 NSDictionary *apps = [self infoForScheme: scheme]; 3194 NSDictionary *prefs; 3195 NSDictionary *info; 3196 3197 scheme = [scheme lowercaseString]; 3198 3199 /* 3200 * Look for the name of the preferred app in this role. 3201 * A 'nil' roll is a wildcard - find the preferred Editor or Viewer. 3202 */ 3203 prefs = [urlPreferences objectForKey: scheme]; 3204 if (role == nil || [role isEqualToString: @"Editor"]) 3205 { 3206 appName = [prefs objectForKey: @"Editor"]; 3207 if (appName != nil) 3208 { 3209 info = [apps objectForKey: appName]; 3210 if (info != nil) 3211 { 3212 if (app != 0) 3213 { 3214 *app = appName; 3215 } 3216 return YES; 3217 } 3218 else if ([self locateApplicationBinary: appName] != nil) 3219 { 3220 /* 3221 * Return the preferred application even though it doesn't 3222 * say it opens this type of file ... preferences overrule. 3223 */ 3224 if (app != 0) 3225 { 3226 *app = appName; 3227 } 3228 return YES; 3229 } 3230 } 3231 } 3232 if (role == nil || [role isEqualToString: @"Viewer"]) 3233 { 3234 appName = [prefs objectForKey: @"Viewer"]; 3235 if (appName != nil) 3236 { 3237 info = [apps objectForKey: appName]; 3238 if (info != nil) 3239 { 3240 if (app != 0) 3241 { 3242 *app = appName; 3243 } 3244 return YES; 3245 } 3246 else if ([self locateApplicationBinary: appName] != nil) 3247 { 3248 /* 3249 * Return the preferred application even though it doesn't 3250 * say it opens this type of file ... preferences overrule. 3251 */ 3252 if (app != 0) 3253 { 3254 *app = appName; 3255 } 3256 return YES; 3257 } 3258 } 3259 } 3260 3261 /* 3262 * Go through the dictionary of apps that know about this file type and 3263 * determine the best application to open the file by examining the 3264 * type information for each app. 3265 * The 'NSRole' field specifies what the app can do with the file - if it 3266 * is missing, we assume an 'Editor' role. 3267 */ 3268 if (apps == nil || [apps count] == 0) 3269 { 3270 return NO; 3271 } 3272 enumerator = [apps keyEnumerator]; 3273 3274 if (role == nil) 3275 { 3276 BOOL found = NO; 3277 3278 /* 3279 * If the requested role is 'nil', we can accept an app that is either 3280 * an Editor (preferred) or a Viewer, or unknown. 3281 */ 3282 while ((appName = [enumerator nextObject]) != nil) 3283 { 3284 NSString *str; 3285 3286 info = [apps objectForKey: appName]; 3287 str = [info objectForKey: @"NSRole"]; 3288 /* NB. If str is nil or an empty string, there is no role set, 3289 * and we treat this as an Editor since the role is unrestricted. 3290 */ 3291 if ([str length] == 0 || [str isEqualToString: @"Editor"]) 3292 { 3293 if (app != 0) 3294 { 3295 *app = appName; 3296 } 3297 return YES; 3298 } 3299 if ([str isEqualToString: @"Viewer"]) 3300 { 3301 if (app != 0) 3302 { 3303 *app = appName; 3304 } 3305 found = YES; 3306 } 3307 } 3308 return found; 3309 } 3310 else 3311 { 3312 while ((appName = [enumerator nextObject]) != nil) 3313 { 3314 NSString *str; 3315 3316 info = [apps objectForKey: appName]; 3317 str = [info objectForKey: @"NSRole"]; 3318 if ((str == nil && [role isEqualToString: @"Editor"]) 3319 || [str isEqualToString: role]) 3320 { 3321 if (app != 0) 3322 { 3323 *app = appName; 3324 } 3325 return YES; 3326 } 3327 } 3328 return NO; 3329 } 3330} 3331 3332- (void) _workspacePreferencesChanged: (NSNotification *)aNotification 3333{ 3334 /* FIXME reload only those preferences that really were changed 3335 * TODO add a user info to aNotification, which includes a bitmask 3336 * denoting the updated preference files. 3337 */ 3338 NSFileManager *mgr = [NSFileManager defaultManager]; 3339 NSData *data; 3340 NSDictionary *dict; 3341 3342 if ([mgr isReadableFileAtPath: extPrefPath] == YES) 3343 { 3344 data = [NSData dataWithContentsOfFile: extPrefPath]; 3345 if (data) 3346 { 3347 dict = [NSDeserializer deserializePropertyListFromData: data 3348 mutableContainers: NO]; 3349 ASSIGN(extPreferences, dict); 3350 } 3351 } 3352 3353 if ([mgr isReadableFileAtPath: urlPrefPath] == YES) 3354 { 3355 data = [NSData dataWithContentsOfFile: urlPrefPath]; 3356 if (data) 3357 { 3358 dict = [NSDeserializer deserializePropertyListFromData: data 3359 mutableContainers: NO]; 3360 ASSIGN(urlPreferences, dict); 3361 } 3362 } 3363 3364 if ([mgr isReadableFileAtPath: appListPath] == YES) 3365 { 3366 data = [NSData dataWithContentsOfFile: appListPath]; 3367 if (data) 3368 { 3369 dict = [NSDeserializer deserializePropertyListFromData: data 3370 mutableContainers: NO]; 3371 ASSIGN(applications, dict); 3372 } 3373 } 3374 /* 3375 * Invalidate the cache of icons for file extensions. 3376 */ 3377 [_iconMap removeAllObjects]; 3378} 3379 3380 3381/** 3382 * Launch an application locally (ie without reference to the workspace 3383 * manager application). We should only call this method when we want 3384 * the application launched by this process, either because what we are 3385 * launching IS the workspace manager, or because we have tried to get 3386 * the workspace manager to do the job and been unable to do so. 3387 */ 3388- (BOOL) _launchApplication: (NSString*)appName 3389 arguments: (NSArray*)args 3390{ 3391 NSTask *task; 3392 NSString *path; 3393 NSDictionary *userinfo; 3394 NSString *host; 3395 3396 path = [self locateApplicationBinary: appName]; 3397 if (path == nil) 3398 { 3399 return NO; 3400 } 3401 3402 /* 3403 * Try to ensure that apps we launch display in this workspace 3404 * ie they have the same -NSHost specification. 3405 */ 3406 host = [[NSUserDefaults standardUserDefaults] stringForKey: @"NSHost"]; 3407 if (host != nil) 3408 { 3409 NSHost *h; 3410 3411 h = [NSHost hostWithName: host]; 3412 if ([h isEqual: [NSHost currentHost]] == NO) 3413 { 3414 if ([args containsObject: @"-NSHost"] == NO) 3415 { 3416 NSMutableArray *a; 3417 3418 if (args == nil) 3419 { 3420 a = [NSMutableArray arrayWithCapacity: 2]; 3421 } 3422 else 3423 { 3424 a = AUTORELEASE([args mutableCopy]); 3425 } 3426 [a insertObject: @"-NSHost" atIndex: 0]; 3427 [a insertObject: host atIndex: 1]; 3428 args = a; 3429 } 3430 } 3431 } 3432 /* 3433 * App being launched, send 3434 * NSWorkspaceWillLaunchApplicationNotification 3435 */ 3436 userinfo = [NSDictionary dictionaryWithObjectsAndKeys: 3437 [[appName lastPathComponent] stringByDeletingPathExtension], 3438 @"NSApplicationName", 3439 appName, @"NSApplicationPath", 3440 nil]; 3441 [_workspaceCenter 3442 postNotificationName: NSWorkspaceWillLaunchApplicationNotification 3443 object: self 3444 userInfo: userinfo]; 3445 3446 task = [NSTask launchedTaskWithLaunchPath: path arguments: args]; 3447 if (task == nil) 3448 { 3449 return NO; 3450 } 3451 /* 3452 * The NSWorkspaceDidLaunchApplicationNotification will be 3453 * sent by the started application itself. 3454 */ 3455 [_launched setObject: task forKey: appName]; 3456 return YES; 3457} 3458 3459- (id) _connectApplication: (NSString*)appName 3460{ 3461 NSTimeInterval replyTimeout = 0.0; 3462 NSTimeInterval requestTimeout = 0.0; 3463 NSString *host; 3464 NSString *port; 3465 NSDate *when = nil; 3466 NSConnection *conn = nil; 3467 id app = nil; 3468 3469 while (app == nil) 3470 { 3471 host = [[NSUserDefaults standardUserDefaults] stringForKey: @"NSHost"]; 3472 if (host == nil) 3473 { 3474 host = @""; 3475 } 3476 else 3477 { 3478 NSHost *h; 3479 3480 h = [NSHost hostWithName: host]; 3481 if ([h isEqual: [NSHost currentHost]] == YES) 3482 { 3483 host = @""; 3484 } 3485 } 3486 port = [[appName lastPathComponent] stringByDeletingPathExtension]; 3487 /* 3488 * Try to contact a running application. 3489 */ 3490 NS_DURING 3491 { 3492 conn = [NSConnection connectionWithRegisteredName: port host: host]; 3493 requestTimeout = [conn requestTimeout]; 3494 [conn setRequestTimeout: 5.0]; 3495 replyTimeout = [conn replyTimeout]; 3496 [conn setReplyTimeout: 5.0]; 3497 app = [conn rootProxy]; 3498 } 3499 NS_HANDLER 3500 { 3501 /* Fatal error in DO */ 3502 conn = nil; 3503 app = nil; 3504 } 3505 NS_ENDHANDLER 3506 3507 if (app == nil) 3508 { 3509 NSTask *task = [_launched objectForKey: appName]; 3510 NSDate *limit; 3511 3512 if (task == nil || [task isRunning] == NO) 3513 { 3514 if (task != nil) // Not running 3515 { 3516 [_launched removeObjectForKey: appName]; 3517 } 3518 break; // Need to launch the app 3519 } 3520 3521 if (when == nil) 3522 { 3523 when = [[NSDate alloc] init]; 3524 } 3525 else if ([when timeIntervalSinceNow] < -5.0) 3526 { 3527 int result; 3528 3529 DESTROY(when); 3530 result = NSRunAlertPanel(appName, 3531 @"Application seems to have hung", 3532 @"Continue", @"Terminate", @"Wait"); 3533 3534 if (result == NSAlertDefaultReturn) 3535 { 3536 break; // Finished without app 3537 } 3538 else if (result == NSAlertOtherReturn) 3539 { 3540 // Continue to wait for app startup. 3541 } 3542 else 3543 { 3544 [task terminate]; 3545 [_launched removeObjectForKey: appName]; 3546 break; // Terminate hung app 3547 } 3548 } 3549 3550 // Give it another 0.5 of a second to start up. 3551 limit = [[NSDate alloc] initWithTimeIntervalSinceNow: 0.5]; 3552 [[NSRunLoop currentRunLoop] runUntilDate: limit]; 3553 RELEASE(limit); 3554 } 3555 } 3556 if (conn != nil) 3557 { 3558 /* Use original timeouts 3559 */ 3560 [conn setRequestTimeout: requestTimeout]; 3561 [conn setReplyTimeout: replyTimeout]; 3562 } 3563 TEST_RELEASE(when); 3564 return app; 3565} 3566 3567- (id) _workspaceApplication 3568{ 3569 static NSUserDefaults *defs = nil; 3570 static GSServicesManager *smgr = nil; 3571 NSString *appName; 3572 NSString *myName; 3573 id app; 3574 3575 if (defs == nil) 3576 { 3577 defs = RETAIN([NSUserDefaults standardUserDefaults]); 3578 } 3579 if (smgr == nil) 3580 { 3581 smgr = RETAIN([GSServicesManager manager]); 3582 } 3583 /* What Workspace application? */ 3584 appName = [defs stringForKey: @"GSWorkspaceApplication"]; 3585 if (appName == nil) 3586 { 3587 appName = @"GWorkspace"; 3588 } 3589 /* 3590 * If this app is the workspace app, there is no sense contacting 3591 * it as it would cause recursion ... so we return nil. 3592 */ 3593 myName = [smgr port]; 3594 if ([appName isEqual: myName] == YES) 3595 { 3596 return nil; 3597 } 3598 3599 app = [self _connectApplication: appName]; 3600 if (app == nil) 3601 { 3602 NSString *host; 3603 3604 host = [[NSUserDefaults standardUserDefaults] stringForKey: @"NSHost"]; 3605 if (host == nil) 3606 { 3607 host = @""; 3608 } 3609 else 3610 { 3611 NSHost *h; 3612 3613 h = [NSHost hostWithName: host]; 3614 if ([h isEqual: [NSHost currentHost]] == YES) 3615 { 3616 host = @""; 3617 } 3618 } 3619 /** 3620 * We can only launch a workspace app if we are displaying to the 3621 * local host (since if we are displaying on another host we want 3622 * to to talk to the workspace app on that host too). 3623 */ 3624 if ([host isEqual: @""] == YES) 3625 { 3626 if ([self _launchApplication: appName 3627 arguments: nil] == YES) 3628 { 3629 app = [self _connectApplication: appName]; 3630 } 3631 } 3632 } 3633 3634 return app; 3635} 3636 3637@end 3638