/* * 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 #if HAVE_X11_EXTENSIONS_XTEST_H # include #else # error The XTest extension is required to build synergy #endif #if HAVE_X11_EXTENSIONS_DPMS_H extern "C" { # include # include # 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) { // 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(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(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 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 }