How do I change the login screen background in macOS Mojave?

I just updated to macOS Mojave, and immediately noticed a couple of things:

  • My custom login screen wallpaper is gone.
  • When you click on a user's name in the login screen, it switches to their personal background (their usual wallpaper for the first space on the primary monitor).

I assumed it had just overwritten my cached image file. But when I went to replace it, nothing happened. It turns out that com.apple.desktop.admin.png is gone entirely!

no-cached-image

Right after taking that screenshot, I decided to poke into Desktop Pictures and found my personal login screen background, which looks promising. It contains one other folder, which  probably  (edit: confirmed) contains the login screen background of my administrator account.


Solution 1:

I've fixed it! You'll have to edit the dune HEIC picture though. If you're willing, follow these steps:

​1) Go To: /Library/Desktop Pictures/

2) Find the file called "Mojave.heic"

3) Save a copy as a backup somewhere else

4) Select the picture you want to have instead

5) Edit image values (DPI, size, etc.) to fit

6) Rename this edited picture as Mojave.heic

Solution 2:

Expanding on Leonard's answer:

You can do this by replacing the Mojave.heic default desktop background. This does not require disabling SIP, as it's in /Library.

  • Back up /Library/Desktop Pictures/Mojave.heic by copying it to Mojave.heic.orig or similar.

  • Get your new image and scale/crop it to exactly fit the display. If you don't know your screen resolution, you can go to  > About This Mac.

  • Replace Mojave.heic with your new file. Don't worry if it's JPG or similar, it'll still work even after you rename it to Mojave.heic.*

  • If you have FileVault enabled, change a login option in System Preferences. For example, whether to show a list of users or name and password fields. Just change it back if you don't actually want it changed.

    This is because when you boot with FileVault, at the login screen your system hasn't really booted up all the way! It's actually running a tiny system on your EFI partition, since your main partition is encrypted. Changing a login option will make System Preferences change the EFI system's settings, including picking up the wallpaper change. See this answer.

  • Reboot and enjoy!

* I've only tested this with JPEG images, but it may work for other types.


Completely Unnecessary Timesaver

I've made a small Swift program that does all this for you (it detects the OS version and works both on Mojave and earlier versions). You'll need Xcode to compile it.

It shouldn't break your system, but I can't guarantee anything -- make sure you have backups first!

This is now also available on GitHub. It may or may not be updated here in the future.

//
// loginwindowbgconverter
// by SilverWolf
// 2018-09-27
//

import Foundation
import AppKit

func printUsage() {
    print("""
    usage: \(CommandLine.arguments[0]) \u{1B}[4mimage-file\u{1B}[0m
    It needs to be run as root, as it saves to /Library/Desktop Pictures.
    """)
}

guard CommandLine.arguments.indices.contains(1) else {
    printUsage()
    exit(1)
}
let inputFile = CommandLine.arguments[1]

guard let inputImage = NSImage(contentsOfFile: inputFile) else {
    print("\(CommandLine.arguments[0]): can't load image from \(inputFile)")
    exit(2)
}

let iw = inputImage.size.width
let ih = inputImage.size.height
let iaspect = Double(iw) / Double(ih)

// use System Profiler to get screen size

var sw = 0, sh = 0

enum ScreenSizeError: Error {
    case foundNil
}
do {
    let task = Process()
    if #available(macOS 10.13, *) {
        task.executableURL = URL(fileURLWithPath: "/bin/zsh")
    } else {
        task.launchPath = "/bin/zsh"
    }
    task.arguments = ["-f", "-c", "system_profiler SPDisplaysDataType | awk '/Resolution/{print $2, $4}' | head -n 1"]
    
    let stdoutPipe = Pipe()
    task.standardOutput = stdoutPipe
    
    if #available(macOS 10.13, *) {
        try task.run()
    } else {
        task.launch()
    }
    task.waitUntilExit()
    
    let data = stdoutPipe.fileHandleForReading.readDataToEndOfFile()
    guard let text = String(data: data, encoding: .utf8) else {
        throw ScreenSizeError.foundNil
    }
    let sizes = (text as NSString).replacingOccurrences(of: "\n", with: "").components(separatedBy: " ")
    sw = Int(sizes[0]) ?? 0
    sh = Int(sizes[1]) ?? 0
    guard sw != 0 && sh != 0 else {
        throw ScreenSizeError.foundNil
    }
} catch {
    print("\(CommandLine.arguments[0]): can't get screen resolution")
    exit(3)
}

print("Screen size: \(sw)x\(sh)")

var nw = 0, nh = 0
var x = 0, y = 0 // offsets

let saspect = Double(sw) / Double(sh)
if saspect > iaspect { // screen is wider
    nw = sw
    nh = Int(Double(sw) / iaspect) // keep input image aspect ratio
    y = -1 * (nh - sh) / 2 // half the difference
} else { // screen is narrower
    nh = sh
    nw = Int(Double(sh) * iaspect)
    x = -1 * (nw - sw) / 2
}

// draw into new image
guard let newImage = NSBitmapImageRep(bitmapDataPlanes: nil,
                                pixelsWide: Int(sw),
                                pixelsHigh: Int(sh),
                                bitsPerSample: 8,
                                samplesPerPixel: 4,
                                hasAlpha: true,
                                isPlanar: false,
                                colorSpaceName: .deviceRGB,
                                bytesPerRow: sw * 4,
                                bitsPerPixel: 32) else {
    print("\(CommandLine.arguments[0]): can't create bitmap image to draw into!")
    exit(2)
}

NSGraphicsContext.saveGraphicsState()
let graphicsContext = NSGraphicsContext(bitmapImageRep: newImage)
NSGraphicsContext.current = graphicsContext
graphicsContext?.imageInterpolation = .high
let r = NSMakeRect(CGFloat(x), CGFloat(y), CGFloat(nw), CGFloat(nh))
print("drawing rect: \(r)")
inputImage.draw(in: r)

graphicsContext?.flushGraphics()
NSGraphicsContext.restoreGraphicsState()

print("image size: \(newImage.size)")

// write to file
if #available(macOS 10.14, *) { // macOS Mojave has a completely different system
    let targetFile = "/Library/Desktop Pictures/Mojave.heic"
    let origFile =  "/Library/Desktop Pictures/Mojave.heic.orig"
    if !FileManager.default.fileExists(atPath: origFile) { // no backup of original Mojave.heic
        print("Backing up original Mojave.heic (this should only happen once)")
        do {
            try FileManager.default.copyItem(atPath: targetFile, toPath: origFile)
        } catch {
            print("\(CommandLine.arguments[0]): \u{1B}[1mbackup failed, aborting!\u{1B}[0m \(error.localizedDescription)")
            exit(1)
        }
    }
    
    print("Saving to \(targetFile)")
    // actual writing
    let imageData = newImage.representation(using: .jpeg, properties: [:])!
    do {
        try imageData.write(to: URL(fileURLWithPath: targetFile))
    } catch {
        print("\(CommandLine.arguments[0]): can't write image data: \(error)")
        print("(are you root?)")
        exit(1)
    }
} else {
    let targetFile = "/Library/Caches/com.apple.desktop.admin.png"
    print("Saving to \(targetFile)")
    let pngData = newImage.representation(using: .png, properties: [:])!
    do {
        try pngData.write(to: URL(fileURLWithPath: targetFile))
    } catch {
        print("\(CommandLine.arguments[0]): can't write image data: \(error)")
        print("(are you root?)")
        exit(1)
    }
}

//
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// For more information, please refer to <https://unlicense.org/>.
//

Solution 3:

I receive a weird image when I just replace the file with a JPG, renaming it too HEIC. However, when I take the image I want as the background and export it in the HEIC format in Preview, it all works great. I was working with a 5333×3333 image to start with:

  1. Open image you want as your background in Preview
  2. In Preview, select File > Export...
  3. Set the Format to HEIC and Quality to best (I got a completely blank image when I tried a quality less than "Best")
  4. Rename the exported file as Mojave (the extension should already be .heic)
  5. Copy exported image to /Library/Desktop\ Pictures

When you logout, you should see your new background. Try restarting, if don't see the image show up right away.

If you run into issues with exporting the file as .heic, try adjusting the size of the image using Preview: Tools > Adjust Size. As a start, set it to the size of your screen as seen in System Information > Graphics/Displays.