/* ApplicationsEntry.m Implementation of the ApplicationsEntry class for the EtoileMenuServer application. Copyright (C) 2005, 2006 Saso Kiselkov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #import "ApplicationsEntry.h" #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import "CheckProcessExists.h" //#import @implementation ApplicationsEntry - (void) dealloc { [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver: self]; TEST_RELEASE (launchedApplications); TEST_RELEASE (sortedAppNames); TEST_RELEASE (menuItem); TEST_RELEASE (menu); [autocheckTimer invalidate]; TEST_RELEASE (autocheckTimer); [super dealloc]; } - init { if ((self = [super init]) != nil) { NSNotificationCenter * nc = [[NSWorkspace sharedWorkspace] notificationCenter]; NSInvocation * inv; launchedApplications = [NSMutableArray new]; sortedAppNames = [NSMutableArray new]; [nc addObserver: self selector: @selector(noteAppLaunched:) name: NSWorkspaceDidLaunchApplicationNotification object: nil]; [nc addObserver: self selector: @selector(noteAppTerminated:) name: NSWorkspaceDidTerminateApplicationNotification object: nil]; inv = [NSInvocation invocationWithMethodSignature: [self methodSignatureForSelector: @selector (checkRunningApps)]]; [inv setTarget: self]; [inv setSelector: @selector (checkRunningApps)]; ASSIGN (autocheckTimer, [NSTimer scheduledTimerWithTimeInterval: 1.0 invocation: inv repeats: YES]); } return self; } - (id ) menuItem { NSMenuItem * item; NSEnumerator * e; NSDictionary * appInfo; if (menuItem != nil) { return menuItem; } // build the Applications -> Applications menu item and submenu menuItem = [[NSMenuItem alloc] initWithTitle: _(@"Applications") action: NULL keyEquivalent: nil]; menu = [[NSMenu alloc] initWithTitle: _(@"Applications")]; [menuItem setSubmenu: menu]; // add the top two elements in the Applications submenu: // - the "Applications List..." menu item // - the separator item = [[[NSMenuItem alloc] initWithTitle: _(@"Applications List...") action: @selector(showApplicationsPanel:) keyEquivalent: nil] autorelease]; [item setTarget: self]; [menu addItem: item]; // [menu addItem: [NSMenuItem separatorItem]]; e = [[[NSWorkspace sharedWorkspace] launchedApplications] objectEnumerator]; while ((appInfo = [e nextObject]) != nil) { [self noteAppLaunched: [NSNotification notificationWithName: NSWorkspaceDidLaunchApplicationNotification object: self userInfo: appInfo]]; } // [self noteAppListChanged: nil]; return menuItem; } - (void) noteAppLaunched: (NSNotification *) notif { NSWorkspace *ws = [NSWorkspace sharedWorkspace]; NSDictionary * appInfo = [notif userInfo]; NSString * appName = [appInfo objectForKey: @"NSApplicationName"]; NSString * appPath = [appInfo objectForKey: @"NSApplicationPath"]; NSImage * icon = [[[ws iconForFile: appPath] copy] autorelease]; NSNumber * pid = [appInfo objectForKey: @"NSApplicationProcessIdentifier"]; NSMenuItem * appMenuItem; int index; // don't accept duplicate entries if ([[launchedApplications valueForKey: @"NSApplicationProcessIdentifier"] indexOfObject: pid] != NSNotFound) { return; } [icon setScalesWhenResized: YES]; [icon setSize: NSMakeSize(18, 18)]; appMenuItem = [[[NSMenuItem alloc] initWithTitle: appName action: @selector(activateApplication:) keyEquivalent: nil] autorelease]; [appMenuItem setTarget: self]; [appMenuItem setImage: icon]; [sortedAppNames addObject: appName]; [sortedAppNames sortUsingSelector: @selector (caseInsensitiveCompare:)]; index = [sortedAppNames indexOfObject: appName]; [launchedApplications insertObject: [notif userInfo] atIndex: index]; [menu insertItem: appMenuItem atIndex: index]; [menu sizeToFit]; if (window != nil) { // refresh the interface if necessary [appTable reloadData]; [self showApplication: self]; } } - (void) noteAppTerminated: (NSNotification *) notif { NSDictionary * appInfo = [notif userInfo]; int index = [launchedApplications indexOfObject: appInfo], index2 = [sortedAppNames indexOfObject: [appInfo objectForKey: @"NSApplicationName"]]; if (index != NSNotFound) { [launchedApplications removeObjectAtIndex: index]; [sortedAppNames removeObjectAtIndex: index2]; [menu removeItemAtIndex: index2]; } if (window != nil) { // refresh the interface if necessary [appTable reloadData]; [self showApplication: self]; } } - (void) checkRunningApps { int i, n; for (i = 0, n = [launchedApplications count]; i < n; i++) { NSDictionary * appInfo = [launchedApplications objectAtIndex: i]; int pid = [[appInfo objectForKey: @"NSApplicationProcessIdentifier"] intValue]; if (!CheckProcessExists (pid)) { [self noteAppTerminated: [NSNotification notificationWithName: NSWorkspaceDidTerminateApplicationNotification object: self userInfo: appInfo]]; i--; n--; } } } - (NSString *) menuGroup { return @"Etoile"; } - (void) awakeFromNib { [appTable setDoubleAction: @selector(activateApplication:)]; } - (void) showApplicationsPanel: sender { if (window == nil) { [NSBundle loadNibNamed: @"ApplicationsPanel" owner: self]; } [window makeKeyAndOrderFront: nil]; } - (void) showApplication: sender { int row = [appTable selectedRow]; if (row >= 0) { NSDictionary * entry = [launchedApplications objectAtIndex: row]; NSString * appName = [entry objectForKey: @"NSApplicationName"], * appPath = [entry objectForKey: @"NSApplicationPath"]; [appIcon setImage: [[NSWorkspace sharedWorkspace] iconForFile: appPath]]; [appNameField setStringValue: appName]; [appPathField setStringValue: appPath]; [appPIDField setObjectValue: [entry objectForKey: @"NSApplicationProcessIdentifier"]]; [killButton setEnabled: YES]; [killButton setTransparent: NO]; } else { [appIcon setImage: nil]; [appNameField setStringValue: nil]; [appPathField setStringValue: nil]; [appPIDField setStringValue: nil]; [killButton setEnabled: NO]; [killButton setTransparent: YES]; } } - (void) kill: sender { NSDictionary * entry = [launchedApplications objectAtIndex: [appTable selectedRow]]; NSString * appName = [entry objectForKey: @"NSApplicationName"]; int pid = [[entry objectForKey: @"NSApplicationProcessIdentifier"] intValue]; if (NSRunAlertPanel(_(@"Really kill application?"), _(@"Really kill application %@?"), _(@"Yes"), _(@"Cancel"), nil, appName) == NSAlertDefaultReturn) { if (kill(pid, SIGKILL) < 0) { NSRunAlertPanel(_(@"Couldn't kill application"), _(@"Couldn't kill application %@, PID %i: %s"), nil, nil, nil, appName, pid, strerror(errno)); } } } - (void) activateApplication: sender { /* * This was previously implemented using a special WorkspaceCommKit * framework to contact and launch applications, but since it's not * included in Etoile, this functionality currently has to be disabled. * Ask Quentin on what mechanism we'll be using in the future. */ #if 0 // find out from where we have been invoked if (sender == appTable) { row = [appTable selectedRow]; } else { row = [menu indexOfItem: sender]; } if (row >= 0) { NSString * appName = [[launchedApplications objectAtIndex: row] objectForKey: @"NSApplicationName"]; id app; app = [[NSWorkspace sharedWorkspace] connectToApplication: appName launch: NO]; if (app != nil) { NS_DURING [app activateIgnoringOtherApps: YES]; NS_HANDLER NSRunAlertPanel (_(@"Failed to activate application"), _(@"Exception occured while trying to activate " @"application %@: %@"), nil, nil, nil, appName, [localException reason]); NS_ENDHANDLER } else { NSRunAlertPanel(_(@"Failed to activate application"), _(@"Failed to connect to application %@ to activate it"), nil, nil, nil, appName); } } #endif } - (int) numberOfRowsInTableView: (NSTableView *)aTableView { return [launchedApplications count]; } - (id) tableView: (NSTableView *)aTableView objectValueForTableColumn: (NSTableColumn *)aTableColumn row: (int)rowIndex { return [sortedAppNames objectAtIndex: rowIndex]; } @end