1/* Implementation of class NSSpeechRecognizer
2   Copyright (C) 2019 Free Software Foundation, Inc.
3
4   By: Gregory John Casamento
5   Date: Fri Dec  6 04:55:59 EST 2019
6
7   This file is part of the GNUstep Library.
8
9   This library is free software; you can redistribute it and/or
10   modify it under the terms of the GNU Lesser General Public
11   License as published by the Free Software Foundation; either
12   version 2 of the License, or (at your option) any later version.
13
14   This library is distributed in the hope that it will be useful,
15   but WITHOUT ANY WARRANTY; without even the implied warranty of
16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17   Lesser General Public License for more details.
18
19   You should have received a copy of the GNU Lesser General Public
20   License along with this library; if not, write to the Free
21   Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22   Boston, MA 02110 USA.
23*/
24
25#import <AppKit/NSSpeechRecognizer.h>
26#import <AppKit/NSApplication.h>
27#import <Foundation/NSDistantObject.h>
28#import <Foundation/NSString.h>
29#import <Foundation/NSDictionary.h>
30#import <Foundation/NSArray.h>
31#import <Foundation/NSThread.h>
32#import <Foundation/NSError.h>
33#import <Foundation/NSConnection.h>
34#import <Foundation/NSDistributedNotificationCenter.h>
35#import <Foundation/NSDebug.h>
36#import <Foundation/NSUUID.h>
37#import "GSFastEnumeration.h"
38#import "AppKit/NSWorkspace.h"
39#import "AppKit/NSWindow.h"
40
41id   _speechRecognitionServer = nil;
42BOOL _serverLaunchTested = NO;
43
44#define SPEECH_RECOGNITION_SERVER @"GSSpeechRecognitionServer"
45
46@interface NSObject (SpeechRecognitionServerPrivate)
47
48- (void) addToBlockingRecognizers: (NSString *)s;
49- (void) removeFromBlockingRecognizers: (NSString *)s;
50- (BOOL) isBlocking: (NSString *)s;
51- (void) addClient;
52
53@end
54
55@implementation NSSpeechRecognizer
56
57+ (void) initialize
58{
59  if (self == [NSSpeechRecognizer class])
60    {
61      // Test for an existant server...
62      _speechRecognitionServer =
63        [NSConnection rootProxyForConnectionWithRegisteredName: SPEECH_RECOGNITION_SERVER
64                                                          host: nil];
65
66      // if none exists, start one.  We will connect with it in init.
67      if (nil == _speechRecognitionServer)
68        {
69          NSWorkspace *ws = [NSWorkspace sharedWorkspace];
70          [ws launchApplication: SPEECH_RECOGNITION_SERVER
71                       showIcon: NO
72                     autolaunch: NO];
73        }
74    }
75}
76
77- (void) _restartServer
78{
79  if (nil == _speechRecognitionServer && !_serverLaunchTested)
80    {
81      unsigned int i = 0;
82
83      // Wait for up to five seconds  for the server to launch, then give up.
84      for (i = 0 ; i < 50 ; i++)
85        {
86          _speechRecognitionServer = [NSConnection
87                                           rootProxyForConnectionWithRegisteredName: SPEECH_RECOGNITION_SERVER
88                                                                               host: nil];
89          RETAIN(_speechRecognitionServer);
90          if (nil != _speechRecognitionServer)
91            {
92              NSDebugLog(@"Server found!!!");
93              break;
94            }
95          else
96            {
97              NS_DURING
98                {
99                  NSWorkspace *ws = [NSWorkspace sharedWorkspace];
100                  [ws launchApplication: SPEECH_RECOGNITION_SERVER
101                               showIcon: NO
102                             autolaunch: NO];
103                }
104              NS_HANDLER
105                {
106                }
107              NS_ENDHANDLER;
108            }
109          [NSThread sleepForTimeInterval: 0.1];
110        }
111
112      // Set a flag so we don't bother waiting for the speech recognition server to
113      // launch the next time if it didn't work this time.
114      _serverLaunchTested = YES;
115    }
116
117  if (_speechRecognitionServer == nil)
118    {
119      NSLog(@"Cannot restart speech recognition server.");
120    }
121}
122
123- (void) processNotification: (NSNotification *)note
124{
125  NSString *word = (NSString *)[note object];
126  NSDebugLog(@"Notified");
127  if (_listensInForegroundOnly)
128    {
129      if (_appInForeground == NO)
130        {
131          NSDebugLog(@"Only in foreground..");
132          return;
133        }
134    }
135
136  if (_blocksOtherRecognizers)
137    {
138      NS_DURING
139        {
140          /*
141          if ([_speechRecognitionServer isBlocking: [_uuid UUIDString]] == NO)
142            {
143              // If we are not a blocking recognizer, then we are blocked...
144              NSDebugLog(@"Blocked...");
145              return;
146            }
147          */
148        }
149      NS_HANDLER
150        {
151          NSLog(@"%@", localException);
152          [self _restartServer];
153        }
154      NS_ENDHANDLER;
155    }
156
157  word = [word lowercaseString];
158  FOR_IN(NSString*, obj, _commands)
159    {
160      if ([[obj lowercaseString] isEqualToString: word])
161        {
162          [_delegate speechRecognizer: self
163                  didRecognizeCommand: word];
164        }
165    }
166  END_FOR_IN(_commands);
167}
168
169- (void) processAppStatusNotification: (NSNotification *)note
170{
171  NSString *name = [note name];
172
173  if ([name isEqualToString: NSApplicationDidBecomeActiveNotification] ||
174      [name isEqualToString: NSApplicationDidFinishLaunchingNotification] ||
175      [name isEqualToString: NSWindowDidBecomeKeyNotification])
176    {
177      _appInForeground = YES;
178    }
179  else
180    {
181      _appInForeground = NO;
182    }
183}
184
185// Initialize
186- (instancetype) init
187{
188  self = [super init];
189  if (self != nil)
190    {
191      [[NSNotificationCenter defaultCenter]
192        addObserver: self
193           selector: @selector(processAppStatusNotification:)
194               name: NSApplicationDidFinishLaunchingNotification
195             object: nil];
196
197      [[NSNotificationCenter defaultCenter]
198        addObserver: self
199           selector: @selector(processAppStatusNotification:)
200               name: NSWindowDidBecomeKeyNotification
201             object: nil];
202
203      [[NSNotificationCenter defaultCenter]
204        addObserver: self
205           selector: @selector(processAppStatusNotification:)
206               name: NSApplicationDidBecomeActiveNotification
207             object: nil];
208
209      [[NSNotificationCenter defaultCenter]
210        addObserver: self
211           selector: @selector(processAppStatusNotification:)
212               name: NSApplicationDidResignActiveNotification
213             object: nil];
214
215      _delegate = nil;
216      _blocksOtherRecognizers = NO;
217      _listensInForegroundOnly = YES;
218      _appInForeground = YES;
219      _uuid = [NSUUID UUID];
220
221      [self _restartServer];
222    }
223
224  NS_DURING
225    {
226      [_speechRecognitionServer addClient];  // do this to update the client count;
227    }
228  NS_HANDLER
229    {
230      NSLog(@"%@", localException);
231      [self _restartServer];
232    }
233  NS_ENDHANDLER;
234
235  return self;
236}
237
238- (void) dealloc
239{
240  [[NSDistributedNotificationCenter defaultCenter] removeObserver: self];
241  [[NSNotificationCenter defaultCenter] removeObserver: self];
242  _delegate = nil;
243  [super dealloc];
244}
245
246// Delegate
247- (id<NSSpeechRecognizerDelegate>) delegate
248{
249  return _delegate;
250}
251
252- (void) setDelegate: (id<NSSpeechRecognizerDelegate>)delegate
253{
254  _delegate = delegate;
255}
256
257// Configuring...
258- (NSArray *) commands
259{
260  return _commands;
261}
262
263- (void) setCommands: (NSArray *)commands
264{
265  ASSIGNCOPY(_commands, commands);
266}
267
268- (NSString *) displayCommandsTitle
269{
270  return _displayCommandsTitle;
271}
272
273- (void) setDisplayCommandsTitle: (NSString *)displayCommandsTitle
274{
275  ASSIGNCOPY(_displayCommandsTitle, displayCommandsTitle);
276}
277
278- (BOOL) listensInForegroundOnly
279{
280  return _listensInForegroundOnly;
281}
282
283- (void) setListensInForegroundOnly: (BOOL)listensInForegroundOnly
284{
285  _listensInForegroundOnly = listensInForegroundOnly;
286}
287
288- (BOOL) blocksOtherRecognizers
289{
290  return _blocksOtherRecognizers;
291}
292
293- (void) setBlocksOtherRecognizers: (BOOL)blocksOtherRecognizers
294{
295  NS_DURING
296    {
297      if (blocksOtherRecognizers == YES)
298        {
299          [_speechRecognitionServer addToBlockingRecognizers: [_uuid UUIDString]];
300        }
301      else
302        {
303          [_speechRecognitionServer removeFromBlockingRecognizers: [_uuid UUIDString]];
304        }
305      _blocksOtherRecognizers = blocksOtherRecognizers;
306    }
307  NS_HANDLER
308    {
309      NSLog(@"%@", localException);
310      [self _restartServer];
311    }
312  NS_ENDHANDLER;
313}
314
315// Listening
316- (void) startListening
317{
318  // Start listening to the notification being sent by the server....
319  [[NSDistributedNotificationCenter defaultCenter]
320        addObserver: self
321           selector: @selector(processNotification:)
322               name: GSSpeechRecognizerDidRecognizeWordNotification
323             object: nil];
324
325}
326
327- (void) stopListening
328{
329  // Remove the observer for the notification....
330  [[NSDistributedNotificationCenter defaultCenter]
331        removeObserver: self
332                  name: GSSpeechRecognizerDidRecognizeWordNotification
333                object: nil];
334}
335@end
336