Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,28 @@ struct TBVirtualDisplayIdentity {
usesDedicatedArrangementIdentity: false
)

static func extendedDesktop() -> TBVirtualDisplayIdentity {
let random = UInt32.random(in: 0x0100...0xFFFE)
static func extendedDesktop(for profile: TBMonitorDisplayProfile) -> TBVirtualDisplayIdentity {
// Deterministic identity per receiver so macOS retains window placement
// and the saved extended-desktop arrangement across reconnects.
let key = "\(profile.receiverName)|\(profile.panelWidth)x\(profile.panelHeight)"
let hash = djb2(key)
let productLow = (hash & 0x00FF) | 0x01
let serialLow = (hash & 0xFFFE) | 0x0100
return TBVirtualDisplayIdentity(
productID: 0x6000 | (random & 0x00FF),
serialNumber: 0x2027_0000 | random,
productID: 0x6000 | productLow,
serialNumber: 0x2027_0000 | UInt32(serialLow),
displayNamePrefix: "TB Extend",
usesDedicatedArrangementIdentity: true
)
}

private static func djb2(_ input: String) -> UInt32 {
var hash: UInt32 = 5381
for byte in input.utf8 {
hash = hash &* 33 &+ UInt32(byte)
}
return hash
}
}

@MainActor
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
enum TBDisplaySenderBuildInfo {
static let marketingVersion = "2.0"
static let buildNumber = "20260521211758"
static let buildNumber = "20260526205217"
static let versionDisplay = "\(marketingVersion) + build \(buildNumber)"
}
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,12 @@ struct TBDisplaySenderContentView: View {

Toggle(TBDisplaySenderL10n.largeCursor(service.language), isOn: $service.largeCursor)
.disabled(service.anyConnected)

Toggle(TBDisplaySenderL10n.preventDisplaySleep(service.language), isOn: $service.preventDisplaySleep)

Toggle(TBDisplaySenderL10n.autoRestartOnWake(service.language), isOn: $service.autoRestartOnWake)

Toggle(TBDisplaySenderL10n.verboseDisplayLogging(service.language), isOn: $service.verboseDisplayLogging)
}
}
}
Expand Down Expand Up @@ -336,6 +342,12 @@ private struct TBDisplaySenderSessionCard: View {
.buttonStyle(.bordered)
.disabled(session.isConnected || session.isStreaming || session.isCableTesting || trimmedReceiverIP.isEmpty || session.localTBIP.isEmpty)

Button(TBDisplaySenderL10n.restartCaptureButton(service.language)) {
session.restartCaptureNow()
}
.buttonStyle(.bordered)
.disabled(!session.canRestartCapture)

Button(TBDisplaySenderL10n.removeSessionButton(service.language)) {
service.removeSession(session)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,38 @@ enum TBDisplaySenderL10n {
}
}

static func preventDisplaySleep(_ language: TBDisplaySenderLanguage) -> String {
switch language {
case .italian: return "Impedisci screensaver e sospensione display durante lo streaming"
case .english: return "Prevent screensaver / display sleep while streaming"
case .german: return "Bildschirmschoner und Display-Ruhezustand beim Streamen verhindern"
}
}

static func autoRestartOnWake(_ language: TBDisplaySenderLanguage) -> String {
switch language {
case .italian: return "Riavvia automaticamente la cattura al risveglio"
case .english: return "Auto-restart capture after wake / unlock"
case .german: return "Aufnahme nach Aufwachen/Entsperren automatisch neu starten"
}
}

static func restartCaptureButton(_ language: TBDisplaySenderLanguage) -> String {
switch language {
case .italian: return "Riavvia cattura"
case .english: return "Restart capture"
case .german: return "Aufnahme neu starten"
}
}

static func verboseDisplayLogging(_ language: TBDisplaySenderLanguage) -> String {
switch language {
case .italian: return "Diagnostica display in Console (verboso)"
case .english: return "Log virtual display events to Console (verbose)"
case .german: return "Virtuelle Display-Ereignisse in Console protokollieren (ausführlich)"
}
}

static func showMainWindow(_ language: TBDisplaySenderLanguage) -> String {
switch language {
case .italian: return "Mostra finestra principale"
Expand Down
29 changes: 28 additions & 1 deletion TargetBridge-Sender/TBDisplaySender/TBDisplaySenderManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,27 @@ final class TBDisplaySenderService: ObservableObject {
objectWillChange.send()
}
}
@Published var preventDisplaySleep: Bool = UserDefaults.standard.bool(forKey: "fd.tbdisplaysender.preventDisplaySleep") {
didSet {
UserDefaults.standard.set(preventDisplaySleep, forKey: "fd.tbdisplaysender.preventDisplaySleep")
sessions.forEach { $0.preventDisplaySleep = preventDisplaySleep }
objectWillChange.send()
}
}
@Published var autoRestartOnWake: Bool = UserDefaults.standard.bool(forKey: "fd.tbdisplaysender.autoRestartOnWake") {
didSet {
UserDefaults.standard.set(autoRestartOnWake, forKey: "fd.tbdisplaysender.autoRestartOnWake")
sessions.forEach { $0.autoRestartOnWake = autoRestartOnWake }
objectWillChange.send()
}
}
@Published var verboseDisplayLogging: Bool = UserDefaults.standard.bool(forKey: "fd.tbdisplaysender.verboseDisplayLogging") {
didSet {
UserDefaults.standard.set(verboseDisplayLogging, forKey: "fd.tbdisplaysender.verboseDisplayLogging")
sessions.forEach { $0.verboseDisplayLogging = verboseDisplayLogging }
objectWillChange.send()
}
}

private var sessionCancellables: [UUID: AnyCancellable] = [:]
private let receiverDiscovery = TBReceiverDiscovery()
Expand Down Expand Up @@ -70,7 +91,13 @@ final class TBDisplaySenderService: ObservableObject {
}

func addSession() {
let session = TBDisplaySenderSession(language: language, largeCursor: largeCursor)
let session = TBDisplaySenderSession(
language: language,
largeCursor: largeCursor,
preventDisplaySleep: preventDisplaySleep,
autoRestartOnWake: autoRestartOnWake,
verboseDisplayLogging: verboseDisplayLogging
)
if let previous = sessions.last {
session.capturePreset = previous.capturePreset
session.captureSource = previous.captureSource
Expand Down
Loading