/** |
* Name: KeyMouseRelay |
* Type: iPhone OS 2.x SpringBoard extension (MobileSubstrate-based) |
* Description: Extension to allow sending keyboard/mouse events to SpringBoard |
* from an external application |
* Author: Lance Fetters (aka. ashikase) |
* Last-modified: 2009-02-23 19:41:21 |
*/ |
/** |
* Copyright (C) 2009 Lance Fetters (aka. ashikase) |
* All rights reserved. |
* |
* Redistribution and use in source and binary forms, with or without |
* modification, are permitted provided that the following conditions |
* are met: |
* |
* 1. Redistributions of source code must retain the above copyright |
* notice, this list of conditions and the following disclaimer. |
* |
* 2. Redistributions in binary form must reproduce the above copyright |
* notice, this list of conditions and the following disclaimer in |
* the documentation and/or other materials provided with the |
* distribution. |
* |
* 3. The name of the author may not be used to endorse or promote |
* products derived from this software without specific prior |
* written permission. |
* |
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS |
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, |
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING |
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
* POSSIBILITY OF SUCH DAMAGE. |
*/ |
#import "KeyMouseRelay.h" |
#include <substrate.h> |
#include <mach/mach_port.h> |
#include <mach/mach_init.h> |
#include <sys/socket.h> |
#include <netinet/in.h> |
#include <arpa/inet.h> |
#import <Foundation/Foundation.h> |
#import <CoreGraphics/CGGeometry.h> |
#import <GraphicsServices/GraphicsServices.h> |
#import <QuartzCore/CAWindowServer.h> |
#import <QuartzCore/CAWindowServerDisplay.h> |
#import <SpringBoard/SpringBoard.h> |
#define HOOK(class, name, type, args...) \ |
static type (*_ ## class ## $ ## name)(class *self, SEL sel, ## args); \ |
static type $ ## class ## $ ## name(class *self, SEL sel, ## args) |
#define CALL_ORIG(class, name, args...) \ |
_ ## class ## $ ## name(self, sel, ## args) |
//______________________________________________________________________________ |
//______________________________________________________________________________ |
// NOTE: The following two functions are modified from Jay Freeman (saurik)'s |
// Veency |
// http://svn.saurik.com/repos/menes/trunk/veency |
static int buttons_ = 0; |
static int x_ = 0, y_ = 0; |
// WAS: void VNCPointer(int buttons, int x, int y, rfbClientPtr client) |
static void relayMouse(int buttons, int x, int y) |
{ |
x_ = x; y_ = y; |
int diff = buttons_ ^ buttons; |
bool twas((buttons_ & 0x1) != 0); |
bool tis((buttons & 0x1) != 0); |
buttons_ = buttons; |
mach_port_t purple(0); |
if ((diff & 0x10) != 0) { |
struct GSEventRecord record; |
memset(&record, 0, sizeof(record)); |
record.type = (buttons & 0x4) != 0 ? |
GSEventTypeHeadsetButtonDown : |
GSEventTypeHeadsetButtonUp; |
record.timestamp = GSCurrentEventTimestamp(); |
GSSendSystemEvent(&record); |
} |
if ((diff & 0x04) != 0) { |
struct GSEventRecord record; |
memset(&record, 0, sizeof(record)); |
record.type = (buttons & 0x4) != 0 ? |
GSEventTypeMenuButtonDown : |
GSEventTypeMenuButtonUp; |
record.timestamp = GSCurrentEventTimestamp(); |
GSSendSystemEvent(&record); |
} |
if ((diff & 0x02) != 0) { |
struct GSEventRecord record; |
memset(&record, 0, sizeof(record)); |
record.type = (buttons & 0x2) != 0 ? |
GSEventTypeLockButtonDown : |
GSEventTypeLockButtonUp; |
record.timestamp = GSCurrentEventTimestamp(); |
GSSendSystemEvent(&record); |
} |
if (twas != tis || tis) { |
struct { |
struct GSEventRecord record; |
struct { |
struct GSEventRecordInfo info; |
struct GSPathInfo path; |
} data; |
} event; |
memset(&event, 0, sizeof(event)); |
event.record.type = GSEventTypeMouse; |
event.record.locationInWindow.x = x; |
event.record.locationInWindow.y = y; |
event.record.timestamp = GSCurrentEventTimestamp(); |
event.record.size = sizeof(event.data); |
event.data.info.handInfo.type = twas == tis ? |
GSMouseEventTypeDragged : |
tis ? |
GSMouseEventTypeDown : |
GSMouseEventTypeUp; |
event.data.info.handInfo.x34 = 0x1; |
event.data.info.handInfo.x38 = tis ? 0x1 : 0x0; |
event.data.info.pathPositions = 1; |
event.data.path.x00 = 0x01; |
event.data.path.x01 = 0x02; |
event.data.path.x02 = tis ? 0x03 : 0x00; |
event.data.path.position = event.record.locationInWindow; |
mach_port_t port(0); |
if (CAWindowServer *server = [CAWindowServer serverIfRunning]) { |
NSArray *displays([server displays]); |
if (displays != nil && [displays count] != 0) |
if (CAWindowServerDisplay *display = [displays objectAtIndex:0]) |
port = [display clientPortAtPosition:event.record.locationInWindow]; |
} |
if (port == 0) { |
if (purple == 0) |
purple = GSCopyPurpleSystemEventPort(); |
port = purple; |
} |
GSSendEvent(&event.record, port); |
} |
if (purple != 0) |
mach_port_deallocate(mach_task_self(), purple); |
} |
// WAS: void VNCKeyboard(rfbBool down, rfbKeySym key, rfbClientPtr client) |
static void relayKeyboard(unsigned short key) |
{ |
if (key <= 0xfff) { |
struct { |
struct GSEventRecord record; |
struct GSEventKeyInfo data; |
} event; |
memset(&event, 0, sizeof(event)); |
event.record.type = GSEventTypeKeyDown; |
event.record.timestamp = GSCurrentEventTimestamp(); |
event.record.size = sizeof(event.data); |
event.data.character = key; |
mach_port_t port(0); |
if (CAWindowServer *server = [CAWindowServer serverIfRunning]) { |
NSArray *displays([server displays]); |
if (displays != nil && [displays count] != 0) |
if (CAWindowServerDisplay *display = [displays objectAtIndex:0]) |
//port = [display clientPortAtPosition:CGPointMake(x_, y_)]; |
port = [display clientPortAtPosition:CGPointMake(160, 240)]; |
} |
if (port != 0) |
GSSendEvent(&event.record, port); |
} |
} |
//______________________________________________________________________________ |
//______________________________________________________________________________ |
// NOTE: The following two functions are modified from zataang's SpringBridge |
// http://zataangstuff.googlecode.com/svn/trunk/SpringBridge/ |
static void relayDataCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) |
{ |
NSData *d = (NSData *)data; |
if ([d length] == sizeof(KeyMouseEvent)) { |
const KeyMouseEvent *event = reinterpret_cast<const KeyMouseEvent *>([d bytes]); |
if (event->type == 0) { |
// Key event |
NSLog(@"KeyMouseRelay: received key press: %c", event->key); |
relayKeyboard(event->key); |
} else { |
// Mouse event |
NSLog(@"KeyMouseRelay: received mouse event at x:%d, y:%d, buttons:%d", |
event->mouse.x, event->mouse.y, event->mouse.buttons); |
relayMouse(event->mouse.buttons, event->mouse.x, event->mouse.y); |
} |
} |
} |
static void listenForRelayConnections() |
{ |
CFSocketRef sock = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_DGRAM, |
IPPROTO_UDP, kCFSocketDataCallBack, (CFSocketCallBack)&relayDataCallBack, NULL); |
if (sock) { |
struct sockaddr_in addr4; |
memset(&addr4, 0, sizeof(addr4)); |
addr4.sin_len = sizeof(addr4); |
addr4.sin_family = AF_INET; |
addr4.sin_port = htons(KEYMOUSERELAY_PORT); |
addr4.sin_addr.s_addr = inet_addr("127.0.0.1"); |
NSData *address4 = [NSData dataWithBytes:&addr4 length:sizeof(addr4)]; |
CFSocketError err; |
if ((err = CFSocketSetAddress(sock, (CFDataRef)address4)) == kCFSocketSuccess) { |
CFRunLoopRef cfrl = CFRunLoopGetCurrent(); |
CFRunLoopSourceRef source4 = CFSocketCreateRunLoopSource(kCFAllocatorDefault, sock, 0); |
CFRunLoopAddSource(cfrl, source4, kCFRunLoopCommonModes); |
CFRelease(source4); |
NSLog(@"KeyMouseRelay: Listening for connections on port: %d", KEYMOUSERELAY_PORT); |
} else { |
CFRelease(sock); |
} |
} |
} |
//______________________________________________________________________________ |
//______________________________________________________________________________ |
HOOK(SpringBoard, applicationDidFinishLaunching$, void, id app) |
{ |
CALL_ORIG(SpringBoard, applicationDidFinishLaunching$, app); |
listenForRelayConnections(); |
} |
//______________________________________________________________________________ |
//______________________________________________________________________________ |
extern "C" void KeyMouseRelayInitialize() |
{ |
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; |
// NOTE: This library should only be loaded for SpringBoard |
NSString *identifier = [[NSBundle mainBundle] bundleIdentifier]; |
if (![identifier isEqualToString:@"com.apple.springboard"]) |
return; |
// Setup hooks |
Class $SpringBoard(objc_getClass("SpringBoard")); |
_SpringBoard$applicationDidFinishLaunching$ = |
MSHookMessage($SpringBoard, @selector(applicationDidFinishLaunching:), &$SpringBoard$applicationDidFinishLaunching$); |
[pool release]; |
} |
/* vim: set syntax=objcpp sw=4 ts=4 sts=4 expandtab textwidth=80 ff=unix: */
http://code.google.com/p/iphone-tweaks/source/browse/trunk/KeyMouseRelay/KeyMouseRelay.mm |