diff --git a/lt_presentation.py b/lt_presentation.py --- a/lt_presentation.py +++ b/lt_presentation.py @@ -1,365 +1,365 @@ #!/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 class ConfigManager(QtGui.QWidget): def __init__(self, config, parent = None): QtGui.QWidget.__init__(self, parent) self.config = config class Manager(): 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.workrave_mode = None 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 if self.config['general']['inhibit_xdg_pm'] == 'yes': try: pm = dbus.SessionBus().get_object("org.freedesktop.PowerManagement", "/org/freedesktop/PowerManagement/Inhibit") pm.UnInhibit(self.pm_cookie, dbus_interface='org.freedesktop.PowerManagement.Inhibit') except: QtGui.QMessageBox.critical(self.parent, 'Error resetting PM!', 'Failed to reset the Power Manager') if self.config['general']['xfce_pm_presentation_mode'] == 'yes': try: xfc = dbus.SessionBus().get_object('org.xfce.Xfconf', '/org/xfce/Xfconf') xfc.SetProperty('xfce4-power-manager', '/xfce4-power-manager/presentation-mode', self.presentation_mode) except: QtGui.QMessageBox.critical(self.parent, 'Error resetting Xfce PM!', 'Failed to reset the Xfce Power Manager') if self.config['general']['inhibit_workrave'] == 'yes': try: wr = dbus.SessionBus().get_object('org.workrave.Workrave', '/org/workrave/Workrave/Core') iface = dbus.Interface(wr, 'org.workrave.CoreInterface') iface.SetOperationMode(self.workrave_mode) except: QtGui.QMessageBox.critical(self.parent, 'Error resetting Workrave!', 'Failed to reset Workrave state') def check_viewer(self): if self.viewer: return True else: return False def check_xdotool(self): 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): 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([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([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): devnames = [a.strip() for a in devname.split(',')] devices = [evdev.InputDevice(fn) for fn in evdev.list_devices()] for dev in devices: if dev.name in devnames: print('Found possible device: ', dev.fn) # Check for pointing devices if 3 in dev.capabilities(): continue return dev.fn return False def send_key(self, wid, key): # Save window focus # Focus window # Send key # Restore focused window ret = subprocess.run([self.xdotool, "getwindowfocus"], stdout = subprocess.PIPE, universal_newlines = True) if ret.returncode == 0: wid_save = ret.stdout.replace('\n', '') else: return ret.returncode subprocess.run([self.xdotool, "windowfocus", "--sync", wid]) ret = subprocess.run([self.xdotool, "key", key]) subprocess.run([self.xdotool, "windowfocus", "--sync", wid_save]) 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(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)') if fname == "": return False 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, 'PDF Viewer not found!', 'Could not find the PDF presentation window in time') return False self.wid = wid print('Found PDF Viewer Window: ' + wid) # Inhibit power management if self.config['general']['inhibit_xdg_pm'] == 'yes': try: pm = dbus.SessionBus().get_object("org.freedesktop.PowerManagement", "/org/freedesktop/PowerManagement/Inhibit") self.pm_cookie = pm.Inhibit("lt_presentation", "Presentation Starting", dbus_interface='org.freedesktop.PowerManagement.Inhibit') except: QtGui.QMessageBox.critical(self.parent, 'Power Management!', 'Could not inhibit Power Management. Is the daemon active?') if self.config['general']['xfce_pm_presentation_mode'] == 'yes': try: 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)) except: QtGui.QMessageBox.critical(self.parent, 'Xfce Power Manager!', 'Could not set the Xfce Power Manager to presentation mode! Is it active?') if self.config['general']['inhibit_workrave'] == 'yes': try: wr = dbus.SessionBus().get_object('org.workrave.Workrave', '/org/workrave/Workrave/Core') iface = dbus.Interface(wr, 'org.workrave.CoreInterface') self.workrave_mode = iface.GetOperationMode() iface.SetOperationMode('suspended') except: QtGui.QMessageBox.critical(self.parent, 'Workrave!', 'Could not suspend Workrave. Is it running?') self.presentation_active = True self.timer.start(10) def quit(self): if self.presentation_active: self.stop_presentation() QtGui.qApp.quit() 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(m.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', 'inhibit_workrave' : 'yes' } config['programs'] = { 'pdfviewer' : 'atril' } config['devices'] = { 'presenter' : 'Logitech USB Receiver, Genius Ring Presenter, Wireless Presenter' } 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(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) 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 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') + QtGui.QMessageBox.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()