Main Page | Class Hierarchy | Alphabetical List | Class List | File List | Class Members

CXWindowsScreenSaver.cpp

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

Generated on Fri Nov 6 00:21:15 2009 for synergy-plus by  doxygen 1.3.9.1