How to standby a monitor using xset in multi monitor setup?

While having two or more monitor working together, Is there any way to put a single one of them on standby/suspend by issuing a command like: xset dpms force suspend? or having a time set for that purpose like: xset dpms 100 0 0 which works on these monitors separately?

I've got two monitor working along each other, eDP1 (My Laptop) and VGA1 (An External monitor).

I want each of them to go into suspend/standby mode separately if I'm not directly interacting with them, suppose I'm watching a movie on VGA1, and for an hour and half eDP1 is on doing nothing.

I'm not interested in using xrandor --off --output eDP1 because it's not fast enough to work with.

I want my monitor to be ready to work, with a simple mouse movement so I'm able to switch between them fast.

  • Running: Ubuntu 18.04
  • Window Manager: OpenBox

Solution 1:

Controlling individual monitors is not possible with xset ( and X11 actually)

As the title suggests, it is not possible for reasons of how xset is built and due to the X11 functions it uses. If we look at the source code, xset calls DPMSForceLevel(dpy,DPMSModeSuspend) (line 557), and the display variable dpy comes from XOpenDisplay() function ( line 203 ), and that is by definition:

A server, together with its screens and input devices, is called a display.

In other words, xset applies settings globally to the whole display, not individual Screens. It'd be necessary to change xset source code in order for that to work. DPMS extensions themselves mostly seem to only call whole display, not individual screens, so it is not possible to even write custom code with X11 library.

Manually controlling that setting via /sys subsystem also doesn't appear to be working

$ sudo bash -c 'echo Off > /sys/class/drm/card0-VGA-1/dpms'
[sudo] password for admin: 
bash: /sys/class/drm/card0-VGA-1/dpms: Permission denied

Screens also are taken out of DPMS mode when key or mouse events occur, so considering that you may want to move your mouse or use keyboard, either of those actions would cause the monitor to leave DPMS mode.

Alternative workarounds

Best alternative (and actually physically working solution) is xrandr - it could be used to control the individual "outputs". In particular,

xrandr --output VGA-1 -off

will set that output off. Yes, you've mentioned that you don't want to use this solution since it is not fast enough, however so far it is the best one available. It has couple of advantages:

  • immune to key and mouse events
  • independently controls outpus unlike xset

The xrandr --output VGA-1 --brightness 0.1 will colorize the screen in such way that it appears off, even though --brightness is a software solution, so the display is not actually dimmed at hardware level, nor it is off on hardware level. However, it does the job of blanking a screen and is also resistant to key/mouse events.

I've looked source code of Mate and Budgie screensavers, which are both forks of GNOME screensaver, however in either case they seem to be a software solution, since there's no mention of DPMS in the source code.

Solution 2:

Temporary comment

  1. On request of OP, I made the script below switch the screen off by means of xrandr. On a longer test, this worked out pretty badly. Not so much the switching off failed, but at reactivating the screen, screen layout was totally messed up. I'd be happy to post it to see if it works in your case however, but my advice is not to use it.
    In the script, I went back to setting brightness to zero instead.
  2. There was some discussion on wether we should define the active monitor by mouse location, or by location of the active window. The latter will not work if no window exists. We might not have a window at all (apart from the desktop itself) in which case the choice of window to black out would be random (or break if we won't include the exception). IMO the only option that makes sense -and would work in a predictable way in all cases- is to define the active screen by mouse position. Furthermore, that is also how the window manager decides where new windows should appear.

Then what did I change in this version?
The idle time is now defined by both keyboard- and mouse activity by default. Waking up is also done by either one.


Automatically dim inactive screen

As said by my fellow answerers, switching screens off from cli separately is a challenge at best, and I didn't find an option either.

What I did find is a way to automatically dim all screens, except the one where the mouse is, after x time.

Here we go

#!/usr/bin/env python3
import subprocess
import gi
gi.require_version("Gdk", "3.0")
from gi.repository import Gdk
import time
import sys


def get_idle():
    try:
        return int(subprocess.check_output("xprintidle")) / 1000
    except subprocess.CalledProcessError:
        return 0


def get_monitors():
    screen = Gdk.Screen.get_default()
    n_mons = display.get_n_monitors()
    mons = [screen.get_monitor_plug_name(i) for i in range(n_mons)]
    return mons


def set_mon_dimmed(mon, dim):
    print(mon, dim)
    val = "0.0" if dim else "1"
    try:
        subprocess.Popen(["xrandr", "--output", mon, "--brightness", val])
    except subprocess.CalledProcessError:
        print("oops")


def mousepos():
    # find out mouse location
    return Gdk.get_default_root_window().get_pointer()[1:3]


def get_currmonitor_atpos(x, y, display=None):
    """
    fetch the current monitor (obj) at position. display is optional to save
    fuel if it is already fetched elsewhere
    """
    if not display:
        display = Gdk.Display.get_default()
    return display.get_monitor_at_point(x, y)


display = Gdk.Display.get_default()
wait = int(sys.argv[1])
elapsed = 0
# set intervals to check
res = 2
monitors = [m for m in get_monitors()]
for m in monitors:
    set_mon_dimmed(m, False)

monrecord = {}
for m in monitors:
    monrecord[m] = {"idle": 0, "dimmed": False}

display = Gdk.Display.get_default()
idle1 = 0


while True:
    time.sleep(res)
    curr_mousepos = mousepos()
    activemon = get_currmonitor_atpos(
        curr_mousepos[0], curr_mousepos[1]
    ).get_model()
    idle2 = get_idle()
    if idle2 < idle1:
        monrecord[activemon]["idle"] = 0
        if monrecord[activemon]["dimmed"]:
            set_mon_dimmed(activemon, False)
            monrecord[activemon]["dimmed"] = False

    for m in monrecord.keys():
        curr_idle = monrecord[m]["idle"]
        print(m, curr_idle)
        if all([
            curr_idle > wait,
            monrecord[m]["dimmed"] is not True,
            m != activemon
        ]):
            set_mon_dimmed(m, True)
            monrecord[m]["dimmed"] = True         
        else:
            if m != activemon:
                monrecord[m]["idle"] = curr_idle + res

    idle1 = idle2

How to set up

Set up is straightforward:

  1. Make sure you have both python3-gi and xprintidle installed

    sudo apt install python3-gi xprintidle
    
  2. Copy the script above into an empty file, save it as dim_inactive, and make it executable

  3. Run it by the command:

    /path/to/dim_inactive <idle_time_in_seconds>
    

    an example:

    /path/to/dim_inactive 120
    

    will dim all screens where the mouse is not after two minutes

Additional information / explanation

  • The script lists all screens on startup
  • It keeps record if idle time per monitor (possibly more than 2). If a monitor isn't accessed for x seconds, it is blacked out, apart from the monitor where the mouse is.
  • According to a good (but bad) tradition, Gnome keeps breaking stuff and keeps changing API's. As a result, running this script on 19.04 and above, You'll get a few deprecated warnings. At the same time, it doesn't break on PEP8. Will nevertheless update to the latest API's.

Solution 3:

For years I had my laptop setup such that when lid is closed laptop would suspend and external monitors would go blank.

For your reason of wanting to watch a video for 90 minutes on external monitor and have laptop screen go blank I changed lid close option to "Do Nothing":

  • Advantage: When I close Laptop lid all Laptop windows go below full screen video.
  • Advantage: When I open Laptop lid windows are restored and are no longer below full screen video.
  • Disadvantage: I have to make video non-full screen to access top bar menu to select suspend from gear menu.
  • Advantage: When system is suspended by menu on external monitor, opening laptop lid still resumes system.

I'm not using DPMS for external monitors but you could check your settings with xset q command:

$ xset q
Keyboard Control:
  auto repeat:  on    key click percent:  0    LED mask:  00000002
  XKB indicators:
    00: Caps Lock:   off    01: Num Lock:    on     02: Scroll Lock: off
    03: Compose:     off    04: Kana:        off    05: Sleep:       off
    06: Suspend:     off    07: Mute:        off    08: Misc:        off
    09: Mail:        off    10: Charging:    off    11: Shift Lock:  off
    12: Group 2:     off    13: Mouse Keys:  off
  auto repeat delay:  500    repeat rate:  33
  auto repeating keys:  00ffffffdffffbbf
                        fadfffefffedffff
                        9fffffffffffffff
                        fff7ffffffffffff
  bell percent:  50    bell pitch:  400    bell duration:  100
Pointer Control:
  acceleration:  5/1    threshold:  5
Screen Saver:
  prefer blanking:  yes    allow exposures:  yes
  timeout:  0    cycle:  0
Colors:
  default colormap:  0xb3    BlackPixel:  0x0    WhitePixel:  0xffffff
Font Path:
  /usr/share/fonts/X11/misc,/usr/share/fonts/X11/Type1,built-ins
DPMS (Energy Star):
  Standby: 0    Suspend: 0    Off: 0
  DPMS is Disabled

Notice these lines:

Screen Saver:
  prefer blanking:  yes
  • You would likely want prefer blanking: no

Also notice these lines:

DPMS (Energy Star):
  Standby: 0    Suspend: 0    Off: 0
  DPMS is Disabled
  • You would likely want DPMS is enabled to set monitor to Standby when desired.

Hopefully other users have used these options and post a detailed answer for you.

Solution 4:

How about simply closing the laptop?

Why?

These two monitors are one display area so turning one off will create a number of issues like screen redrawing, applications moving to the main monitor, ...

(I went down that road a few years ago and the only reliable way I found of doing what you want to do is to push the button on the external monitor or close the laptop)

Just ensure you set these power settings with gsettings set:

org.gnome.settings-daemon.plugins.power lid-close-suspend-with-external-monitor false
org.gnome.settings-daemon.plugins.power lid-close-ac-action 'nothing'
org.gnome.settings-daemon.plugins.power lid-close-battery-action 'nothing'