Search code examples
arraysswiftmacosnvidiamirroring

Swift "Array" memory allocation, plus toggle display mirroring


NOTE: See amended post below re: Hardware mirroring

I have written two Swift functions that toggle the display mirroring in OSX. Both work; the difference between them is just syntax when dealing with pointers. For the convenience of those interested in learning how to toggle mirroring in Swift, I have included the text of the playground file below.

My question is about memory allocation. Here is the section of interest:

toggleMirroringUgly

    // allocate space for array
    let displayListPtr = displayIDListPtr.alloc(Int(displayCount)) //see typealias above
    // fill the list
    postError(CGGetActiveDisplayList(displayCount, displayListPtr, &activeCount))

toggleMirroring

    // allocate space for list of displays
    var displayIDList = Array<CGDirectDisplayID>(count: Int(displayCount), repeatedValue: kCGNullDirectDisplay)
    // fill the list
    postError(CGGetActiveDisplayList(displayCount, &displayIDList, &activeCount))

CGGetActiveDisplayList is a low-level function call that relies on data being arranged in consecutive memory locations. I am reasonably confident that “alloc” from the ugly version is contiguous. Empirically, it seems that the “Array(…)” call is also contiguous, but can I rely on that always being true (e.g., if the number of displays grows)? Is this assumption about the Swift array initializer poor form?


Here’s all the code; apologies for formatting issues. Note that only one of the two functions should be called; otherwise, you’ll end up where you started.

//: Playground - noun: a place where people can play

import Cocoa

// apparently not defined in Swift version of SDK 10.11 (XCode 7.3.1), so add manually
let kCGNullDirectDisplay = CGDirectDisplayID(0)
let kCGDirectMainDisplay = CGMainDisplayID()        // not used here, just for the record

let maxDisplays:UInt32 = 20    // not used
var onlineCount:UInt32 = 0     // not used

func postError(error : CGError){
    if error != CGError.Success {
        print("got an error")
    }
}


// this toggles all active displays, online or not
func toggleMirroring(){
    var displayCount:UInt32 = 0
    var activeCount:UInt32 = 0
    //var onlineCount:UInt32 = 0      //not used

    //get count of active displays (by passing nil to CGGetActiveDisplayList
    postError(CGGetActiveDisplayList(0, nil, &displayCount))

    if displayCount < 2 { return }  // no point in any mirroring functions

    //***
    // allocate space for list of displays
    var displayIDList = Array<CGDirectDisplayID>(count: Int(displayCount), repeatedValue: kCGNullDirectDisplay)

    // fill the list
    postError(CGGetActiveDisplayList(displayCount, &displayIDList, &activeCount))
    //***

    // determine if mirroring is active
    // hack to convert from boolean_t (aka UInt32) to swift's bool
    let displaysMirrored = CGDisplayIsInMirrorSet(CGMainDisplayID()) != 0

    // set master based on current mirroring state
    // if mirroring, master = null, if not, master = main display
    let master = (true == displaysMirrored) ? kCGNullDirectDisplay : CGMainDisplayID()

    // start the configuration
    var configRef:CGDisplayConfigRef = nil  //swift 3 syntax

    postError(CGBeginDisplayConfiguration(&configRef));

    for i in 0..<Int(displayCount) {
        let currentDisplay = CGDirectDisplayID(displayIDList[i])
        if CGMainDisplayID() != currentDisplay {
        CGConfigureDisplayMirrorOfDisplay(configRef, currentDisplay, master);
        }
    }

    if (false){     // change to true in order to execute the toggle
        postError(CGCompleteDisplayConfiguration (configRef,CGConfigureOption.Permanently))
    }

// The first entry in the list of active displays is the main display. In case of mirroring, the first entry is the largest drawable display or, if all are the same size, the display with the greatest pixel depth.
// The "Permanently" option might not survive reboot when run from playground, but does when run in an application
}

func toggleMirroringUgly(){
// just to decrease eye strain
typealias displayIDListPtr = UnsafeMutablePointer<CGDirectDisplayID>
typealias configurationRefPtr = UnsafeMutablePointer<CGDisplayConfigRef>

//get count of active displays (by passing nil to CGGetActiveDisplayList
postError(CGGetActiveDisplayList(0, nil, &displayCount))

if displayCount < 2 { return }  // no point in any mirroring functions

// ***
// allocate space for array
let displayListPtr = displayIDListPtr.alloc(Int(displayCount)) //see typealias above
// fill the list
postError(CGGetActiveDisplayList(displayCount, displayListPtr, &activeCount))
// ***

// determine if mirroring is active
// hack to convert from boolean_t (aka UInt32) to swift's bool
let displaysMirrored = CGDisplayIsInMirrorSet(CGMainDisplayID()) != 0
// set master based on current mirroring state
// if mirroring master = null, if not, master = main display
let master = (true == displaysMirrored) ? kCGNullDirectDisplay : CGMainDisplayID()

// make room for the configuration reference
let configRefPtr = configurationRefPtr.alloc(1)     //see typealias above
// start the configuration
postError(CGBeginDisplayConfiguration (configRefPtr));

for i in 0..<displayCount {
    let currentDisplay = CGDirectDisplayID(displayListPtr[Int(i)])
    if CGMainDisplayID() != currentDisplay {
        CGConfigureDisplayMirrorOfDisplay(configRefPtr[0], currentDisplay, master);
    }
}

if (false){         //change to true in order to flip the mirroring
    // make it happen
    postError(CGCompleteDisplayConfiguration (configRefPtr[0],CGConfigureOption.Permanently));
}
// The first entry in the list of active displays is the main display. In case of mirroring, the first entry is the largest drawable display or, if all are the same size, the display with the greatest pixel depth.
// The "Permanently" option might not survive reboot when run from playground, but does when run in an application
}


toggleMirroring()

Solution

  • I upgraded my computer with a new video card and NVIDIA drivers and discovered my code above no longer works fully - turns mirroring on but not off. Apparently, there's an option for drivers to use hardware or software mirroring, and that changes the coding. I post below a revised version.

    It has only been tested on my system (10.12.2) and card (GTX 980Ti), but I think the logic should accommodate software mirroring and fairly recent OS versions as well. If you have more than 2 displays, you can probably modify it, with heroic effort, to mirror arbitrary combinations. My code will just mirror whatever is considered the main display (or the lowest rez one, in software mirroring) on all the others.

    Although jbandes' note re: ContiguousArray was informative, it does not work in this case - see the comments in the code. This code assumes that the allocated array of UInt32s will be contiguous. (Too much work to get fancy with malloc and casting, but this is not production ready.)

    Good luck to the 2 people who might be interested!

    //: Playground - noun: a place where people can play
    import Cocoa
    import Foundation
    
    func postError(_ error : CGError){
        if error != CGError.success {
            print("got an error")
        }
    }
    
    func disableHardwareMirroring(){
        // designed for hardware mirroring with > 1 display
        // should be no penalty for running with only 1 display, using either hardware or software mirroring drivers
        // but not tested
    
        // start the configuration
        var configRef:CGDisplayConfigRef? = nil
        postError(CGBeginDisplayConfiguration(&configRef))
    
        // only interested in the main display
        // kCGNullDirectDisplay parameter disables hardware mirroring
        CGConfigureDisplayMirrorOfDisplay(configRef, CGMainDisplayID(), kCGNullDirectDisplay)
    
        // may not be permanent between boots using Playgroud, but is in an application
        postError(CGCompleteDisplayConfiguration (configRef,CGConfigureOption.permanently))
    }
    
    func toggleMirroring(){
        var displayCount:UInt32 = 0
        var activeCount:UInt32 = 0      //used as a parameter, but value is ignored
        //var onlineCount:UInt32 = 0    //not used
    
        //get count of active displays (by passing nil to CGGetActiveDisplayList
        postError(CGGetActiveDisplayList(0, nil, &displayCount))
    
        if displayCount == 1 {
            // either it's hardware mirroring or who cares?
            disableHardwareMirroring()
            return
        }
    
        // allocate space for list of displays
        // tried to use ContiguousArray, but CGGetActiveDisplayList requires Array<CGDirectDisplayID> parameter
        // ContiguousArrays cannot be typecast to Arrays (at least not easily)
    
        var displayIDList = Array<CGDirectDisplayID>(repeating: kCGNullDirectDisplay, count: Int(displayCount))
    
        // fill the list
        postError(CGGetActiveDisplayList(displayCount, &(displayIDList), &activeCount))
    
    
        // determine if mirroring is active (only relevant for software mirroring)
        // hack to convert from boolean_t (aka UInt32) to swift's bool
        let displaysMirrored = CGDisplayIsInMirrorSet(CGMainDisplayID()) != 0
    
        // set master based on current mirroring state
        // if mirroring, master = null, if not, master = main display
        let master = (true == displaysMirrored) ? kCGNullDirectDisplay : CGMainDisplayID()
    
        // start the configuration
        var configRef:CGDisplayConfigRef? = nil
    
        postError(CGBeginDisplayConfiguration(&configRef))
    
        for i in 0..<Int(displayCount) {
            let currentDisplay = CGDirectDisplayID(displayIDList[i])
            if CGMainDisplayID() != currentDisplay {
                CGConfigureDisplayMirrorOfDisplay(configRef, currentDisplay, master)
            }
        }
    
        postError(CGCompleteDisplayConfiguration (configRef,CGConfigureOption.permanently))
    
        // The first entry in the list of active displays is the main display. In case of mirroring, the first entry is the largest drawable display or, if all are the same size, the display with the greatest pixel depth.
        // The "Permanently" option might not survive reboot when run from playground, but does when run in an application
    }
    
    
    if (false) {  // change to true to run the code, false to edit
        toggleMirroring()
    }