603 lines
14 KiB
C++
603 lines
14 KiB
C++
/*
|
|
* synergy-plus -- mouse and keyboard sharing utility
|
|
* Copyright (C) 2009 The Synergy+ Project
|
|
* 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.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#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 HAVE_X11_EXTENSIONS_XTEST_H
|
|
# include <X11/extensions/XTest.h>
|
|
#else
|
|
# error The XTest extension is required to build synergy
|
|
#endif
|
|
#if HAVE_X11_EXTENSIONS_DPMS_H
|
|
extern "C" {
|
|
# include <X11/Xmd.h>
|
|
# include <X11/extensions/dpms.h>
|
|
# if !HAVE_DPMS_PROTOTYPES
|
|
# undef DPMSModeOn
|
|
# undef DPMSModeStandby
|
|
# undef DPMSModeSuspend
|
|
# undef DPMSModeOff
|
|
# define DPMSModeOn 0
|
|
# define DPMSModeStandby 1
|
|
# define DPMSModeSuspend 2
|
|
# define DPMSModeOff 3
|
|
extern Bool DPMSQueryExtension(Display *, int *, int *);
|
|
extern Bool DPMSCapable(Display *);
|
|
extern Status DPMSEnable(Display *);
|
|
extern Status DPMSDisable(Display *);
|
|
extern Status DPMSForceLevel(Display *, CARD16);
|
|
extern Status DPMSInfo(Display *, CARD16 *, BOOL *);
|
|
# endif
|
|
}
|
|
#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),
|
|
m_disablePos(0)
|
|
{
|
|
// 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 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;
|
|
XWindowAttributes attr;
|
|
{
|
|
CXWindowsUtil::CErrorLock lock(m_display, &error);
|
|
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)
|
|
{
|
|
// get window attributes
|
|
bool error = false;
|
|
XWindowAttributes attr;
|
|
{
|
|
CXWindowsUtil::CErrorLock lock(m_display, &error);
|
|
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) {
|
|
error = false;
|
|
{
|
|
CXWindowsUtil::CErrorLock lock(m_display, &error);
|
|
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 = m_disablePos;
|
|
event.xmotion.y = 0;
|
|
event.xmotion.x_root = m_disablePos;
|
|
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);
|
|
|
|
m_disablePos = 20 - m_disablePos;
|
|
}
|
|
}
|
|
|
|
void
|
|
CXWindowsScreenSaver::activateDPMS(bool activate)
|
|
{
|
|
#if 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 HAVE_X11_EXTENSIONS_DPMS_H
|
|
if (m_dpms) {
|
|
if (enable) {
|
|
DPMSEnable(m_display);
|
|
}
|
|
else {
|
|
DPMSDisable(m_display);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool
|
|
CXWindowsScreenSaver::isDPMSEnabled() const
|
|
{
|
|
#if 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 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
|
|
}
|