/******************************************************************** Copyright (C) 2008 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 . *********************************************************************/ #include "randrmonitor.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY(RandrMonitorModuleFactory, registerPlugin(); ) K_EXPORT_PLUGIN(RandrMonitorModuleFactory("randrmonitor")) RandrMonitorModule::RandrMonitorModule( QObject* parent, const QList& ) : KDEDModule( parent ) , have_randr( false ) , dialog(0) { m_inhibitionCookie = -1; setModuleName( "randrmonitor" ); initRandr(); QDBusReply re = QDBusConnection::systemBus().interface()->isServiceRegistered("org.kde.Solid.PowerManagement"); if (!re.value()) { kDebug() << "PowerManagement not loaded, waiting for it"; QDBusServiceWatcher *serviceWatcher = new QDBusServiceWatcher("org.kde.Solid.PowerManagement", QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForRegistration, this); connect(serviceWatcher, SIGNAL(serviceRegistered(QString)), this, SLOT(checkInhibition())); connect(serviceWatcher, SIGNAL(serviceRegistered(QString)), this, SLOT(checkResumeFromSuspend())); return; } checkInhibition(); checkResumeFromSuspend(); } RandrMonitorModule::~RandrMonitorModule() { if( have_randr ) { Display* dpy = QX11Info::display(); XDestroyWindow( dpy, window ); delete helper; have_randr = false; } } void RandrMonitorModule::initRandr() { Display* dpy = QX11Info::display(); if( !XRRQueryExtension( dpy, &randr_base, &randr_error )) return; int major = 1; int minor = 2; if( !XRRQueryVersion( dpy, &major, &minor ) || major < 1 || (major == 1 && minor < 2 )) return; have_randr = true; // It looks like we need a separate window for getting the events, so that we don't // change e.g. Qt's event mask. window = XCreateSimpleWindow( dpy, DefaultRootWindow( dpy ), 0, 0, 1, 1, 0, 0, 0 ); XRRSelectInput( dpy, window, RROutputChangeNotifyMask ); #if 0 // xrandr apparently can't detect hw changes and on some systems polling freezes X :( // HACK: see poll() QTimer* timer = new QTimer( this ); timer->start( 10000 ); // 10 s connect( timer, SIGNAL(timeout()), this, SLOT(poll())); #endif helper = new RandrMonitorHelper( this ); kapp->installX11EventFilter( helper ); currentMonitors = connectedMonitors(); KActionCollection* coll = new KActionCollection( this ); KAction* act = coll->addAction( "display" ); act->setText( i18n( "Switch Display" )); act->setGlobalShortcut( KShortcut( Qt::Key_Display )); connect( act, SIGNAL(triggered(bool)), SLOT(switchDisplay())); } void RandrMonitorModule::poll() { // HACK: It seems that RRNotify/RRNotify_OutputChange event (i.e. detecting a newly // plugged or unplugged monitor) does not work without polling some randr functionality. int dummy; XRRGetScreenSizeRange( QX11Info::display(), window, &dummy, &dummy, &dummy, &dummy ); } void RandrMonitorModule::processX11Event( XEvent* e ) { if( e->xany.type == randr_base + RRNotify ) { XRRNotifyEvent* e2 = reinterpret_cast< XRRNotifyEvent* >( e ); if( e2->subtype == RRNotify_OutputChange ) // TODO && e2->window == window ) { kDebug() << "Monitor change detected"; QStringList newMonitors = connectedMonitors(); //If we are already inhibiting and we should stop it, do it checkInhibition(); if( newMonitors == currentMonitors ) { kDebug() << "Same monitors"; return; } if( QDBusConnection::sessionBus().interface()->isServiceRegistered( "org.kde.internal.KSettingsWidget-kcm_randr" )) { // already running return; } kapp->updateUserTimestamp(); // well, let's say plugging in a monitor is a user activity currentMonitors = newMonitors; // TODO make sure to be on right screen after a screen has been disconnected if (!dialog) { dialog = new KDialog(); dialog->setCaption(i18n("Monitor setup has changed")); QLabel *icon = new QLabel(); icon->setPixmap(KIcon("preferences-desktop-display").pixmap(QSize(64, 64), QIcon::Normal, QIcon::On)); QString question = (newMonitors.count() < currentMonitors.count()) ? i18n("A monitor output has been disconnected.") : i18n("A new monitor output has been connected.") + "\n\n" + i18n("Do you wish to run a configuration tool to adjust the monitor setup?"); QLabel *label = new QLabel(question); QHBoxLayout *layout = new QHBoxLayout(); layout->setSpacing(4); layout->addWidget(icon); layout->addWidget(label); QWidget *mainWidget = new QWidget(dialog); mainWidget->setLayout(layout); dialog->setMainWidget(mainWidget); dialog->setButtons(KDialog::Yes | KDialog::No | KDialog::Try); dialog->setDefaultButton(KDialog::Yes); dialog->setButtonText(KDialog::Try, i18nc("@Button: try to adjust screen configuration automatically", "Try Automatically")); connect(dialog, SIGNAL(yesClicked()), this, SLOT(showKcm())); connect(dialog, SIGNAL(tryClicked()), this, SLOT(tryAutoConfig())); } if (!dialog->isVisible()) { dialog->show(); } dialog->raise(); dialog->activateWindow(); } } } void RandrMonitorModule::showKcm() { KToolInvocation::kdeinitExec("kcmshell4", QStringList() << "display"); } void RandrMonitorModule::tryAutoConfig() { QProcess::execute("xrandr", QStringList() << "--auto"); } QStringList RandrMonitorModule::connectedMonitors() const { QStringList ret; Display* dpy = QX11Info::display(); XRRScreenResources* resources = XRRGetScreenResources( dpy, window ); for( int i = 0; i < resources->noutput; ++i ) { XRROutputInfo* info = XRRGetOutputInfo( dpy, resources, resources->outputs[ i ] ); QString name = QString::fromUtf8( info->name ); if( info->connection == RR_Connected ) ret.append( name ); XRRFreeOutputInfo( info ); } XRRFreeScreenResources( resources ); return ret; } QStringList RandrMonitorModule::activeMonitors() const { QStringList ret; Display* dpy = QX11Info::display(); XRRScreenResources* resources = XRRGetScreenResources( dpy, window ); for( int i = 0; i < resources->noutput; ++i ) { XRROutputInfo* info = XRRGetOutputInfo( dpy, resources, resources->outputs[ i ] ); QString name = QString::fromUtf8( info->name ); if(info->crtc != None) ret.append( name ); XRRFreeOutputInfo( info ); } XRRFreeScreenResources( resources ); return ret; } void RandrMonitorModule::checkInhibition() { if (!have_randr) { kDebug() << "Can't check inhibition, XRandR minor to 1.2 detected"; return; } if (!isLidPresent()) { kDebug() << "This feature is only for laptop, and there is no Lid present"; return; } QStringList activeMonitorsList = activeMonitors(); kDebug() << "Active monitor list"; kDebug() << activeMonitorsList; bool inhibit = false; Q_FOREACH(const QString monitor, activeMonitorsList) { //LVDS is the default type reported by most drivers, default is needed because the //NVIDIA binary blob always report default as active monitor. if (!monitor.contains("LVDS") && !monitor.contains("default") && !monitor.contains("eDP")) { inhibit = true; break; } } if (m_inhibitionCookie > 0 && !inhibit) { kDebug() << "Stopping: " << m_inhibitionCookie; Solid::PowerManagement::stopSuppressingSleep(m_inhibitionCookie); m_inhibitionCookie = -1; } else if (m_inhibitionCookie < 0 && inhibit) { // If we are NOT inhibiting and we should, do it m_inhibitionCookie = Solid::PowerManagement::beginSuppressingSleep(); kDebug() << "Inhibing: " << m_inhibitionCookie; } } bool RandrMonitorModule::isLidPresent() { // get a list of all devices that are Buttons foreach (Solid::Device device, Solid::Device::listFromType(Solid::DeviceInterface::Button, QString())) { Solid::Button *button = device.as(); if (button->type() == Solid::Button::LidButton) { return true; } } return false; } void RandrMonitorModule::checkResumeFromSuspend() { connect( Solid::PowerManagement::notifier(), SIGNAL(resumingFromSuspend()), this, SLOT(resumedFromSuspend()) ); } void RandrMonitorModule::switchDisplay() { QDBusMessage call = QDBusMessage::createMethodCall("org.kde.Solid.PowerManagement", "/org/kde/Solid/PowerManagement", "org.kde.Solid.PowerManagement", "isLidClosed"); QDBusMessage msg = QDBusConnection::sessionBus().call(call); QDBusReply reply(msg); if (reply.isValid() && reply.value()) { kDebug() << "Lid is closed, ignoring the event"; //TODO: When we rewrite this, be sure that in this case LVDS is disabled instead of ignoring return; } QList< RandROutput* > outputs; RandRDisplay display; outputs = connectedOutputs( display ); if( outputs.count() == 0 ) // nothing connected, do nothing return; if( outputs.count() == 1 ) // just one, enable it { enableOutput( outputs[0], true ); for( int scr = 0; scr < display.numScreens(); ++scr ) { foreach( RandROutput* output, display.screen( scr )->outputs()) { if( !output->isConnected()) enableOutput( output, false ); // switch off every output that's not connected } } return; } if( outputs.count() == 2 ) // alternative between one, second, both { if( outputs[ 0 ]->isActive() && !outputs[ 1 ]->isActive()) { enableOutput( outputs[ 1 ], true ); enableOutput( outputs[ 0 ], false ); } else if( !outputs[ 0 ]->isActive() && outputs[ 1 ]->isActive()) { enableOutput( outputs[ 1 ], true ); enableOutput( outputs[ 0 ], true ); } else { enableOutput( outputs[ 0 ], true ); enableOutput( outputs[ 1 ], false ); } return; } // no idea what to do here KToolInvocation::kdeinitExec( "kcmshell4", QStringList() << "display" ); } void RandrMonitorModule::resumedFromSuspend() { RandRDisplay display; QList< RandROutput* > m_connectedOutputs, m_validCrtcOutputs; m_connectedOutputs = connectedOutputs( display ); m_validCrtcOutputs = validCrtcOutputs( display ); if( m_connectedOutputs.count() == 0 ) return; // We have at least one connected output. // We check all outputs with valid crtc if they are still connected. // If not, we are going to disable them. QList outputsToDisable; foreach( RandROutput* output, m_validCrtcOutputs ) { if( !output->isConnected() ) outputsToDisable.append( output ); } // If no active output is still connected we are going to enable the first connected output. if( outputsToDisable.size() == m_validCrtcOutputs.size() ) enableOutput( m_connectedOutputs[0], true); // Now we can disable the disconnected outputs foreach( RandROutput* output, outputsToDisable) { enableOutput( output, false ); } } void RandrMonitorModule::enableOutput( RandROutput* output, bool enable ) { // a bit lame, but I don't know how to do this easily with this codebase :-/ QProcess::execute( "xrandr", QStringList() << "--output" << output->name() << ( enable ? "--auto" : "--off" )); } QList< RandROutput* > RandrMonitorModule::connectedOutputs( RandRDisplay &display ) { return outputs( display, true, false, false ); } QList< RandROutput* > RandrMonitorModule::activeOutputs( RandRDisplay &display ) { return outputs( display, false, true, false ); } QList< RandROutput* > RandrMonitorModule::validCrtcOutputs( RandRDisplay &display ) { return outputs( display, false, false, true ); } QList< RandROutput* > RandrMonitorModule::outputs( RandRDisplay &display, bool connected, bool active, bool validCrtc ) { QList< RandROutput* > outputs; for( int scr = 0; scr < display.numScreens(); ++scr ) { foreach( RandROutput* output, display.screen( scr )->outputs() ) { if( !output->isConnected() && connected ) continue; if( !output->isActive() && active ) continue; if( !output->crtc()->isValid() && validCrtc ) continue; if( !outputs.contains( output ) ) outputs.append( output ); } } return outputs; } bool RandrMonitorHelper::x11Event( XEvent* e ) { module->processX11Event( e ); return QWidget::x11Event( e ); } #include "moc_randrmonitor.cpp"