Page MenuHomePhabricator

No OneTemporary

diff --git a/monitorDaemon.py b/monitorDaemon.py
--- a/monitorDaemon.py
+++ b/monitorDaemon.py
@@ -1,455 +1,439 @@
#!/usr/bin/env python2
import os
import hashlib
import sys
import ConfigParser
-from Xlib import X, display
+from Xlib import display
from Xlib.ext import randr
-from PyQt4.QtCore import QThread, QTimer, pyqtSignal, QObject
+from PyQt4.QtCore import QThread, QTimer, pyqtSignal, QObject, QMetaObject, Qt
from PyQt4 import QtGui
-# TODO: We could move away from outputs completely
-# and make configuration EDID-based only
-
-DEFAULT_TIMEOUT = 500
+DEFAULT_TIMEOUT = 250
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)
+ #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)
def screenInformationChanged(self):
self.getScreenInformation()
self.screenConfigChanged.emit()
def disableEvents(self):
self.window.xrandr_select_input(0)
def enableEvents(self):
self.window.xrandr_select_input(
randr.RRScreenChangeNotifyMask
#| randr.RRCrtcChangeNotifyMask
#| randr.RROutputChangeNotifyMask
#| randr.RROutputPropertyNotifyMask
)
def getScreenInformation(self):
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]['width'] = crtcinfo['width']
- #self.crtcStatus[crtc]['height'] = crtcinfo['height']
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'])
- #print(self.crtcStatus)
for output in outputs:
info = self.display.xrandr_get_output_info(output, resources['config_timestamp'])._data
- #print(info)
if info['connection'] != 0:
continue
edid = self.get_edid(output)
name = info['name']
crtc = info['crtc']
if crtc == 0:
continue
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):
- # Rework to use getOutputForEdid and applying
- # configuration based on EDID only
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:
- info = self.display.xrandr_get_output_info(output, resources['config_timestamp'])._data
- name = info['name']
found = False
for ii in range(0, int(config['edidcount'])):
- if config['output' + str(ii+1)] == name and config['mode' + str(ii+1)] != '0':
- found = True
+ 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)
+ # Apply stored screen configuration, based on EDID
for ii in range(0, int(config['edidcount'])):
if config['mode' + str(ii+1)] != '0':
- output = self.getOutputForOutputname(config['output' + str(ii+1)])
+ 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()
def updateScreenSize(self):
# 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
- mm_width = 0
- mm_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
- wmm = oi['mm_width']
- hmm = oi['mm_height']
- if info['x'] != 0:
- mm_width += wmm
- else:
- if wmm > mm_width:
- mm_width = wmm
- if info['y'] != 0:
- mm_height += hmm
- else:
- if hmm > mm_height:
- mm_height = hmm
w = info['x'] + info['width']
- h = info['y'] = info['height']
+ 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):
return self.outputStatus
def getOldScreenConfiguration(self):
return self.oldOutputStatus
def disableCrtc(self, crtc):
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):
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):
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):
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):
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):
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):
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):
resources = self.window.xrandr_get_screen_resources()._data
for output in resources['outputs']:
medid = self.get_edid(output)
if edid == medid:
return output
- return 0
+ return None
# query the edid module for output_nr
def get_edid(self, output_nr):
PROPERTY_EDID = self.display.intern_atom("EDID", 0)
INT_TYPE = 19
props = self.display.xrandr_list_output_properties(output_nr)
if PROPERTY_EDID in props.atoms:
try:
rawedid = self.display.xrandr_get_output_property(output_nr, PROPERTY_EDID, INT_TYPE, 0, 400)
except:
print("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:
print('running')
e = self.display.next_event()
# Window has been destroyed, quit
- if e.type == X.DestroyNotify:
- sys.exit(0)
+ #if e.type == X.DestroyNotify:
+ # sys.exit(0)
# Screen information has changed
- elif e.type == self.display.extension_event.ScreenChangeNotify:
+ if e.type == self.display.extension_event.ScreenChangeNotify:
print('Screen change')
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
- #self.getScreenInformation()
self.timer.stop()
self.timer.start(DEFAULT_TIMEOUT)
# 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)
+ #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):
print('Creating new config file: ' + self.cfgfile)
config = ConfigParser.ConfigParser()
config.add_section('General')
config.set('General', 'numEntries', '0')
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:
print(self.dic[name])
#for jj in range(0, len(screenConfiguration)):
return (self.dic[name], name)
return (None, None)
def saveConfiguration(self, screenConfiguration):
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 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)
class Manager(QObject):
applyConfig = pyqtSignal([dict])
def __init__(self, config, xri, parent = None):
QObject.__init__(self)
self.config = config
self.parent = parent
self.xri = xri
- self.xri.start()
+ #self.xri.start()
def saveConfig(self):
screenConfig = self.xri.getScreenConfiguration()
self.config.saveConfiguration(screenConfig)
def loadAndApply(self):
print('loadAndApply')
screenConfig = self.xri.getScreenConfiguration()
storedConfig, name = self.config.getConfig(screenConfig)
print(storedConfig)
if storedConfig:
self.applyConfig.emit(storedConfig)
print('done')
def screenConfigurationChanged(self):
print('Slot Received')
oldConfig = self.xri.getOldScreenConfiguration()
newConfig = self.xri.getScreenConfiguration()
if len(oldConfig.keys()) != len(newConfig.keys()):
self.loadAndApply()
else:
for output in oldConfig:
if newConfig.has_key(output):
if oldConfig[output]['edid'] == newConfig[output]['edid']:
continue
self.loadAndApply()
break
print('done.')
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)
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
Wed, Jan 8, 7:50 AM (2 d, 9 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
532450
Default Alt Text
(19 KB)

Event Timeline