1
0
Fork 0
mirror of https://github.com/kbumsik/VirtScreen.git synced 2025-02-12 19:31:50 +00:00

#7: Added CLI options

This commit is contained in:
Bumsik Kim 2018-06-08 18:00:15 -04:00
parent 439769ca21
commit 30ffe32f82
No known key found for this signature in database
GPG key ID: E31041C8EC5B01C6

View file

@ -1,10 +1,19 @@
#!/usr/bin/python3 #!/usr/bin/python3
# Python standard packages # Python standard packages
import sys, os, subprocess, signal, re, atexit, time, json, shutil import sys
import os
import subprocess
import signal
import re
import atexit
import time
import json
import shutil
import argparse
from pathlib import Path from pathlib import Path
from enum import Enum from enum import Enum
from typing import List, Dict from typing import List, Dict, Callable
# PyQt5 packages # PyQt5 packages
from PyQt5.QtWidgets import QApplication from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import QObject, QUrl, Qt, pyqtProperty, pyqtSlot, pyqtSignal, Q_ENUMS from PyQt5.QtCore import QObject, QUrl, Qt, pyqtProperty, pyqtSlot, pyqtSignal, Q_ENUMS
@ -356,13 +365,23 @@ class XRandR(SubprocessWrapper):
self._update_screens() self._update_screens()
return self.virt return self.virt
def create_virtual_screen(self, width, height, portrait=False, hidpi=False) -> None: def create_virtual_screen(self, width, height, portrait=False, hidpi=False, pos='') -> None:
print("creating: ", self.virt)
self._update_screens() self._update_screens()
print("creating: ", self.virt)
self._add_screen_mode(width, height, portrait, hidpi) self._add_screen_mode(width, height, portrait, hidpi)
arg_pos = ['left', 'right', 'above', 'below']
xrandr_pos = ['--left-of', '--right-of', '--above', '--below']
if pos and pos in arg_pos:
# convert pos for xrandr
pos = xrandr_pos[arg_pos.index(pos)]
pos += ' ' + self.primary.name
elif not pos:
pos = '--preferred'
else:
raise RuntimeError("Incorrect position option selected.")
self.check_output(f"xrandr --output {self.virt.name} --mode {self.mode_name}") self.check_output(f"xrandr --output {self.virt.name} --mode {self.mode_name}")
self.check_output("sleep 5") self.check_output("sleep 5")
self.check_output(f"xrandr --output {self.virt.name} --preferred") self.check_output(f"xrandr --output {self.virt.name} {pos}")
self._update_screens() self._update_screens()
def delete_virtual_screen(self) -> None: def delete_virtual_screen(self) -> None:
@ -530,11 +549,11 @@ class Backend(QObject):
# Qt Slots # Qt Slots
@pyqtSlot(str, int, int, bool, bool) @pyqtSlot(str, int, int, bool, bool)
def createVirtScreen(self, device, width, height, portrait, hidpi): def createVirtScreen(self, device, width, height, portrait, hidpi, pos=''):
self.xrandr.virt_name = device self.xrandr.virt_name = device
print("Creating a Virtual Screen...") print("Creating a Virtual Screen...")
try: try:
self.xrandr.create_virtual_screen(width, height, portrait, hidpi) self.xrandr.create_virtual_screen(width, height, portrait, hidpi, pos)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
self.onError.emit(str(e.cmd) + '\n' + e.stdout.decode('utf-8')) self.onError.emit(str(e.cmd) + '\n' + e.stdout.decode('utf-8'))
return return
@ -672,7 +691,7 @@ class Backend(QObject):
if force: if force:
# Usually called from atexit(). # Usually called from atexit().
self.vncServer.kill() self.vncServer.kill()
time.sleep(2) # Make sure X11VNC shutdown before execute next atexit(). time.sleep(3) # Make sure X11VNC shutdown before execute next atexit().
if self._vncState in (self.VNCState.WAITING, self.VNCState.CONNECTED): if self._vncState in (self.VNCState.WAITING, self.VNCState.CONNECTED):
self.vncServer.kill() self.vncServer.kill()
else: else:
@ -691,41 +710,83 @@ class Backend(QObject):
# ------------------------------------------------------------------------------- # -------------------------------------------------------------------------------
# Main Code # Main Code
# ------------------------------------------------------------------------------- # -------------------------------------------------------------------------------
def main(): def main() -> None:
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling) parser = argparse.ArgumentParser(
app = QApplication(sys.argv) formatter_class=argparse.RawTextHelpFormatter,
description='Make your iPad/tablet/computer as a secondary monitor on Linux.\n\n'
from PyQt5.QtWidgets import QSystemTrayIcon, QMessageBox 'You can start VirtScreen in the following two modes:\n\n'
' - GUI mode: A system tray icon will appear when no argument passed.\n'
if not QSystemTrayIcon.isSystemTrayAvailable(): ' You need to use this first to configure a virtual screen.\n'
QMessageBox.critical(None, "VirtScreen", ' - CLI mode: After configured the virtual screen, you can start VirtScreen\n'
"Cannot detect system tray on this system.") ' in CLI mode if you do not want a GUI, by passing any arguments\n',
epilog='example:\n'
'virtscreen # GUI mode. You need to use this first\n'
' to configure the screen\n'
'virtscreen --auto # CLI mode. Scrren will be created using previous\n'
' settings (from both GUI mode and CLI mode)\n'
'virtscreen --left # CLI mode. On the left to the primary monitor\n'
'virtscreen --below # CLI mode. Below the primary monitor.\n'
'virtscreen --below --portrait # Below, and portrait mode.\n'
'virtscreen --below --portrait --hipdi # Below, portrait, HiDPI mode.\n')
parser.add_argument('--auto', action='store_true',
help='create a virtual screen automatically using previous\n'
'settings (from both GUI mode and CLI mode)')
parser.add_argument('--left', action='store_true',
help='a virtual screen will be created left to the primary\n'
'monitor')
parser.add_argument('--right', action='store_true',
help='right to the primary monitor')
parser.add_argument('--above', '--up', action='store_true',
help='above the primary monitor')
parser.add_argument('--below', '--down', action='store_true',
help='below the primary monitor')
parser.add_argument('--portrait', action='store_true',
help='Portrait mode. Width and height of the screen are swapped')
parser.add_argument('--hidpi', action='store_true',
help='HiDPI mode. Width and height are doubled')
args = parser.parse_args()
if any(vars(args).values()):
main_cli(args)
else:
main_gui()
print('Program should not reach here.')
sys.exit(1) sys.exit(1)
if os.environ['XDG_SESSION_TYPE'] == 'wayland': def check_env(msg: Callable[[str], None]) -> None:
QMessageBox.critical(None, "VirtScreen", if os.environ['XDG_SESSION_TYPE'].lower() == 'wayland':
"Currently Wayland is not supported") msg("Currently Wayland is not supported")
sys.exit(1) sys.exit(1)
if not HOME_PATH: if not HOME_PATH:
QMessageBox.critical(None, "VirtScreen", msg("Cannot detect home directory.")
"Cannot detect home directory.")
sys.exit(1) sys.exit(1)
if not os.path.exists(HOME_PATH): if not os.path.exists(HOME_PATH):
try: try:
os.makedirs(HOME_PATH) os.makedirs(HOME_PATH)
except: except:
QMessageBox.critical(None, "VirtScreen", msg("Cannot create ~/.config/virtscreen")
"Cannot create ~/.config/virtscreen")
sys.exit(1) sys.exit(1)
if not shutil.which('x11vnc'): if not shutil.which('x11vnc'):
QMessageBox.critical(None, "VirtScreen", msg("x11vnc is not installed.")
"x11vnc is not installed.")
sys.exit(1) sys.exit(1)
try: try:
test = XRandR() test = XRandR()
except RuntimeError as e: except RuntimeError as e:
QMessageBox.critical(None, "VirtScreen", str(e)) msg(str(e))
sys.exit(1) sys.exit(1)
def main_gui():
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
app = QApplication(sys.argv)
# Check environment first
from PyQt5.QtWidgets import QMessageBox, QSystemTrayIcon
def dialog(message: str) -> None:
QMessageBox.critical(None, "VirtScreen", message)
if not QSystemTrayIcon.isSystemTrayAvailable():
dialog("Cannot detect system tray on this system.")
sys.exit(1)
check_env(dialog)
# Replace Twisted reactor with qt5reactor # Replace Twisted reactor with qt5reactor
import qt5reactor # pylint: disable=E0401 import qt5reactor # pylint: disable=E0401
qt5reactor.install() qt5reactor.install()
@ -744,10 +805,63 @@ def main():
engine = QQmlApplicationEngine() engine = QQmlApplicationEngine()
engine.load(QUrl(MAIN_QML_PATH)) engine.load(QUrl(MAIN_QML_PATH))
if not engine.rootObjects(): if not engine.rootObjects():
QMessageBox.critical(None, "VirtScreen", "Failed to load QML") dialog("Failed to load QML")
sys.exit(1) sys.exit(1)
sys.exit(app.exec_()) sys.exit(app.exec_())
reactor.run() reactor.run()
def main_cli(args: argparse.Namespace):
for key, value in vars(args).items():
print(key, ": ", value)
# Check the environment
check_env(print)
if not os.path.exists(CONFIG_PATH):
print("Configuration file does not exist.\n"
"Configure a virtual screen using GUI first.")
sys.exit(1)
# By instantiating the backend, additional verifications of config
# file will be done.
backend = Backend()
# Get settings
with open(CONFIG_PATH, 'r') as f:
config = json.load(f)
# Override settings from arguments
position = ''
if not args.auto:
args_virt = ['portrait', 'hidpi']
for prop in args_virt:
if vars(args)[prop]:
config['virt'][prop] = True
args_position = ['left', 'right', 'above', 'below']
tmp_args = {k: vars(args)[k] for k in args_position}
if not any(tmp_args.values()):
print("Choose a position relative to the primary monitor. (e.g. --left)")
sys.exit(1)
for key, value in tmp_args.items():
if value:
position = key
# Turn settings object into VNC arguments format
vnc_option = ''
for key, value in config['x11vncOptions'].items():
if value['available'] and value['enabled']:
vnc_option += key + ' '
if value['arg'] is not None:
vnc_option += str(value['arg']) + ' '
# Create virtscreen and Start VNC
def handle_error(msg):
print('Error: ', msg)
sys.exit(1)
backend.onError.connect(handle_error)
backend.createVirtScreen(config['virt']['device'], config['virt']['width'],
config['virt']['height'], config['virt']['portrait'],
config['virt']['hidpi'], position)
def handle_vnc_changed(state):
if state is backend.VNCState.OFF:
sys.exit(0)
backend.onVncStateChanged.connect(handle_vnc_changed)
from twisted.internet import reactor # pylint: disable=E0401
backend.startVNC(config['vnc']['port'], vnc_option)
reactor.run()
if __name__ == '__main__': if __name__ == '__main__':
main() main()