mirror of
https://github.com/dredx/prole.git
synced 2026-09-24 19:24:32 +00:00
- Replaced all IRCKit (0.16) usage with NozeIO `swift-nio-irc-client` (SwiftPM). - Enabled IRC window with new client: connects and joins `#prole` (plain TCP for now; SSL checkbox displays a notice). - Enabled RoyalVNC and removed all optional/flag-based handling. Integrated RoyalVNC via SwiftPM (`royalvnc` on `main`). - Updated VNC UI to current RoyalVNC API: `VNCConnection` + `VNCCAFramebufferView` with a strong delegate reference. - Simplified build to use SwiftPM for both IRC and RoyalVNC. - Fixed runtime embedding and loader issues: - Copy SwiftPM-built `.dylib` products (e.g., `libRoyalVNCKit.dylib`) into `Contents/Frameworks` and codesign them. - Added `@executable_path/../Frameworks` to app rpaths with `install_name_tool`. - Removed manual RoyalVNCKit `.xcframework` building/embedding and any IRCKit traces. #### Key changes - `prole-app/build.sh`: - Build app via SwiftPM; removed IRCKit and manual RoyalVNC build logic. - Embed SwiftPM `.dylib` outputs into `Contents/Frameworks` and set app rpath. - Cleaned usage text; dependencies now handled by SwiftPM. - `prole-app/Package.swift`: - Add `swift-nio-irc-client` (NozeIO) dependency. - Add RoyalVNC via SwiftPM: `https://github.com/royalapplications/royalvnc` on `main`. - `prole-app/Sources/IRCWindowController.swift`: - Migrate to NozeIO `IRC` package API; re-enable Connect; join `#prole`. - Import AppKit; provide convenience init for transcript view. - `prole-app/Sources/WorkstationWindowController.swift`: - Import `RoyalVNCKit`; use `VNCConnection` + `VNCCAFramebufferView`. - Implement `VNCConnectionDelegate` and keep a strong reference to the delegate. - `prole-app/debug.sh`: - Removed IRCKit logs section; kept RoyalVNC logs earlier; then removed manual RoyalVNC altogether. This build now launches successfully (no dyld errors), opens the Workstation window (RoyalVNC), and the IRC window connects via the new client. ### Suggested follow-ups - If TLS is required for IRC, add `NIOSSL` integration and wire SSL checkbox to TLS connection. - Optionally remove leftover Vendor references if any local cache remains.
151 lines
6.2 KiB
Swift
151 lines
6.2 KiB
Swift
import AppKit
|
|
import RoyalVNCKit
|
|
|
|
// A secondary window that hosts the Prole Workstation (VNC viewer)
|
|
// Version 1: viewer-only (no interactive session wiring yet)
|
|
final class WorkstationWindowController: NSWindowController {
|
|
private let viewer = VNCViewerHostView()
|
|
|
|
init() {
|
|
let style: NSWindow.StyleMask = [.titled, .closable, .miniaturizable, .resizable]
|
|
// Reasonable default for a VNC desktop view
|
|
let initialRect = NSRect(x: 0, y: 0, width: 1024, height: 768)
|
|
let window = NSWindow(contentRect: initialRect, styleMask: style, backing: .buffered, defer: false)
|
|
super.init(window: window)
|
|
|
|
window.isReleasedWhenClosed = false
|
|
window.title = "Prole Workstation"
|
|
window.center()
|
|
window.level = .normal
|
|
window.collectionBehavior = [.canJoinAllSpaces]
|
|
window.appearance = NSAppearance(named: .aqua)
|
|
|
|
let content = NSView()
|
|
content.translatesAutoresizingMaskIntoConstraints = false
|
|
window.contentView = content
|
|
|
|
viewer.translatesAutoresizingMaskIntoConstraints = false
|
|
content.addSubview(viewer)
|
|
NSLayoutConstraint.activate([
|
|
viewer.topAnchor.constraint(equalTo: content.topAnchor),
|
|
viewer.leadingAnchor.constraint(equalTo: content.leadingAnchor),
|
|
viewer.trailingAnchor.constraint(equalTo: content.trailingAnchor),
|
|
viewer.bottomAnchor.constraint(equalTo: content.bottomAnchor)
|
|
])
|
|
|
|
// Configure default connection (localhost:5901)
|
|
viewer.connectDefault()
|
|
}
|
|
|
|
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
|
|
|
func show() {
|
|
guard let window = window else { return }
|
|
window.makeKeyAndOrderFront(nil)
|
|
NSApp.activate(ignoringOtherApps: false)
|
|
}
|
|
|
|
func hide() { window?.orderOut(nil) }
|
|
var isVisible: Bool { window?.isVisible ?? false }
|
|
|
|
// Position this window to the right of a reference window with a small gap.
|
|
func position(rightOf refWindow: NSWindow, gap: CGFloat = 12) {
|
|
guard let this = window else { return }
|
|
let refFrame = refWindow.frame
|
|
var newFrame = this.frame
|
|
newFrame.origin.x = refFrame.maxX + gap
|
|
// Align tops if possible, else keep current y
|
|
newFrame.origin.y = refFrame.origin.y + (refFrame.height - newFrame.height)
|
|
this.setFrame(newFrame, display: true, animate: false)
|
|
}
|
|
}
|
|
|
|
// Host view that embeds RoyalVNCKit viewer.
|
|
final class VNCViewerHostView: NSView {
|
|
private var framebufferView: VNCCAFramebufferView?
|
|
private var connection: VNCConnection?
|
|
// Keep a strong reference to the delegate; VNCConnection holds it weakly
|
|
private var connectionDelegateStrongRef: VNCConnectionDelegate?
|
|
|
|
override init(frame frameRect: NSRect) {
|
|
super.init(frame: frameRect)
|
|
wantsLayer = true
|
|
layer?.backgroundColor = NSColor.windowBackgroundColor.cgColor
|
|
// The actual framebuffer view will be created when the connection creates the framebuffer
|
|
}
|
|
|
|
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
|
|
|
func connectDefault() {
|
|
connect(hostname: "localhost", port: 5901)
|
|
}
|
|
|
|
func connect(hostname: String, port: Int) {
|
|
// Construct full settings according to current RoyalVNCKit API
|
|
let settings = VNCConnection.Settings(
|
|
isDebugLoggingEnabled: false,
|
|
hostname: hostname,
|
|
port: UInt16(port),
|
|
isShared: true,
|
|
isScalingEnabled: true,
|
|
useDisplayLink: true,
|
|
inputMode: .forwardKeyboardShortcutsIfNotInUseLocally,
|
|
isClipboardRedirectionEnabled: true,
|
|
colorDepth: .depth24Bit,
|
|
frameEncodings: .default
|
|
)
|
|
|
|
let conn = VNCConnection(settings: settings)
|
|
self.connection = conn
|
|
|
|
// Minimal delegate to attach framebuffer to a view
|
|
class Delegate: VNCConnectionDelegate {
|
|
weak var host: VNCViewerHostView?
|
|
init(host: VNCViewerHostView) { self.host = host }
|
|
|
|
func connection(_ connection: VNCConnection, stateDidChange connectionState: VNCConnection.ConnectionState) {
|
|
// No-op for now; could update UI
|
|
}
|
|
|
|
func connection(_ connection: VNCConnection, credentialFor authenticationType: VNCAuthenticationType, completion: @escaping (VNCCredential?) -> Void) {
|
|
// For now, no auth
|
|
completion(nil)
|
|
}
|
|
|
|
func connection(_ connection: VNCConnection, didCreateFramebuffer framebuffer: VNCFramebuffer) {
|
|
guard let host = host else { return }
|
|
DispatchQueue.main.async {
|
|
let fbView = VNCCAFramebufferView(frame: host.bounds, framebuffer: framebuffer, connection: connection)
|
|
fbView.translatesAutoresizingMaskIntoConstraints = false
|
|
host.subviews.forEach { $0.removeFromSuperview() }
|
|
host.addSubview(fbView)
|
|
NSLayoutConstraint.activate([
|
|
fbView.topAnchor.constraint(equalTo: host.topAnchor),
|
|
fbView.leadingAnchor.constraint(equalTo: host.leadingAnchor),
|
|
fbView.trailingAnchor.constraint(equalTo: host.trailingAnchor),
|
|
fbView.bottomAnchor.constraint(equalTo: host.bottomAnchor)
|
|
])
|
|
host.framebufferView = fbView
|
|
}
|
|
}
|
|
|
|
func connection(_ connection: VNCConnection, didResizeFramebuffer framebuffer: VNCFramebuffer) {
|
|
// VNCCAFramebufferView queries connection/framebuffer for size; nothing required here
|
|
}
|
|
|
|
func connection(_ connection: VNCConnection, didUpdateFramebuffer framebuffer: VNCFramebuffer, x: UInt16, y: UInt16, width: UInt16, height: UInt16) {
|
|
// View should handle drawing updates internally
|
|
}
|
|
|
|
func connection(_ connection: VNCConnection, didUpdateCursor cursor: VNCCursor) {
|
|
// Cursor updates handled by the framebuffer view
|
|
}
|
|
}
|
|
|
|
let delegate = Delegate(host: self)
|
|
self.connectionDelegateStrongRef = delegate
|
|
conn.delegate = delegate
|
|
conn.connect()
|
|
}
|
|
}
|