an experiment in making a cocoa webkit browser manageable under X11
1#import <X11/Xlib.h>
2#import <X11/Xutil.h>
3
4#import <err.h>
5#import <stdlib.h>
6#import <stdio.h>
7
8#import "X11Window.h"
9#import "WKWindow.h"
10
11extern int debug;
12
13@implementation X11Window
14
15- (id)init
16{
17 self = [super init];
18
19 display = XOpenDisplay(NULL);
20 screen = DefaultScreen(display);
21
22 window = XCreateSimpleWindow(
23 display,
24 RootWindow(display, screen),
25 0, 0, 100, 100,
26 0,
27 BlackPixel(display, screen),
28 BlackPixel(display, screen)
29 );
30
31 [self setWindowTitle:@"shadowebkit"];
32
33 XMapWindow(display, window);
34 XSelectInput(display, window, KeyPressMask | KeyReleaseMask |
35 ExposureMask | FocusChangeMask | StructureNotifyMask);
36
37 XFlush(display);
38 XSync(display, False);
39
40 return self;
41}
42
43- (void)mainLoopWithWKWindow: (id)wkwobj
44{
45 XEvent e;
46 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
47
48 wkw = wkwobj;
49
50 for(;;) {
51 XNextEvent(display, &e);
52 XFlush(display);
53 XSync(display, False);
54
55 if (e.type == KeyPress || e.type == KeyRelease)
56 [self sendKeyFromXEvent:e];
57 else
58 [self updateWKWindowPosition];
59 }
60
61 [pool release];
62}
63
64- (void)setWindowTitle:(NSString *)title
65{
66 XTextProperty winNameProp;
67 const char *winName = [title UTF8String];
68
69 if (XStringListToTextProperty(&winName, 1, &winNameProp) == 0)
70 errx(1, "XStringListToTextProperty");
71
72 XSetWMName(display, window, &winNameProp);
73}
74
75- (void)updateWKWindowPosition
76{
77 XWindowAttributes xwa;
78 XGetWindowAttributes(display, window, &xwa);
79
80 NSArray *pos = [[NSArray alloc] initWithObjects:
81 [NSNumber numberWithInt:(xwa.x + xwa.border_width)],
82 [NSNumber numberWithInt:(xwa.y + xwa.border_width)],
83 [NSNumber numberWithInt:xwa.width],
84 [NSNumber numberWithInt:xwa.height],
85 nil];
86
87 [wkw performSelectorOnMainThread:@selector(setPosition:)
88 withObject:pos
89 waitUntilDone:true];
90}
91
92/* translate an x11 key event into an equivalent keycode or string and send
93 * the NSEvent to the app, which will handle sending it to webkit or the url
94 * bar */
95- (void)sendKeyFromXEvent:(XEvent)e
96{
97 char str[256+1];
98 char strNoMod[2] = { '\0' };
99 char *ksname;
100 KeySym ks;
101 int keycode = 0;
102 int repeating = 0;
103 int modifier = 0;
104
105 XLookupString(&e.xkey, str, 256, &ks, NULL);
106
107 /* X11 keysyms in /usr/X11/include/X11/keysymdef.h, carbon key codes
108 * in /System/Library/Frameworks/Carbon.framework/Frameworks/HIToolbox.framework/Headers/Events.h */
109
110 switch (ks) {
111 case XK_Control_L: keycode = kVK_Control; break;
112 case XK_Control_R: keycode = kVK_RightControl; break;
113 case XK_Delete: keycode = kVK_ForwardDelete; break;
114 case XK_Down: keycode = kVK_DownArrow; break;
115 case XK_End: keycode = kVK_End; break;
116 case XK_Escape: keycode = kVK_Escape; break;
117 case XK_Home: keycode = kVK_Home; break;
118 case XK_Left: keycode = kVK_LeftArrow; break;
119 case XK_Meta_L: keycode = kVK_Command; break;
120 case XK_Meta_R: keycode = kVK_Command; break;
121 case XK_Mode_switch: keycode = kVK_Option; break;
122 case XK_Next: keycode = kVK_PageDown; break;
123 case XK_Prior: keycode = kVK_PageUp; break;
124 case XK_Return: keycode = kVK_Return; break;
125 case XK_Right: keycode = kVK_RightArrow; break;
126 case XK_Shift_L: keycode = kVK_Shift; break;
127 case XK_Tab: keycode = kVK_Tab; break;
128 case XK_Up: keycode = kVK_UpArrow; break;
129
130 default:
131 /* translate keys that don't have XK_* equivalents */
132
133 if (!(ksname = XKeysymToString(ks)))
134 ksname = "no name";
135
136 if (!strcasecmp(ksname, "backspace")) {
137 keycode = kVK_Delete;
138 break;
139 }
140 else if (!strcasecmp(ksname, "space")) {
141 keycode = kVK_Space;
142 break;
143 }
144 else if (strlen(ksname) == 1) {
145 strlcpy(str, ksname, sizeof(str));
146 strlcpy(strNoMod, ksname, sizeof(strNoMod));
147 }
148 else if (strlen(ksname) > 1 && debug)
149 /* probably a named key */
150 printf("should probably translate \"%s\"\n", ksname);
151
152 /* otherwise, assume it's just an ascii letter or number we
153 * can pass through as 'characters' param and a 0 keyCode */
154 if (debug)
155 printf("key %s 0x%x (X11 \"%s\") -> key \"%s\"\n",
156 (e.type == KeyPress ? "press" : "release"),
157 e.xkey.keycode,
158 ksname,
159 str);
160 }
161
162 /* if we are looking at a keyrelease event and there's a pending
163 * keypress event with the same timestamp, we're holding a key down */
164 if (e.type == KeyRelease && XEventsQueued(display,
165 QueuedAfterReading)) {
166 XEvent nev;
167 XPeekEvent(display, &nev);
168
169 if (nev.type == KeyPress && nev.xkey.time == e.xkey.time &&
170 nev.xkey.keycode == e.xkey.keycode)
171 repeating = 1;
172 }
173
174 if (e.xkey.state) {
175 if (e.xkey.state & ShiftMask)
176 modifier |= NSShiftKeyMask;
177 if (e.xkey.state & LockMask)
178 modifier |= NSAlphaShiftKeyMask;
179 if (e.xkey.state & ControlMask)
180 modifier |= NSControlKeyMask;
181 if (e.xkey.state & Mod2Mask)
182 modifier |= NSCommandKeyMask;
183 if (e.xkey.state & 0x2000)
184 modifier |= NSAlternateKeyMask;
185
186 /* TODO: what do mod1, mod3, mod4, and mod5 map to? */
187 }
188
189 NSEvent *fakeEvent = [NSEvent
190 keyEventWithType:(e.type == KeyPress ? NSKeyDown : NSKeyUp)
191 location:[NSEvent mouseLocation]
192 modifierFlags:modifier
193 timestamp:0
194 windowNumber:[[NSApp mainWindow] windowNumber]
195 context:nil
196 characters:[NSString stringWithFormat:@"%s", str]
197 charactersIgnoringModifiers:[NSString stringWithFormat:@"%s", strNoMod]
198 isARepeat:(BOOL)repeating
199 keyCode:keycode];
200
201 if (debug)
202 NSLog(@"%@", fakeEvent);
203
204 [NSApp postEvent:fakeEvent atStart:NO];
205}
206
207@end