@@ -97,10 +97,9 @@ final class DisplayMapperModel: ObservableObject {
9797 return
9898 }
9999
100- let virtualID = try createVirtualDisplay ( for: targetID, width: width, height: height, hiDPI: requestedHiDPI)
101- try setMirrorWithRetry ( targetID: targetID, sourceID: virtualID)
102- saveLastMapping ( targetID: targetID, width: width, height: height, hiDPI: requestedHiDPI)
103- refreshDisplays ( )
100+ let origin = displayOrigin ( for: targetID)
101+ try applyMapping ( targetID: targetID, width: width, height: height, hiDPI: requestedHiDPI, origin: origin)
102+ saveLastMapping ( targetID: targetID, width: width, height: height, hiDPI: requestedHiDPI, origin: origin)
104103 status = " Mapped \( DisplayNames . name ( for: targetID) ) to \( width) x \( height) . "
105104 } catch {
106105 status = error. localizedDescription
@@ -124,7 +123,19 @@ final class DisplayMapperModel: ObservableObject {
124123 customHeight = " \( mapping. height) "
125124 useHiDPI = mapping. hiDPI
126125 useCustomResolution = true
127- applySelectedMapping ( )
126+
127+ Task { @MainActor in
128+ isBusy = true
129+ defer { isBusy = false }
130+
131+ do {
132+ let origin = mapping. savedOrigin ?? displayOrigin ( for: targetID)
133+ try applyMapping ( targetID: targetID, width: mapping. width, height: mapping. height, hiDPI: mapping. hiDPI, origin: origin)
134+ status = " Restored \( DisplayNames . name ( for: targetID) ) to \( mapping. width) x \( mapping. height) . "
135+ } catch {
136+ status = error. localizedDescription
137+ }
138+ }
128139 }
129140
130141 func unmapSelected( ) {
@@ -166,6 +177,26 @@ final class DisplayMapperModel: ObservableObject {
166177 }
167178 }
168179
180+ private func applyMapping( targetID: CGDirectDisplayID , width: Int , height: Int , hiDPI: Bool , origin: CGPoint ? ) throws {
181+ if let origin {
182+ try setDisplayOriginWithRetry ( displayID: targetID, origin: origin)
183+ }
184+
185+ let virtualID = try createVirtualDisplay ( for: targetID, width: width, height: height, hiDPI: hiDPI)
186+
187+ if let origin {
188+ try setDisplayOriginWithRetry ( displayID: virtualID, origin: origin)
189+ }
190+
191+ try setMirrorWithRetry ( targetID: targetID, sourceID: virtualID)
192+
193+ if let origin {
194+ try setDisplayOriginWithRetry ( displayID: virtualID, origin: origin)
195+ }
196+
197+ refreshDisplays ( )
198+ }
199+
169200 private func createVirtualDisplay( for targetID: CGDirectDisplayID , width: Int , height: Int , hiDPI: Bool ) throws -> CGDirectDisplayID {
170201 let targetKey = Self . identityKey ( for: targetID)
171202 let virtualSerial = Self . stableVirtualSerial ( for: targetKey)
@@ -256,9 +287,57 @@ final class DisplayMapperModel: ObservableObject {
256287 throw lastError ?? MapperError . message ( " Mirror setup failed. " )
257288 }
258289
259- private func saveLastMapping( targetID: CGDirectDisplayID , width: Int , height: Int , hiDPI: Bool ) {
290+ private func setDisplayOrigin( displayID: CGDirectDisplayID , origin: CGPoint ) throws {
291+ var config : CGDisplayConfigRef ?
292+ guard CGBeginDisplayConfiguration ( & config) == . success else {
293+ throw MapperError . message ( " Could not begin display origin configuration. " )
294+ }
295+
296+ let originError = CGConfigureDisplayOrigin ( config, displayID, Int32 ( origin. x. rounded ( ) ) , Int32 ( origin. y. rounded ( ) ) )
297+ guard originError == . success else {
298+ CGCancelDisplayConfiguration ( config)
299+ throw MapperError . message ( " Display origin setup failed: \( originError. rawValue) . " )
300+ }
301+
302+ let completeError = CGCompleteDisplayConfiguration ( config, . forSession)
303+ guard completeError == . success else {
304+ CGCancelDisplayConfiguration ( config)
305+ throw MapperError . message ( " Display origin configuration failed: \( completeError. rawValue) . " )
306+ }
307+ }
308+
309+ private func setDisplayOriginWithRetry( displayID: CGDirectDisplayID , origin: CGPoint ) throws {
310+ var lastError : Error ?
311+
312+ for _ in 0 ..< 4 {
313+ do {
314+ try setDisplayOrigin ( displayID: displayID, origin: origin)
315+ return
316+ } catch {
317+ lastError = error
318+ Thread . sleep ( forTimeInterval: 0.25 )
319+ refreshDisplays ( )
320+ }
321+ }
322+
323+ throw lastError ?? MapperError . message ( " Display origin setup failed. " )
324+ }
325+
326+ private func displayOrigin( for id: CGDirectDisplayID ) -> CGPoint {
327+ CGDisplayBounds ( id) . origin
328+ }
329+
330+ private func saveLastMapping( targetID: CGDirectDisplayID , width: Int , height: Int , hiDPI: Bool , origin: CGPoint ) {
260331 let monitorKey = Self . identityKey ( for: targetID)
261- let mapping = SavedMapping ( targetDisplayID: targetID, monitorKey: monitorKey, width: width, height: height, hiDPI: hiDPI)
332+ let mapping = SavedMapping (
333+ targetDisplayID: targetID,
334+ monitorKey: monitorKey,
335+ width: width,
336+ height: height,
337+ hiDPI: hiDPI,
338+ originX: Int32 ( origin. x. rounded ( ) ) ,
339+ originY: Int32 ( origin. y. rounded ( ) )
340+ )
262341 if let data = try ? JSONEncoder ( ) . encode ( mapping) {
263342 defaults. set ( data, forKey: savedMappingKey)
264343 }
0 commit comments