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
2021-02-17 15:20:44 +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 {
2021-03-20 19:57:28 +03:00
const int ScreenPool : : FIRSTSCREENID ;
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 ( ) ;
} ) ;
2021-02-17 16:12:27 +03:00
connect ( qGuiApp , & QGuiApplication : : screenAdded , this , & ScreenPool : : onScreenAdded , Qt : : UniqueConnection ) ;
connect ( qGuiApp , & QGuiApplication : : screenRemoved , this , & ScreenPool : : onScreenRemoved , Qt : : UniqueConnection ) ;
}
ScreenPool : : ~ ScreenPool ( )
{
m_configGroup . sync ( ) ;
2017-02-24 21:58:21 +03:00
}
void ScreenPool : : load ( )
{
2021-02-17 13:14:46 +03:00
m_lastPrimaryConnector = QString ( ) ;
2021-02-17 14:37:02 +03:00
m_screensTable . clear ( ) ;
2017-02-24 21:58:21 +03:00
QScreen * primary = qGuiApp - > primaryScreen ( ) ;
2017-03-12 23:14:30 +03:00
2017-02-24 21:58:21 +03:00
if ( primary ) {
2021-02-17 13:14:46 +03:00
m_lastPrimaryConnector = primary - > name ( ) ;
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 ( ) ) {
2021-03-13 18:10:51 +03:00
if ( key . toInt ( ) < = 0 ) {
continue ;
}
2021-02-17 14:37:02 +03:00
QString serialized = m_configGroup . readEntry ( key , QString ( ) ) ;
Data : : Screen screenRecord ( key , serialized ) ;
//qDebug() << "org.kde.latte ::: " << screenRecord.id << ":" << screenRecord.serialize();
if ( ! key . isEmpty ( ) & & ! serialized . isEmpty ( ) & & ! m_screensTable . containsId ( key ) ) {
m_screensTable < < screenRecord ;
2021-02-17 15:20:44 +03:00
qDebug ( ) < < " org.kde.latte :: Known Screen - " < < screenRecord . id < < " : " < < screenRecord . name < < " : " < < screenRecord . geometry ;
2017-02-24 21:58:21 +03:00
}
}
// 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 ( ) ) {
2021-02-17 16:12:27 +03:00
onScreenRemoved ( screen ) ;
2021-02-17 14:37:02 +03:00
if ( ! m_screensTable . containsName ( screen - > name ( ) ) ) {
2021-02-17 15:20:44 +03:00
insertScreenMapping ( screen - > name ( ) ) ;
} else {
2021-02-17 16:12:27 +03:00
updateScreenGeometry ( screen ) ;
2017-02-24 21:58:21 +03:00
}
2021-02-17 16:12:27 +03:00
onScreenAdded ( screen ) ;
2017-02-24 21:58:21 +03:00
}
}
2021-02-17 16:12:27 +03:00
void ScreenPool : : onScreenAdded ( const QScreen * screen )
2017-02-24 21:58:21 +03:00
{
2021-02-17 16:12:27 +03:00
connect ( screen , & QScreen : : geometryChanged , this , [ & , screen ] ( ) {
updateScreenGeometry ( screen ) ;
} ) ;
}
void ScreenPool : : onScreenRemoved ( const QScreen * screen )
{
disconnect ( screen , & QScreen : : geometryChanged , this , nullptr ) ;
}
void ScreenPool : : updateScreenGeometry ( const QScreen * screen )
{
if ( ! screen ) {
return ;
}
if ( m_screensTable . containsName ( screen - > name ( ) ) ) {
updateScreenGeometry ( id ( screen - > name ( ) ) , screen - > geometry ( ) ) ;
}
2017-02-24 21:58:21 +03:00
}
2021-02-17 15:20:44 +03:00
void ScreenPool : : updateScreenGeometry ( const int & screenId , const QRect & screenGeometry )
{
QString scrIdStr = QString : : number ( screenId ) ;
if ( ! m_screensTable . containsId ( scrIdStr ) | | m_screensTable [ scrIdStr ] . geometry = = screenGeometry ) {
return ;
}
m_screensTable [ scrIdStr ] . geometry = screenGeometry ;
save ( ) ;
}
2017-06-26 23:24:27 +03:00
2021-04-04 12:25:20 +03:00
Latte : : Data : : ScreensTable ScreenPool : : screensTable ( )
2021-04-10 13:45:45 +03:00
{
2021-04-04 12:25:20 +03:00
return m_screensTable ;
}
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 ;
2021-02-17 15:20:44 +03:00
/* report += "<table cellspacing='8'>";
2019-07-14 13:59:02 +03:00
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 ) ;
2021-02-17 13:14:46 +03:00
bool primary = m_lastPrimaryConnector = = connector ;
2021-02-17 13:24:36 +03:00
bool secondary = ! primary & & isScreenActive ( scrId ) ;
2019-07-14 13:59:02 +03:00
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> " ;
}
2021-02-17 14:37:02 +03:00
report + = " </table> " ; */
2019-07-13 17:00:02 +03:00
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
void ScreenPool : : save ( )
{
QMap < int , QString > : : const_iterator i ;
2017-03-12 23:14:30 +03:00
2021-02-17 14:37:02 +03:00
for ( int i = 0 ; i < m_screensTable . rowCount ( ) ; + + i ) {
Data : : Screen screenRecord = m_screensTable [ i ] ;
2021-03-20 19:57:28 +03:00
if ( screenRecord . id . toInt ( ) > = FIRSTSCREENID ) {
2021-03-13 17:52:45 +03:00
m_configGroup . writeEntry ( screenRecord . id , screenRecord . serialize ( ) ) ;
}
2017-02-24 21:58:21 +03:00
}
2017-03-12 23:14:30 +03:00
2021-02-17 15:20:44 +03:00
//write to disck every 10 seconds at most
m_configSaveTimer . start ( 10000 ) ;
2017-02-24 21:58:21 +03:00
}
2021-02-17 15:20:44 +03:00
void ScreenPool : : insertScreenMapping ( const QString & connector )
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
2021-02-17 15:20:44 +03:00
if ( m_screensTable . containsName ( connector ) | | connector . startsWith ( " : " ) ) {
2017-02-26 21:06:31 +03:00
return ;
2021-02-17 14:37:02 +03:00
}
2017-02-26 21:06:31 +03:00
qDebug ( ) < < " add connector... " < < connector ;
2021-02-17 15:20:44 +03:00
Data : : Screen screenRecord ;
2021-03-13 17:52:45 +03:00
screenRecord . id = QString : : number ( firstAvailableId ( ) ) ;
2021-02-17 15:20:44 +03:00
screenRecord . name = connector ;
2021-02-17 14:37:02 +03:00
2021-02-17 15:20:44 +03:00
//! update screen geometry
for ( const auto scr : qGuiApp - > screens ( ) ) {
if ( scr - > name ( ) = = connector ) {
screenRecord . geometry = scr - > geometry ( ) ;
break ;
2021-02-17 14:37:02 +03:00
}
2017-02-24 21:58:21 +03:00
}
2021-02-17 15:20:44 +03:00
m_screensTable < < screenRecord ;
2017-02-24 21:58:21 +03:00
save ( ) ;
}
int ScreenPool : : id ( const QString & connector ) const
{
2021-02-17 14:37:02 +03:00
QString screenId = m_screensTable . idForName ( connector ) ;
return screenId . isEmpty ( ) ? - 1 : screenId . toInt ( ) ;
2017-02-24 21:58:21 +03:00
}
QString ScreenPool : : connector ( int id ) const
2019-05-01 18:52:03 +03:00
{
2021-02-17 15:20:44 +03:00
QString idStr = QString : : number ( id ) ;
return ( m_screensTable . containsId ( idStr ) ? m_screensTable [ idStr ] . name : QString ( ) ) ;
2017-02-24 21:58:21 +03:00
}
int ScreenPool : : firstAvailableId ( ) const
{
2021-02-17 14:37:02 +03:00
//start counting from 10, first numbers will be used for special cases e.g. primaryScreen, id=0
2021-03-20 19:57:28 +03:00
int availableId = FIRSTSCREENID ;
2021-02-17 14:37:02 +03:00
for ( int row = 0 ; row < m_screensTable . rowCount ( ) ; + + row ) {
2021-02-17 15:20:44 +03:00
if ( ! m_screensTable . containsId ( QString : : number ( availableId ) ) ) {
2021-02-17 14:37:02 +03:00
return availableId ;
2017-02-24 21:58:21 +03:00
}
2017-03-12 23:14:30 +03:00
2021-02-17 14:37:02 +03:00
availableId + + ;
2017-02-24 21:58:21 +03:00
}
2021-02-17 14:37:02 +03:00
return availableId ;
2017-02-24 21:58:21 +03:00
}
2021-02-17 13:24:36 +03:00
bool ScreenPool : : hasScreenId ( int screenId ) const
2019-05-01 18:52:03 +03:00
{
2021-02-17 15:20:44 +03:00
return ( ( screenId > = 0 ) & & m_screensTable . containsId ( QString : : number ( screenId ) ) ) ;
2019-05-01 18:52:03 +03:00
}
2021-02-17 13:24:36 +03:00
bool ScreenPool : : isScreenActive ( int screenId ) const
2018-07-24 22:25:19 +03:00
{
2021-02-17 13:24:36 +03:00
if ( hasScreenId ( screenId ) ) {
QString scrName = connector ( screenId ) ;
2018-07-24 22:25:19 +03:00
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 ( ) } ;
2021-02-17 14:37:02 +03:00
if ( hasScreenId ( id ) ) {
2017-06-11 23:55:34 +03:00
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 ) {
2021-02-17 13:14:46 +03:00
if ( qGuiApp - > primaryScreen ( ) - > name ( ) ! = m_lastPrimaryConnector ) {
2017-02-26 03:43:41 +03:00
//new screen?
if ( id ( qGuiApp - > primaryScreen ( ) - > name ( ) ) < 0 ) {
2021-02-17 15:20:44 +03:00
insertScreenMapping ( qGuiApp - > primaryScreen ( ) - > name ( ) ) ;
2017-02-26 03:43:41 +03:00
}
2017-03-12 23:14:30 +03:00
2021-02-17 13:14:46 +03:00
m_lastPrimaryConnector = qGuiApp - > primaryScreen ( ) - > name ( ) ;
2017-02-26 03:43:41 +03:00
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"