diff --git a/lt_presentation.py b/lt_presentation.py --- a/lt_presentation.py +++ b/lt_presentation.py @@ -1,240 +1,307 @@ #!/usr/bin/env python # This is lt_presentation # A simple system tray tool to send LogiTech Presenter events # directly to the desired presentation tool. # # It wraps around Atril, xdotool and wmctrl and requires python-evdev # to communicate with the Presenter. # # Xfce's Presentation Mode is automatically activated and reset whenever # a presentation is active. import dbus import evdev import subprocess import sys import time +import os +import configparser from PyQt4.QtCore import QTimer from PyQt4 import QtGui -DEV_C = "Logitech USB Receiver" +class ConfigManager(QtGui.QWidget): + def __init__(self, config, parent = None): + QtGui.QWidget.__init__(self, parent) + self.config = config class Manager(): - def __init__(self, parent = None): + def __init__(self, config, parent = None): + self.config = config self.parent = parent self.timer = QTimer() self.timer.timeout.connect(self.check_events) self.process = None self.device = None self.wid = None self.pm_cookie = None self.presentation_mode = False self.presentation_active = False + self.viewer = self.which(self.config['programs']['pdfviewer']) + self.wmctrl = self.which('wmctrl') + self.xdotool = self.which('xdotool') def check_events(self): if self.process: if self.process.poll() == None: try: for event in self.device.read(): if event.type == evdev.ecodes.EV_KEY: if event.code == evdev.ecodes.KEY_PAGEUP and event.value == 1: self.send_key(self.wid, 'Page_Up') elif event.code == evdev.ecodes.KEY_PAGEDOWN and event.value == 1: self.send_key(self.wid, 'Page_Down') elif event.code == evdev.ecodes.KEY_F5 and event.value == 1: self.send_key(self.wid, 'F5') elif event.code == evdev.ecodes.KEY_ESC and event.value == 1: self.send_key(self.wid, 'Escape') elif event.code == evdev.ecodes.KEY_DOT and event.value == 1: self.send_key(self.wid, 'b') except BlockingIOError: pass else: self.stop_presentation() else: self.stop_presentation() def stop_presentation(self): print('Stopping Presentation') self.presentation_active = False self.timer.stop() try: self.device.ungrab() except IOError: pass self.device.close() self.device = None self.wid = None - pm = dbus.SessionBus().get_object("org.freedesktop.PowerManagement", "/org/freedesktop/PowerManagement/Inhibit") - pm.UnInhibit(self.pm_cookie) - xfc = dbus.SessionBus().get_object('org.xfce.Xfconf', '/org/xfce/Xfconf') - xfc.SetProperty('xfce4-power-manager', '/xfce4-power-manager/presentation-mode', self.presentation_mode) + if self.config['general']['inhibit_xdg_pm'] == 'yes': + pm = dbus.SessionBus().get_object("org.freedesktop.PowerManagement", "/org/freedesktop/PowerManagement/Inhibit") + pm.UnInhibit(self.pm_cookie) + if self.config['general']['xfce_pm_presentation_mode'] == 'yes': + xfc = dbus.SessionBus().get_object('org.xfce.Xfconf', '/org/xfce/Xfconf') + xfc.SetProperty('xfce4-power-manager', '/xfce4-power-manager/presentation-mode', self.presentation_mode) def check_viewer(self): - ret = subprocess.run(["atril", "--version"], stdout = subprocess.PIPE, - universal_newlines = True) - if ret.returncode == 0: - version = ret.stdout.replace('MATE Document Viewer ', '').replace('\n', '') - return version + if self.viewer: + return True else: return False def check_xdotool(self): - ret = subprocess.run(["xdotool", "--version"], stdout = subprocess.PIPE, - universal_newlines = True) - if ret.returncode == 0: - version = ret.stdout.replace('xdotool version ', '').replace('\n', '') - return version - else: - return False + if self.xdotool: + ret = subprocess.run([self.xdotool, "--version"], stdout = subprocess.PIPE, + universal_newlines = True) + if ret.returncode == 0: + version = ret.stdout.replace('xdotool version ', '').replace('\n', '') + return version + + return False def check_wmctrl(self): - ret = subprocess.run(["wmctrl", "--version"], stdout = subprocess.PIPE, - universal_newlines = True) - if ret.returncode == 0: - version = ret.stdout.replace('\n', '') - return version - else: - return False + if self.wmctrl: + ret = subprocess.run([self.wmctrl, "--version"], stdout = subprocess.PIPE, + universal_newlines = True) + if ret.returncode == 0: + version = ret.stdout.replace('\n', '') + return version + + return False def find_window_for_pid(self, pid): print('Looking for window for PID: ' + str(pid)) - ret = subprocess.run(["wmctrl", "-l", "-p"], stdout = subprocess.PIPE, + ret = subprocess.run([self.wmctrl, "-l", "-p"], stdout = subprocess.PIPE, universal_newlines = True) if ret.returncode == 0: wpid = 0 wmid = 0 for line in ret.stdout.split('\n'): args = line.split(' ') pos = 1 for arg in args: if arg == '': continue if pos == 1: wmid = arg elif pos == 3: wpid = arg pos += 1 if wpid == str(pid): return wmid return False else: return False def get_window(self, pattern): - ret = subprocess.run(["xdotool", "search", "--name", pattern], + ret = subprocess.run([self.xdotool, "search", "--name", pattern], stdout = subprocess.PIPE, universal_newlines = True) if ret.returncode == 0: return ret.stdout.replace('\n', '') else: return False def get_device(self, devname): devices = [evdev.InputDevice(fn) for fn in evdev.list_devices()] for dev in devices: if dev.name == devname: print('Found device: ', dev.fn) return dev.fn return False def send_key(self, wid, key): - ret = subprocess.run(["xdotool", "key", "--window", wid, key]) + ret = subprocess.run([self.xdotool, "key", "--window", wid, key]) return ret.returncode def startPresentation(self): print('Start Presentation') if self.presentation_active: QtGui.QMessageBox.critical(self.parent, 'Presentation already running!', 'There is already a presentation running.') return False - device = self.get_device(DEV_C) + device = self.get_device(self.config['devices']['presenter']) if not device: QtGui.QMessageBox.critical(self.parent, 'Device not found!', 'Could not find presentation remote control') return False self.device = evdev.InputDevice(device) try: self.device.grab() except IOError: QtGui.QMessageBox.critical(self.parent, 'Error grabbing device', 'Could not grab Input device.') return False fname = QtGui.QFileDialog.getOpenFileName(self.parent, 'Open file', '', 'PDF (*.pdf)') - self.process = subprocess.Popen(["atril", fname]) - # Give Atril some time to show its window + self.process = subprocess.Popen([self.viewer, fname]) + # Give the PDF viewer some time to show its window wid = self.find_window_for_pid(self.process.pid) count = 0 while not wid and count < 10: time.sleep(0.5) wid = self.find_window_for_pid(self.process.pid) count += 1 + if not wid: - QtGui.QMessageBox.critical(self.parent, 'Atril not found!', - 'Could not fin Atril presentation window in time') + QtGui.QMessageBox.critical(self.parent, 'PDF Viewer not found!', + 'Could not find the PDF presentation window in time') return False + self.wid = wid - print('Found Atril Window: ' + wid) + print('Found PDF Viewer Window: ' + wid) + # Inhibit power management - pm = dbus.SessionBus().get_object("org.freedesktop.PowerManagement", "/org/freedesktop/PowerManagement/Inhibit") - self.pm_cookie = pm.Inhibit("lt_presentation", "Presentation Starting") - xfc = dbus.SessionBus().get_object('org.xfce.Xfconf', '/org/xfce/Xfconf') - self.presentation_mode = xfc.GetProperty('xfce4-power-manager', '/xfce4-power-manager/presentation-mode') - xfc.SetProperty('xfce4-power-manager', '/xfce4-power-manager/presentation-mode', dbus.Boolean(True, variant_level=1)) + if self.config['general']['inhibit_xdg_pm'] == 'yes': + pm = dbus.SessionBus().get_object("org.freedesktop.PowerManagement", "/org/freedesktop/PowerManagement/Inhibit") + self.pm_cookie = pm.Inhibit("lt_presentation", "Presentation Starting") + + if self.config['general']['xfce_pm_presentation_mode'] == 'yes': + xfc = dbus.SessionBus().get_object('org.xfce.Xfconf', '/org/xfce/Xfconf') + self.presentation_mode = xfc.GetProperty('xfce4-power-manager', '/xfce4-power-manager/presentation-mode') + xfc.SetProperty('xfce4-power-manager', '/xfce4-power-manager/presentation-mode', dbus.Boolean(True, variant_level=1)) + self.presentation_active = True self.timer.start(10) + def which(self, program): + def is_exe(fpath): + return os.path.isfile(fpath) and os.access(fpath, os.X_OK) + + fpath, fname = os.path.split(program) + if fpath: + if is_exe(program): + return program + else: + for path in os.environ["PATH"].split(os.pathsep): + path = path.strip('"') + exe_file = os.path.join(path, program) + if is_exe(exe_file): + return exe_file + + return None + class SystemTrayIcon(QtGui.QSystemTrayIcon): def __init__(self, icon, m, parent=None): self.parent = parent QtGui.QSystemTrayIcon.__init__(self, icon, parent) menu = QtGui.QMenu(parent) presentationAction = menu.addAction("Start Presentation...") presentationAction.triggered.connect(m.startPresentation) exitAction = menu.addAction("Exit") exitAction.triggered.connect(QtGui.qApp.quit) self.setContextMenu(menu) +def load_config(cfgFile): + config = configparser.ConfigParser() + config.read(cfgFile) + return config + +def save_config(config, cfgFile): + with open(cfgFile, 'w') as configFile: + config.write(configFile) + +def recreate_config(cfgFile): + config = configparser.ConfigParser() + config['general'] = { + 'xfce_pm_presentation_mode' : 'yes', + 'inhibit_xdg_pm' : 'yes' + } + config['programs'] = { + 'pdfviewer' : 'atril' + } + config['devices'] = { + 'presenter' : 'Logitech USB Receiver' + } + save_config(config, cfgFile) + def main(): app = QtGui.QApplication(sys.argv) app.setQuitOnLastWindowClosed(False); + cfgPath = os.path.expanduser("~") + "/.lt_presentation/" + if not os.path.isdir(cfgPath): + os.makedirs(cfgPath) + cfgFile = cfgPath + "config.ini" + if not os.path.exists(cfgFile): + recreate_config(cfgFile) + + cfg = load_config(cfgFile) + w = QtGui.QWidget() - m = Manager(w) + m = Manager(cfg, w) + version = m.check_xdotool() if version: print('Running on xdotool version ' + version) else: QtGui.QMessageBox.critical(w, 'Tool not found', 'Could not find xdotool, will now quit') sys.exit(1) - atrilVersion = m.check_viewer() - if atrilVersion: - print('Running on Atril version ' + atrilVersion) + viewer = m.check_viewer() + if viewer: + print('Found configured PDF viewer ' + cfg['programs']['pdfviewer']) else: - QtGui.QMessageBox.critical(w, 'Tool not found', 'Could not find Atril, will now quit') + QtGui.QMessageBox.critical(w, 'Tool not found', 'Could not find PDF viewer, will now quit') sys.exit(1) wmctrlVersion = m.check_wmctrl() if wmctrlVersion: print('Running on wmctrl version ' + wmctrlVersion) else: QtGui.QMssageBox.critical(w, 'Tool not found', 'Could not find wmctrl, will now quit') sys.exit(1) trayIcon = SystemTrayIcon(QtGui.QIcon("/usr/share/icons/lt_presentation.png"), m, w) trayIcon.show() sys.exit(app.exec_()) if __name__ == '__main__': main()