mirror of
https://github.com/debauchee/barrier.git
synced 2024-12-18 08:22:06 +03:00
a1c807ba67
that allows you to power down the display. Previously, synergy would not power on the display if DPMS was enabled and activated and xscreensaver was not running. It also wouldn't disable DPMS so the display would power down normally on a synergy client if there was no input activity.
570 lines
14 KiB
C++
570 lines
14 KiB
C++
/*
|
|
* synergy -- mouse and keyboard sharing utility
|
|
* Copyright (C) 2002 Chris Schoeneman
|
|
*
|
|
* This package is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* found in the file COPYING that should have accompanied this file.
|
|
*
|
|
* This package is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
#include "CXWindowsScreenSaver.h"
|
|
#include "CXWindowsUtil.h"
|
|
#include "IPlatformScreen.h"
|
|
#include "CLog.h"
|
|
#include "CEvent.h"
|
|
#include "IEventQueue.h"
|
|
#include "TMethodEventJob.h"
|
|
#include <X11/Xatom.h>
|
|
#if defined(HAVE_X11_EXTENSIONS_XTEST_H)
|
|
# include <X11/extensions/XTest.h>
|
|
#else
|
|
# error The XTest extension is required to build synergy
|
|
#endif
|
|
#if defined(HAVE_X11_EXTENSIONS_DPMS_H)
|
|
extern "C" {
|
|
# include <X11/extensions/dpms.h>
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// CXWindowsScreenSaver
|
|
//
|
|
|
|
CXWindowsScreenSaver::CXWindowsScreenSaver(
|
|
Display* display, Window window, void* eventTarget) :
|
|
m_display(display),
|
|
m_xscreensaverSink(window),
|
|
m_eventTarget(eventTarget),
|
|
m_xscreensaver(None),
|
|
m_xscreensaverActive(false),
|
|
m_dpms(false),
|
|
m_disabled(false),
|
|
m_suppressDisable(false),
|
|
m_disableTimer(NULL)
|
|
{
|
|
// get atoms
|
|
m_atomScreenSaver = XInternAtom(m_display,
|
|
"SCREENSAVER", False);
|
|
m_atomScreenSaverVersion = XInternAtom(m_display,
|
|
"_SCREENSAVER_VERSION", False);
|
|
m_atomScreenSaverActivate = XInternAtom(m_display,
|
|
"ACTIVATE", False);
|
|
m_atomScreenSaverDeactivate = XInternAtom(m_display,
|
|
"DEACTIVATE", False);
|
|
|
|
// check for DPMS extension. this is an alternative screen saver
|
|
// that powers down the display.
|
|
#if defined(HAVE_X11_EXTENSIONS_DPMS_H)
|
|
int eventBase, errorBase;
|
|
if (DPMSQueryExtension(m_display, &eventBase, &errorBase)) {
|
|
if (DPMSCapable(m_display)) {
|
|
// we have DPMS
|
|
m_dpms = true;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// watch top-level windows for changes
|
|
{
|
|
bool error = false;
|
|
CXWindowsUtil::CErrorLock lock(m_display, &error);
|
|
Window root = DefaultRootWindow(m_display);
|
|
XWindowAttributes attr;
|
|
XGetWindowAttributes(m_display, root, &attr);
|
|
m_rootEventMask = attr.your_event_mask;
|
|
XSelectInput(m_display, root, m_rootEventMask | SubstructureNotifyMask);
|
|
if (error) {
|
|
LOG((CLOG_DEBUG "didn't set root event mask"));
|
|
m_rootEventMask = 0;
|
|
}
|
|
}
|
|
|
|
// get the built-in settings
|
|
XGetScreenSaver(m_display, &m_timeout, &m_interval,
|
|
&m_preferBlanking, &m_allowExposures);
|
|
|
|
// get the DPMS settings
|
|
m_dpmsEnabled = isDPMSEnabled();
|
|
|
|
// get the xscreensaver window, if any
|
|
if (!findXScreenSaver()) {
|
|
setXScreenSaver(None);
|
|
}
|
|
|
|
// install disable timer event handler
|
|
EVENTQUEUE->adoptHandler(CEvent::kTimer, this,
|
|
new TMethodEventJob<CXWindowsScreenSaver>(this,
|
|
&CXWindowsScreenSaver::handleDisableTimer));
|
|
}
|
|
|
|
CXWindowsScreenSaver::~CXWindowsScreenSaver()
|
|
{
|
|
// done with disable job
|
|
if (m_disableTimer != NULL) {
|
|
EVENTQUEUE->deleteTimer(m_disableTimer);
|
|
}
|
|
EVENTQUEUE->removeHandler(CEvent::kTimer, this);
|
|
|
|
if (m_display != NULL) {
|
|
enableDPMS(m_dpmsEnabled);
|
|
XSetScreenSaver(m_display, m_timeout, m_interval,
|
|
m_preferBlanking, m_allowExposures);
|
|
clearWatchForXScreenSaver();
|
|
CXWindowsUtil::CErrorLock lock(m_display);
|
|
XSelectInput(m_display, DefaultRootWindow(m_display), m_rootEventMask);
|
|
}
|
|
}
|
|
|
|
void
|
|
CXWindowsScreenSaver::destroy()
|
|
{
|
|
m_display = NULL;
|
|
delete this;
|
|
}
|
|
|
|
bool
|
|
CXWindowsScreenSaver::handleXEvent(const XEvent* xevent)
|
|
{
|
|
switch (xevent->type) {
|
|
case CreateNotify:
|
|
if (m_xscreensaver == None) {
|
|
if (isXScreenSaver(xevent->xcreatewindow.window)) {
|
|
// found the xscreensaver
|
|
setXScreenSaver(xevent->xcreatewindow.window);
|
|
}
|
|
else {
|
|
// another window to watch. to detect the xscreensaver
|
|
// window we look for a property but that property may
|
|
// not yet exist by the time we get this event so we
|
|
// have to watch the window for property changes.
|
|
// this would be so much easier if xscreensaver did the
|
|
// smart thing and stored its window in a property on
|
|
// the root window.
|
|
addWatchXScreenSaver(xevent->xcreatewindow.window);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case DestroyNotify:
|
|
if (xevent->xdestroywindow.window == m_xscreensaver) {
|
|
// xscreensaver is gone
|
|
LOG((CLOG_DEBUG "xscreensaver died"));
|
|
setXScreenSaver(None);
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case PropertyNotify:
|
|
if (xevent->xproperty.state == PropertyNewValue) {
|
|
if (isXScreenSaver(xevent->xproperty.window)) {
|
|
// found the xscreensaver
|
|
setXScreenSaver(xevent->xcreatewindow.window);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case MapNotify:
|
|
if (xevent->xmap.window == m_xscreensaver) {
|
|
// xscreensaver has activated
|
|
setXScreenSaverActive(true);
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case UnmapNotify:
|
|
if (xevent->xunmap.window == m_xscreensaver) {
|
|
// xscreensaver has deactivated
|
|
setXScreenSaverActive(false);
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void
|
|
CXWindowsScreenSaver::enable()
|
|
{
|
|
// for xscreensaver
|
|
m_disabled = false;
|
|
updateDisableTimer();
|
|
|
|
// for built-in X screen saver
|
|
XSetScreenSaver(m_display, m_timeout, m_interval,
|
|
m_preferBlanking, m_allowExposures);
|
|
|
|
// for DPMS
|
|
enableDPMS(m_dpmsEnabled);
|
|
}
|
|
|
|
void
|
|
CXWindowsScreenSaver::disable()
|
|
{
|
|
// for xscreensaver
|
|
m_disabled = true;
|
|
updateDisableTimer();
|
|
|
|
// use built-in X screen saver
|
|
XGetScreenSaver(m_display, &m_timeout, &m_interval,
|
|
&m_preferBlanking, &m_allowExposures);
|
|
XSetScreenSaver(m_display, 0, m_interval,
|
|
m_preferBlanking, m_allowExposures);
|
|
|
|
// for DPMS
|
|
m_dpmsEnabled = isDPMSEnabled();
|
|
enableDPMS(false);
|
|
|
|
// FIXME -- now deactivate?
|
|
}
|
|
|
|
void
|
|
CXWindowsScreenSaver::activate()
|
|
{
|
|
// remove disable job timer
|
|
m_suppressDisable = true;
|
|
updateDisableTimer();
|
|
|
|
// enable DPMS if it was enabled
|
|
enableDPMS(m_dpmsEnabled);
|
|
|
|
// try xscreensaver
|
|
findXScreenSaver();
|
|
if (m_xscreensaver != None) {
|
|
sendXScreenSaverCommand(m_atomScreenSaverActivate);
|
|
return;
|
|
}
|
|
|
|
// try built-in X screen saver
|
|
if (m_timeout != 0) {
|
|
XForceScreenSaver(m_display, ScreenSaverActive);
|
|
}
|
|
|
|
// try DPMS
|
|
activateDPMS(true);
|
|
}
|
|
|
|
void
|
|
CXWindowsScreenSaver::deactivate()
|
|
{
|
|
// reinstall disable job timer
|
|
m_suppressDisable = false;
|
|
updateDisableTimer();
|
|
|
|
// try DPMS
|
|
activateDPMS(false);
|
|
|
|
// disable DPMS if screen saver is disabled
|
|
if (m_disabled) {
|
|
enableDPMS(false);
|
|
}
|
|
|
|
// try xscreensaver
|
|
findXScreenSaver();
|
|
if (m_xscreensaver != None) {
|
|
sendXScreenSaverCommand(m_atomScreenSaverDeactivate);
|
|
return;
|
|
}
|
|
|
|
// use built-in X screen saver
|
|
XForceScreenSaver(m_display, ScreenSaverReset);
|
|
}
|
|
|
|
bool
|
|
CXWindowsScreenSaver::isActive() const
|
|
{
|
|
// check xscreensaver
|
|
if (m_xscreensaver != None) {
|
|
return m_xscreensaverActive;
|
|
}
|
|
|
|
// check DPMS
|
|
if (isDPMSActivated()) {
|
|
return true;
|
|
}
|
|
|
|
// can't check built-in X screen saver activity
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
CXWindowsScreenSaver::findXScreenSaver()
|
|
{
|
|
// do nothing if we've already got the xscreensaver window
|
|
if (m_xscreensaver == None) {
|
|
// find top-level window xscreensaver window
|
|
Window root = DefaultRootWindow(m_display);
|
|
Window rw, pw, *cw;
|
|
unsigned int nc;
|
|
if (XQueryTree(m_display, root, &rw, &pw, &cw, &nc)) {
|
|
for (unsigned int i = 0; i < nc; ++i) {
|
|
if (isXScreenSaver(cw[i])) {
|
|
setXScreenSaver(cw[i]);
|
|
break;
|
|
}
|
|
}
|
|
XFree(cw);
|
|
}
|
|
}
|
|
|
|
return (m_xscreensaver != None);
|
|
}
|
|
|
|
void
|
|
CXWindowsScreenSaver::setXScreenSaver(Window window)
|
|
{
|
|
LOG((CLOG_DEBUG "xscreensaver window: 0x%08x", window));
|
|
|
|
// save window
|
|
m_xscreensaver = window;
|
|
|
|
if (m_xscreensaver != None) {
|
|
// clear old watch list
|
|
clearWatchForXScreenSaver();
|
|
|
|
// see if xscreensaver is active
|
|
bool error = false;
|
|
CXWindowsUtil::CErrorLock lock(m_display, &error);
|
|
XWindowAttributes attr;
|
|
XGetWindowAttributes(m_display, m_xscreensaver, &attr);
|
|
setXScreenSaverActive(!error && attr.map_state != IsUnmapped);
|
|
|
|
// save current DPMS state; xscreensaver may have changed it.
|
|
m_dpmsEnabled = isDPMSEnabled();
|
|
}
|
|
else {
|
|
// screen saver can't be active if it doesn't exist
|
|
setXScreenSaverActive(false);
|
|
|
|
// start watching for xscreensaver
|
|
watchForXScreenSaver();
|
|
}
|
|
}
|
|
|
|
bool
|
|
CXWindowsScreenSaver::isXScreenSaver(Window w) const
|
|
{
|
|
// check for m_atomScreenSaverVersion string property
|
|
Atom type;
|
|
return (CXWindowsUtil::getWindowProperty(m_display, w,
|
|
m_atomScreenSaverVersion,
|
|
NULL, &type, NULL, False) &&
|
|
type == XA_STRING);
|
|
}
|
|
|
|
void
|
|
CXWindowsScreenSaver::setXScreenSaverActive(bool activated)
|
|
{
|
|
if (m_xscreensaverActive != activated) {
|
|
LOG((CLOG_DEBUG "xscreensaver %s on window 0x%08x", activated ? "activated" : "deactivated", m_xscreensaver));
|
|
m_xscreensaverActive = activated;
|
|
|
|
// if screen saver was activated forcefully (i.e. against
|
|
// our will) then just accept it. don't try to keep it
|
|
// from activating since that'll just pop up the password
|
|
// dialog if locking is enabled.
|
|
m_suppressDisable = activated;
|
|
updateDisableTimer();
|
|
|
|
if (activated) {
|
|
EVENTQUEUE->addEvent(CEvent(
|
|
IPlatformScreen::getScreensaverActivatedEvent(),
|
|
m_eventTarget));
|
|
}
|
|
else {
|
|
EVENTQUEUE->addEvent(CEvent(
|
|
IPlatformScreen::getScreensaverDeactivatedEvent(),
|
|
m_eventTarget));
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
CXWindowsScreenSaver::sendXScreenSaverCommand(Atom cmd, long arg1, long arg2)
|
|
{
|
|
XEvent event;
|
|
event.xclient.type = ClientMessage;
|
|
event.xclient.display = m_display;
|
|
event.xclient.window = m_xscreensaverSink;
|
|
event.xclient.message_type = m_atomScreenSaver;
|
|
event.xclient.format = 32;
|
|
event.xclient.data.l[0] = static_cast<long>(cmd);
|
|
event.xclient.data.l[1] = arg1;
|
|
event.xclient.data.l[2] = arg2;
|
|
event.xclient.data.l[3] = 0;
|
|
event.xclient.data.l[4] = 0;
|
|
|
|
LOG((CLOG_DEBUG "send xscreensaver command: %d %d %d", (long)cmd, arg1, arg2));
|
|
bool error = false;
|
|
CXWindowsUtil::CErrorLock lock(m_display, &error);
|
|
XSendEvent(m_display, m_xscreensaver, False, 0, &event);
|
|
if (error) {
|
|
findXScreenSaver();
|
|
}
|
|
}
|
|
|
|
void
|
|
CXWindowsScreenSaver::watchForXScreenSaver()
|
|
{
|
|
// clear old watch list
|
|
clearWatchForXScreenSaver();
|
|
|
|
// add every child of the root to the list of windows to watch
|
|
Window root = DefaultRootWindow(m_display);
|
|
Window rw, pw, *cw;
|
|
unsigned int nc;
|
|
if (XQueryTree(m_display, root, &rw, &pw, &cw, &nc)) {
|
|
for (unsigned int i = 0; i < nc; ++i) {
|
|
addWatchXScreenSaver(cw[i]);
|
|
}
|
|
XFree(cw);
|
|
}
|
|
|
|
// now check for xscreensaver window in case it set the property
|
|
// before we could request property change events.
|
|
if (findXScreenSaver()) {
|
|
// found it so clear out our watch list
|
|
clearWatchForXScreenSaver();
|
|
}
|
|
}
|
|
|
|
void
|
|
CXWindowsScreenSaver::clearWatchForXScreenSaver()
|
|
{
|
|
// stop watching all windows
|
|
CXWindowsUtil::CErrorLock lock(m_display);
|
|
for (CWatchList::iterator index = m_watchWindows.begin();
|
|
index != m_watchWindows.end(); ++index) {
|
|
XSelectInput(m_display, index->first, index->second);
|
|
}
|
|
m_watchWindows.clear();
|
|
}
|
|
|
|
void
|
|
CXWindowsScreenSaver::addWatchXScreenSaver(Window window)
|
|
{
|
|
bool error = false;
|
|
CXWindowsUtil::CErrorLock lock(m_display, &error);
|
|
|
|
// get window attributes
|
|
XWindowAttributes attr;
|
|
XGetWindowAttributes(m_display, window, &attr);
|
|
|
|
// if successful and window uses override_redirect (like xscreensaver
|
|
// does) then watch it for property changes.
|
|
if (!error && attr.override_redirect == True) {
|
|
XSelectInput(m_display, window,
|
|
attr.your_event_mask | PropertyChangeMask);
|
|
if (!error) {
|
|
// if successful then add the window to our list
|
|
m_watchWindows.insert(std::make_pair(window, attr.your_event_mask));
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
CXWindowsScreenSaver::updateDisableTimer()
|
|
{
|
|
if (m_disabled && !m_suppressDisable && m_disableTimer == NULL) {
|
|
// 5 seconds should be plenty often to suppress the screen saver
|
|
m_disableTimer = EVENTQUEUE->newTimer(5.0, this);
|
|
}
|
|
else if ((!m_disabled || m_suppressDisable) && m_disableTimer != NULL) {
|
|
EVENTQUEUE->deleteTimer(m_disableTimer);
|
|
m_disableTimer = NULL;
|
|
}
|
|
}
|
|
|
|
void
|
|
CXWindowsScreenSaver::handleDisableTimer(const CEvent&, void*)
|
|
{
|
|
// send fake mouse motion directly to xscreensaver
|
|
if (m_xscreensaver != None) {
|
|
XEvent event;
|
|
event.xmotion.type = MotionNotify;
|
|
event.xmotion.display = m_display;
|
|
event.xmotion.window = m_xscreensaver;
|
|
event.xmotion.root = DefaultRootWindow(m_display);
|
|
event.xmotion.subwindow = None;
|
|
event.xmotion.time = CurrentTime;
|
|
event.xmotion.x = 0;
|
|
event.xmotion.y = 0;
|
|
event.xmotion.x_root = 0;
|
|
event.xmotion.y_root = 0;
|
|
event.xmotion.state = 0;
|
|
event.xmotion.is_hint = NotifyNormal;
|
|
event.xmotion.same_screen = True;
|
|
|
|
CXWindowsUtil::CErrorLock lock(m_display);
|
|
XSendEvent(m_display, m_xscreensaver, False, 0, &event);
|
|
}
|
|
}
|
|
|
|
void
|
|
CXWindowsScreenSaver::activateDPMS(bool activate)
|
|
{
|
|
#if defined(HAVE_X11_EXTENSIONS_DPMS_H)
|
|
if (m_dpms) {
|
|
// DPMSForceLevel will generate a BadMatch if DPMS is disabled
|
|
CXWindowsUtil::CErrorLock lock(m_display);
|
|
DPMSForceLevel(m_display, activate ? DPMSModeStandby : DPMSModeOn);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void
|
|
CXWindowsScreenSaver::enableDPMS(bool enable)
|
|
{
|
|
#if defined(HAVE_X11_EXTENSIONS_DPMS_H)
|
|
if (m_dpms) {
|
|
if (enable) {
|
|
DPMSEnable(m_display);
|
|
}
|
|
else {
|
|
DPMSDisable(m_display);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool
|
|
CXWindowsScreenSaver::isDPMSEnabled() const
|
|
{
|
|
#if defined(HAVE_X11_EXTENSIONS_DPMS_H)
|
|
if (m_dpms) {
|
|
CARD16 level;
|
|
BOOL state;
|
|
DPMSInfo(m_display, &level, &state);
|
|
return (state != False);
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool
|
|
CXWindowsScreenSaver::isDPMSActivated() const
|
|
{
|
|
#if defined(HAVE_X11_EXTENSIONS_DPMS_H)
|
|
if (m_dpms) {
|
|
CARD16 level;
|
|
BOOL state;
|
|
DPMSInfo(m_display, &level, &state);
|
|
return (level != DPMSModeOn);
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|