an experiment in making a cocoa webkit browser manageable under X11
1#import "WKWindow.h"
2#import "X11Window.h"
3#import "NSString+HTML.h"
4
5#import <Cocoa/Cocoa.h>
6
7@implementation WKWindow
8
9- (id)init
10{
11 self = [super init];
12
13 screen = [NSScreen mainScreen];
14 screen_frame = [screen visibleFrame];
15
16 NSRect coords = NSMakeRect(0, 0, 300, 300);
17 window = [[[WKWindow alloc] initWithContentRect:coords
18 styleMask:NSBorderlessWindowMask
19 backing:NSBackingStoreBuffered
20 defer:NO
21 screen:screen] autorelease];
22 [window setBackgroundColor:[NSColor blackColor]];
23 [window setAcceptsMouseMovedEvents:YES];
24
25 currentURL = [[NSURL alloc] init];
26
27 NSRect bframe = NSMakeRect(0, 0, 300, 300);
28 browser = [[WebView alloc] initWithFrame:bframe
29 frameName:nil
30 groupName:nil];
31 [browser setGroupName:@"shadowebkit"];
32 [browser setUIDelegate:self];
33 [browser setResourceLoadDelegate:self];
34 [browser setFrameLoadDelegate:self];
35 wframe = [browser mainFrame];
36 [window.contentView addSubview:browser];
37
38 urlField = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 0, 0)];
39 [urlField setTarget:self];
40 [urlField setAction:@selector(loadURLFromTextField)];
41 [window.contentView addSubview:urlField];
42
43 statusBar = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 0, 0)];
44 [statusBar setTarget:self];
45 [statusBar setEditable:false];
46 [statusBar setSelectable:false];
47 [statusBar setBordered:false];
48 [statusBar setTextColor:[NSColor lightGrayColor]];
49 [statusBar setBackgroundColor:[NSColor blackColor]];
50 [self setStatus:@""];
51 [window.contentView addSubview:statusBar];
52
53 [window makeFirstResponder:browser];
54 [window makeKeyAndOrderFront:window];
55
56 return self;
57}
58
59- (void)keyDown:(NSEvent *)event {
60 int keyCode = -1;
61
62 if ([event type] != NSKeyDown)
63 return;
64
65 /* vi keys */
66 if ([event modifierFlags] == 0 && [[event characters] isEqual: @"j"])
67 keyCode = kVK_DownArrow;
68 else if ([event modifierFlags] == 0 && [[event characters] isEqual: @"k"])
69 keyCode = kVK_UpArrow;
70 else if ([event modifierFlags] == 0 && [[event characters] isEqual: @"h"])
71 keyCode = kVK_LeftArrow;
72 else if ([event modifierFlags] == 0 && [[event characters] isEqual: @"l"])
73 keyCode = kVK_RightArrow;
74
75 if (keyCode > 0)
76 [NSApp postEvent:[NSEvent
77 keyEventWithType:NSKeyDown
78 location:[NSEvent mouseLocation]
79 modifierFlags:0
80 timestamp:0
81 windowNumber:[[NSApp mainWindow] windowNumber]
82 context:nil
83 characters:@""
84 charactersIgnoringModifiers:@""
85 isARepeat:NO
86 keyCode:keyCode] atStart:NO];
87}
88
89/* return key pressed on urlField */
90- (void)loadURLFromTextField
91{
92 [self loadURL:[urlField stringValue]];
93 [window makeFirstResponder:browser];
94}
95
96- (void)loadURL:(NSString *)url
97{
98 NSURL *u = [NSURL URLWithString:url];
99
100 if ([[u scheme] length] == 0)
101 u = [NSURL URLWithString:[NSString stringWithFormat:@"http://%@",
102 url]];
103
104 [wframe loadRequest:[NSURLRequest requestWithURL:u]];
105}
106
107- (void)setPosition:(NSArray *)aCoords
108{
109 int x = [[aCoords objectAtIndex:0] intValue];
110 int y = [[aCoords objectAtIndex:1] intValue];
111 int width = [[aCoords objectAtIndex:2] intValue];
112 int height = [[aCoords objectAtIndex:3] intValue];
113
114 /* convert normal coordinates into cocoa's upside-down
115 * 0,0-is-bottom-left */
116 NSRect coords = NSMakeRect(
117 x,
118 (int)screen_frame.size.height - (int)screen_frame.origin.y -
119 height - y,
120 width,
121 height
122 );
123
124 [window setFrame:coords display:true];
125
126 [urlField setFrame:NSMakeRect(0, height - 23, width, 23)];
127
128 /* browser's coordinates are relative to the window */
129 [browser setFrame:NSMakeRect(0, 17, width, height - 17 - 24)];
130
131 [statusBar setFrame:NSMakeRect(0, 0, width, 17)];
132
133 [window makeKeyAndOrderFront:window];
134}
135
136- (void)setShadow: (X11Window *)input
137{
138 [shadow autorelease];
139 shadow = [input retain];
140}
141
142- (void)setStatus:(NSString *)text
143{
144 [statusBar setStringValue:text];
145}
146
147- (void)setStatusToResourceCounts
148{
149 if (resourceCompletedCount + resourceFailedCount >= resourceCount)
150 [self setStatus:@""];
151 else
152 [self setStatus:[NSString
153 stringWithFormat:@"Loading \"%@\", completed %d of %d item%s",
154 [currentURL absoluteString], resourceCompletedCount,
155 resourceCount, (resourceCount == 1 ? "" : "s")]];
156}
157
158- (void)setTitle:(NSString *)text
159{
160 [shadow setWindowTitle:text];
161}
162
163/* these are needed because setting styleMask to NSBorderlessWindowMask turns
164 * them off */
165- (BOOL)canBecomeKeyWindow
166{
167 return YES;
168}
169
170- (BOOL)canBecomeMainWindow
171{
172 return YES;
173}
174
175
176/* WebFrameLoadDelegate glue */
177
178- (void)webView:(WebView *)sender didFailProvisionalLoadWithError:(NSError *)error forFrame:(WebFrame *)frame
179{
180 NSString *message = [NSString stringWithFormat:
181 @"<html>"
182 @"<title>Failed to open page</title>"
183 @"<body style=\"font-family: helvetica neue; font-size: 10pt;\">"
184 @"<h2>:(</h2>"
185 @"<p>Could not access the URL <strong>%@</strong></p>"
186 @"<p>%@</p>"
187 @"</body></html>",
188 [[[[[frame provisionalDataSource] request] URL] absoluteString]
189 stringByEncodingHTMLEntities],
190 [[error localizedDescription] stringByEncodingHTMLEntities]];
191
192 [frame loadAlternateHTMLString:message baseURL:nil
193 forUnreachableURL:[[[frame provisionalDataSource] request] URL]];
194
195 [self setStatus:@""];
196}
197
198- (void)webView:(WebView *)sender didFailLoadWithError:(NSError *)error forFrame:(WebFrame *)frame
199{
200 return [self webView:sender didFailProvisionalLoadWithError:error
201 forFrame:frame];
202}
203
204- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame
205{
206 if (frame != [sender mainFrame])
207 return;
208
209 [self setStatus:@""];
210}
211
212- (void)webView:(WebView *)sender didReceiveTitle:(NSString *)title forFrame:(WebFrame *)frame
213{
214 if (frame != [sender mainFrame])
215 return;
216
217 [self setTitle:title];
218}
219
220- (void)webView:(WebView *)sender didStartProvisionalLoadForFrame:(WebFrame *)frame
221{
222 if (frame != [sender mainFrame])
223 return;
224
225 currentURL = [[NSURL URLWithString:[[[[frame provisionalDataSource]
226 request] URL] absoluteString]] retain];
227
228 resourceCount = 0;
229 resourceCompletedCount = 0;
230 resourceFailedCount = 0;
231
232 [urlField setStringValue:[currentURL absoluteString]];
233 [self setStatus:[NSString stringWithFormat:@"Loading \"%@\"...",
234 [currentURL absoluteString]]];
235}
236
237
238/* WebResourceLoadDelegate glue */
239
240- (id)webView:(WebView *)sender identifierForInitialRequest:(NSURLRequest *)request fromDataSource:(WebDataSource *)dataSource
241{
242 return [NSNumber numberWithInt:resourceCount++];
243}
244
245- (NSURLRequest *)webView:(WebView *)sender resource:(id)identifier willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponsefromDataSource:(WebDataSource *)dataSource
246{
247 /* TODO: implement an ad blocker here? */
248 [self setStatusToResourceCounts];
249 return request;
250}
251
252- (void)webView:(WebView *)sender resource:(id)identifier didFailLoadingWithError:(NSError *)error fromDataSource:(WebDataSource *)dataSource
253{
254 resourceFailedCount++;
255 [self setStatusToResourceCounts];
256}
257
258- (void)webView:(WebView *)sender resource:(id)identifier didFinishLoadingFromDataSource:(WebDataSource *)dataSource
259{
260 resourceCompletedCount++;
261 [self setStatusToResourceCounts];
262}
263
264
265/* WebUIDelegate glue */
266
267/* for javascript alert() */
268- (void)webView:(WebView *)sender runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WebFrame *)frame
269{
270 sheetResponse = -1;
271
272 NSBeginInformationalAlertSheet(
273 [currentURL absoluteString],
274 nil,
275 nil,
276 nil,
277 [sender window],
278 self,
279 @selector(handleSheetResponse:returnCode:contextInfo:),
280 nil,
281 nil,
282 message);
283
284 /* block until the user responds */
285 while (sheetResponse == -1)
286 [NSApp sendEvent:[NSApp nextEventMatchingMask:NSAnyEventMask
287 untilDate:[NSDate distantFuture]
288 inMode:NSModalPanelRunLoopMode dequeue:YES]];
289}
290
291/* for javascript confirm() */
292- (BOOL)webView:(WebView *)sender runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WebFrame *)frame
293{
294 sheetResponse = -1;
295
296 NSBeginInformationalAlertSheet(
297 NSLocalizedString(@"JavaScript", @""),
298 NSLocalizedString(@"OK", @""),
299 NSLocalizedString(@"Cancel", @""),
300 nil,
301 [sender window],
302 self,
303 @selector(handleSheetResponse:returnCode:contextInfo:),
304 nil,
305 nil,
306 message);
307
308 /* block until the user responds */
309 while (sheetResponse == -1)
310 [NSApp sendEvent:[NSApp nextEventMatchingMask:NSAnyEventMask
311 untilDate:[NSDate distantFuture]
312 inMode:NSModalPanelRunLoopMode dequeue:YES]];
313
314 if (sheetResponse == NSAlertDefaultReturn)
315 return YES;
316 else
317 return NO;
318}
319
320- (void)handleSheetResponse:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
321{
322 sheetResponse = returnCode;
323}
324
325/* for javascript window.status */
326- (void)webView:(WebView *)sender setStatusText:(NSString *)text
327{
328 [self setStatus:text];
329}
330
331/* for javascript window.status */
332- (NSString *)webViewStatusText:(WebView *)sender
333{
334 return [statusBar stringValue];
335}
336
337@end