Page MenuHomePhabricator

No OneTemporary

diff --git a/monitorDaemon.py b/monitorDaemon.py
--- a/monitorDaemon.py
+++ b/monitorDaemon.py
@@ -1,599 +1,621 @@
#!/usr/bin/env python2
# monitorDaemon.py
# (c) 2017 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
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)
-log.info("Verbose output.")
DEFAULT_TIMEOUT = 500
-GRACE_TIMEOUT = 5000
+GRACE_TIMEOUT = 3000
VERBOSE = True
class xrandrInterface(QThread):
screenConfigChanged = pyqtSignal()
def __init__(self):
QThread.__init__(self)
self.display = display.Display()
self.screen = self.display.screen()
self.running = 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)
@pyqtSlot()
def screenInformationChanged(self):
- log.info("screenInformationChanged")
+ log.debug("screenInformationChanged")
self.getScreenInformation()
self.screenConfigChanged.emit()
def disableEvents(self):
+ log.debug("disableEvents")
self.window.xrandr_select_input(0)
def enableEvents(self):
+ log.debug("enableEvents")
self.window.xrandr_select_input(
randr.RRScreenChangeNotifyMask
#| randr.RRCrtcChangeNotifyMask
#| randr.RROutputChangeNotifyMask
#| randr.RROutputPropertyNotifyMask
)
def getScreenInformation(self):
+ log.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):
- log.info("applyConfiguration")
+ log.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.")
def updateScreenSize(self):
+ log.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):
+ log.debug("getScreenConfiguration")
return self.outputStatus
def getOldScreenConfiguration(self):
+ log.debug("getOldScreenConfiguration")
return self.oldOutputStatus
def disableCrtc(self, crtc):
+ log.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):
+ log.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):
+ log.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):
+ log.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):
+ log.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):
+ log.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):
+ log.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):
+ log.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):
+ log.debug("get_edid")
PROPERTY_EDID = self.display.intern_atom("EDID", 0)
INT_TYPE = 19
-
- props = self.display.xrandr_list_output_properties(output_nr)
+ try:
+ props = self.display.xrandr_list_output_properties(output_nr)
+ except:
+ log.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)
return None
edidstream = rawedid._data['value']
hx = bytearray()
hx.extend(edidstream)
hs = hashlib.sha1(hx).hexdigest()
return hs
else:
return None
def run(self):
while self.running:
ev = self.display.pending_events()
if(ev == 0):
time.sleep(0.001)
continue
e = self.display.next_event()
+ log.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")
# 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...")
self.timer.stop()
self.timer.start(DEFAULT_TIMEOUT)
+ else:
+ log.debug("graceTimer active")
# 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')
# 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:
def __init__(self, cfgfile):
self.cfgfile = cfgfile
if not os.path.exists(cfgfile):
self.recreateCfg()
self.dic = {}
self.cfg2dic()
def getDic(self):
return self.dic
def recreateCfg(self):
log.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):
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):
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):
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):
if self.dic['General']['autosave'] == '1':
return True
return False
def loadOnStartupEnabled(self):
if self.dic['General']['loadonstartup'] == '1':
return True
return False
def getApplicationConfig(self):
return self.dic['General']
def saveApplicationConfig(self, cfg):
ne = self.dic['General']
for key in cfg:
self.dic['General'][key] = cfg[key]
self.dic2cfg()
def saveConfiguration(self, screenConfiguration):
log.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):
loadSettings = pyqtSignal()
saveSettings = pyqtSignal([dict])
def __init__(self, parent=None):
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):
QtGui.qApp.quit()
@pyqtSlot()
def saveBtnClicked(self):
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):
if reason != QtGui.QSystemTrayIcon.Trigger:
return
if self.isVisible():
self.hide()
else:
self.loadSettings.emit()
self.show()
@pyqtSlot(dict)
def settingsLoaded(self, settings):
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):
def __init__(self, icon, m, parent=None):
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):
applyConfig = pyqtSignal([dict])
settingsLoaded = pyqtSignal([dict])
def __init__(self, config, xri, parent = None):
QObject.__init__(self)
self.config = config
self.parent = parent
self.xri = xri
#self.xri.start()
@pyqtSlot()
def saveConfig(self):
screenConfig = self.xri.getScreenConfiguration()
self.config.saveConfiguration(screenConfig)
@pyqtSlot()
def loadAndApply(self):
log.info("loadAndApply")
screenConfig = self.xri.getScreenConfiguration()
storedConfig, name = self.config.getConfig(screenConfig)
if storedConfig:
self.applyConfig.emit(storedConfig)
@pyqtSlot(dict)
def saveSettings(self, cfg):
self.config.saveApplicationConfig(cfg)
@pyqtSlot()
def loadSettings(self):
cfg = self.config.getApplicationConfig()
self.settingsLoaded.emit(cfg)
@pyqtSlot()
def loadOnStartup(self):
log.info("loadOnStartup")
if self.config.loadOnStartupEnabled():
self.loadAndApply()
@pyqtSlot()
def screenConfigurationChanged(self):
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()
@pyqtSlot()
def quit(self):
QtGui.qApp.quit()
def main():
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__':
main()

File Metadata

Mime Type
text/x-diff
Expires
Sat, Mar 30, 7:46 PM (4 w, 6 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
535609
Default Alt Text
(23 KB)

Event Timeline