Add preference to change fake Esc modifier key
[MacVim.git] / src / MacVim / MMApplication.m
blob3bea9b836ca9703910ecbededaeb67db94f6b5b0
1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
2  *
3  * VIM - Vi IMproved            by Bram Moolenaar
4  *                              MacVim GUI port by Bjorn Winckler
5  *
6  * Do ":help uganda"  in Vim to read copying and usage conditions.
7  * Do ":help credits" in Vim to see a list of people who contributed.
8  * See README.txt for an overview of the Vim source code.
9  */
11  * MMApplication
12  *
13  * Some default NSApplication key input behavior is overridden here.
14  */
16 #import "MMApplication.h"
17 #import "Miscellaneous.h"
19 // Ctrl-Tab is broken on pre 10.5, so we add a hack to make it work.
20 #if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4
21 # import "MMTextView.h"
22 # define MM_CTRL_TAB_HACK 1
23 #endif
28 @implementation MMApplication
30 - (void)awakeFromNib
32     [self fakeEscModifierKeyChanged:nil];
35 - (void)sendEvent:(NSEvent *)event
37     NSEventType type = [event type];
38     unsigned flags = [event modifierFlags];
40     // The following hack allows the user to set one modifier key of choice
41     // (Ctrl, Alt, or Cmd) to generate an Esc key press event.  In order for
42     // the key to still be used as a modifier we only send the "faked" Esc
43     // event if the modifier was pressed and released without any other keys
44     // being pressed in between.  The user may elect to have the chosen
45     // modifier sending Esc on key down, since sending it on key up makes it
46     // appear a bit sluggish.  However, this effectively disables the modifier
47     // key (but only the left key and not the right one, in case there are two
48     // on the keyboard).
49     //
50     // This hack is particularly useful in conjunction with Mac OS X's ability
51     // to turn Caps-Lock into a modifier key of choice because it enables us to
52     // turn Caps-Lock into a quasi-Esc key!  (This remapping be done inside
53     // "System Preferences -> Keyboard & Mouse -> Modifier Keys...".)
54     //
55     if (fakeEscKeyCode != 0) {
56         if (NSFlagsChanged == type && [event keyCode] == fakeEscKeyCode) {
57             BOOL sendEsc = NO;
58             CFAbsoluteTime timeNow = CFAbsoluteTimeGetCurrent();
60             if ((flags & fakeEscModifierMask) == 0) {
61                 // The chosen modifier was released.  If the modifier was
62                 // recently pressed then convert this event to a "fake" Esc key
63                 // press event.
64                 if (!blockFakeEscEvent && !fakeEscOnKeyDown &&
65                         timeNow - fakeEscTimeDown < fakeEscTimeout)
66                     sendEsc = YES;
68                 blockFakeEscEvent = YES;
69                 blockKeyDown = NO;
70             } else {
71                 // The chosen modifier was pressed.
72                 blockFakeEscEvent = NO;
73                 fakeEscTimeDown = timeNow;
75                 if (fakeEscOnKeyDown) {
76                     sendEsc = YES;
78                     // Block key down while the fake Esc modifier key is held,
79                     // otherwise "marked text" may pop up if a key is pressed
80                     // while the fake Esc modifier is held (which looks ugly,
81                     // but is harmless).
82                     blockKeyDown = YES;
83                 }
84             }
86             if (sendEsc) {
87                 NSEvent *e = [NSEvent keyEventWithType:NSKeyDown
88                                          location:[event locationInWindow]
89                                     modifierFlags:flags & 0x0000ffffU
90                                         timestamp:[event timestamp]
91                                      windowNumber:[event windowNumber]
92                                           context:[event context]
93                                        characters:@"\x1b"   // Esc
94                       charactersIgnoringModifiers:@"\x1b"
95                                         isARepeat:NO
96                                           keyCode:53];
98                 [self postEvent:e atStart:YES];
99                 return;
100             }
101         } else if (type != NSKeyUp) {
102             // Another event occurred, so don't send any fake Esc events now
103             // (else the modifier would not function as a modifier key any
104             // more).
105             blockFakeEscEvent = YES;
106         }
108         if (blockKeyDown && type == NSKeyDown)
109             return;
110     }
112 #ifdef MM_CTRL_TAB_HACK
113     NSResponder *firstResponder = [[self keyWindow] firstResponder];
115     if (NSKeyDown == type && NSControlKeyMask & flags && 48 == [event keyCode]
116             && [firstResponder isKindOfClass:[MMTextView class]]) {
117         // HACK! This is a Ctrl-Tab key down event and the first responder is
118         // an MMTextView; send the event directly to the text view, else it
119         // will never receive it on pre 10.5 systems.
120         [firstResponder keyDown:event];
121         return;
122     }
123 #endif
125     // HACK! Intercept 'help' key presses and clear the 'help key flag', else
126     // Cocoa turns the mouse cursor into a question mark and goes into 'context
127     // help mode' (the keyDown: event itself never reaches the text view).  By
128     // clearing the 'help key flag' this event will be treated like a normal
129     // key event.
130     if ((NSKeyDown == type || NSKeyUp == type) && (flags & NSHelpKeyMask)) {
131         flags &= ~NSHelpKeyMask;
132         NSEvent *e = [NSEvent keyEventWithType:[event type]
133                                  location:[event locationInWindow]
134                             modifierFlags:flags
135                                 timestamp:[event timestamp]
136                              windowNumber:[event windowNumber]
137                                   context:[event context]
138                                characters:[event characters]
139               charactersIgnoringModifiers:[event charactersIgnoringModifiers]
140                                 isARepeat:[event isARepeat]
141                                   keyCode:[event keyCode]];
143         [self postEvent:e atStart:YES];
144         return;
145     }
147     [super sendEvent:event];
151 - (void)orderFrontStandardAboutPanel:(id)sender
153     NSString *version = [[NSBundle mainBundle] objectForInfoDictionaryKey:
154             @"CFBundleVersion"];
155     NSString *marketingVersion = [[NSBundle mainBundle]
156             objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
157     NSString *title = [NSString stringWithFormat:
158             @"Custom Version %@ (%@)", marketingVersion, version];
160     [self orderFrontStandardAboutPanelWithOptions:
161             [NSDictionary dictionaryWithObjectsAndKeys:
162                 @"",    @"Version",
163                 title,  @"ApplicationVersion",
164                 nil]];
167 - (IBAction)fakeEscModifierKeyChanged:(id)sender
169     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
170     switch ([ud integerForKey:MMFakeEscModifierKey]) {
171     case MMCtrlFakeEsc:
172         fakeEscKeyCode = 59;
173         fakeEscModifierMask = NSControlKeyMask;
174         break;
175     case MMAltFakeEsc:
176         fakeEscKeyCode = 58;
177         fakeEscModifierMask = NSAlternateKeyMask;
178         break;
179     case MMCmdFakeEsc:
180         fakeEscKeyCode = 55;
181         fakeEscModifierMask = NSCommandKeyMask;
182         break;
183     default:
184         fakeEscKeyCode = fakeEscModifierMask = 0;
185     }
187     fakeEscTimeout = [ud floatForKey:MMFakeEscTimeoutKey];
188     fakeEscOnKeyDown = [ud boolForKey:MMFakeEscOnKeyDownKey];
191 @end