diff --git a/xdg/display/display.go b/xdg/display/display.go index bc506fe..1807d9a 100644 --- a/xdg/display/display.go +++ b/xdg/display/display.go @@ -1,48 +1,74 @@ package display +// #cgo CFLAGS: --std=gnu99 -pedantic -Wall +// #cgo LDFLAGS: -lX11 -lXext +// #include "dpms.h" +// #include +// #include +import "C" import ( - "os/exec" - "regexp" - "strings" + "unsafe" + "errors" + "os" ) -// Xset monitors/controls the display using xset +// Xset monitors/controls the display using the DPMS X extension type Xset struct {} // Suspend the display func (Xset) Suspend() error { - cmd := exec.Command("xset", - "dpms", - "force", - "suspend", - ) - return cmd.Run() + xDisplayName := C.CString(tryGetXDisplay()) + defer C.free(unsafe.Pointer(xDisplayName)) + + err := C.turn_display_off(xDisplayName) + if err != C.int(0) { + 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 func (Xset) IsOn() (bool, error) { - cmd := exec.Command("xset", "q") - outputBytes, err := cmd.CombinedOutput() - if err != nil { - return false, err - } - return determineIfDisplayIsOn(string(outputBytes)), nil -} + xDisplayName := C.CString(tryGetXDisplay()) + defer C.free(unsafe.Pointer(xDisplayName)) + var isDisplayOn C.bool -func determineIfDisplayIsOn(xsetOutput string) bool { - if ! strings.Contains(xsetOutput, "Monitor") { - // after booting xset q is missing this line - // lacking better ideas assume display is on - return true + err := C.is_display_on(xDisplayName, &isDisplayOn) + if err != C.int(0) { + return false, convertErrorIntToGoError(err) } - re := regexp.MustCompile(`Monitor is (\w+)`) - matches := re.FindStringSubmatch(xsetOutput) - if len(matches) >= 2 { - if matches[1] == "On" { - return true - } + if isDisplayOn { + return true, nil } - return false + return false, nil } diff --git a/xdg/display/dpms.c b/xdg/display/dpms.c new file mode 100644 index 0000000..2fbf552 --- /dev/null +++ b/xdg/display/dpms.c @@ -0,0 +1,130 @@ +/* +* Based on xorg-xset +* https://github.com/freedesktop/xorg-xset/blob/master/xset.c +*/ + +#include "dpms.h" + +#include +#include +#include +#include +#include + +#include +#include + + +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; +} diff --git a/xdg/display/dpms.h b/xdg/display/dpms.h new file mode 100644 index 0000000..1191201 --- /dev/null +++ b/xdg/display/dpms.h @@ -0,0 +1,14 @@ +#pragma once + +#include + + +#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);