an experiment in making a cocoa webkit browser manageable under X11
at master 337 lines 8.8 kB view raw
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