• Main Page
  • Classes
  • Files
  • File List

CXWindowsScreenSaver.cpp

00001 /*
00002  * synergy -- mouse and keyboard sharing utility
00003  * Copyright (C) 2012 Bolton Software Ltd.
00004  * Copyright (C) 2002 Chris Schoeneman
00005  * 
00006  * This package is free software; you can redistribute it and/or
00007  * modify it under the terms of the GNU General Public License
00008  * found in the file COPYING that should have accompanied this file.
00009  * 
00010  * This package is distributed in the hope that it will be useful,
00011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013  * GNU General Public License for more details.
00014  *
00015  * You should have received a copy of the GNU General Public License
00016  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
00017  */
00018 
00019 #include "CXWindowsScreenSaver.h"
00020 #include "CXWindowsUtil.h"
00021 #include "IPlatformScreen.h"
00022 #include "CLog.h"
00023 #include "CEvent.h"
00024 #include "IEventQueue.h"
00025 #include "TMethodEventJob.h"
00026 #include <X11/Xatom.h>
00027 #if HAVE_X11_EXTENSIONS_XTEST_H
00028 #   include <X11/extensions/XTest.h>
00029 #else
00030 #   error The XTest extension is required to build synergy
00031 #endif
00032 #if HAVE_X11_EXTENSIONS_DPMS_H
00033 extern "C" {
00034 #   include <X11/Xmd.h>
00035 #   include <X11/extensions/dpms.h>
00036 #   if !HAVE_DPMS_PROTOTYPES
00037 #       undef DPMSModeOn
00038 #       undef DPMSModeStandby
00039 #       undef DPMSModeSuspend
00040 #       undef DPMSModeOff
00041 #       define DPMSModeOn       0
00042 #       define DPMSModeStandby  1
00043 #       define DPMSModeSuspend  2
00044 #       define DPMSModeOff      3
00045 extern Bool DPMSQueryExtension(Display *, int *, int *);
00046 extern Bool DPMSCapable(Display *);
00047 extern Status DPMSEnable(Display *);
00048 extern Status DPMSDisable(Display *);
00049 extern Status DPMSForceLevel(Display *, CARD16);
00050 extern Status DPMSInfo(Display *, CARD16 *, BOOL *);
00051 #   endif
00052 }
00053 #endif
00054 
00055 //
00056 // CXWindowsScreenSaver
00057 //
00058 
00059 CXWindowsScreenSaver::CXWindowsScreenSaver(
00060                 Display* display, Window window, void* eventTarget, IEventQueue& eventQueue) :
00061     m_display(display),
00062     m_xscreensaverSink(window),
00063     m_eventTarget(eventTarget),
00064     m_xscreensaver(None),
00065     m_xscreensaverActive(false),
00066     m_dpms(false),
00067     m_disabled(false),
00068     m_suppressDisable(false),
00069     m_disableTimer(NULL),
00070     m_disablePos(0),
00071     m_eventQueue(eventQueue)
00072 {
00073     // get atoms
00074     m_atomScreenSaver           = XInternAtom(m_display,
00075                                         "SCREENSAVER", False);
00076     m_atomScreenSaverVersion    = XInternAtom(m_display,
00077                                         "_SCREENSAVER_VERSION", False);
00078     m_atomScreenSaverActivate   = XInternAtom(m_display,
00079                                         "ACTIVATE", False);
00080     m_atomScreenSaverDeactivate = XInternAtom(m_display,
00081                                         "DEACTIVATE", False);
00082 
00083     // check for DPMS extension.  this is an alternative screen saver
00084     // that powers down the display.
00085 #if HAVE_X11_EXTENSIONS_DPMS_H
00086     int eventBase, errorBase;
00087     if (DPMSQueryExtension(m_display, &eventBase, &errorBase)) {
00088         if (DPMSCapable(m_display)) {
00089             // we have DPMS
00090             m_dpms  = true;
00091         }
00092     }
00093 #endif
00094 
00095     // watch top-level windows for changes
00096     bool error = false;
00097     {
00098         CXWindowsUtil::CErrorLock lock(m_display, &error);
00099         Window root = DefaultRootWindow(m_display);
00100         XWindowAttributes attr;
00101         XGetWindowAttributes(m_display, root, &attr);
00102         m_rootEventMask = attr.your_event_mask;
00103         XSelectInput(m_display, root, m_rootEventMask | SubstructureNotifyMask);
00104     }
00105     if (error) {
00106         LOG((CLOG_DEBUG "didn't set root event mask"));
00107         m_rootEventMask = 0;
00108     }
00109 
00110     // get the built-in settings
00111     XGetScreenSaver(m_display, &m_timeout, &m_interval,
00112                                 &m_preferBlanking, &m_allowExposures);
00113 
00114     // get the DPMS settings
00115     m_dpmsEnabled = isDPMSEnabled();
00116 
00117     // get the xscreensaver window, if any
00118     if (!findXScreenSaver()) {
00119         setXScreenSaver(None);
00120     }
00121 
00122     // install disable timer event handler
00123     m_eventQueue.adoptHandler(CEvent::kTimer, this,
00124                             new TMethodEventJob<CXWindowsScreenSaver>(this,
00125                                 &CXWindowsScreenSaver::handleDisableTimer));
00126 }
00127 
00128 CXWindowsScreenSaver::~CXWindowsScreenSaver()
00129 {
00130     // done with disable job
00131     if (m_disableTimer != NULL) {
00132         m_eventQueue.deleteTimer(m_disableTimer);
00133     }
00134     m_eventQueue.removeHandler(CEvent::kTimer, this);
00135 
00136     if (m_display != NULL) {
00137         enableDPMS(m_dpmsEnabled);
00138         XSetScreenSaver(m_display, m_timeout, m_interval,
00139                                 m_preferBlanking, m_allowExposures);
00140         clearWatchForXScreenSaver();
00141         CXWindowsUtil::CErrorLock lock(m_display);
00142         XSelectInput(m_display, DefaultRootWindow(m_display), m_rootEventMask);
00143     }
00144 }
00145 
00146 void
00147 CXWindowsScreenSaver::destroy()
00148 {
00149     m_display = NULL;
00150     delete this;
00151 }
00152 
00153 bool
00154 CXWindowsScreenSaver::handleXEvent(const XEvent* xevent)
00155 {
00156     switch (xevent->type) {
00157     case CreateNotify:
00158         if (m_xscreensaver == None) {
00159             if (isXScreenSaver(xevent->xcreatewindow.window)) {
00160                 // found the xscreensaver
00161                 setXScreenSaver(xevent->xcreatewindow.window);
00162             }
00163             else {
00164                 // another window to watch.  to detect the xscreensaver
00165                 // window we look for a property but that property may
00166                 // not yet exist by the time we get this event so we
00167                 // have to watch the window for property changes.
00168                 // this would be so much easier if xscreensaver did the
00169                 // smart thing and stored its window in a property on
00170                 // the root window.
00171                 addWatchXScreenSaver(xevent->xcreatewindow.window);
00172             }
00173         }
00174         break;
00175 
00176     case DestroyNotify:
00177         if (xevent->xdestroywindow.window == m_xscreensaver) {
00178             // xscreensaver is gone
00179             LOG((CLOG_DEBUG "xscreensaver died"));
00180             setXScreenSaver(None);
00181             return true;
00182         }
00183         break;
00184 
00185     case PropertyNotify:
00186         if (xevent->xproperty.state == PropertyNewValue) {
00187             if (isXScreenSaver(xevent->xproperty.window)) {
00188                 // found the xscreensaver
00189                 setXScreenSaver(xevent->xcreatewindow.window);
00190             }
00191         }
00192         break;
00193 
00194     case MapNotify:
00195         if (xevent->xmap.window == m_xscreensaver) {
00196             // xscreensaver has activated
00197             setXScreenSaverActive(true);
00198             return true;
00199         }
00200         break;
00201 
00202     case UnmapNotify:
00203         if (xevent->xunmap.window == m_xscreensaver) {
00204             // xscreensaver has deactivated
00205             setXScreenSaverActive(false);
00206             return true;
00207         }
00208         break;
00209     }
00210 
00211     return false;
00212 }
00213 
00214 void
00215 CXWindowsScreenSaver::enable()
00216 {
00217     // for xscreensaver
00218     m_disabled = false;
00219     updateDisableTimer();
00220 
00221     // for built-in X screen saver
00222     XSetScreenSaver(m_display, m_timeout, m_interval,
00223                                 m_preferBlanking, m_allowExposures);
00224 
00225     // for DPMS
00226     enableDPMS(m_dpmsEnabled);
00227 }
00228 
00229 void
00230 CXWindowsScreenSaver::disable()
00231 {
00232     // for xscreensaver
00233     m_disabled = true;
00234     updateDisableTimer();
00235 
00236     // use built-in X screen saver
00237     XGetScreenSaver(m_display, &m_timeout, &m_interval,
00238                                 &m_preferBlanking, &m_allowExposures);
00239     XSetScreenSaver(m_display, 0, m_interval,
00240                                 m_preferBlanking, m_allowExposures);
00241 
00242     // for DPMS
00243     m_dpmsEnabled = isDPMSEnabled();
00244     enableDPMS(false);
00245 
00246     // FIXME -- now deactivate?
00247 }
00248 
00249 void
00250 CXWindowsScreenSaver::activate()
00251 {
00252     // remove disable job timer
00253     m_suppressDisable = true;
00254     updateDisableTimer();
00255 
00256     // enable DPMS if it was enabled
00257     enableDPMS(m_dpmsEnabled);
00258 
00259     // try xscreensaver
00260     findXScreenSaver();
00261     if (m_xscreensaver != None) {
00262         sendXScreenSaverCommand(m_atomScreenSaverActivate);
00263         return;
00264     }
00265 
00266     // try built-in X screen saver
00267     if (m_timeout != 0) {
00268         XForceScreenSaver(m_display, ScreenSaverActive);
00269     }
00270 
00271     // try DPMS
00272     activateDPMS(true);
00273 }
00274 
00275 void
00276 CXWindowsScreenSaver::deactivate()
00277 {
00278     // reinstall disable job timer
00279     m_suppressDisable = false;
00280     updateDisableTimer();
00281 
00282     // try DPMS
00283     activateDPMS(false);
00284 
00285     // disable DPMS if screen saver is disabled
00286     if (m_disabled) {
00287         enableDPMS(false);
00288     }
00289 
00290     // try xscreensaver
00291     findXScreenSaver();
00292     if (m_xscreensaver != None) {
00293         sendXScreenSaverCommand(m_atomScreenSaverDeactivate);
00294         return;
00295     }
00296 
00297     // use built-in X screen saver
00298     XForceScreenSaver(m_display, ScreenSaverReset);
00299 }
00300 
00301 bool
00302 CXWindowsScreenSaver::isActive() const
00303 {
00304     // check xscreensaver
00305     if (m_xscreensaver != None) {
00306         return m_xscreensaverActive;
00307     }
00308 
00309     // check DPMS
00310     if (isDPMSActivated()) {
00311         return true;
00312     }
00313 
00314     // can't check built-in X screen saver activity
00315     return false;
00316 }
00317 
00318 bool
00319 CXWindowsScreenSaver::findXScreenSaver()
00320 {
00321     // do nothing if we've already got the xscreensaver window
00322     if (m_xscreensaver == None) {
00323         // find top-level window xscreensaver window
00324         Window root = DefaultRootWindow(m_display);
00325         Window rw, pw, *cw;
00326         unsigned int nc;
00327         if (XQueryTree(m_display, root, &rw, &pw, &cw, &nc)) {
00328             for (unsigned int i = 0; i < nc; ++i) {
00329                 if (isXScreenSaver(cw[i])) {
00330                     setXScreenSaver(cw[i]);
00331                     break;
00332                 }
00333             }
00334             XFree(cw);
00335         }
00336     }
00337 
00338     return (m_xscreensaver != None);
00339 }
00340 
00341 void
00342 CXWindowsScreenSaver::setXScreenSaver(Window window)
00343 {
00344     LOG((CLOG_DEBUG "xscreensaver window: 0x%08x", window));
00345 
00346     // save window
00347     m_xscreensaver = window;
00348 
00349     if (m_xscreensaver != None) {
00350         // clear old watch list
00351         clearWatchForXScreenSaver();
00352 
00353         // see if xscreensaver is active
00354         bool error = false;
00355         XWindowAttributes attr;
00356         {
00357             CXWindowsUtil::CErrorLock lock(m_display, &error);
00358             XGetWindowAttributes(m_display, m_xscreensaver, &attr);
00359         }
00360         setXScreenSaverActive(!error && attr.map_state != IsUnmapped);
00361 
00362         // save current DPMS state;  xscreensaver may have changed it.
00363         m_dpmsEnabled = isDPMSEnabled();
00364     }
00365     else {
00366         // screen saver can't be active if it doesn't exist
00367         setXScreenSaverActive(false);
00368 
00369         // start watching for xscreensaver
00370         watchForXScreenSaver();
00371     }
00372 }
00373 
00374 bool
00375 CXWindowsScreenSaver::isXScreenSaver(Window w) const
00376 {
00377     // check for m_atomScreenSaverVersion string property
00378     Atom type;
00379     return (CXWindowsUtil::getWindowProperty(m_display, w,
00380                                     m_atomScreenSaverVersion,
00381                                     NULL, &type, NULL, False) &&
00382                                 type == XA_STRING);
00383 }
00384 
00385 void
00386 CXWindowsScreenSaver::setXScreenSaverActive(bool activated)
00387 {
00388     if (m_xscreensaverActive != activated) {
00389         LOG((CLOG_DEBUG "xscreensaver %s on window 0x%08x", activated ? "activated" : "deactivated", m_xscreensaver));
00390         m_xscreensaverActive = activated;
00391 
00392         // if screen saver was activated forcefully (i.e. against
00393         // our will) then just accept it.  don't try to keep it
00394         // from activating since that'll just pop up the password
00395         // dialog if locking is enabled.
00396         m_suppressDisable = activated;
00397         updateDisableTimer();
00398 
00399         if (activated) {
00400             m_eventQueue.addEvent(CEvent(
00401                             IPlatformScreen::getScreensaverActivatedEvent(),
00402                             m_eventTarget));
00403         }
00404         else {
00405             m_eventQueue.addEvent(CEvent(
00406                             IPlatformScreen::getScreensaverDeactivatedEvent(),
00407                             m_eventTarget));
00408         }
00409     }
00410 }
00411 
00412 void
00413 CXWindowsScreenSaver::sendXScreenSaverCommand(Atom cmd, long arg1, long arg2)
00414 {
00415     XEvent event;
00416     event.xclient.type         = ClientMessage;
00417     event.xclient.display      = m_display;
00418     event.xclient.window       = m_xscreensaverSink;
00419     event.xclient.message_type = m_atomScreenSaver;
00420     event.xclient.format       = 32;
00421     event.xclient.data.l[0]    = static_cast<long>(cmd);
00422     event.xclient.data.l[1]    = arg1;
00423     event.xclient.data.l[2]    = arg2;
00424     event.xclient.data.l[3]    = 0;
00425     event.xclient.data.l[4]    = 0;
00426 
00427     LOG((CLOG_DEBUG "send xscreensaver command: %d %d %d", (long)cmd, arg1, arg2));
00428     bool error = false;
00429     {
00430         CXWindowsUtil::CErrorLock lock(m_display, &error);
00431         XSendEvent(m_display, m_xscreensaver, False, 0, &event);
00432     }
00433     if (error) {
00434         findXScreenSaver();
00435     }
00436 }
00437 
00438 void
00439 CXWindowsScreenSaver::watchForXScreenSaver()
00440 {
00441     // clear old watch list
00442     clearWatchForXScreenSaver();
00443 
00444     // add every child of the root to the list of windows to watch
00445     Window root = DefaultRootWindow(m_display);
00446     Window rw, pw, *cw;
00447     unsigned int nc;
00448     if (XQueryTree(m_display, root, &rw, &pw, &cw, &nc)) {
00449         for (unsigned int i = 0; i < nc; ++i) {
00450             addWatchXScreenSaver(cw[i]);
00451         }
00452         XFree(cw);
00453     }
00454 
00455     // now check for xscreensaver window in case it set the property
00456     // before we could request property change events.
00457     if (findXScreenSaver()) {
00458         // found it so clear out our watch list
00459         clearWatchForXScreenSaver();
00460     }
00461 }
00462 
00463 void
00464 CXWindowsScreenSaver::clearWatchForXScreenSaver()
00465 {
00466     // stop watching all windows
00467     CXWindowsUtil::CErrorLock lock(m_display);
00468     for (CWatchList::iterator index = m_watchWindows.begin();
00469                                 index != m_watchWindows.end(); ++index) {
00470         XSelectInput(m_display, index->first, index->second);
00471     }
00472     m_watchWindows.clear();
00473 }
00474 
00475 void
00476 CXWindowsScreenSaver::addWatchXScreenSaver(Window window)
00477 {
00478     // get window attributes
00479     bool error = false;
00480     XWindowAttributes attr;
00481     {
00482         CXWindowsUtil::CErrorLock lock(m_display, &error);
00483         XGetWindowAttributes(m_display, window, &attr);
00484     }
00485 
00486     // if successful and window uses override_redirect (like xscreensaver
00487     // does) then watch it for property changes.  
00488     if (!error && attr.override_redirect == True) {
00489         error = false;
00490         {
00491             CXWindowsUtil::CErrorLock lock(m_display, &error);
00492             XSelectInput(m_display, window,
00493                                 attr.your_event_mask | PropertyChangeMask);
00494         }
00495         if (!error) {
00496             // if successful then add the window to our list
00497             m_watchWindows.insert(std::make_pair(window, attr.your_event_mask));
00498         }
00499     }
00500 }
00501 
00502 void
00503 CXWindowsScreenSaver::updateDisableTimer()
00504 {
00505     if (m_disabled && !m_suppressDisable && m_disableTimer == NULL) {
00506         // 5 seconds should be plenty often to suppress the screen saver
00507         m_disableTimer = m_eventQueue.newTimer(5.0, this);
00508     }
00509     else if ((!m_disabled || m_suppressDisable) && m_disableTimer != NULL) {
00510         m_eventQueue.deleteTimer(m_disableTimer);
00511         m_disableTimer = NULL;
00512     }
00513 }
00514 
00515 void
00516 CXWindowsScreenSaver::handleDisableTimer(const CEvent&, void*)
00517 {
00518     // send fake mouse motion directly to xscreensaver
00519     if (m_xscreensaver != None) {
00520         XEvent event;
00521         event.xmotion.type         = MotionNotify;
00522         event.xmotion.display      = m_display;
00523         event.xmotion.window       = m_xscreensaver;
00524         event.xmotion.root         = DefaultRootWindow(m_display);
00525         event.xmotion.subwindow    = None;
00526         event.xmotion.time         = CurrentTime;
00527         event.xmotion.x            = m_disablePos;
00528         event.xmotion.y            = 0;
00529         event.xmotion.x_root       = m_disablePos;
00530         event.xmotion.y_root       = 0;
00531         event.xmotion.state        = 0;
00532         event.xmotion.is_hint      = NotifyNormal;
00533         event.xmotion.same_screen  = True;
00534 
00535         CXWindowsUtil::CErrorLock lock(m_display);
00536         XSendEvent(m_display, m_xscreensaver, False, 0, &event);
00537 
00538         m_disablePos = 20 - m_disablePos;
00539     }
00540 }
00541 
00542 void
00543 CXWindowsScreenSaver::activateDPMS(bool activate)
00544 {
00545 #if HAVE_X11_EXTENSIONS_DPMS_H
00546     if (m_dpms) {
00547         // DPMSForceLevel will generate a BadMatch if DPMS is disabled
00548         CXWindowsUtil::CErrorLock lock(m_display);
00549         DPMSForceLevel(m_display, activate ? DPMSModeStandby : DPMSModeOn);
00550     }
00551 #endif
00552 }
00553 
00554 void
00555 CXWindowsScreenSaver::enableDPMS(bool enable)
00556 {
00557 #if HAVE_X11_EXTENSIONS_DPMS_H
00558     if (m_dpms) {
00559         if (enable) {
00560             DPMSEnable(m_display);
00561         }
00562         else {
00563             DPMSDisable(m_display);
00564         }
00565     }
00566 #endif
00567 }
00568 
00569 bool
00570 CXWindowsScreenSaver::isDPMSEnabled() const
00571 {
00572 #if HAVE_X11_EXTENSIONS_DPMS_H
00573     if (m_dpms) {
00574         CARD16 level;
00575         BOOL state;
00576         DPMSInfo(m_display, &level, &state);
00577         return (state != False);
00578     }
00579     else {
00580         return false;
00581     }
00582 #else
00583     return false;
00584 #endif
00585 }
00586 
00587 bool
00588 CXWindowsScreenSaver::isDPMSActivated() const
00589 {
00590 #if HAVE_X11_EXTENSIONS_DPMS_H
00591     if (m_dpms) {
00592         CARD16 level;
00593         BOOL state;
00594         DPMSInfo(m_display, &level, &state);
00595         return (level != DPMSModeOn);
00596     }
00597     else {
00598         return false;
00599     }
00600 #else
00601     return false;
00602 #endif
00603 }

Generated on Fri May 24 2013 00:00:04 for Synergy by  doxygen 1.7.1