aboutsummaryrefslogtreecommitdiffstats
path: root/code/macosx/CGMouseDeltaFix.m
blob: 15ce74017cc3b43b874bebb94cc15080500c3b8f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.

This file is part of Quake III Arena source code.

Quake III Arena source code 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.

Quake III Arena source code 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 Foobar; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/
#import "CGMouseDeltaFix.h"
#import "CGPrivateAPI.h"

#import <Foundation/Foundation.h>
#import <mach-o/dyld.h>


// We will try to automatically fall back to using the original CGGetLastMouseDelta when we are on a system new enough to have the fix.  Any version of CoreGraphics past 1.93.0 will have the fixed version.


static BOOL originalVersionShouldWork = YES;
static CGMouseDelta CGFix_Mouse_DeltaX, CGFix_Mouse_DeltaY;

static void CGFix_NotificationCallback(CGSNotificationType note, CGSNotificationData data, CGSByteCount dataLength, CGSNotificationArg arg);

static CGSRegisterNotifyProcType registerNotifyProc = NULL;

void CGFix_Initialize()
{
    NSAutoreleasePool *pool;
    NSBundle *cgBundle;
    NSString *version;
    NSArray *components;
    
    if (registerNotifyProc)
        // We've already been called once and have registered our callbacks.  If the original version works, this will be NULL, but we'll end up doing nothing (again, possibly).
        return;

    //NSLog(@"CGFix_Initialize\n");
        
    pool = [[NSAutoreleasePool alloc] init];
    cgBundle = [NSBundle bundleWithPath: @"/System/Library/Frameworks/ApplicationServices.framework/Frameworks/CoreGraphics.framework"];
    if (!cgBundle) {
        // If it's moved, it must be newer than what we know about and should work
        //NSLog(@"No /System/Library/Frameworks/ApplicationServices.framework/Frameworks/CoreGraphics.framework\n");
        goto done;
    }
    
    version = [[cgBundle infoDictionary] objectForKey: @"CFBundleShortVersionString"];
    components = [version componentsSeparatedByString: @"."];
    //NSLog(@"version = %@\n", version);
    //NSLog(@"components = %@\n", components);


    if ([components count] < 2)
        // We don't understand this versioning scheme.  Must have changed.
        goto done;
    
    if (![[components objectAtIndex: 0] isEqualToString: @"1"] || [[components objectAtIndex: 1] intValue] > 93)
        // This version should be new enough to work
        goto done;
    
    // Look up the function pointer we need to register our callback.
    if (!NSIsSymbolNameDefined("_CGSRegisterNotifyProc")) {
        //NSLog(@"No _CGSRegisterNotifyProc\n");
        goto done;
    }
    
    registerNotifyProc = NSAddressOfSymbol(NSLookupAndBindSymbol("_CGSRegisterNotifyProc"));
    //NSLog(@"registerNotifyProc = 0x%08x", registerNotifyProc);

    // Must not work if we got here
    originalVersionShouldWork = NO;
    
    // We want to catch all the events that could possible indicate mouse movement and sum them up
    registerNotifyProc( CGFix_NotificationCallback, kCGSEventNotificationMouseMoved, NULL);
    registerNotifyProc( CGFix_NotificationCallback, kCGSEventNotificationLeftMouseDragged, NULL);
    registerNotifyProc( CGFix_NotificationCallback, kCGSEventNotificationRightMouseDragged, NULL);
    registerNotifyProc( CGFix_NotificationCallback, kCGSEventNotificationNotificationOtherMouseDragged, NULL);
    
done:
    [pool release];
}

void CGFix_GetLastMouseDelta(CGMouseDelta *dx, CGMouseDelta *dy)
{
    if (originalVersionShouldWork) {
        CGGetLastMouseDelta(dx, dy);
        return;
    }
    
    *dx = CGFix_Mouse_DeltaX;
    *dy = CGFix_Mouse_DeltaY;
    
    CGFix_Mouse_DeltaX = CGFix_Mouse_DeltaY = 0;
}

static void CGFix_NotificationCallback(CGSNotificationType note, CGSNotificationData data, CGSByteCount dataLength, CGSNotificationArg arg)
{
    CGSEventRecordPtr event;

    //fprintf(stderr, "CGFix_NotificationCallback(note=%d, date=0x%08x, dataLength=%d, arg=0x%08x)\n", note, data, dataLength, arg);
    
#ifdef DEBUG
    if ((note != kCGSEventNotificationMouseMoved && 
         note != kCGSEventNotificationLeftMouseDragged &&
         note != kCGSEventNotificationRightMouseDragged &&
         note != kCGSEventNotificationNotificationOtherMouseDragged) ||
         dataLength != sizeof (CGSEventRecord))
         fprintf(stderr, "Unexpected arguments to callback function CGFix_NotificationCallback(note=%d, date=0x%08x, dataLength=%d, arg=0x%08x)\n", note, data, dataLength, arg);
         abort();
    }
#endif

    event = (CGSEventRecordPtr)data;

    CGFix_Mouse_DeltaX += event->data.move.deltaX;
    CGFix_Mouse_DeltaY += event->data.move.deltaY;
    //fprintf(stderr, "  dx += %d, dy += %d\n", event->data.move.deltaX, event->data.move.deltaY);
}