Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F1842555
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Size
19 KB
Subscribers
None
View Options
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
Details
Attached
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)
Attached To
rMD monitorDaemon
Event Timeline
Log In to Comment