#include "CXWindowsScreenSaver.h" #include "CXWindowsScreen.h" #include "CXWindowsUtil.h" #include "CLog.h" #include "TMethodJob.h" #include #if defined(HAVE_X11_EXTENSIONS_XTEST_H) # include #else # error The XTest extension is required to build synergy #endif // // CXWindowsScreenSaver // CXWindowsScreenSaver::CXWindowsScreenSaver( CXWindowsScreen* screen, Display* display) : m_screen(screen), m_display(display), m_notify(None), m_xscreensaver(None), m_xscreensaverActive(false), m_disabled(false) { // screen saver disable callback m_disableJob = new TMethodJob(this, &CXWindowsScreenSaver::disableCallback); // 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); // create dummy window to receive xscreensaver responses. this // shouldn't be necessary (we should be able to send responses // to None) but it doesn't hurt. XSetWindowAttributes attr; attr.event_mask = 0;//PropertyChangeMask; attr.do_not_propagate_mask = 0; attr.override_redirect = True; m_xscreensaverSink = XCreateWindow(m_display, DefaultRootWindow(m_display), 0, 0, 1, 1, 0, 0, InputOnly, CopyFromParent, CWDontPropagate | CWEventMask | CWOverrideRedirect, &attr); log((CLOG_DEBUG "xscreensaver sink window is 0x%08x", m_xscreensaverSink)); // 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 xscreensaver window, if any if (!findXScreenSaver()) { setXScreenSaver(None); } // get the built-in settings XGetScreenSaver(m_display, &m_timeout, &m_interval, &m_preferBlanking, &m_allowExposures); } CXWindowsScreenSaver::~CXWindowsScreenSaver() { // clear watch list clearWatchForXScreenSaver(); // stop watching root for events CXWindowsUtil::CErrorLock lock(m_display); Window root = DefaultRootWindow(m_display); XSelectInput(m_display, root, m_rootEventMask); // destroy dummy sink window XDestroyWindow(m_display, m_xscreensaverSink); // done with disable job m_screen->removeTimer(m_disableJob); delete m_disableJob; } bool CXWindowsScreenSaver::onPreDispatch(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::setNotify(Window notify) { m_notify = notify; } void CXWindowsScreenSaver::enable() { // for xscreensaver m_disabled = false; m_screen->removeTimer(m_disableJob); // for built-in X screen saver XSetScreenSaver(m_display, m_timeout, m_interval, m_preferBlanking, m_allowExposures); } void CXWindowsScreenSaver::disable() { // for xscreensaver. 5 seconds should be plenty often to // suppress the screen saver. m_disabled = true; m_screen->addTimer(m_disableJob, 5.0); // 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); // FIXME -- now deactivate? } void CXWindowsScreenSaver::activate() { // remove disable job timer if (m_disabled) { m_screen->removeTimer(m_disableJob); } // try xscreensaver findXScreenSaver(); if (m_xscreensaver != None) { sendXScreenSaverCommand(m_atomScreenSaverActivate); return; } // use built-in X screen saver XForceScreenSaver(m_display, ScreenSaverActive); } void CXWindowsScreenSaver::deactivate() { // reinstall disable job timer if (m_disabled) { m_screen->addTimer(m_disableJob, 5.0); } // 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; } // can't check built-in X screen saver activity return false; } void CXWindowsScreenSaver::sendNotify(bool activated) { if (m_notify != None) { XEvent event; event.xclient.type = ClientMessage; event.xclient.display = m_display; event.xclient.window = m_notify; event.xclient.message_type = m_atomScreenSaver; event.xclient.format = 32; event.xclient.data.l[0] = activated ? 1 : 0; event.xclient.data.l[1] = 0; event.xclient.data.l[2] = 0; event.xclient.data.l[3] = 0; event.xclient.data.l[4] = 0; CXWindowsUtil::CErrorLock lock(m_display); XSendEvent(m_display, m_notify, False, 0, &event); } } 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); } 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", activated ? "activated" : "deactivated")); m_xscreensaverActive = activated; sendNotify(activated); } } 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::disableCallback(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); } // force screen saver off and reset the timer XForceScreenSaver(m_display, ScreenSaverReset); }