diff --git a/12-logitech-usb.rules b/12-logitech-usb.rules new file mode 100644 --- /dev/null +++ b/12-logitech-usb.rules @@ -0,0 +1,9 @@ +ACTION!="add|remove", GOTO="usb_lt_end" +SUBSYSTEMS=="usb", GOTO="usb_lt_check" +GOTO="usb_lt_end" + +LABEL="usb_lt_check" + +ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c538", MODE="0666" + +LABEL="usb_lt_end" diff --git a/lt_presentation.py b/lt_presentation.py --- a/lt_presentation.py +++ b/lt_presentation.py @@ -1,189 +1,240 @@ #!/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 from PyQt4.QtCore import QTimer from PyQt4 import QtGui DEV_C = "Logitech USB Receiver" class Manager(): def __init__(self, parent = None): 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 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) 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 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 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 def find_window_for_pid(self, pid): print('Looking for window for PID: ' + str(pid)) ret = subprocess.run(["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], 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]) 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) if not device: - print('Could not find 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 - time.sleep(2) 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') return False self.wid = wid print('Found Atril 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)) + self.presentation_active = True self.timer.start(10) 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 main(): app = QtGui.QApplication(sys.argv) + app.setQuitOnLastWindowClosed(False); w = QtGui.QWidget() m = Manager(w) version = m.check_xdotool() if version: print('Running on xdotool version ' + version) else: - print('Could not find xdotool, will now quit') + 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) else: - print('Could not find Atril, will now quit') + QtGui.QMessageBox.critical(w, 'Tool not found', 'Could not find Atril, will now quit') sys.exit(1) wmctrlVersion = m.check_wmctrl() if wmctrlVersion: print('Running on wmctrl version ' + wmctrlVersion) else: - print('Could not find wmctrl, will now quit') + QtGui.QMssageBox.critical(w, 'Tool not found', 'Could not find wmctrl, will now quit') sys.exit(1) trayIcon = SystemTrayIcon(QtGui.QIcon("lt_presentation.ico"), m, w) trayIcon.show() sys.exit(app.exec_()) if __name__ == '__main__': main()