#import <Cocoa/Cocoa.h>
#import <WebKit/WebKit.h>
#include "tether.h"
struct _tether {
CFTypeRef window;
CFTypeRef webview;
};
@interface RKWindow : NSWindow
@end
@implementation RKWindow
- (BOOL)canBecomeKeyWindow { return YES; }
- (BOOL)canBecomeMainWindow { return YES; }
@end
@interface RKDelegate : NSObject <NSWindowDelegate, WKScriptMessageHandler>
@end
@implementation RKDelegate {
tether_options opts;
tether handle;
}
- (id)initWithOptions:(tether_options)x
tether:(tether)y {
opts = x;
handle = y;
return self;
}
- (void)userContentController:(WKUserContentController *)userContentController
didReceiveScriptMessage:(WKScriptMessage *)scriptMessage {
(void)userContentController;
if (!handle) return;
id body = [scriptMessage body];
if (![body isKindOfClass:[NSString class]]) return;
opts.message(opts.data, [body UTF8String]);
}
- (void)windowWillClose:(NSNotification *)notification {
(void)notification;
if (!handle) return;
tether x = handle;
handle = NULL;
WKWebView *wv = (__bridge WKWebView *)x->webview;
WKUserContentController *ucc = [[wv configuration] userContentController];
[ucc removeScriptMessageHandlerForName:@"__tether"];
opts.closed(opts.data);
free(x);
}
@end
void tether_start(void (*func)(void)) {
NSApplication *app = [NSApplication sharedApplication];
[app setActivationPolicy:NSApplicationActivationPolicyRegular];
NSProcessInfo *process = [NSProcessInfo processInfo];
NSString *appname = [process processName];
NSMenu *menu = [[NSMenu alloc] initWithTitle:@"MainMenu"],
*submenu;
NSMenuItem *item_app = [menu addItemWithTitle:appname action:nil keyEquivalent:@""],
*item_edit = [menu addItemWithTitle:@"Edit" action:nil keyEquivalent:@""],
*item_window = [menu addItemWithTitle:@"Window" action:nil keyEquivalent:@""],
*item;
submenu = [[NSMenu alloc] initWithTitle:appname];
item = [submenu addItemWithTitle:[@"Hide " stringByAppendingString:appname]
action:@selector(hide:)
keyEquivalent:@"h"];
item = [submenu addItemWithTitle:@"Hide Others"
action:@selector(hideOtherApplications:)
keyEquivalent:@"h"];
[item setKeyEquivalentModifierMask:NSEventModifierFlagCommand | NSEventModifierFlagOption];
item = [submenu addItemWithTitle:@"Show All"
action:@selector(unhideAllApplications:)
keyEquivalent:@""];
[submenu addItem:[NSMenuItem separatorItem]];
item = [submenu addItemWithTitle:[@"Quit " stringByAppendingString:appname]
action:@selector(terminate:)
keyEquivalent:@"q"];
[menu setSubmenu:submenu forItem:item_app];
submenu = [[NSMenu alloc] initWithTitle:@"Edit"];
item = [submenu addItemWithTitle:@"Undo"
action:@selector(undo:)
keyEquivalent:@"z"];
item = [submenu addItemWithTitle:@"Redo"
action:@selector(undo:)
keyEquivalent:@"z"];
[item setKeyEquivalentModifierMask:NSEventModifierFlagCommand | NSEventModifierFlagShift];
[submenu addItem:[NSMenuItem separatorItem]];
item = [submenu addItemWithTitle:@"Cut"
action:@selector(cut:)
keyEquivalent:@"x"];
item = [submenu addItemWithTitle:@"Copy"
action:@selector(copy:)
keyEquivalent:@"c"];
item = [submenu addItemWithTitle:@"Paste"
action:@selector(paste:)
keyEquivalent:@"v"];
item = [submenu addItemWithTitle:@"Delete"
action:@selector(delete:)
keyEquivalent:@""];
item = [submenu addItemWithTitle:@"Select All"
action:@selector(selectAll:)
keyEquivalent:@"a"];
[menu setSubmenu:submenu forItem:item_edit];
submenu = [[NSMenu alloc] initWithTitle:@"Window"];
item = [submenu addItemWithTitle:@"Close"
action:@selector(close)
keyEquivalent:@"w"];
item = [submenu addItemWithTitle:@"Minimize"
action:@selector(miniaturize:)
keyEquivalent:@"m"];
item = [submenu addItemWithTitle:@"Toggle Full Screen"
action:@selector(toggleFullScreen:)
keyEquivalent:@"f"];
[item setKeyEquivalentModifierMask:NSEventModifierFlagCommand | NSEventModifierFlagControl];
[menu setSubmenu:submenu forItem:item_window];
[app setWindowsMenu:submenu];
[app setMainMenu:menu];
func();
[app activateIgnoringOtherApps:YES];
[app run];
}
void tether_dispatch(void *data, void (*func)(void *data)) {
dispatch_async(dispatch_get_main_queue(), ^{ func(data); });
}
void tether_exit(void) {
[NSApp terminate:nil];
}
tether tether_new(tether_options opts) {
tether self = malloc(sizeof *self);
assert(self);
NSRect initial_size = NSMakeRect(0, 0, opts.initial_width, opts.initial_height);
NSWindowStyleMask style = 0;
if (!opts.borderless) style |= NSWindowStyleMaskTitled
| NSWindowStyleMaskClosable
| NSWindowStyleMaskMiniaturizable
| NSWindowStyleMaskResizable;
NSWindow *window =
[[RKWindow alloc] initWithContentRect:initial_size
styleMask:style
backing:NSBackingStoreBuffered
defer:NO];
[window setContentMinSize:NSMakeSize(opts.minimum_width, opts.minimum_height)];
[window setReleasedWhenClosed:NO];
[window center];
WKWebViewConfiguration *config = [WKWebViewConfiguration new];
WKPreferences *prefs = [config preferences];
WKUserContentController *manager = [config userContentController];
[prefs setJavaScriptCanOpenWindowsAutomatically:NO];
if (opts.debug) [prefs setValue:@YES forKey:@"developerExtrasEnabled"];
WKWebView *webview = [[WKWebView alloc] initWithFrame:NSZeroRect configuration:config];
RKDelegate *delegate = [[RKDelegate alloc] initWithOptions:opts tether:self];
[manager addScriptMessageHandler:delegate name:@"__tether"];
[manager addUserScript:[[WKUserScript alloc] initWithSource:@"window.tether = function (s) { window.webkit.messageHandlers.__tether.postMessage(s); };"
injectionTime:WKUserScriptInjectionTimeAtDocumentStart
forMainFrameOnly:YES]];
if (!opts.debug) {
[manager addUserScript:[[WKUserScript alloc] initWithSource:@"document.addEventListener('contextmenu', function (e) { e.preventDefault(); return false; });"
injectionTime:WKUserScriptInjectionTimeAtDocumentStart
forMainFrameOnly:NO]];
}
[window setDelegate:delegate];
[window setContentView:webview];
[window makeKeyAndOrderFront:nil];
self->window = (__bridge void *)window;
self->webview = (__bridge void *)webview;
return self;
}
void tether_eval(tether self, const char *js) {
WKWebView *wv = (__bridge WKWebView *)self->webview;
[wv evaluateJavaScript:[NSString stringWithUTF8String:js] completionHandler:nil];
}
void tether_load(tether self, const char *html) {
WKWebView *wv = (__bridge WKWebView *)self->webview;
[wv loadHTMLString:[NSString stringWithUTF8String:html] baseURL:nil];
}
void tether_title(tether self, const char *title) {
NSWindow *w = (__bridge NSWindow *)self->window;
[w setTitle:[NSString stringWithUTF8String:title]];
}
void tether_focus(tether self) {
NSWindow *w = (__bridge NSWindow *)self->window;
[w makeKeyAndOrderFront:nil];
}
void tether_close(tether self) {
NSWindow *w = (__bridge NSWindow *)self->window;
[w close];
}