Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F1821712
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Size
18 KB
Subscribers
None
View Options
diff --git a/12-logitech-usb.rules b/12-logitech-usb.rules
--- a/12-logitech-usb.rules
+++ b/12-logitech-usb.rules
@@ -1,12 +1,20 @@
ACTION!="add|remove", GOTO="usb_lt_end"
+
SUBSYSTEMS=="usb", GOTO="usb_lt_check"
+SUBSYSTEM=="input", GOTO="input_lt_check"
+GOTO="usb_lt_end"
+
+LABEL="input_lt_check"
+
+ATTRS{NAME}==""Logi R500 Keyboard"", MODE="0666"
+
GOTO="usb_lt_end"
LABEL="usb_lt_check"
ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c538", MODE="0666"
ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c540", MODE="0666"
ATTRS{idVendor}=="0458", ATTRS{idProduct}=="00f1", MODE="0666"
ATTRS{idVendor}=="05b8", ATTRS{idProduct}=="3223", 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,392 +1,403 @@
#!/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.
#
# (c) 2017 Andreas Böhler
# This file is free software; you can redistribute it and/or modify
# it under the terms of either the GNU General Public License version 2
# or the GNU Lesser General Public License version 2.1, both as
# published by the Free Software Foundation.
import dbus
import evdev
import subprocess
import sys
import time
import os
import configparser
from PyQt5.QtCore import QTimer
from PyQt5 import QtGui, QtWidgets
class ConfigManager(QtWidgets.QWidget):
def __init__(self, config, parent = None):
QtWidgets.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.devices = []
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_LEFT and event.value == 1:
- self.send_key(self.wid, 'Page_Up')
- elif event.code == evdev.ecodes.KEY_RIGHT and event.value == 1:
- self.send_key(self.wid, 'Page_Down')
- elif 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
+ for device in self.devices:
+ try:
+ for event in device.read():
+ if event.type == evdev.ecodes.EV_KEY:
+ if event.code == evdev.ecodes.KEY_LEFT and event.value == 1:
+ self.send_key(self.wid, 'Page_Up')
+ elif event.code == evdev.ecodes.KEY_RIGHT and event.value == 1:
+ self.send_key(self.wid, 'Page_Down')
+ elif 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
+ for device in self.devices:
+ try:
+ device.ungrab()
+ except IOError:
+ pass
+ device.close()
+ self.devices = []
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:
QtWidgets.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)
+ xfiface = dbus.Interface(xfc, 'org.xfce.Xfconf')
+ xfiface.SetProperty('xfce4-power-manager', '/xfce4-power-manager/presentation-mode', self.presentation_mode)
except:
QtWidgets.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:
QtWidgets.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):
+ def get_devices(self, devname):
+ devicelist = []
devnames = [a.strip() for a in devname.split(',')]
devices = [evdev.InputDevice(fn) for fn in evdev.list_devices()]
for dev in devices:
+ print(dev.name)
if dev.name in devnames:
print('Found possible device: ', dev.path)
# Check for pointing devices
# 1 = EV_KEY
#
caps = dev.capabilities()
if not 1 in caps or 3 in caps:
continue
- return dev.path
- return False
+ devicelist.append(dev.path)
+ return devicelist
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:
QtWidgets.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:
+ devicelist = self.get_devices(self.config['devices']['presenter'])
+ if not devicelist:
QtWidgets.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:
+ self.devices = [evdev.InputDevice(device) for device in devicelist]
+ gotone = False
+ for device in self.devices:
+ try:
+ device.grab()
+ gotone = True
+ except IOError:
+ pass
+ if not gotone:
QtWidgets.QMessageBox.critical(self.parent, 'Error grabbing device',
- 'Could not grab Input device.')
+ 'Could not grab Input device.')
return False
fname, ffilter = QtWidgets.QFileDialog.getOpenFileName(self.parent, 'Open file', '', 'PDF (*.pdf)')
if fname == "":
return False
opts = self.config['programs']['pdfvieweropts']
print(self.viewer)
print(fname)
if opts:
self.process = subprocess.Popen([self.viewer, opts, fname])
else:
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:
QtWidgets.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:
QtWidgets.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))
+ xfiface = dbus.Interface(xfc, 'org.xfce.Xfconf')
+ self.presentation_mode = xfiface.GetProperty('xfce4-power-manager', '/xfce4-power-manager/presentation-mode')
+ xfiface.SetProperty('xfce4-power-manager', '/xfce4-power-manager/presentation-mode', dbus.Boolean(True, variant_level=1))
except:
QtWidgets.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:
QtWidgets.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()
QtWidgets.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(QtWidgets.QSystemTrayIcon):
def __init__(self, icon, m, parent=None):
self.parent = parent
QtWidgets.QSystemTrayIcon.__init__(self, icon, parent)
menu = QtWidgets.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' : 'okular',
'pdfvieweropts' : '--presentation',
}
config['devices'] = {
'presenter' : 'Logitech USB Receiver, Genius Ring Presenter, Wireless Presenter'
}
save_config(config, cfgFile)
def main():
app = QtWidgets.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 = QtWidgets.QWidget()
m = Manager(cfg, w)
version = m.check_xdotool()
if version:
print('Running on xdotool version ' + version)
else:
QtWidgets.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:
QtWidgets.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:
QtWidgets.QMessageBox.critical(w, 'Tool not found', 'Could not find wmctrl, will now quit')
sys.exit(1)
# Wait for up to 30 seconds for the systemTray to become available (required for, e.g., Xfce)
count = 0
while not QtWidgets.QSystemTrayIcon.isSystemTrayAvailable() and count < 30:
time.sleep(1)
count += 1
trayIcon = SystemTrayIcon(QtGui.QIcon("/usr/share/icons/lt_presentation.png"), m, w)
trayIcon.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Tue, Dec 24, 8:53 AM (23 h, 44 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
534220
Default Alt Text
(18 KB)
Attached To
rLTPRES Logitech Presentation Tool
Event Timeline
Log In to Comment