Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F1720833
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Size
39 KB
Subscribers
None
View Options
diff --git a/monitorDaemon.py b/monitorDaemon.py
--- a/monitorDaemon.py
+++ b/monitorDaemon.py
@@ -1,841 +1,961 @@
#!/usr/bin/env python2
# monitorDaemon.py
# (c) 2017-2018 Andreas Boehler <dev _AT_ aboehler.at>
# This file is free software; you can redistribute it and/or modify
# it under the terms of either the GNU General Public License version 2
# or the GNU Lesser General Public License version 2.1, both as
# published by the Free Software Foundation.
import os
import hashlib
import sys
import ConfigParser
from Xlib import display
from Xlib.ext import randr
from PyQt4.QtCore import QThread, QTimer, pyqtSignal, QObject, QMetaObject, Qt, pyqtSlot
from PyQt4 import QtGui
+#from PyQt4 import QtDBus
+from dbus.mainloop.qt import DBusQtMainLoop
+import dbus
import time
import logging as log
-log.basicConfig(format="%(levelname)s:%(message)s", level=log.ERROR)
-#log.basicConfig(format="%(levelname)s: %(message)s", level=log.DEBUG)
+logger = log.getLogger('monitorDaemon')
+logger.setLevel(log.DEBUG)
+path = os.path.abspath(os.path.expanduser('~/monitorDaemon.log'))
+fh = log.FileHandler(path)
+fh.setLevel(log.DEBUG)
+ch = log.StreamHandler()
+ch.setLevel(log.ERROR)
+formatter = log.Formatter("%(asctime)s %(levelname)s: %(message)s")
+fh.setFormatter(formatter)
+ch.setFormatter(formatter)
+logger.addHandler(fh)
+logger.addHandler(ch)
DEFAULT_TIMEOUT = 500
GRACE_TIMEOUT = 3000
VERBOSE = True
class xrandrInterface(QThread):
""" Provides an interface to the python implementation of xrandr.
It does all the processing used to enable/disable monitors etc.
Inhertis from QThread.
"""
screenConfigChanged = pyqtSignal()
def __init__(self):
""" Initialize the module. """
QThread.__init__(self)
self.display = display.Display()
self.screen = self.display.screen()
self.running = True
+ self.processing = True
self.window = self.screen.root.create_window(0, 0, 1, 1, 1,
self.screen.root_depth,
#event_mask = (X.ExposureMask |
#X.StructureNotifyMask |
#X.ButtonPressMask |
#X.ButtonReleaseMask |
#X.Button1MotionMask)
)
self.outputStatus = {}
- self.oldOutputStatus = {}
self.crtcStatus = {}
+
self.getScreenInformation()
self.enableEvents()
self.timer = QTimer()
self.timer.setSingleShot(True)
self.timer.timeout.connect(self.screenInformationChanged)
- self.graceTimer = QTimer()
- self.graceTimer.setSingleShot(True)
+
+ def resumeWork(self):
+ """ Resume processing """
+ self.processing = True
+
+ def suspendWork(self):
+ """ Suspend processing """
+ self.processing = False
+ self.timer.stop()
@pyqtSlot()
def screenInformationChanged(self):
""" Is called when the daemon detects a change in the screen
configuration.
:return: Nothing
"""
- log.debug("screenInformationChanged")
+ logger.debug("screenInformationChanged")
self.getScreenInformation()
self.screenConfigChanged.emit()
def disableEvents(self):
""" Disables event processing in the xrandr interface. You can
call it to temporarily switch events off.
:return: Nothing
"""
- log.debug("disableEvents")
+ logger.debug("disableEvents")
self.window.xrandr_select_input(0)
def enableEvents(self):
""" Enable event processing in the xrandr interface. You need to call
it to switch events back on after calling :func:`disableEvents`
:return: Nothing
"""
- log.debug("enableEvents")
+ logger.debug("enableEvents")
self.window.xrandr_select_input(
randr.RRScreenChangeNotifyMask
#| randr.RRCrtcChangeNotifyMask
#| randr.RROutputChangeNotifyMask
#| randr.RROutputPropertyNotifyMask
)
def getScreenInformation(self):
""" Retrieves the current screen information from the XRandR extension
and stores it at self.outputStates as well as self.crtcStatus.
It takes a while to process, be sure to not call it too often.
:return: Nothing
"""
- log.debug("getScreenInformation")
+ logger.debug("getScreenInformation")
self.oldOutputStatus = self.outputStatus
resources = self.window.xrandr_get_screen_resources()._data
outputs = resources['outputs']
self.outputStatus = {}
self.crtcStatus = {}
for crtc in resources['crtcs']:
crtcinfo = self.display.xrandr_get_crtc_info(crtc, resources['config_timestamp'])._data
self.crtcStatus[crtc] = {}
self.crtcStatus[crtc]['x'] = crtcinfo['x']
self.crtcStatus[crtc]['y'] = crtcinfo['y']
self.crtcStatus[crtc]['mode'] = crtcinfo['mode']
self.crtcStatus[crtc]['outputs'] = crtcinfo['outputs']
self.crtcStatus[crtc]['possible_outputs'] = crtcinfo['possible_outputs']
modedata = self.parseModes(resources['mode_names'], resources['modes'])
for output in outputs:
info = self.display.xrandr_get_output_info(output, resources['config_timestamp'])._data
if info['connection'] != 0:
continue
edid = self.get_edid(output)
name = info['name']
crtc = info['crtc']
if crtc == 0:
modename = '0'
posx = '0'
posy = '0'
else:
mode = self.crtcStatus[crtc]['mode']
modename = modedata[mode]['name']
posx = self.crtcStatus[crtc]['x']
posy = self.crtcStatus[crtc]['y']
self.outputStatus[name] = {}
self.outputStatus[name]['edid'] = edid
self.outputStatus[name]['crtc'] = crtc
self.outputStatus[name]['modename'] = modename
self.outputStatus[name]['posx'] = posx
self.outputStatus[name]['posy'] = posy
def applyConfiguration(self, config):
""" Apply a configuration that has been previously stored.
:param config: The configuration to apply
:return: Nothing
"""
- log.debug("applyConfiguration")
+ logger.debug("applyConfiguration")
resources = self.window.xrandr_get_screen_resources()._data
outputs = resources['outputs']
# Find all available outputs and disable outputs
# not present in the config
for output in outputs:
found = False
for ii in range(0, int(config['edidcount'])):
edid = self.get_edid(output)
if edid:
if config['edid' + str(ii+1)] == edid and config['mode' + str(ii+1)] != '0':
found = True
if not found:
crtc = self.getCurrentCrtcForOutput(output)
if crtc:
self.disableCrtc(crtc)
- self.graceTimer.start(GRACE_TIMEOUT)
# Apply stored screen configuration, based on EDID
for ii in range(0, int(config['edidcount'])):
if config['mode' + str(ii+1)] != '0':
output = self.getOutputForEdid(config['edid' + str(ii+1)])
crtc = self.getCurrentCrtcForOutput(output)
if not crtc:
crtc = self.getUsableCrtcForOutput(output)
mode = self.findModeByName(output, config['mode' + str(ii+1)])
self.configureCrtcForOutputAndMode(crtc, output, mode, int(config['posx' + str(ii+1)]), int(config['posy' + str(ii+1)]))
self.updateScreenSize()
self.getScreenInformation()
- log.debug("loadAndApply done.")
+ logger.debug("loadAndApply done.")
def updateScreenSize(self):
""" Calculate the required screen size based on the connectec CRTC sizes
and update the virtual screen size accordingly.
:return: Nothing
"""
- log.debug("updateScreenSize")
+ logger.debug("updateScreenSize")
# get all CRTC configs and calculate the required screen size
# set screen size afterwards
resources = self.window.xrandr_get_screen_resources()._data
outputs = resources['outputs']
width = 0
height = 0
for output in outputs:
oi = self.display.xrandr_get_output_info(output, resources['config_timestamp'])._data
crtc = oi['crtc']
if crtc:
info = self.display.xrandr_get_crtc_info(crtc, resources['config_timestamp'])._data
w = info['x'] + info['width']
h = info['y'] + info['height']
if w > width:
width = w
if h > height:
height = h
# Do the same as the other RandR implementations: set width/height in mm
# so that we match 96 dpi.
mm_width = (width / 96.0) * 25.4 + 0.5;
mm_height = (height / 96.0) * 25.4 + 0.5;
self.window.xrandr_set_screen_size(width, height, mm_width, mm_height)
def getScreenConfiguration(self):
""" Retrieve the current screen configuration. This is not live, i.e.
it simply returns the stored screen configuration based on hotplug events.
:return: The output status
"""
- log.debug("getScreenConfiguration")
+ logger.debug("getScreenConfiguration")
return self.outputStatus
- def getOldScreenConfiguration(self):
- """ Retrieve the previous screen configuration, i.e. the configuration
- used before the last hotplug event fired. Can be used to determine
- the difference between the configurations.
-
- :return: The old output status
- """
- log.debug("getOldScreenConfiguration")
- return self.oldOutputStatus
-
def disableCrtc(self, crtc):
""" Disable a CRTC.
:param crtc: The number of the CRTC to disable.
:return: Nothing
"""
- log.debug("disableCrtc")
+ logger.debug("disableCrtc")
resources = self.window.xrandr_get_screen_resources()._data
self.display.xrandr_set_crtc_config(crtc, resources['config_timestamp'], 0, 0, 0, randr.Rotate_0, [])
def getCurrentCrtcForOutput(self, output):
""" Retrieve the CRTC for a given output.
:param output: The output to work on
:return: The connected CRTC
"""
- log.debug("getCurrentCrtcForOutput")
+ logger.debug("getCurrentCrtcForOutput")
resources = self.window.xrandr_get_screen_resources()._data
info = self.display.xrandr_get_output_info(output, resources['config_timestamp'])._data
return info['crtc']
def configureCrtcForOutputAndMode(self, crtc, output, mode, posx, posy):
""" Configures a CRTC to a given output and a given mode.
:param crtc: The CRTC to configure
:param output: The output to configure
:param mode: The new mode to set
:param posx: The X position of the screen
:param posy: The Y position of the screen
:return: Nothing
"""
- log.debug("configureCrtcForOutputAndMode")
+ logger.debug("configureCrtcForOutputAndMode")
resources = self.window.xrandr_get_screen_resources()._data
self.display.xrandr_set_crtc_config(crtc, resources['config_timestamp'], posx, posy, mode, randr.Rotate_0, [output])
def getOutputForOutputname(self, name):
""" Retrieve the output number by output name
:param name: The output name
:return: The output number
"""
- log.debug("getOutputForOutputName")
+ logger.debug("getOutputForOutputName")
resources = self.window.xrandr_get_screen_resources()._data
for output in resources['outputs']:
info = self.display.xrandr_get_output_info(output, resources['config_timestamp'])._data
if info['name'] == name:
return output
return None
def getUsableCrtcForOutput(self, output):
""" Return the first CRTC an output can be connected to.
:param output: The output to retrieve information from
:return: The CRTC number or None
"""
- log.debug("getUsableCrtcForOuput")
+ logger.debug("getUsableCrtcForOuput")
resources = self.window.xrandr_get_screen_resources()._data
for crtc in resources['crtcs']:
crtcinfo = self.display.xrandr_get_crtc_info(crtc, resources['config_timestamp'])._data
if len(crtcinfo['outputs']) == 0:
if output in crtcinfo['possible_outputs']:
return crtc
return None
def findModeByName(self, output, modename):
""" Find the internal mode number associated with a given mode name.
:param output: The output to work with
:param modename: The modename to retrieve
:return: The mode number or None
"""
- log.debug("findModeByName")
+ logger.debug("findModeByName")
resources = self.window.xrandr_get_screen_resources()._data
modedata = self.parseModes(resources['mode_names'], resources['modes'])
info = self.display.xrandr_get_output_info(output, resources['config_timestamp'])._data
modes = info['modes']
for mode in modes:
if modedata[mode]['name'] == modename:
return mode
return None
def parseModes(self, mode_names, modes):
""" Parse the mode names returned by an output into name and data dict
:param mode_names: The mode names to extract
:param modes: The raw mode data
:return: A dict with matched up mode data and mode names
"""
- log.debug("parseModes")
+ logger.debug("parseModes")
lastIdx = 0
modedatas = dict()
for mode in modes:
modedata = dict(mode._data)
modedata['name'] = mode_names[lastIdx:lastIdx + modedata['name_length']]
modedatas[modedata['id']] = modedata
lastIdx += modedata['name_length']
return modedatas
def getOutputForEdid(self, edid):
""" Return the output a screen with a given EDID is connected to.
:param edid: The EDID to look for
:return: The output number or None
"""
- log.debug("getOuputForEdid")
+ logger.debug("getOuputForEdid")
resources = self.window.xrandr_get_screen_resources()._data
for output in resources['outputs']:
medid = self.get_edid(output)
if edid == medid:
return output
return None
# query the edid module for output_nr
def get_edid(self, output_nr):
""" Retrieve the EDID for a given output
:param output_nr: The output to look for
:return: The hexdigest of the EDID or None
"""
- log.debug("get_edid")
+ logger.debug("get_edid")
PROPERTY_EDID = self.display.intern_atom("EDID", 0)
INT_TYPE = 19
try:
props = self.display.xrandr_list_output_properties(output_nr)
except:
- log.error("Exception gettint output properties")
+ logger.error("Exception gettint output properties")
return None
if PROPERTY_EDID in props.atoms:
try:
rawedid = self.display.xrandr_get_output_property(output_nr, PROPERTY_EDID, INT_TYPE, 0, 400)
except:
- log.error("Error loading EDID data of output ", output_nr)
+ logger.error("Error loading EDID data of output ", output_nr)
return None
edidstream = rawedid._data['value']
hx = bytearray()
hx.extend(edidstream)
hs = hashlib.sha1(hx).hexdigest()
return hs
else:
return None
def run(self):
""" The main loop of the thread.
It constantly pools the xrandr interface for new events,
since doing blocked I/O sometimes locks up the interface.
New events a processed on demand, especially, the change of
screen information.
A grace timer protects the events to be processed several
times.
"""
while self.running:
ev = self.display.pending_events()
if(ev == 0):
time.sleep(0.01)
continue
e = self.display.next_event()
- log.debug("Got Event: " + str(e._data))
+ logger.debug("Got Event: " + str(e._data))
# Window has been destroyed, quit
#if e.type == X.DestroyNotify:
# sys.exit(0)
# Screen information has changed
if e.type == self.display.extension_event.ScreenChangeNotify:
- log.debug("ScreenChangeNotify")
+ logger.debug("ScreenChangeNotify")
# print(e._data)
- # Trigger a QTimer here for a few seconds that
- # then updates the screen information and
- # runs the update code
- # If a timer is running, reset it to the default
- if not self.graceTimer.isActive():
- log.info("Starting Timer...")
+ if self.processing:
+ logger.info("Starting Timer...")
self.timer.stop()
self.timer.start(DEFAULT_TIMEOUT)
else:
- log.debug("graceTimer active")
+ logginer.debug("Processing is inhibited")
# CRTC information has changed
#elif e.type == self.display.extension_event.CrtcChangeNotify:
# print('CRTC change')
# print(e._data)
# Output information has changed
#elif e.type == self.display.extension_event.OutputChangeNotify:
- # print('Output change')
+ # logger.debug('Output change')
# print(e._data)
# Output property information has changed
#elif e.type == self.display.extension_event.OutputPropertyNotify:
# print('Output property change')
# print(e._data)
# Somebody wants to tell us something
#elif e.type == X.ClientMessage:
# if e.client_type == self.WM_PROTOCOLS:
# fmt, data = e.data
# if fmt == 32 and data[0] == self.WM_DELETE_WINDOW:
# sys.exit(0)
class ConfigManager:
""" Provides an interface to the configuration file """
def __init__(self, cfgfile):
""" Initialize the module """
self.cfgfile = cfgfile
if not os.path.exists(cfgfile):
self.recreateCfg()
self.dic = {}
self.cfg2dic()
def getDic(self):
""" Retrieve the current configuration
:return: The dictionary
"""
return self.dic
def recreateCfg(self):
""" Create a new configuration file, overwriting an existing file.
:return: Nothing
"""
- log.info('Creating new config file: ' + self.cfgfile)
+ logger.info('Creating new config file: ' + self.cfgfile)
config = ConfigParser.ConfigParser()
config.add_section('General')
config.set('General', 'numentries', '0')
config.set('General', 'autosave', '0')
config.set('General', 'loadonstartup', '1')
with open(self.cfgfile, 'w') as configfile:
config.write(configfile)
def cfg2dic(self):
""" Convert the configuration file to a dictionary.
:return: The parsed dictionary
"""
dic = {}
config = ConfigParser.ConfigParser()
config.read(self.cfgfile)
for section in config.sections():
dic[section] = {}
for key, value in config.items(section):
dic[section][key] = value
self.dic = dic
return dic
def dic2cfg(self):
""" Convert the dictionary to the configuration file and save it.
:return: Nothing
"""
config = ConfigParser.ConfigParser()
for section in self.dic:
config.add_section(section)
for key in self.dic[section]:
config.set(section, key, self.dic[section][key])
with open(self.cfgfile, 'w') as configfile:
config.write(configfile)
def getConfig(self, screenConfiguration):
""" Retrieve the stored configuration mathing a given
screen configuration.
:return: A tuple with the configuration and the name or (None, None)
"""
ne = int(self.dic['General']['numentries'])
for ii in range(0, ne):
name = 'Config' + str(ii+1)
if int(self.dic[name]['edidcount']) != len(screenConfiguration):
continue
found = True
for outputName in screenConfiguration:
lFound = False
for jj in range(0, len(screenConfiguration)):
if self.dic[name]['edid' + str(jj+1)] == screenConfiguration[outputName]['edid']:
lFound = True
if lFound and found:
found = True
else:
found = False
if found:
#for jj in range(0, len(screenConfiguration)):
return (self.dic[name], name)
return (None, None)
def autosaveEnabled(self):
""" Check if autosave is enabled.
:return: bool
"""
if self.dic['General']['autosave'] == '1':
return True
return False
def loadOnStartupEnabled(self):
""" Check if load on startup is enabled.
:return: bool
"""
if self.dic['General']['loadonstartup'] == '1':
return True
return False
def getApplicationConfig(self):
""" Return the application configuration dictionary, i.e. the
"General" section.
:return: The dictionary configuration
"""
return self.dic['General']
def saveApplicationConfig(self, cfg):
""" Save the application configuration, i.e. the "General" section.
:param cfg: The dictionary with the configuration to save
:return: Nothing
"""
ne = self.dic['General']
for key in cfg:
self.dic['General'][key] = cfg[key]
self.dic2cfg()
def saveConfiguration(self, screenConfiguration):
""" Save a given screen configuration to the configuratoin file,
overwriting an existing identical configuration.
:param screenConfiguration: The screen configuration to save
:return: bool
"""
- log.info("saveConfiguration")
+ logger.info("saveConfiguration")
oldConfig, oldname = self.getConfig(screenConfiguration)
if oldConfig:
name = oldname
else:
ne = int(self.dic['General']['numentries'])
ne += 1
self.dic['General']['numentries'] = str(ne)
name = 'Config' + str(ne)
self.dic[name] = {}
self.dic[name]['edidcount'] = str(len(screenConfiguration))
cnt = 1
for outputName in screenConfiguration.keys():
self.dic[name]['edid'+str(cnt)] = screenConfiguration[outputName]['edid']
self.dic[name]['output'+str(cnt)] = outputName
self.dic[name]['mode'+str(cnt)] = screenConfiguration[outputName]['modename']
self.dic[name]['posx'+str(cnt)] = str(screenConfiguration[outputName]['posx'])
self.dic[name]['posy'+str(cnt)] = str(screenConfiguration[outputName]['posy'])
cnt += 1
self.dic2cfg()
return True
class SettingsGui(QtGui.QWidget):
""" This module contains the graphical user interface for the user-configurable
settings.
"""
loadSettings = pyqtSignal()
saveSettings = pyqtSignal([dict])
def __init__(self, parent=None):
""" Initialize the module. """
self.parent = parent
QtGui.QWidget.__init__(self, parent)
self.setWindowTitle('monitorDaemon Settings GUI')
grid = QtGui.QGridLayout()
self.setLayout(grid)
lbl1 = QtGui.QLabel('Load on Startup')
lbl2 = QtGui.QLabel('AutoSave')
self.cbAutoSave = QtGui.QCheckBox()
self.cbLoadOnStartup = QtGui.QCheckBox()
btn1 = QtGui.QPushButton('Save')
btn2 = QtGui.QPushButton('Quit')
grid.addWidget(lbl1, 1, 1)
grid.addWidget(self.cbLoadOnStartup, 1, 2)
grid.addWidget(lbl2, 2, 1)
grid.addWidget(self.cbAutoSave, 2, 2)
grid.addWidget(btn1, 3, 1)
grid.addWidget(btn2, 3, 2)
btn1.clicked.connect(self.saveBtnClicked)
btn2.clicked.connect(self.quit)
@pyqtSlot()
def quit(self):
""" Slot that is called when the application should be quit.
:return: Nothing
"""
QtGui.qApp.quit()
@pyqtSlot()
def saveBtnClicked(self):
""" Slot that is invoked when the save button has been clicked.
:return: Nothing
"""
cfg = {}
if self.cbAutoSave.isChecked():
cfg['autosave'] = '1'
else:
cfg['autosave'] = '0'
if self.cbLoadOnStartup.isChecked():
cfg['loadonstartup'] = '1'
else:
cfg['loadonstartup'] = '0'
self.saveSettings.emit(cfg)
def toggleVisibility(self, reason):
""" Toggle the visibility of the settings manager.
:return: Nothing
"""
if reason != QtGui.QSystemTrayIcon.Trigger:
return
if self.isVisible():
self.hide()
else:
self.loadSettings.emit()
self.show()
@pyqtSlot(dict)
def settingsLoaded(self, settings):
""" Slot that is called by the when the
settings GUI should be initialized with values.
:return: Nothing
"""
if settings['autosave'] == '1':
self.cbAutoSave.setChecked(True)
else:
self.cbAutoSave.setChecked(False)
if settings['loadonstartup'] == '1':
self.cbLoadOnStartup.setChecked(True)
else:
self.cbLoadOnStartup.setChecked(False)
class SystemTrayIcon(QtGui.QSystemTrayIcon):
""" Provides the system tray icon. """
def __init__(self, icon, m, parent=None):
""" Initialize the module and connect signals/slots. """
self.parent = parent
QtGui.QSystemTrayIcon.__init__(self, icon, parent)
menu = QtGui.QMenu(parent)
saveAction = menu.addAction("Save Current Configuration")
saveAction.triggered.connect(m.saveConfig)
loadAction = menu.addAction("Load and Apply Configuration")
loadAction.triggered.connect(m.loadAndApply)
exitAction = menu.addAction("Exit")
exitAction.triggered.connect(m.quit)
self.setContextMenu(menu)
self.settingsGui = SettingsGui()
self.activated.connect(self.settingsGui.toggleVisibility)
self.settingsGui.loadSettings.connect(m.loadSettings)
self.settingsGui.saveSettings.connect(m.saveSettings)
m.settingsLoaded.connect(self.settingsGui.settingsLoaded)
class Manager(QObject):
""" Handles interaction between SystemTrayIcon, Settings and XRandR interface. """
applyConfig = pyqtSignal([dict])
settingsLoaded = pyqtSignal([dict])
def __init__(self, config, xri, parent = None):
""" Initialize the module. """
QObject.__init__(self)
+ self.pendingConfigChange = False
self.config = config
self.parent = parent
self.xri = xri
- #self.xri.start()
+ self.previousConfig = {}
+ self.xdgSessionId = os.getenv('XDG_SESSION_ID')
+ self.systemBus = None
+ self.initDbus()
+
+ def unlockHandler(self):
+ """ Handle unlocking the session. Delay processing until
+ the session is active again.
+ """
+ logger.debug("unlockHandler")
+ if self.pendingConfigChange:
+ logger.debug("Waiting up to five seconds for the session to be active again..")
+ count = 0
+ timeout = False
+ while count < 5:
+ if isSessionActive():
+ logger.debug("Running pending screenConfigurationChanged")
+ self.pendingConfigChange = False
+ self.screenConfigurationChanged()
+ return
+ else:
+ count += 1
+ time.sleep(1)
+
+ def suspendHandler(self, sleeping):
+ """ Handle system suspend. If system is going to sleep, suspend
+ any work. If we are resuming, resume work operation
+ """
+ if sleeping:
+ logger.debug('System going to sleep')
+ self.xri.suspendWork()
+ else:
+ logger.debug('System resuming')
+ self.xri.resumeWork()
+
+ def initDbus(self):
+ """ Get on the system bus to register for session lock/unlock requests """
+
+ dbus_loop = DBusQtMainLoop(set_as_default=True)
+ self.systemBus = dbus.SystemBus(mainloop = dbus_loop)
+
+ sessionInterface = 'org.freedesktop.login1.Session'
+ managerInterface = 'org.freedesktop.login1.Manager'
+
+ self.systemBus.add_signal_receiver(self.unlockHandler, 'Unlock', sessionInterface)
+ self.systemBus.add_signal_receiver(self.suspendHandler, 'PrepareForSleep', managerInterface)
+
+ def isSessionActive(self):
+ """ Checks whether the session is active.
+
+ :return: True if active, otherwise False
+ """
+ serviceName = 'org.freedesktop.login1'
+ servicePath = '/org/freedesktop/login1/session/' + self.xdgSessionId
+ interface = 'org.freedesktop.login1.Session'
+ sessionObj = self.systemBus.get_object(serviceName, servicePath)
+ props_iface = dbus.Interface(sessionObj, 'org.freedesktop.DBus.Properties')
+ props = props_iface.GetAll(interface)
+ return props['State'] == "active"
@pyqtSlot()
def saveConfig(self):
""" Slot that is called when the user clicks the "Save Settings" button.
:return: Nothing
"""
screenConfig = self.xri.getScreenConfiguration()
self.config.saveConfiguration(screenConfig)
@pyqtSlot()
def loadAndApply(self):
""" Slot that is called when the user clicks the "Load and Apply" button.
:return: Nothing
"""
- log.info("loadAndApply")
+ logger.info("loadAndApply")
screenConfig = self.xri.getScreenConfiguration()
storedConfig, name = self.config.getConfig(screenConfig)
if storedConfig:
self.applyConfig.emit(storedConfig)
+ self.previousConfig = storedConfig
@pyqtSlot(dict)
def saveSettings(self, cfg):
""" Slot that is called by the settings GUI to save application settings.
:return: Nothing
"""
self.config.saveApplicationConfig(cfg)
@pyqtSlot()
def loadSettings(self):
""" Slot that is called by the settings GUI to load the application settings.
:return: Nothing
"""
cfg = self.config.getApplicationConfig()
self.settingsLoaded.emit(cfg)
@pyqtSlot()
def loadOnStartup(self):
""" Slot that is called when the application starts up. It loads the
current configuration if it matches and auto-load on startup is enabled.
:return: Nothing
"""
- log.info("loadOnStartup")
+ logger.info("loadOnStartup")
if self.config.loadOnStartupEnabled():
- self.loadAndApply()
+ self.screenConfigurationChanged()
+
+ def comparePreviousConfig(self, actConfig, previousConfig):
+ """ Compare actual config with previous config.
+
+ :return: True if both configs match, otherwise False
+ """
+ logger.debug("Comparing previous configuration")
+
+ logger.debug(actConfig)
+ logger.debug(previousConfig)
+
+ numOutputs = len(actConfig.keys())
+ numConfigs = len(previousConfig.keys()) / 5
+
+ if numOutputs != numConfigs:
+ logger.debug("Different number of outputs")
+ return False
+
+ for output in actConfig:
+ edid = actConfig[output]['edid']
+ lFound = False
+ for ii in range(1, numConfigs+1):
+ if previousConfig.has_key("edid"+str(ii)):
+ if previousConfig["edid"+str(ii)] == edid:
+ lFound = True
+ if not lFound:
+ logger.debug("EDID not found in previous config: %s", edid)
+ return False
+
+ logger.debug("Outputs match")
+ return True
+
+ def compareConfig(self, actConfig, storedConfig):
+ """ Function that checks whether two configurations are the same
+ with regard to the screen configuration. The CRTC configuration
+ does not matter here.
+
+ :return: True if configurations match, otherwise False
+ """
+ logger.debug("Comparing configurations")
+
+ numOutputs = len(actConfig.keys())
+
+ for output in actConfig:
+ for ii in range(1, numOutputs+1):
+ if actConfig[output]['edid'] == storedConfig['edid'+str(ii)]:
+ if actConfig[output]['modename'] != storedConfig['mode'+str(ii)]:
+ logger.debug('Modename different: %s vs %s', actConfig[output]['modename'], storedConfig['mode'+str(ii)])
+ return False
+ if actConfig[output]['posx'] != int(storedConfig['posx'+str(ii)]):
+ logger.debug('PosX different: %s vs %s', actConfig[output]['posx'], storedConfig['posx'+str(ii)])
+ return False
+ if actConfig[output]['posy'] != int(storedConfig['posy'+str(ii)]):
+ logger.debug('PosY different: %s vs %s', actConfig[output]['posy'], storedConfig['posy'+str(ii)])
+ return False
+
+ logger.debug("Configurations match")
+ return True
@pyqtSlot()
def screenConfigurationChanged(self):
""" Slot that is called by the XRandR interface when the screen
information changes.
This is probably one of the most important methods since it triggers
the retrieval of the stored configuration and applies the configuration
if necessary.
:return: Nothing
"""
- log.info("screenConfigurationChanged")
- oldConfig = self.xri.getOldScreenConfiguration()
- newConfig = self.xri.getScreenConfiguration()
- if len(oldConfig.keys()) != len(newConfig.keys()):
- self.loadAndApply()
- else:
- load = False
- # Check every output, if we encounter one with a changed
- # EDID, then we need to load and apply the configuration
- # otherwise, nothing has changed - save it?
- for output in oldConfig:
- if newConfig.has_key(output):
- if oldConfig[output]['edid'] == newConfig[output]['edid']:
- continue
- load = True
- break
- if load:
- self.loadAndApply()
- else:
- if self.config.autosaveEnabled():
- self.saveConfig()
+ logger.info("screenConfigurationChanged")
+
+ if not self.isSessionActive():
+ logger.debug("Deferring screen config change since session is not active")
+ self.pendingConfigChange = True
+ return
+
+ actConfig = self.xri.getScreenConfiguration()
+ if self.comparePreviousConfig(actConfig, self.previousConfig):
+ logger.debug('No new connection, triggering save if necessary')
+ if self.config.autosaveEnabled():
+ self.saveConfig()
+ return
+ storedConfig, name = self.config.getConfig(actConfig)
+ if storedConfig and not self.compareConfig(actConfig, storedConfig):
+ self.applyConfig.emit(storedConfig)
+ self.previousConfig = storedConfig
+ elif storedConfig:
+ self.previousConfig = storedConfig
@pyqtSlot()
def quit(self):
""" Slot that is called when the aplication is to quit. """
QtGui.qApp.quit()
-
-
def main():
""" Main application startup. Initialize the configuration file,
the GUI and load applicatoin settings. Fire up the XRandR interface and
listen for events.
There are no shared queues or objects, since all communication is done
via Qt signals/slots.
"""
app = QtGui.QApplication(sys.argv)
app.setQuitOnLastWindowClosed(False);
settingsdir = os.path.join(os.environ['HOME'], '.config')
cfgfile = os.path.join(settingsdir, 'monitorDaemon.ini')
cm = ConfigManager(cfgfile)
w = QtGui.QWidget()
x = xrandrInterface()
m = Manager(cm, x, w)
x.screenConfigChanged.connect(m.screenConfigurationChanged)
m.applyConfig.connect(x.applyConfiguration)
QMetaObject.invokeMethod(x, "start", Qt.QueuedConnection)
QMetaObject.invokeMethod(m, "loadOnStartup", Qt.QueuedConnection)
-
+
# Wait for up to 30 seconds for the systemTray to become available (required for, e.g., Xfce)
count = 0
while not QtGui.QSystemTrayIcon.isSystemTrayAvailable() and count < 30:
time.sleep(1)
count += 1
trayIcon = SystemTrayIcon(QtGui.QIcon("/usr/share/icons/monitorDaemon.png"), m, w)
trayIcon.show()
sys.exit(app.exec_())
if __name__ == '__main__':
""" Call the main method if we run standalone. """
main()
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Fri, Nov 22, 9:04 PM (13 h, 35 m ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
533767
Default Alt Text
(39 KB)
Attached To
rMD monitorDaemon
Event Timeline
Log In to Comment