Series-Scripts

A Custom Monitor Rest Mode

Last year, I bought a new Mac mini and a Redmi G Pro 27U monitor.

This monitor is pretty good overall. At around 2,000 RMB, it performs well in many areas, but for me it has two small drawbacks:

  1. It is a bit slow to wake up, taking about 5 seconds when waking from the keyboard.
  2. After the monitor goes to sleep, I can no longer use Xiao Ai voice commands.

I looked through the monitor settings and could not find an option to keep it on standby without sleeping, so I decided to make one myself.

Basic idea: This is a miniLED monitor. When HDR mode is enabled in macOS, the system can control the monitor brightness directly. This does not work in SDR mode, where brightness can only be adjusted through the monitor itself. Because of miniLED local dimming, when the brightness is set to 0, the whole panel stops emitting light.

So all I need to do is set the brightness to 0 when I am not using the computer. This effectively turns off the monitor panel and saves power, while the system keeps running and can still respond to Xiao Ai commands at any time.

Prerequisites

  1. A miniLED monitor with HDR enabled in macOS - System Settings - Displays.
  2. One Switch - Keep Awake.
  3. Hammerspoon.

Implementation

1. Install Hammerspoon

  1. Go to the official website and download Hammerspoon.
  2. After opening it, click Hammerspoon -> Open Config in the menu bar.
  3. This will open ~/.hammerspoon/init.lua.

You can also open ~/.hammerspoon/init.lua directly with an editor such as VS Code.

2. Configure init.lua

Paste the following code into init.lua:

init.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
-- Screensaver dimming mode:
-- after entering the screensaver, wait delayBeforeDim minutes before lowering brightness;
-- dimDuration controls how long it takes to dim to 0.
-- When leaving the screensaver, for example by pressing Enter to wake, restore the original brightness.

local brightness = require("hs.brightness")
local caffeinate = require("hs.caffeinate")
local timer = require("hs.timer")

-- Configuration
local delayBeforeDim = 1 * 60 -- Wait 1 minute after entering the screensaver before dimming (seconds)
local dimDuration = 5 -- Total time to dim from the current brightness to 0 (seconds)
local steps = 2 -- Number of dimming steps; higher values are smoother
local originalBrightness = nil
local dimTimer = nil
local delayTimer = nil

-- Stop all timers and restore brightness
local function restoreBrightness()
if delayTimer then
delayTimer:stop()
delayTimer = nil
end
if dimTimer then
dimTimer:stop()
dimTimer = nil
end
if originalBrightness then
brightness.set(originalBrightness)
originalBrightness = nil
end
end

-- Start dimming
local function startDimming()
-- Avoid starting another dimming timer if one is already running
if dimTimer ~= nil then return end

originalBrightness = brightness.get()
if not originalBrightness or originalBrightness <= 0 then
return
end

local currentStep = 0
local stepInterval = dimDuration / steps
local stepDelta = originalBrightness / steps

dimTimer = timer.doEvery(stepInterval, function()
currentStep = currentStep + 1
local newValue = originalBrightness - stepDelta * currentStep
if newValue < 0 then newValue = 0 end
brightness.set(math.floor(newValue + 0.5))

if currentStep >= steps then
dimTimer:stop()
dimTimer = nil
end
end)
end

-- When the screensaver starts: wait delayBeforeDim minutes, then start dimming
local function onScreensaverStart()
-- Prevent duplicate timers
restoreBrightness()

delayTimer = timer.doAfter(delayBeforeDim, function()
startDimming()
end)
end

-- When the screensaver stops: restore brightness immediately
local function onScreensaverStop()
restoreBrightness()
end

local watcher = caffeinate.watcher.new(function(event)
if event == caffeinate.watcher.screensaverDidStart then
-- Entered screensaver
onScreensaverStart()

elseif event == caffeinate.watcher.screensaverDidStop then
-- Left screensaver, usually triggered by a key press or mouse movement
onScreensaverStop()

elseif event == caffeinate.watcher.screensDidLock then
-- Screen locked, either manually or automatically
onScreensaverStart()

elseif event == caffeinate.watcher.screensDidUnlock then
-- Successfully unlocked, after password or Touch ID
onScreensaverStop()

elseif event == caffeinate.watcher.systemDidWake then
-- The Mac woke from sleep
restoreBrightness()

elseif event == caffeinate.watcher.screensDidWake then
-- The screen woke from sleep
restoreBrightness()
end
end)

watcher:start()

Save the file.

3. Reload Config

Click Hammerspoon -> Reload Config in the menu bar.

The setup is now complete. You can press Control + Command + Q to lock the screen and test the effect.

Conclusion

With this custom script, I got the monitor rest mode I wanted.

Limitations:

  1. On the macOS login screen, the script cannot listen for events and restore brightness. You can temporarily adjust brightness manually with the brightness keys on the keyboard.
  2. Compared with true sleep, this will use more power. I have not tested it, but I suppose the difference is not too large.

Advantages:

  1. There is no need to wait for the monitor to wake up, and I do not need to worry about issues such as burn-in.
  2. Xiao Ai is always on standby. Last night, I called Xiao Ai in the middle of the night to turn on the air conditioner for me.