File size: 6,316 Bytes
df41178
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
// kernel_window.swift — カーネルの窓(WKWebView)。/Applications/カーネル.app/Contents/MacOS/kernel-window の元。
// 作り方: swiftc -O -o kernel-window kernel_window.swift -framework Cocoa -framework WebKit
// 10/3: 前の窓は元の文が残っておらず、メニューが無かった。macOS は「編集」メニューが無いと
//   ⌘C・⌘V・⌘X・⌘A が WKWebView に届かず、コピー・貼り付けができなかった(本人)。メニューを付けて作り直した。
import Cocoa
import WebKit

final class Mado: NSObject, NSApplicationDelegate, NSWindowDelegate, WKNavigationDelegate, WKUIDelegate {
    let url: URL
    var window: NSWindow!
    var web: WKWebView!

    init(url: URL) { self.url = url }

    func applicationDidFinishLaunching(_ notification: Notification) {
        NSApp.mainMenu = menu()
        let config = WKWebViewConfiguration()
        config.preferences.setValue(true, forKey: "developerExtrasEnabled")
        web = WKWebView(frame: .zero, configuration: config)
        web.navigationDelegate = self
        web.uiDelegate = self
        window = NSWindow(contentRect: NSRect(x: 0, y: 0, width: 1180, height: 820),
                          styleMask: [.titled, .closable, .miniaturizable, .resizable],
                          backing: .buffered, defer: false)
        window.title = "カーネル"
        window.contentView = web
        window.delegate = self
        window.center()
        window.setFrameAutosaveName("kernel-window")
        window.makeKeyAndOrderFront(nil)
        NSApp.activate(ignoringOtherApps: true)
        web.load(URLRequest(url: url))
    }

    // ── メニュー(編集が無いと ⌘C・⌘V が効かない)──
    func menu() -> NSMenu {
        let main = NSMenu()
        func add(_ title: String, _ items: [(String, Selector?, String)]) {
            let top = NSMenuItem()
            let sub = NSMenu(title: title)
            for (name, action, key) in items {
                if name == "-" { sub.addItem(.separator()); continue }
                sub.addItem(NSMenuItem(title: name, action: action, keyEquivalent: key))
            }
            top.submenu = sub
            main.addItem(top)
        }
        add("カーネル", [("カーネルを隠す", #selector(NSApplication.hide(_:)), "h"), ("-", nil, ""),
                       ("カーネルを終了", #selector(NSApplication.terminate(_:)), "q")])
        add("編集", [("取り消す", Selector(("undo:")), "z"), ("やり直す", Selector(("redo:")), "Z"), ("-", nil, ""),
                    ("切り取り", #selector(NSText.cut(_:)), "x"), ("コピー", #selector(NSText.copy(_:)), "c"),
                    ("貼り付け", #selector(NSText.paste(_:)), "v"), ("すべてを選択", #selector(NSText.selectAll(_:)), "a")])
        add("表示", [("読み込み直す", #selector(reload), "r")])
        add("ウインドウ", [("しまう", #selector(NSWindow.performMiniaturize(_:)), "m"),
                         ("閉じる", #selector(NSWindow.performClose(_:)), "w")])
        return main
    }

    @objc func reload() { web.reload() }

    // ── 閉じたら終わる。Dock を押したら前に出す ──
    func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { true }

    func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
        window.makeKeyAndOrderFront(nil)
        return true
    }

    // ── うちのサーバー以外へ行くリンクは、ふだんのブラウザで開く ──
    func webView(_ webView: WKWebView, decidePolicyFor action: WKNavigationAction,
                 decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
        if let target = action.request.url, action.navigationType == .linkActivated, target.host != url.host {
            NSWorkspace.shared.open(target)
            decisionHandler(.cancel)
            return
        }
        decisionHandler(.allow)
    }

    func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration,
                 for action: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
        if let target = action.request.url { NSWorkspace.shared.open(target) }   // target="_blank"
        return nil
    }

    // ── つながらない時は、何が起きたかを窓に出す(黙って白い窓にしない)──
    func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
        FileHandle.standardError.write("kernel-window: 読み込み失敗: \(error.localizedDescription)\n".data(using: .utf8)!)
        webView.loadHTMLString("""
            <meta charset="utf-8"><body style="font-family:-apple-system;padding:2em">
            <h2>うちうちのサーバーにつながりませんでした</h2>
            <p>記録: <code>~/Library/Logs/kernel-ai.log</code></p>
            <p>いったん閉じて、もう一度アイコンを押してみてください。</p></body>
            """, baseURL: nil)
    }

    // ── confirm()・alert() も出す(前の窓では黙って false になっていた)──
    func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String,
                 initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
        let alert = NSAlert()
        alert.messageText = message
        alert.runModal()
        completionHandler()
    }

    func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String,
                 initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
        let alert = NSAlert()
        alert.messageText = message
        alert.addButton(withTitle: "OK")
        alert.addButton(withTitle: "やめる")
        completionHandler(alert.runModal() == .alertFirstButtonReturn)
    }
}

guard CommandLine.arguments.count >= 2, let url = URL(string: CommandLine.arguments[1]) else {
    FileHandle.standardError.write("使い方: kernel-window <URL>\n".data(using: .utf8)!)
    exit(2)
}
let app = NSApplication.shared
app.setActivationPolicy(.regular)
let mado = Mado(url: url)
app.delegate = mado
app.run()