16

I'm trying to set a desktop background for all screens AND spaces (preexisting and new). However, I can't seem to find a way to set the background for all the existing spaces (and any new spaces created use the old background).

Here is what I have so far:

let sqlData = NSMutableArray()
let paths = NSSearchPathForDirectoriesInDomains(.ApplicationSupportDirectory, .UserDomainMask, true)
let appSupportDirectory = paths.first! as NSString
let dbPath = appSupportDirectory.stringByAppendingPathComponent("Dock/desktoppicture.db") as NSString

var db: COpaquePointer = nil
if sqlite3_open(dbPath.UTF8String, &db) == SQLITE_OK {
    var statement: COpaquePointer = nil

    if sqlite3_exec(db, "DELETE FROM data", nil, nil, nil) != SQLITE_OK {
        let errmsg = String.fromCString(sqlite3_errmsg(db))
        print("error deleting table row: \(errmsg)")
    }

    if sqlite3_exec(db, "INSERT INTO DATA (VALUE) VALUES ('\(getBackgroundImagePath())');", nil, nil, nil) != SQLITE_OK {
        let errmsg = String.fromCString(sqlite3_errmsg(db))
        print("error inserting table row: \(errmsg)")
    }

    let workspace = NSWorkspace.sharedWorkspace()

    for screen in NSScreen.screens()! {
        do {
            let options = workspace.desktopImageOptionsForScreen(screen)
            try workspace.setDesktopImageURL(NSURL(fileURLWithPath: getBackgroundImagePath()), forScreen: screen, options: options!)
        } catch let error as NSError {
            NSLog("\(error.localizedDescription)")
        }
    }

    system("/usr/bin/killall Dock")
}

sqlite3_close(db)

Note: I update the .db file found in ~/Library/Application Support/Dock/desktoppicture.db. Since this doesn't actually update the background, I then proceed to loop through each screen and set them manually.

Although this changes all of the screen's backgrounds, any non-active spaces are not changed, and any new spaces created use the old background.

I'm using this code within a small app I made on GitHub, and this is an issue a user reported. You can find the issue here (with a terminal solution).

Apple has a seemingly relevant project here, but even they don't update multiple spaces.

Also, if you update the background through the default mac settings app, it also doesn't change pre-existing spaces. Is it impossible?

Josue Espinosa
  • 5,009
  • 16
  • 47
  • 81

1 Answers1

3

OS X Desktop Picture Global Updating Across Spaces

In desktoppicture.db an update query can be run on the data table with the path of the new image (value). It shouldn't be necessary to delete and then insert the values, or use loops. Using an unscoped query calls update, and by doing so it will update every row in the data table.

(The example below uses the SQLite.swift language layer, which uses a type-safe pure Swift interface)

func globalDesktopPicture(image: String) {

    let paths: [String] = NSSearchPathForDirectoriesInDomains(.ApplicationSupportDirectory,
                                                              .UserDomainMask, true)
    let appSup: String = paths.first!
    let dbPath: String = (appSup as NSString).stringByAppendingPathComponent("Dock/desktoppicture.db")

    let dbase = try? Connection("\(dbPath)")
    let value = Expression<String?>("value")    
    let table = Table("data")
    try! dbase!.run(table.update(value <- image))

    system("/usr/bin/killall Dock")
}

The same concept/principle of updating the db applies just the same if you instead decide to use the traditional/legacy (bridging headers) method of doing SQLite3 queries:

func legacyGlobalDesktopPicture(image: String) {

    let paths: [String] = NSSearchPathForDirectoriesInDomains(.ApplicationSupportDirectory,
                                                              .UserDomainMask, true)
    let appSup: String = paths.first!
    let dbPath: String = (appSup as NSString).stringByAppendingPathComponent("Dock/desktoppicture.db")

    var db: COpaquePointer = nil
    if sqlite3_open(dbPath, &db) == SQLITE_OK {

        if sqlite3_exec(db, "UPDATE DATA SET VALUE = ('\(image)');", nil, nil, nil) != SQLITE_OK {
            let errmsg = String.fromCString(sqlite3_errmsg(db))
            print("error inserting table row: \(errmsg)")
        }

        system("/usr/bin/killall Dock")
    }
    sqlite3_close(db)
}

NOTE: The examples above are quite minimal and should include some error checking, etc.

Additional Info:

Community
  • 1
  • 1
l'L'l
  • 44,951
  • 10
  • 95
  • 146
  • This looks like a clean way to access the db, but does it answer the question? – Eiko Apr 27 '16 at 09:05
  • @Eiko: I wouldn't have answered if I thought it didn't. If the goal is to globally replace the wallpaper across spaces then my answer definitely fulfills that. – l'L'l Apr 27 '16 at 09:10
  • What library/framework are you using? I was only using sqlite3.h, and wanted to test this code out because it does look like it should work. I would be really grateful if you could rewrite your answer to use sqlite3.h or tell me what else you're using – Josue Espinosa May 02 '16 at 04:42
  • I'm using the SQLite.swift language layer in the first example (link added), since it avoids the need to use bridging headers, etc. The same principle applies to using the sqlite3 library with bridging headers (the first paragraph of my answer literally explains everything about what should happen regardless). – l'L'l May 05 '16 at 20:12
  • My apologies, this was nearing finals so I was a bit out of it, I only skimmed the answer tbh. Thank you for the explanation and the in-depth answer. – Josue Espinosa May 16 '16 at 15:31