Use the XLib DPMS extension from C wrappers instead of subprocess calls

This commit is contained in:
Kristóf Tóth 2021-10-24 14:12:22 +02:00
parent 00bacdec3e
commit 0ce211b46d
3 changed files with 199 additions and 29 deletions

View File

@ -1,48 +1,74 @@
package display package display
// #cgo CFLAGS: --std=gnu99 -pedantic -Wall
// #cgo LDFLAGS: -lX11 -lXext
// #include "dpms.h"
// #include <stdlib.h>
// #include <stdbool.h>
import "C"
import ( import (
"os/exec" "unsafe"
"regexp" "errors"
"strings" "os"
) )
// Xset monitors/controls the display using xset // Xset monitors/controls the display using the DPMS X extension
type Xset struct {} type Xset struct {}
// Suspend the display // Suspend the display
func (Xset) Suspend() error { func (Xset) Suspend() error {
cmd := exec.Command("xset", xDisplayName := C.CString(tryGetXDisplay())
"dpms", defer C.free(unsafe.Pointer(xDisplayName))
"force",
"suspend", err := C.turn_display_off(xDisplayName)
) if err != C.int(0) {
return cmd.Run() return convertErrorIntToGoError(err)
}
return nil
}
func tryGetXDisplay() string {
display := os.Getenv("DISPLAY")
if display == "" {
panic(errors.New("DISPLAY envvar not set"))
}
return display
}
func convertErrorIntToGoError(err C.int) error {
errorMsg := ""
switch err {
case C.BADDISPLAY:
errorMsg = "Failed to open X display"
case C.NODPMSEXTENSION:
errorMsg = "DPMS extension is not available"
case C.NOTDPMSCAPABLE:
errorMsg = "Display is not DPMS capable"
case C.UNKNOWNDPMSMODE:
errorMsg = "Reported DPMS state unknown"
case C.DPMSDISABLED:
errorMsg = "DPMS is disabled"
default:
errorMsg = "Unknown error from dpms.h"
}
return errors.New(errorMsg)
} }
// IsOn determines if the display is on // IsOn determines if the display is on
func (Xset) IsOn() (bool, error) { func (Xset) IsOn() (bool, error) {
cmd := exec.Command("xset", "q") xDisplayName := C.CString(tryGetXDisplay())
outputBytes, err := cmd.CombinedOutput() defer C.free(unsafe.Pointer(xDisplayName))
if err != nil { var isDisplayOn C.bool
return false, err
}
return determineIfDisplayIsOn(string(outputBytes)), nil
}
func determineIfDisplayIsOn(xsetOutput string) bool { err := C.is_display_on(xDisplayName, &isDisplayOn)
if ! strings.Contains(xsetOutput, "Monitor") { if err != C.int(0) {
// after booting xset q is missing this line return false, convertErrorIntToGoError(err)
// lacking better ideas assume display is on
return true
} }
re := regexp.MustCompile(`Monitor is (\w+)`) if isDisplayOn {
matches := re.FindStringSubmatch(xsetOutput) return true, nil
if len(matches) >= 2 {
if matches[1] == "On" {
return true
} }
} return false, nil
return false
} }

130
xdg/display/dpms.c Normal file
View File

@ -0,0 +1,130 @@
/*
* Based on xorg-xset
* https://github.com/freedesktop/xorg-xset/blob/master/xset.c
*/
#include "dpms.h"
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <stdbool.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/extensions/dpms.h>
int check_dpms_extension(Display* disp)
{
int err = 0;
int dummy;
if (DPMSQueryExtension(disp, &dummy, &dummy))
{
if (!DPMSCapable(disp))
err = NOTDPMSCAPABLE;
}
else
err = NODPMSEXTENSION;
return err;
}
/*
* status->state can result in:
* - DPMSModeOn
* - DPMSModeStandby
* - DPMSModeSuspend
* - DPMSModeOff
*
* anything else means unknown state
*/
int check_dpms_status(Display* disp, CARD16* state)
{
BOOL dpmsEnabled;
int error = check_dpms_extension(disp);
if (!error)
{
DPMSInfo(disp, state, &dpmsEnabled);
if (!dpmsEnabled)
return DPMSDISABLED;
}
else
return error;
return 0;
}
int is_display_on(const char* xDisplayName, bool* isOn)
{
Display* disp = XOpenDisplay(xDisplayName);
if (disp == NULL)
return BADDISPLAY;
CARD16 state;
int returnError = 0;
int error = check_dpms_status(disp, &state);
if (!error)
{
switch (state)
{
case DPMSModeOn:
*isOn = true;
break;
case DPMSModeStandby: case DPMSModeSuspend: case DPMSModeOff:
*isOn = false;
break;
default:
returnError = UNKNOWNDPMSMODE;
break;
}
}
else if (error == DPMSDISABLED)
*isOn = true;
else if (error)
returnError = error;
XCloseDisplay(disp);
return returnError;
}
/*
* Citing the original xorg-xset:
*
* The calls to usleep below are necessary to
* delay the actual DPMS mode setting briefly.
* Without them, it's likely that the mode will be
* set between the Down and Up key transitions, in
* which case the Up transition may immediately
* turn the display back on.
*/
int dpms_force_off(Display* disp)
{
int error = check_dpms_extension(disp);
if (!error)
{
DPMSEnable(disp);
usleep(100000);
DPMSForceLevel(disp, DPMSModeOff);
}
return error;
}
int turn_display_off(const char* xDisplayName)
{
Display* disp = XOpenDisplay(xDisplayName);
if (disp == NULL)
return BADDISPLAY;
int returnError = 0;
int error = dpms_force_off(disp);
if (error)
returnError = error;
XCloseDisplay(disp);
return returnError;
}

14
xdg/display/dpms.h Normal file
View File

@ -0,0 +1,14 @@
#pragma once
#include <stdbool.h>
#define BADDISPLAY -1
#define DPMSDISABLED -2
#define NODPMSEXTENSION -3
#define NOTDPMSCAPABLE -4
#define UNKNOWNDPMSMODE -5
int turn_display_off(const char* xDisplayName);
int is_display_on(const char* xDisplayName, bool* isOn);