2017-02-24 21:58:21 +03:00
/*
* Copyright 2016 Marco Martin < mart @ kde . org >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation ; either version 2 , 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 Library General Public
* License along with this program ; if not , write to the
* Free Software Foundation , Inc . ,
* 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 , USA .
*/
# include "screenpool.h"
2018-12-02 03:05:52 +03:00
// local
2017-02-26 03:43:41 +03:00
# include <config-latte.h>
2017-02-24 21:58:21 +03:00
2018-12-02 03:05:52 +03:00
// Qt
2017-02-26 21:06:31 +03:00
# include <QDebug>
2017-06-26 23:24:27 +03:00
# include <QFile>
2017-02-24 21:58:21 +03:00
# include <QGuiApplication>
# include <QScreen>
2019-07-13 17:00:02 +03:00
// KDE
# include <KLocalizedString>
2018-12-02 03:05:52 +03:00
// X11
2017-02-26 03:43:41 +03:00
# if HAVE_X11
2017-03-12 23:14:30 +03:00
# include <QtX11Extras/QX11Info>
# include <xcb/xcb.h>
# include <xcb/randr.h>
# include <xcb/xcb_event.h>
2017-02-26 03:43:41 +03:00
# endif
2018-02-03 12:34:13 +03:00
namespace Latte {
2017-02-24 21:58:21 +03:00
ScreenPool : : ScreenPool ( KSharedConfig : : Ptr config , QObject * parent )
: QObject ( parent ) ,
m_configGroup ( KConfigGroup ( config , QStringLiteral ( " ScreenConnectors " ) ) )
{
2017-02-26 03:43:41 +03:00
qApp - > installNativeEventFilter ( this ) ;
2017-02-24 21:58:21 +03:00
m_configSaveTimer . setSingleShot ( true ) ;
2017-03-12 23:14:30 +03:00
connect ( & m_configSaveTimer , & QTimer : : timeout , this , [ this ] ( ) {
2017-02-24 21:58:21 +03:00
m_configGroup . sync ( ) ;
} ) ;
}
void ScreenPool : : load ( )
{
m_primaryConnector = QString ( ) ;
m_connectorForId . clear ( ) ;
m_idForConnector . clear ( ) ;
QScreen * primary = qGuiApp - > primaryScreen ( ) ;
2017-03-12 23:14:30 +03:00
2017-02-24 21:58:21 +03:00
if ( primary ) {
m_primaryConnector = primary - > name ( ) ;
2017-03-12 23:14:30 +03:00
2017-02-24 21:58:21 +03:00
if ( ! m_primaryConnector . isEmpty ( ) ) {
2017-02-27 00:46:20 +03:00
//m_connectorForId[0] = m_primaryConnector;
//m_idForConnector[m_primaryConnector] = 0;
2017-02-24 21:58:21 +03:00
}
}
//restore the known ids to connector mappings
2019-04-04 23:55:44 +03:00
for ( const QString & key : m_configGroup . keyList ( ) ) {
2017-02-24 21:58:21 +03:00
QString connector = m_configGroup . readEntry ( key , QString ( ) ) ;
2017-03-12 23:14:30 +03:00
qDebug ( ) < < " connector : " < < connector < < " - " < < key ;
2017-02-24 21:58:21 +03:00
if ( ! key . isEmpty ( ) & & ! connector . isEmpty ( ) & &
! m_connectorForId . contains ( key . toInt ( ) ) & &
! m_idForConnector . contains ( connector ) ) {
m_connectorForId [ key . toInt ( ) ] = connector ;
m_idForConnector [ connector ] = key . toInt ( ) ;
2017-02-26 21:06:31 +03:00
qDebug ( ) < < " Known Screen - " < < connector < < " - " < < key . toInt ( ) ;
2017-02-24 21:58:21 +03:00
} else if ( m_idForConnector . value ( connector ) ! = key . toInt ( ) ) {
m_configGroup . deleteEntry ( key ) ;
}
}
// if there are already connected unknown screens, map those
// all needs to be populated as soon as possible, otherwise
// containment->screen() will return an incorrect -1
// at startup, if it' asked before corona::addOutput()
// is performed, driving to the creation of a new containment
2017-03-12 23:14:30 +03:00
for ( QScreen * screen : qGuiApp - > screens ( ) ) {
2017-02-24 21:58:21 +03:00
if ( ! m_idForConnector . contains ( screen - > name ( ) ) ) {
insertScreenMapping ( firstAvailableId ( ) , screen - > name ( ) ) ;
}
}
}
ScreenPool : : ~ ScreenPool ( )
{
m_configGroup . sync ( ) ;
}
2017-06-26 23:24:27 +03:00
2019-07-14 13:59:02 +03:00
QString ScreenPool : : reportHtml ( const QList < int > & assignedScreens ) const
2019-07-13 17:00:02 +03:00
{
QString report ;
2019-07-14 13:59:02 +03:00
report + = " <table cellspacing='8'> " ;
report + = " <tr><td align='center'><b> " + i18nc ( " screen id " , " ID " ) + " </b></td> " +
" <td align='center'><b> " + i18nc ( " screen name " , " Name " ) + " </b></td> " +
" <td align='center'><b> " + i18nc ( " screen type " , " Type " ) + " </b></td> " +
" <td align='center'><b> " + i18n ( " Docks/Panels " ) + " </b></td></tr> " ;
report + = " <tr><td colspan='4'><hr></td></tr> " ;
2019-07-13 17:00:02 +03:00
for ( const QString & connector : m_connectorForId ) {
2019-07-14 13:59:02 +03:00
int scrId = id ( connector ) ;
bool hasViews = assignedScreens . contains ( scrId ) ;
2019-07-13 17:00:02 +03:00
bool primary = m_primaryConnector = = connector ;
2019-07-14 13:59:02 +03:00
bool secondary = ! primary & & screenExists ( scrId ) ;
2019-07-13 17:00:02 +03:00
report + = " <tr> " ;
//! ScreenId
2019-07-14 23:44:16 +03:00
QString idStr = " [ " + QString : : number ( scrId ) + " ] " ;
2019-07-14 13:59:02 +03:00
if ( primary | | secondary ) {
idStr = " <b> " + idStr + " </b> " ;
}
2019-07-13 17:00:02 +03:00
report + = " <td align='center'> " + idStr + " </td> " ;
2019-07-14 13:59:02 +03:00
//! ScreenName and Primary flag
QString connectorStr = connector ;
if ( primary | | secondary ) {
connectorStr = " <b> " + connector + " </b> " ;
}
2019-07-13 17:00:02 +03:00
report + = " <td align='center'> " + connectorStr + " </td> " ;
2019-07-14 13:59:02 +03:00
//! ScreenType
QString typeStr = " " ;
2019-07-13 17:00:02 +03:00
if ( primary ) {
2019-07-14 13:59:02 +03:00
typeStr = " <b><font color='green'>[ " + i18nc ( " primary screen " , " Primary " ) + " ]</font></b> " ;
} else if ( secondary ) {
typeStr = " <b>[ " + i18nc ( " secondary screen " , " Secondary " ) + " ]</b> " ;
2019-07-14 23:44:16 +03:00
} else {
typeStr = " <i> " + i18nc ( " inactive screen " , " inactive " ) + " </i> " ;
2019-07-13 17:00:02 +03:00
}
2019-07-14 13:59:02 +03:00
report + = " <td align='center'> " + typeStr + " </td> " ;
//! Screen has not assigned any docks/panels
QString notAssignedStr = " " ;
if ( ! hasViews ) {
notAssignedStr = " <font color='red'><i>[ " + i18nc ( " it has not latte docks/panels " , " none " ) + " ]</i></font> " ;
2019-07-14 23:44:16 +03:00
} else {
notAssignedStr = " ✔ " ;
2019-07-14 13:59:02 +03:00
}
report + = " <td align='center'> " + notAssignedStr + " </td> " ;
2019-07-13 17:00:02 +03:00
report + = " </tr> " ;
}
report + = " </table> " ;
return report ;
}
2017-06-26 23:24:27 +03:00
void ScreenPool : : reload ( QString path )
{
QFile rcfile ( QString ( path + " /lattedockrc " ) ) ;
2018-01-08 20:21:34 +03:00
if ( rcfile . exists ( ) ) {
2017-06-26 23:24:27 +03:00
qDebug ( ) < < " load screen connectors from ::: " < < rcfile . fileName ( ) ;
KSharedConfigPtr newFile = KSharedConfig : : openConfig ( rcfile . fileName ( ) ) ;
m_configGroup = KConfigGroup ( newFile , QStringLiteral ( " ScreenConnectors " ) ) ;
load ( ) ;
}
}
2018-01-08 20:21:34 +03:00
int ScreenPool : : primaryScreenId ( ) const
{
return id ( qGuiApp - > primaryScreen ( ) - > name ( ) ) ;
}
2017-02-24 21:58:21 +03:00
QString ScreenPool : : primaryConnector ( ) const
{
return m_primaryConnector ;
}
void ScreenPool : : setPrimaryConnector ( const QString & primary )
{
2017-02-26 21:06:31 +03:00
//the ":" check fixes the strange plasma/qt issues when changing layouts
//there are case that the QScreen instead of the correct screen name
//returns "0:0", this check prevents from breaking the screens database
//from garbage ids
2017-03-12 23:14:30 +03:00
if ( ( m_primaryConnector = = primary ) | | primary . startsWith ( " : " ) ) {
2017-02-24 21:58:21 +03:00
return ;
}
2017-03-12 23:14:30 +03:00
2017-02-24 21:58:21 +03:00
Q_ASSERT ( m_idForConnector . contains ( primary ) ) ;
2017-03-12 23:14:30 +03:00
/* int oldIdForPrimary = m_idForConnector.value(primary);
2017-02-24 21:58:21 +03:00
2017-03-12 23:14:30 +03:00
m_idForConnector [ primary ] = 0 ;
m_connectorForId [ 0 ] = primary ;
m_idForConnector [ m_primaryConnector ] = oldIdForPrimary ;
m_connectorForId [ oldIdForPrimary ] = m_primaryConnector ;
m_primaryConnector = primary ;
*/
2017-02-24 21:58:21 +03:00
save ( ) ;
}
void ScreenPool : : save ( )
{
QMap < int , QString > : : const_iterator i ;
2017-03-12 23:14:30 +03:00
2017-02-24 21:58:21 +03:00
for ( i = m_connectorForId . constBegin ( ) ; i ! = m_connectorForId . constEnd ( ) ; + + i ) {
m_configGroup . writeEntry ( QString : : number ( i . key ( ) ) , i . value ( ) ) ;
}
2017-03-12 23:14:30 +03:00
2017-02-24 21:58:21 +03:00
//write to disck every 30 seconds at most
m_configSaveTimer . start ( 30000 ) ;
}
void ScreenPool : : insertScreenMapping ( int id , const QString & connector )
{
2017-06-14 20:51:47 +03:00
//Q_ASSERT(!m_connectorForId.contains(id) || m_connectorForId.value(id) == connector);
//Q_ASSERT(!m_idForConnector.contains(connector) || m_idForConnector.value(connector) == id);
2017-02-24 21:58:21 +03:00
2017-02-26 21:06:31 +03:00
//the ":" check fixes the strange plasma/qt issues when changing layouts
//there are case that the QScreen instead of the correct screen name
//returns "0:0", this check prevents from breaking the screens database
//from garbage ids
if ( connector . startsWith ( " : " ) )
return ;
qDebug ( ) < < " add connector... " < < connector ;
2017-02-24 21:58:21 +03:00
if ( id = = 0 ) {
m_primaryConnector = connector ;
2017-02-27 00:46:20 +03:00
} else {
m_connectorForId [ id ] = connector ;
m_idForConnector [ connector ] = id ;
2017-02-24 21:58:21 +03:00
}
save ( ) ;
}
int ScreenPool : : id ( const QString & connector ) const
{
if ( ! m_idForConnector . contains ( connector ) ) {
return - 1 ;
}
return m_idForConnector . value ( connector ) ;
}
QString ScreenPool : : connector ( int id ) const
2019-05-01 18:52:03 +03:00
{
2017-02-24 21:58:21 +03:00
Q_ASSERT ( m_connectorForId . contains ( id ) ) ;
return m_connectorForId . value ( id ) ;
}
int ScreenPool : : firstAvailableId ( ) const
{
2017-02-27 00:46:20 +03:00
//start counting from 10, first numbers will
//be used for special cases.
//e.g primaryScreen, id=0
int i = 10 ;
2017-03-12 23:14:30 +03:00
2017-02-24 21:58:21 +03:00
//find the first integer not stored in m_connectorForId
//m_connectorForId is the only map, so the ids are sorted
2019-04-04 23:55:44 +03:00
for ( const int & existingId : m_connectorForId . keys ( ) ) {
2017-02-24 21:58:21 +03:00
if ( i ! = existingId ) {
return i ;
}
2017-03-12 23:14:30 +03:00
2017-02-24 21:58:21 +03:00
+ + i ;
}
return i ;
}
QList < int > ScreenPool : : knownIds ( ) const
{
return m_connectorForId . keys ( ) ;
}
2019-07-14 13:59:02 +03:00
bool ScreenPool : : hasId ( int id ) const
2019-05-01 18:52:03 +03:00
{
return ( ( id ! = - 1 ) & & m_connectorForId . keys ( ) . contains ( id ) ) ;
}
2019-07-14 13:59:02 +03:00
bool ScreenPool : : screenExists ( int id ) const
2018-07-24 22:25:19 +03:00
{
if ( id ! = - 1 & & knownIds ( ) . contains ( id ) ) {
QString scrName = connector ( id ) ;
2019-04-04 23:55:44 +03:00
for ( const auto scr : qGuiApp - > screens ( ) ) {
2018-07-24 22:25:19 +03:00
if ( scr - > name ( ) = = scrName ) {
return true ;
}
}
}
return false ;
}
2017-06-11 23:55:34 +03:00
QScreen * ScreenPool : : screenForId ( int id )
{
const auto screens = qGuiApp - > screens ( ) ;
QScreen * screen { qGuiApp - > primaryScreen ( ) } ;
if ( id ! = - 1 & & knownIds ( ) . contains ( id ) ) {
QString scrName = connector ( id ) ;
2019-04-04 23:55:44 +03:00
for ( const auto scr : screens ) {
2017-06-11 23:55:34 +03:00
if ( scr - > name ( ) = = scrName ) {
return scr ;
}
}
}
return screen ;
}
2017-03-12 23:14:30 +03:00
bool ScreenPool : : nativeEventFilter ( const QByteArray & eventType , void * message , long int * result )
2017-02-26 03:43:41 +03:00
{
Q_UNUSED ( result ) ;
# if HAVE_X11
2017-03-12 23:14:30 +03:00
2017-02-26 03:43:41 +03:00
// a particular edge case: when we switch the only enabled screen
// we don't have any signal about it, the primary screen changes but we have the same old QScreen* getting recycled
// see https://bugs.kde.org/show_bug.cgi?id=373880
// if this slot will be invoked many times, their//second time on will do nothing as name and primaryconnector will be the same by then
if ( eventType ! = " xcb_generic_event_t " ) {
return false ;
}
xcb_generic_event_t * ev = static_cast < xcb_generic_event_t * > ( message ) ;
const auto responseType = XCB_EVENT_RESPONSE_TYPE ( ev ) ;
2017-03-12 23:14:30 +03:00
const xcb_query_extension_reply_t * reply = xcb_get_extension_data ( QX11Info : : connection ( ) , & xcb_randr_id ) ;
2017-02-26 03:43:41 +03:00
if ( responseType = = reply - > first_event + XCB_RANDR_SCREEN_CHANGE_NOTIFY ) {
if ( qGuiApp - > primaryScreen ( ) - > name ( ) ! = primaryConnector ( ) ) {
//new screen?
if ( id ( qGuiApp - > primaryScreen ( ) - > name ( ) ) < 0 ) {
insertScreenMapping ( firstAvailableId ( ) , qGuiApp - > primaryScreen ( ) - > name ( ) ) ;
}
2017-03-12 23:14:30 +03:00
2017-02-26 03:43:41 +03:00
//switch the primary screen in the pool
setPrimaryConnector ( qGuiApp - > primaryScreen ( ) - > name ( ) ) ;
emit primaryPoolChanged ( ) ;
}
}
2017-03-12 23:14:30 +03:00
2017-02-26 03:43:41 +03:00
# endif
return false ;
}
2018-02-03 12:34:13 +03:00
}
2017-02-26 03:43:41 +03:00
2017-02-24 21:58:21 +03:00
# include "moc_screenpool.cpp"