mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-06 04:04:21 +02:00
Merge main into family-comfort; a session state without 'started' can't crash status
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
1203ec0a9e
180 files changed
+62069
-234
No files matched your search
@@ -250,7 +250,7 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "mkdir -p \"${DERIVED_FILE_DIR}\"\npython3 \"${SRCROOT}/scripts/make_frame_bundle.py\" \"${DERIVED_FILE_DIR}/frame-bundle.tar.gz\" > \"${DERIVED_FILE_DIR}/frame-bundle.version\"\nmkdir -p \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}\"\ncp \"${DERIVED_FILE_DIR}/frame-bundle.tar.gz\" \"${DERIVED_FILE_DIR}/frame-bundle.version\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/\"\n";
|
||||
shellScript = "set -e\nmkdir -p \"${DERIVED_FILE_DIR}\"\n# Written aside and moved into place together, so a failed download can't leave a stale pair.\npython3 \"${SRCROOT}/scripts/make_frame_bundle.py\" \"${DERIVED_FILE_DIR}/frame-bundle.new.tar.gz\" > \"${DERIVED_FILE_DIR}/frame-bundle.new.version\"\nmv \"${DERIVED_FILE_DIR}/frame-bundle.new.tar.gz\" \"${DERIVED_FILE_DIR}/frame-bundle.tar.gz\"\nmv \"${DERIVED_FILE_DIR}/frame-bundle.new.version\" \"${DERIVED_FILE_DIR}/frame-bundle.version\"\nmkdir -p \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}\"\ncp \"${DERIVED_FILE_DIR}/frame-bundle.tar.gz\" \"${DERIVED_FILE_DIR}/frame-bundle.version\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/\"\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Citadel
|
||||
import CryptoKit
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
@@ -33,6 +34,11 @@ final class AppModel: ObservableObject {
|
||||
}
|
||||
|
||||
var deviceName: String { UIDevice.current.userInterfaceIdiom == .pad ? "iPad" : "iPhone" }
|
||||
/// Stable per install: a hash of this device's SSH key, which is made once and kept in the Keychain.
|
||||
var clientID: String {
|
||||
let digest = SHA256.hash(data: Data(authorizedKeysLine.utf8))
|
||||
return deviceName.lowercased() + "-" + digest.prefix(6).map { String(format: "%02x", $0) }.joined()
|
||||
}
|
||||
private var hostKey: String? { UserDefaults.standard.string(forKey: Self.hostKeyKey) }
|
||||
|
||||
// MARK: pairing
|
||||
@@ -167,7 +173,7 @@ final class AppModel: ObservableObject {
|
||||
guard current() else { throw CancellationError() }
|
||||
step("Starting Frame Control on the headset")
|
||||
let key = Self.randomKey()
|
||||
let server = try await HeadsetServer.start(in: dir, over: l, key: key, device: deviceName)
|
||||
let server = try await HeadsetServer.start(in: dir, over: l, key: key, device: deviceName, client: clientID)
|
||||
guard current() else { throw CancellationError() }
|
||||
let f = try await PortForwarder.start(over: l, to: server.port)
|
||||
forwarder = f
|
||||
|
||||
@@ -80,8 +80,10 @@ final class HeadsetServer: @unchecked Sendable {
|
||||
}
|
||||
|
||||
/// Starts the server in dir and waits for it to say which port it took.
|
||||
static func start(in dir: String, over link: FrameLink, key: String, device: String) async throws -> HeadsetServer {
|
||||
/// client is a stable id for this install (keyboard-and-trackpad pairing is kept per client).
|
||||
static func start(in dir: String, over link: FrameLink, key: String, device: String, client: String) async throws -> HeadsetServer {
|
||||
let command = "cd \(dir) && FRAME_LOCAL=1 FRAME_UI_KEY=\(key) FRAME_DEVICE=\(shellQuote(device)) "
|
||||
+ "FRAME_CLIENT=\(shellQuote(client)) "
|
||||
+ "exec python3 -I -u -B \"$PWD/ui/server.py\" --port 0 --exit-on-eof 2>&1"
|
||||
let stream = try await link.client.executeCommandStream(command)
|
||||
let box = PortWaiter()
|
||||
|
||||
+5
-1
@@ -52,8 +52,12 @@ targets:
|
||||
- name: Pack the Frame bundle
|
||||
# The server, headset helpers and catalogue, as the app copies them to the Frame.
|
||||
script: |
|
||||
set -e
|
||||
mkdir -p "${DERIVED_FILE_DIR}"
|
||||
python3 "${SRCROOT}/scripts/make_frame_bundle.py" "${DERIVED_FILE_DIR}/frame-bundle.tar.gz" > "${DERIVED_FILE_DIR}/frame-bundle.version"
|
||||
# Written aside and moved into place together, so a failed download can't leave a stale pair.
|
||||
python3 "${SRCROOT}/scripts/make_frame_bundle.py" "${DERIVED_FILE_DIR}/frame-bundle.new.tar.gz" > "${DERIVED_FILE_DIR}/frame-bundle.new.version"
|
||||
mv "${DERIVED_FILE_DIR}/frame-bundle.new.tar.gz" "${DERIVED_FILE_DIR}/frame-bundle.tar.gz"
|
||||
mv "${DERIVED_FILE_DIR}/frame-bundle.new.version" "${DERIVED_FILE_DIR}/frame-bundle.version"
|
||||
mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
|
||||
cp "${DERIVED_FILE_DIR}/frame-bundle.tar.gz" "${DERIVED_FILE_DIR}/frame-bundle.version" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/"
|
||||
basedOnDependencyAnalysis: false
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Pack what Frame Control's server needs to run on the Frame itself (the files the
|
||||
desktop app ships, plus ui/local-bin) into one reproducible .tar.gz.
|
||||
desktop app ships, plus ui/local-bin) into one reproducible .tar.gz. That includes
|
||||
KDE Connect for the keyboard and trackpad (frame/kdeconnect), downloaded first and
|
||||
checked against its pinned SHA-256s.
|
||||
|
||||
The iPhone app copies it to ~/.cache/frame-control/<version> on the Frame and
|
||||
starts ui/server.py there. <version> is the SHA-256 of the archive, so a new
|
||||
@@ -17,7 +19,9 @@ from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
PATTERNS = ["ui/*.py", "ui/*.html", "ui/local-bin/*", "scripts/*.sh", "frame/android/*.sh", "frame/android/*.py",
|
||||
"frame/devkit-utils/**/*", "apk-catalog/*.py", "apk-catalog/pins.json", "apk-catalog/site/apps.js"]
|
||||
"frame/devkit-utils/**/*", "apk-catalog/*.py", "apk-catalog/pins.json", "apk-catalog/site/apps.js",
|
||||
"frame/kdeconnect/packages.json", "frame/kdeconnect/NOTICE.md", "frame/kdeconnect/LICENSES/*/*",
|
||||
"frame/kdeconnect/packages/*.pkg.tar.zst", "LICENSE", "THIRD_PARTY_NOTICES.md"]
|
||||
|
||||
|
||||
def files():
|
||||
@@ -47,6 +51,9 @@ def build():
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 2:
|
||||
sys.exit(__doc__)
|
||||
sys.path.insert(0, str(ROOT / "frame" / "kdeconnect"))
|
||||
import fetch
|
||||
fetch.fetch()
|
||||
data = build()
|
||||
Path(sys.argv[1]).write_bytes(data)
|
||||
print(hashlib.sha256(data).hexdigest()[:16])
|
||||
Reference in new issue
Block a user