Skip to content

Commit

Permalink
Add ActionMenu integration
Browse files Browse the repository at this point in the history
  • Loading branch information
rpetrich committed Aug 21, 2011
1 parent 2fbb130 commit b04c45e
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 30 deletions.
149 changes: 122 additions & 27 deletions VoiceKeys.m
Expand Up @@ -4,6 +4,7 @@
#import <AudioUnit/AUComponent.h>
#import <AudioToolbox/AudioToolbox.h>
#import <JSON/JSON.h>
#import <ActionMenu/ActionMenu.h>

#include <speex/speex.h>
#include <speex/speex_echo.h>
Expand Down Expand Up @@ -513,13 +514,10 @@ - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)err

@end

//
// Notification Callbacks
//

static BOOL weOwnProximitySensor;
static BOOL hasKeyboard;
static CFMutableDataRef speechData;
static CFAbsoluteTime startTime;

static void audioInputFrameCallback(char *buffer, size_t length)
{
Expand All @@ -528,9 +526,124 @@ static void audioInputFrameCallback(char *buffer, size_t length)
CFDataAppendBytes(speechData, (const UInt8 *)buffer, length);
}

static inline BOOL StartRecognition()
{
if (!speechData) {
AudioInputInitialize();
audioInputData.frameCallback = audioInputFrameCallback;
speechData = CFDataCreateMutable(kCFAllocatorDefault, 0);
startTime = CFAbsoluteTimeGetCurrent();
AudioInputSetupDevice();
return YES;
}
return NO;
}

static inline BOOL StopRecognitionAndSend(BOOL shouldSend)
{
BOOL result = NO;
if (speechData) {
AudioInputTeardownDevice();
if (audioInputData.hasSpeech) {
if (CFAbsoluteTimeGetCurrent() < startTime + 2.0)
ProductLog(@"Speech was too short");
else {
ProductLog(@"Sending speech to Google");
if (shouldSend) {
[[[ProductTokenAppend(SpeechClient) alloc] initWithSpeechData:(NSData *)speechData] release];
result = YES;
}
}
} else {
ProductLog(@"No speech detected");
}
CFRelease(speechData);
speechData = NULL;
AudioInputCleanup();
}
return result;
}

// Action Menu

__attribute__((visibility("hidden")))
@interface ProductTokenAppend(ActionMenuHandler) : NSObject <UIAlertViewDelegate> {
@private
UIAlertView *av;
}
@end

@implementation ProductTokenAppend(ActionMenuHandler)

- (id)init
{
if ((self = [super init])) {
av = [[UIAlertView alloc] initWithTitle:@"VoiceKeys" message:@"Recording speech…" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Recognize", nil];
}
return self;
}

- (void)show
{
if (!av.window || av.hidden) {
[self retain];
[av show];
}
}

- (void)dealloc
{
av.delegate = nil;
[av release];
[super dealloc];
}

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex != av.cancelButtonIndex) {
if (!StopRecognitionAndSend(YES)) {
alertView = [[UIAlertView alloc] initWithTitle:@"VoiceKeys" message:@"Unable to convert speech to text!" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
[alertView show];
[alertView release];
}
} else {
StopRecognitionAndSend(NO);
}
[self release];
}

@end

@implementation UIResponder (VoiceKeys)

- (void)performVoiceKeysAction
{
if (StartRecognition()) {
ProductTokenAppend(ActionMenuHandler) *amh = [[ProductTokenAppend(ActionMenuHandler) alloc] init];
[amh show];
[amh release];
}
}

- (BOOL)canPerformVoiceKeysAction
{
return hasKeyboard && !speechData;
}

@end

//
// Notification Callbacks
//

static void KeyboardWillShow(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo)
{
ProductLog(@"Entering text field");
static BOOL hasRegisteredWithActionMenu;
if (!hasRegisteredWithActionMenu) {
[[UIMenuController sharedMenuController] registerAction:@selector(performVoiceKeysAction) title:@"VoiceKeys" canPerform:@selector(canPerformVoiceKeysAction) forPlugin:@"VoiceKeys"];
hasRegisteredWithActionMenu = YES;
}
UIDevice *device = [UIDevice currentDevice];
weOwnProximitySensor = !device.proximityMonitoringEnabled;
if (weOwnProximitySensor)
Expand All @@ -546,32 +659,13 @@ static void KeyboardWillHide(CFNotificationCenterRef center, void *observer, CFS
[UIDevice currentDevice].proximityMonitoringEnabled = NO;
}

static CFAbsoluteTime startTime;

static void ProximityStateDidChange(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo)
{
ProductLog(@"Proximity state did change");
if ([UIDevice currentDevice].proximityState) {
AudioInputInitialize();
audioInputData.frameCallback = audioInputFrameCallback;
speechData = CFDataCreateMutable(kCFAllocatorDefault, 0);
startTime = CFAbsoluteTimeGetCurrent();
AudioInputSetupDevice();
} else {
AudioInputTeardownDevice();
if (audioInputData.hasSpeech) {
if (CFAbsoluteTimeGetCurrent() < startTime + 2.0)
ProductLog(@"Speech was too short");
else {
ProductLog(@"Sending speech to Google");
[[[ProductTokenAppend(SpeechClient) alloc] initWithSpeechData:(NSData *)speechData] release];
}
} else {
ProductLog(@"No speech detected");
}
CFRelease(speechData);
AudioInputCleanup();
}
if ([UIDevice currentDevice].proximityState)
StartRecognition();
else
StopRecognitionAndSend(YES);
}

static void WillEnterForeground(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo)
Expand Down Expand Up @@ -606,6 +700,7 @@ static void LoadSettings()
//

CHConstructor {
CHAutoreleasePoolForScope();
CFNotificationCenterRef center = CFNotificationCenterGetLocalCenter();
CFNotificationCenterAddObserver(center, NULL, KeyboardWillShow, (CFStringRef)UIKeyboardWillShowNotification, NULL, CFNotificationSuspensionBehaviorCoalesce);
CFNotificationCenterAddObserver(center, NULL, KeyboardWillHide, (CFStringRef)UIKeyboardWillHideNotification, NULL, CFNotificationSuspensionBehaviorCoalesce);
Expand Down
6 changes: 3 additions & 3 deletions layout/DEBIAN/control
Expand Up @@ -2,8 +2,8 @@ Package: com.rpetrich.voicekeys
Priority: optional
Section: Tweaks
Architecture: iphoneos-arm
Version: 0.1
Version: 1.0
Description: Dictate text via Google's speech recognition
Name: VoiceKeys
Depends: firmware (>= 3.1), mobilesubstrate, preferenceloader (>= 2.0), libspeex
Author: Ryan Petrich <rpetrich+hearken@gmail.com>
Depends: firmware (>= 4.0), mobilesubstrate, preferenceloader (>= 2.0), libspeex, actionmenu (>= 1.2.3)
Author: Ryan Petrich <rpetrich+voicekeys@gmail.com>
1 change: 1 addition & 0 deletions layout/Library/ActionMenu/Plugins/VoiceKeys.dylib

0 comments on commit b04c45e

Please sign in to comment.