1/* GSKrabManager.m - this file is part of GSKrab
2 *
3 * Copyright (C) 2006 Wolfgang Sourdeau
4 *
5 * Author: Wolfgang Sourdeau <Wolfgang@Contre.COM>
6 *
7 * This file is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2, or (at your option)
10 * any later version.
11 *
12 * This file is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; see the file COPYING.  If not, write to
19 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 * Boston, MA 02111-1307, USA.
21 */
22
23#import <AppKit/NSWorkspace.h>
24#import <Foundation/NSConnection.h>
25#import <Foundation/NSDistantObject.h>
26#import <Foundation/NSHost.h>
27#import <Foundation/NSMapTable.h>
28#import <Foundation/NSNotification.h>
29#import <Foundation/NSPort.h>
30#import <Foundation/NSPortNameServer.h>
31#import <Foundation/NSString.h>
32#import <Foundation/NSTask.h>
33#import <Foundation/NSThread.h>
34#import <Foundation/NSValue.h>
35
36#import "GSKrabDesktopInterface.h"
37#import "GSKrabServer.h"
38
39#ifdef __WIN32__
40#import "GSKrabWin32Interface.h"
41#define DesktopInterface GSKrabWin32Interface
42#else
43#import "GSKrabX11Interface.h"
44#define DesktopInterface GSKrabX11Interface
45#endif
46
47#import "GSKrabManager.h"
48
49static NSNotificationCenter *nc = nil;
50
51@implementation GSKrabManager
52
53+ (void) initialize
54{
55  if (!nc)
56    nc = [NSNotificationCenter defaultCenter];
57}
58
59// - (NSDictionary **) _knownKeys
60// {
61//   NSMutableDictionary **dicts;
62//   NSArray *sampleKeys;
63
64//   dicts = malloc (sizeof (NSDictionary *) * 2);
65//   sampleKeys
66//     = [NSArray arrayWithObjects: [NSNumber numberWithInt: GSKrabPlay],
67//                [NSNumber numberWithInt: GSKrabStop],
68//                [NSNumber numberWithInt: GSKrabEject],
69//                [NSNumber numberWithInt: GSKrabTrackPrevious],
70//                [NSNumber numberWithInt: GSKrabTrackNext],
71//                nil];
72//   dicts[0] = [NSMutableDictionary new];
73//   [dicts[0] setObject: @"Cynthiune.app" forKey: @"Application"];
74//   [dicts[0] setObject: @"CynthiuneClient" forKey: @"ClientName"];
75//   [dicts[0] setObject: sampleKeys forKey: @"Keys"];
76//   [dicts[0] setObject: @"Application" forKey: @"BindingType"];
77//   dicts[1] = [NSMutableDictionary new];
78//   [dicts[1] setObject: @"firefox" forKey: @"Command"];
79//   [dicts[1] setObject:
80//           [NSArray arrayWithObject: [NSNumber numberWithInt: GSKrabWWW]]
81//         forKey: @"Keys"];
82//   [dicts[1] setObject: @"ShellCommand" forKey: @"BindingType"];
83//   dicts[2] = nil;
84
85//   return dicts;
86// }
87
88// - (void) _registerKnownKeys
89// {
90//   NSDictionary **applicationsDicts;
91//   NSDictionary **current;
92//   NSArray *keys;
93//   unsigned int count, max;
94
95//   applicationsDicts = [self _knownKeys];
96//   current = applicationsDicts;
97//   while (*current)
98//     {
99//       keys = [*current objectForKey: @"Keys"];
100//       max = [keys count];
101//       for (count = 0; count < max; count++)
102//         [self registerKey: [[keys objectAtIndex: count] intValue]
103//               forObject: *current];
104//       current++;
105//     }
106// }
107
108- (NSConnection *) _setupConnection
109{
110  NSConnection *connection;
111  NSPort *port;
112
113  port = [NSPort new];
114  connection = [NSConnection connectionWithReceivePort: port
115                             sendPort: nil];
116  if ([[NSPortNameServer systemDefaultPortNameServer] registerPort: port
117                                                      forName: @"GSKrabServer"])
118    {
119      [connection setRootObject: server];
120      [connection setReplyTimeout: 0.5];
121      [connection retain];
122    }
123  else
124    connection = nil;
125
126  [port release];
127
128  return connection;
129}
130
131- (id) init
132{
133  if ((self = [super init]))
134    {
135      interface = [DesktopInterface sharedInterface];
136      [interface setManager: self];
137      keyTable = NSCreateMapTable (NSIntMapKeyCallBacks,
138                                   NSObjectMapValueCallBacks,
139                                   GSKrabMaxNumberOfKeys);
140//       [self _registerKnownKeys];
141      server = [GSKrabServer new];
142      [server setManager: self];
143      serverConnection = [self _setupConnection];
144      applications = [NSMutableArray new];
145      if (serverConnection)
146        {
147          [interface start];
148//           [nc addObserver: self
149//               selector: @selector (connectionDied:)
150//               name: NSConnectionDidDieNotification
151//               object: serverConnection];
152        }
153      else
154        {
155          [self release];
156          self = nil;
157        }
158    }
159
160  return self;
161}
162
163- (void) dealloc
164{
165  [serverConnection release];
166  [interface release];
167  [server release];
168  [applications release];
169  [super dealloc];
170}
171
172- (void) registerKey: (GSKrabKeyCode) krabKeyCode
173           forObject: (NSObject *) object
174{
175  NSMapInsert (keyTable, (void *) krabKeyCode, object);
176  [interface registerKeyCode: krabKeyCode];
177}
178
179- (BOOL) _launchApplication: (NSString *) application
180{
181  NSWorkspace *ws;
182
183//   NSLog (@"launching: %@", application);
184  ws = [NSWorkspace sharedWorkspace];
185  return [ws launchApplication: application];
186}
187
188- (BOOL) _launchCommand: (NSString *) command
189{
190  NSTask *task;
191
192  task = nil;
193  NS_DURING
194    {
195#ifdef __WIN32__
196      task = [NSTask launchedTaskWithLaunchPath: @"cmd.exe"
197                     arguments: [NSArray arrayWithObjects: @"start test.exe",
198                                         nil]];
199#else
200      task = [NSTask launchedTaskWithLaunchPath: command
201                     arguments: nil];
202#endif
203    }
204  NS_HANDLER
205    {
206      if (![[localException name] isEqualToString: NSInvalidArgumentException])
207        [localException raise];
208    }
209  NS_ENDHANDLER
210
211  return (task != nil
212          && ([task isRunning] || [task terminationStatus] == 0));
213}
214
215- (BOOL) _handlePendingKeyPress: (GSKrabKeyCode) krabKeyCode
216                 forApplication: (NSDictionary *) application
217{
218#warning FIXME: should implement method
219//   NSLog (@"application '%@' not launched?", application);
220
221  return NO;
222}
223
224- (BOOL) _sendKeyPress: (GSKrabKeyCode) krabKeyCode
225         toService: (NSDistantObject <GSKrabClient> *) service
226{
227  NSConnection *connection;
228  BOOL result;
229
230  result = NO;
231  NS_DURING
232    {
233      connection = [service connectionForProxy];
234      [connection setReplyTimeout: 0.5];
235      [connection setRequestTimeout: 0.5];
236      [service performActionForKey: krabKeyCode];
237      result = YES;
238    }
239  NS_HANDLER
240    {
241      if (![[localException name] isEqualToString: NSPortTimeoutException])
242        [localException raise];
243    }
244  NS_ENDHANDLER
245
246  return result;
247}
248
249- (BOOL) _sendKeyPress: (GSKrabKeyCode) krabKeyCode
250         toApplication: (NSDictionary *) application
251{
252  NSDistantObject <GSKrabClient> *applService;
253  NSString *serviceName;
254  BOOL result;
255
256  result = NO;
257  serviceName = [application objectForKey: @"ServiceName"];
258  if (serviceName)
259    {
260      applService = [NSConnection rootProxyForConnectionWithRegisteredName: serviceName
261                                  host: nil];
262      if (applService)
263        {
264          [applService setProtocolForProxy: @protocol (GSKrabClient)];
265          result = [self _sendKeyPress: krabKeyCode toService: applService];
266        }
267      else
268        {
269//           NSLog (@"service '%@' not reached", serviceName);
270          result = [self _handlePendingKeyPress: krabKeyCode
271                         forApplication: application];
272        }
273    }
274  else
275    {
276//       NSLog (@"no service name");
277      result = [self _handlePendingKeyPress: krabKeyCode
278                     forApplication: application];
279    }
280
281  return result;
282}
283
284- (BOOL) handleKeyPress: (GSKrabKeyCode) krabKeyCode
285{
286  NSDictionary *keyHandler;
287  BOOL result;
288
289  result = NO;
290
291  keyHandler = NSMapGet (keyTable, (void *) krabKeyCode);
292  if (keyHandler)
293    {
294      if ([[keyHandler objectForKey: @"BindingType"]
295            isEqualToString: @"Application"])
296        result = [self _sendKeyPress: krabKeyCode toApplication: keyHandler];
297      else if ([[keyHandler objectForKey: @"BindingType"]
298                isEqualToString: @"ShellCommand"])
299        result = [self _launchCommand:
300                         [keyHandler objectForKey: @"Command"]];
301    }
302//   else
303//     NSLog (@"no handler for key '%d'", krabKeyCode);
304
305//   NSLog (@"result: %d", result);
306
307  return result;
308}
309
310- (NSDictionary *) _findApplication: (NSString *) application
311{
312  NSEnumerator *enumerator;
313  NSDictionary *currentDict, *applDict;
314
315  applDict = nil;
316  enumerator = [applications objectEnumerator];
317  currentDict = [enumerator nextObject];
318  while (currentDict && !applDict)
319    if ([[currentDict objectForKey: @"Application"]
320          isEqualToString: application])
321      applDict = currentDict;
322    else
323      currentDict = [enumerator nextObject];
324
325  return applDict;
326}
327
328- (NSDictionary *) _findApplicationWithName: (NSString *) serviceName
329{
330  NSEnumerator *enumerator;
331  NSDictionary *currentDict, *applDict;
332
333  applDict = nil;
334  enumerator = [applications objectEnumerator];
335  currentDict = [enumerator nextObject];
336  while (currentDict && !applDict)
337    if ([[currentDict objectForKey: @"ServiceName"]
338          isEqualToString: serviceName])
339      applDict = currentDict;
340    else
341      currentDict = [enumerator nextObject];
342
343  return applDict;
344}
345
346- (void) registerApplication: (NSString *) application
347             withServiceName: (NSString *) serviceName
348{
349  NSDictionary *oldApplDict;
350  NSMutableDictionary *newApplDict;
351
352  oldApplDict = [self _findApplication: application];
353  if (oldApplDict)
354    [applications removeObject: oldApplDict];
355
356  newApplDict = [NSMutableDictionary new];
357  [newApplDict setObject: application forKey: @"Application"];
358  [newApplDict setObject: @"Application" forKey: @"BindingType"];
359  [newApplDict setObject: serviceName forKey: @"ServiceName"];
360  [newApplDict setObject: [NSMutableArray new] forKey: @"Keys"];
361  [applications addObject: newApplDict];
362
363//   NSLog (@"application '%@' registered with service name '%@'", serviceName);
364}
365
366- (void) applicationWithServiceName: (NSString *) serviceName
367                subscribesToKeyCode: (GSKrabKeyCode) krabKeyCode
368{
369  NSDictionary *applDict;
370  NSMutableArray *keys;
371  NSNumber *keyCode;
372
373  applDict = [self _findApplicationWithName: serviceName];
374  if (applDict)
375    {
376      keys = [applDict objectForKey: @"Keys"];
377      keyCode = [NSNumber numberWithInt: krabKeyCode];
378      if ([keys indexOfObject: keyCode] == NSNotFound)
379        {
380          [keys addObject: keyCode];
381          [self registerKey: krabKeyCode forObject: applDict];
382        }
383    }
384//   else
385//     NSLog (@"no application found with service name '%@'", serviceName);
386}
387
388- (void) stop
389{
390  [interface stop];
391  [self release];
392  exit (0);
393}
394
395@end
396