Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 42 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@ on:
branches: [main]

jobs:
macos_big_sur:
runs-on: macos-11
macos_sonoma:
# check https://github.com/actions/runner-images for available versions
runs-on: macos-14

strategy:
matrix:
xcode:
- "13.2" # Swift 5.5
# https://github.com/actions/runner-images/blob/main/images/macos/macos-14-Readme.md
- "15.4"

name: "macOS Big Sur (Xcode ${{ matrix.xcode }})"
name: "macOS 14 Sonoma ARM (Xcode ${{ matrix.xcode }})"

steps:
- name: Checkout
Expand All @@ -33,7 +35,41 @@ jobs:
run: |
make txz
- name: Upload artifact
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: allapps.tar.xz
name: macpin-allapps-arm64.tar.xz
path: ${{ steps.getpaths.outputs.TXZ_PATH }}

##macos_sonoma_x86:
## # check https://github.com/actions/runner-images for available versions
## runs-on: macos-14-large

## strategy:
## matrix:
## xcode:
## # https://github.com/actions/runner-images/blob/main/images/macos/macos-14-Readme.md
## - "15.4"

## name: "macOS 14 Sonoma x86 (Xcode ${{ matrix.xcode }})"

## steps:
## - name: Checkout
## uses: actions/checkout@v1
## - name: Get build paths
## id: getpaths
## run: |
## echo ::set-output name=OUT_PATH::$(make where-out 2>/dev/null | tail -n1)
## echo ::set-output name=TXZ_PATH::$(make where-txz 2>/dev/null | tail -n1)
## - name: Build All Apps
## run: |
## make allapps
## env:
## DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
## - name: Create Package
## run: |
## make txz
## - name: Upload artifact
## uses: actions/upload-artifact@v4
## with:
## name: macpin-allapps-x86.tar.xz
## path: ${{ steps.getpaths.outputs.TXZ_PATH }}
7 changes: 4 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export
#^ export all the variables
builddir ?= build

VERSION := 2022.0.0
VERSION := 2026.0.0

macpin := MacPin
macpin_sites ?= sites
Expand Down Expand Up @@ -219,6 +219,7 @@ $(outdir)/Frameworks/%.framework: $(jumbody) $(jumbody).dSYM $(outdir)/Framework
@cp -RL $(jumbody) $@/Versions/A/$*
@cp -RL $(jumbody).dSYM $@/Versions/A/$*.dSYM
# need a Resources/Info-macos.plist & version.plist
touch $@/Versions/A/Resources/stamp.file
-[ ! -n "$(codesign)" ] || codesign --verbose=4 --sign '$(appsig)' --timestamp --options runtime --force --deep --ignore-resources --strict --entitlements $(outdir)/$*.entitlements.plist $@

#build the app bundle
Expand All @@ -229,15 +230,15 @@ $(appdir)/%.app: $(macpin_sites)/% $(macpin_sites)/%/* $(appdir)/%.app/Contents/
COMMAND_MODE=legacy cp -fRL templates/Resources $@/Contents
#git ls-files -zc $(bundle_untracked) $(macpin_sites)/$* | xargs -0 -J % install -DT % $@/Contents/Resources/
(($(bundle_untracked))) || git archive HEAD $(macpin_sites)/$*/ | tar -xv --strip-components 2 -C $@/Contents/Resources
(($(bundle_untracked))) && COMMAND_MODE=legacy cp -fRL $(macpin_sites)/$*/* $@/Contents/Resources/ || true
(($(bundle_untracked))) && COMMAND_MODE=legacy cp -lfRL $(macpin_sites)/$*/* $@/Contents/Resources/ || true
[ ! -d $@/Contents/Resources/Library ] || ln -sfh Resources/Library $@/Contents/Library
plutil -replace NSHumanReadableCopyright -string "built $(shell date) by $(shell id -F)" $@/Contents/Info.plist >/dev/null
[ ! -f "$(macpin_sites)/$*/Makefile" ] || $(MAKE) -C $@/Contents/Resources
plutil -convert xml1 $(outdir)/$*.entitlements.plist
[ ! -n "$(codesign)" ] || codesign --verbose=4 --sign '$(appsig)' --timestamp --force --ignore-resources --entitlements $(outdir)/$*.entitlements.plist $@
-codesign --display -r- --verbose=4 --deep --entitlements :- $@
-spctl -vvvv --assess --type execute $@ # App Store-ability
[ ! -n "$(codesign)" ] || codesign --verbose=4 --deep --verify --strict $@
[ ! -n "$(codesign)" ] || codesign --verbose=4 --verify --strict $@
-[ ! -z "$(codesign)" ] || codesign --verbose=4 --remove-signature $@
@touch $@
#xattr -w com.apple.application-instance $(shell echo uuidgen) $@
Expand Down
25 changes: 19 additions & 6 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
// swift-tools-version:5.4
// swift-tools-version:5.7
import PackageDescription
import Foundation
let package = Package(
name: "MacPin",
//platforms: [.macOS(.v10_15)],
// whines about a lot of unguarded calls to 10.15.4 apis
platforms: [.macOS(.v11)],
platforms: [.macOS(.v13)],
products: [
.library(name: "MacPin", type: .dynamic, targets: ["MacPin"]),
.executable(name: "MacPin_static", targets: ["MacPin_static"]),
Expand Down Expand Up @@ -60,7 +58,10 @@ if let iosvar = ProcessInfo.processInfo.environment["MACPIN_IOS"], !iosvar.isEmp
dependencies: [
.product(name: "ArgumentParser", package: "swift-argument-parser"),
],
path: "Tools/iconify"
path: "Tools/iconify",
swiftSettings: [
//.swiftLanguageMode(.v5)
]
),
.target(name: "MacPin",
dependencies: [
Expand All @@ -71,17 +72,29 @@ if let iosvar = ProcessInfo.processInfo.environment["MACPIN_IOS"], !iosvar.isEmp
"Linenoise",
"UTIKit",
],
path: "Sources/MacPinOSX"
path: "Sources/MacPinOSX",
swiftSettings: [
//.swiftLanguageMode(.v5),
//.interoperabilityMode(.Cxx)
]
),
.executableTarget(
name: "MacPin_static",
dependencies: [
.target(name: "MacPin")
],
swiftSettings: [
//.swiftLanguageMode(.v5),
//.interoperabilityMode(.Cxx)
]
),
.executableTarget(
name: "MacPin_stub",
dependencies: [],
swiftSettings: [
//.swiftLanguageMode(.v5),
//.interoperabilityMode(.Cxx)
],
linkerSettings: [
.unsafeFlags(["-Xlinker", "-rpath", "-Xlinker", "@loader_path:@loader_path/../Frameworks"])
]
Expand Down
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ $ du -hs build/macosx-x86_64-apple-macosx10.13/apps/{Slack,MacPin}.app/
```

## Project Status
Uses swift 5.4 & WKWebView.
Uses swift 5.7 & WKWebView.
`sites/**/main.js` tries to support some Electron idioms.
* federation of the applet packaging using ES6 modules [is being explored](https://github.com/kfix/MacPin/issues/31)

Expand Down Expand Up @@ -74,8 +74,7 @@ Work is ongoing to make editing and creating app scripts easier, without requiri

## App porting issues

* DRM: Many sites (Spotify, Netflix) are using Chrome/FF only DRMs (Widevine) but Apple-built WebKit only supports FairPlay DRM.
* WebRTC: WebKit is compatible with [H264 & VP8 codecs](https://webkit.org/blog/8672/on-the-road-to-webrtc-1-0-including-vp8/), but Google Chrome is pushing hardware-unaccelerated VP9 on all fronts (incl. general `<video>`).
* DRM: Many content-streaming sites use ["Widevine" DRM](https://en.wikipedia.org/wiki/Widevine) but WebKit only support FairPlay DRM.

### sample main.js
```
Expand All @@ -96,7 +95,7 @@ app.on('AppFinishedLaunching', function() {
```

## Hacking MacPin
Building `main` branch requires macOS 11 "Big Sur" with Xcode 12.5+.
Building `main` branch requires [macOS 13 "Ventura" with Xcode 14+](https://developer.apple.com/xcode/system-requirements/).

All other branches are obsolete & archived for users locked on older macOS (hardware),
but they will recieve no updates.
Expand Down
102 changes: 66 additions & 36 deletions Sources/MacPin/WebView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@ extension WKWebView {
func wkclone(_ options: MPWebViewConfigOptions...) -> MPWebView {
return (self as! MPWebView).clone(options)
}

@nonobjc func gotoURL(_ url: NSURL) { gotoURL(url as URL) }
func gotoURL(_ url: URL) {
let act = ProcessInfo.processInfo.beginActivity(options: [ProcessInfo.ActivityOptions.userInitiated, ProcessInfo.ActivityOptions.idleSystemSleepDisabled], reason: "browsing begun")
if url.scheme == "file", #available(OSX 10.11, iOS 9.1, *) {
let readURL = url.deletingLastPathComponent()
warn("Bypassing CORS: \(readURL)")
loadFileURL(url as URL, allowingReadAccessTo: readURL)
} else {
load(URLRequest(url: url))
}
ProcessInfo.processInfo.endActivity(act)
}
}

enum MPWebViewConfigOptions {
Expand Down Expand Up @@ -269,7 +282,7 @@ extension MPWebViewConfigOptions: RawRepresentable, Hashable {
// https://github.com/WebKit/webkit/blob/master/Source/JavaScriptCore/API/JSWrapperMap.mm#L258
@objc(evalJS::) func evalJS(_ js: String, callback: JSValue?)
@objc(asyncEvalJS:::) func asyncEvalJS(_ js: String, delay: Int, callback: JSValue?)
func loadURL(_ urlstr: String) -> Bool
func load_url(_ urlstr: String) -> Bool
func loadIcon(_ icon: String) -> Bool
func popStyle(_ idx: Int)
func style(_ css: String) -> Bool
Expand All @@ -291,8 +304,7 @@ extension MPWebViewConfigOptions: RawRepresentable, Hashable {
// https://github.com/apple/swift-evolution/blob/master/proposals/0195-dynamic-member-lookup.md
// https://github.com/apple/swift-evolution/blob/master/proposals/0216-dynamic-callable.md

@objcMembers
class MPWebView: WKWebView, WebViewScriptExports {
final class MPWebView: WKWebView, WebViewScriptExports {

//@objc dynamic var isLoading: Bool
//@objc dynamic var estimatedProgress: Double
Expand Down Expand Up @@ -331,7 +343,7 @@ class MPWebView: WKWebView, WebViewScriptExports {

override var urlstr: String { // accessor for JSC, which doesn't support `new URL()`
get { return url?.absoluteString ?? "" }
set { loadURL(newValue) }
set { load_url(newValue) }
}

var userAgent: String {
Expand Down Expand Up @@ -407,7 +419,6 @@ class MPWebView: WKWebView, WebViewScriptExports {
var inspector: _WKInspector? {
get {
if WebKit_version >= (607, 1, 3) {
// https://github.com/WebKit/webkit/commit/1f7d382aa1e6fc67476787ce6a7b9a791b32f434
return _inspector
} else {
return nil
Expand All @@ -427,7 +438,6 @@ class MPWebView: WKWebView, WebViewScriptExports {
}
}


var inspectorAttachmentView: NSView? {
// when inspector is open, subviews.first is actually the inspector (WKWebInspectorWKWebView), not the WKView
// tabitem.view.subviews = [ WKWebInspectorView, WKView, ... ]
Expand Down Expand Up @@ -563,7 +573,7 @@ class MPWebView: WKWebView, WebViewScriptExports {
}
}
}
let prefs = WKPreferences() // http://trac.webkit.org/browser/trunk/Source/WebKit2/UIProcess/API/Cocoa/WKPreferences.mm
let prefs = WKPreferences() // https://github.com/WebKit/WebKit/blob/main/Source/WebKit/UIProcess/API/Cocoa/WKPreferences.mm
#if os(OSX)
prefs._developerExtrasEnabled = true // Enable "Inspect Element" in context menu
prefs._fullScreenEnabled = true
Expand Down Expand Up @@ -595,6 +605,14 @@ class MPWebView: WKWebView, WebViewScriptExports {
if !caching, #available(macOS 10.13.4, *) {
prefs._usesPageCache = false
}

// https://github.com/home-assistant/iOS/issues/4044
// https://github.com/ahalekelly/iOS/commit/3712028
for feat in WKPreferences._features() {
if feat.key == "ScreenTimeEnabled" {
prefs._setEnabled(false, for: feat)
}
}
#endif

if privacy {
Expand All @@ -616,19 +634,41 @@ class MPWebView: WKWebView, WebViewScriptExports {
configuration.suppressesIncrementalRendering = false

var dataStore = privacy ? WKWebsiteDataStore.nonPersistent() : WKWebsiteDataStore.default()

if #available(macOS 10.14.4, iOS 12.2, *) {
let dataStoreConf = privacy ? _WKWebsiteDataStoreConfiguration(nonPersistentConfiguration: ()) : _WKWebsiteDataStoreConfiguration()

if let proxyURL = URL(string: proxy), !proxy.isEmpty {
dataStoreConf.httpProxy = proxyURL
warn("HTTP proxy: \(dataStoreConf.httpProxy?.absoluteString)")
// macOS14: https://webkit.org/blog/14423/building-profiles-with-new-webkit-api/
// if isolated: WKWebsiteDataStore(forIdentifier: <UUID-or-string>)

// configure proxy settings, which is oddly-slaved to the dataStore's config
if #available(macOS 14.0, *) {
// https://developer.apple.com/documentation/network/proxyconfiguration
// seems like direct-to "plain" proxies are no longer on the menu

if let proxyURL = URL(string: proxy), !proxy.isEmpty,
let host = proxyURL.host, let port = proxyURL.port, let uport = UInt16(exactly: port) {
let endpoint = NWEndpoint.hostPort(
host: NWEndpoint.Host(host),
port: NWEndpoint.Port(integerLiteral: uport)
)
let proxyConf = ProxyConfiguration(
httpCONNECTProxy: endpoint,
tlsOptions: nil // FIXME: check port == 443 || scheme == https
)
warn("HTTP (CONNECT) proxy: \(endpoint)")
dataStore.proxyConfigurations.append(proxyConf)
}
if let sproxyURL = URL(string: sproxy), !sproxy.isEmpty {
dataStoreConf.httpsProxy = sproxyURL
warn("HTTPS proxy: \(dataStoreConf.httpsProxy?.absoluteString)")

if let sproxyURL = URL(string: sproxy), !sproxy.isEmpty,
let host = sproxyURL.host, let port = sproxyURL.port, let uport = UInt16(exactly: port) {
let endpoint = NWEndpoint.hostPort(
host: NWEndpoint.Host(host),
port: NWEndpoint.Port(integerLiteral: uport)
)
let proxyConf = ProxyConfiguration(
httpCONNECTProxy: endpoint,
tlsOptions: nil // FIXME: check port == 443 || scheme == https
)
warn("HTTPS (CONNECT) proxy: \(endpoint)")
dataStore.proxyConfigurations.append(proxyConf)
}
dataStore = dataStore._init(with: dataStoreConf)
}

//if #available(OSX 10.11, iOS 9, *) {
Expand Down Expand Up @@ -671,7 +711,6 @@ class MPWebView: WKWebView, WebViewScriptExports {
#elseif os(iOS)
_applicationNameForUserAgent = "Version/10 Mobile/12F70 Safari/\(WebKit_version.major).\(WebKit_version.minor).\(WebKit_version.tiny)"
#endif

var url: URL? = nil

for option in config.options {
Expand Down Expand Up @@ -739,7 +778,6 @@ class MPWebView: WKWebView, WebViewScriptExports {
}

self.notifier = WebNotifier(self)

for originStr in authorizedOriginsForNotifications {
self.notifier?.authorizeNotifications(fromOrigin: URL(string: originStr))
}
Expand Down Expand Up @@ -898,27 +936,14 @@ class MPWebView: WKWebView, WebViewScriptExports {
}
}

@nonobjc func gotoURL(_ url: NSURL) { gotoURL(url as URL) }
func gotoURL(_ url: URL) {
let act = ProcessInfo.processInfo.beginActivity(options: [ProcessInfo.ActivityOptions.userInitiated, ProcessInfo.ActivityOptions.idleSystemSleepDisabled], reason: "browsing begun")
if url.scheme == "file", #available(OSX 10.11, iOS 9.1, *) {
let readURL = url.deletingLastPathComponent()
warn("Bypassing CORS: \(readURL)")
loadFileURL(url as URL, allowingReadAccessTo: readURL)
} else {
load(URLRequest(url: url))
}
ProcessInfo.processInfo.endActivity(act)
}

@discardableResult
func loadURL(_ urlstr: String) -> Bool {
func load_url(_ urlstr: String) -> Bool {
if let url = URL(string: urlstr) {
gotoURL(url)
return true
}
warn("ILLEGAL URL! \(url)") // getting SIGILLd by incomplete URLs:
// $.browser.tabs[0].loadURL("www.example.com")
// $.browser.tabs[0].load_url("www.example.com")
return false // tell JS we were given a malformed URL
}

Expand Down Expand Up @@ -1182,6 +1207,11 @@ class MPWebView: WKWebView, WebViewScriptExports {
// https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSView_Class/index.html#//apple_ref/occ/instm/NSView/beginDraggingSessionWithItems:event:source:
}
*/
//override func _updateScreenTimeBasedOnWindowVisibility() {
// // https://github.com/WebKit/WebKit/blob/101392f63ca9966babdf9757a1a85793f2b38073/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm#L504
// // this is constantly being called .... *sigh**
// super._updateScreenTimeBasedOnWindowVisibility()
//}

//func dumpImage() -> NSImage { return NSImage(data: view.dataWithPDFInsideRect(view.bounds)) }
//var snapshotURL: String { get { NSImage(data: thumbnail.dataWithPDFInsideRect(thumbnail.bounds)) } } //data::
Expand Down Expand Up @@ -1327,7 +1357,7 @@ extension MPWebView { // NSTextFinderClient
warn(obj: scrollBox)
//_setFrame(frame, andScrollBy: scrollBox) // WebKit availability?
//scrollRangeToVisible(range)
}
}

// https://developer.apple.com/documentation/appkit/nstextfinderclient/1526989-scrollrangetovisible
@objc override public func scrollRangeToVisible(_ range: NSRange) {
Expand Down
Loading
Loading