1
0
Fork 0
mirror of https://github.com/kbumsik/VirtScreen.git synced 2025-02-12 11:21:53 +00:00

Use python logging module #8

This commit is contained in:
Bumsik Kim 2018-11-07 04:44:37 +09:00
parent f27db06b17
commit ebbbf97cdf
No known key found for this signature in database
GPG key ID: E31041C8EC5B01C6
6 changed files with 106 additions and 58 deletions

View file

@ -8,10 +8,21 @@ DOCKER_RUN_TTY=docker run --interactive --tty -v $(shell pwd):/app $(DOCKER_NAME
.ONESHELL:
.PHONY: run debug run-appimage debug-appimage
# Run script
run:
python3 -m virtscreen
debug:
QT_DEBUG_PLUGINS=1 QML_IMPORT_TRACE=1 python3 -m virtscreen --log=DEBUG
run-appimage: package/appimage/VirtScreen-x86_64.AppImage
$<
debug-appimage: package/appimage/VirtScreen-x86_64.AppImage
QT_DEBUG_PLUGINS=1 QML_IMPORT_TRACE=1 $< --log=DEBUG
# Docker tools
.PHONY: docker docker-build
@ -36,14 +47,14 @@ wheel-clean:
.PHONY: appimage-clean
.SECONDARY: package/appimage/VirtScreen-x86_64.AppImage
package/appimage/%.AppImage:
package/appimage/VirtScreen-x86_64.AppImage:
$(DOCKER_RUN) package/appimage/build.sh
$(DOCKER_RUN) chown -R $(shell id -u):$(shell id -u) package/appimage
appimage-clean:
-rm -rf package/appimage/virtscreen.AppDir package/appimage/VirtScreen-x86_64.AppImage
# For Debian packaging, https://www.debian.org/doc/manuals/maint-guide/index.en.html
# For Debian packaging, https://www.debian.org/doc/manuals/maint-guide/index.en.html
# https://www.debian.org/doc/manuals/debmake-doc/ch08.en.html#setup-py
.PHONY: deb-contents deb-clean

View file

@ -7,6 +7,8 @@ import signal
import json
import shutil
import argparse
import logging
from logging.handlers import RotatingFileHandler
from typing import Callable
import asyncio
@ -25,8 +27,12 @@ from quamash import QEventLoop
from .display import DisplayProperty
from .xrandr import XRandR
from .qt_backend import Backend, Cursor, Network
from .path import HOME_PATH, ICON_PATH, MAIN_QML_PATH, CONFIG_PATH
from .path import HOME_PATH, ICON_PATH, MAIN_QML_PATH, CONFIG_PATH, LOGGING_PATH
def error(*args, **kwargs) -> None:
"""Error printing"""
args = ('Error: ', *args)
print(*args, file=sys.stderr, **kwargs)
def main() -> None:
"""Start main program"""
@ -63,22 +69,46 @@ def main() -> None:
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')
parser.add_argument('--log', type=str,
help='Python logging level, For example, --log=DEBUG.\n'
'Only used for reporting bugs and debugging')
# Add signal handler
def on_exit(self, signum=None, frame=None):
sys.exit(0)
for sig in [signal.SIGINT, signal.SIGTERM, signal.SIGHUP, signal.SIGQUIT]:
signal.signal(sig, on_exit)
args = vars(parser.parse_args())
# Enable logging
if args['log'] is None:
args['log'] = 'WARNING'
log_level = getattr(logging, args['log'].upper(), None)
if not isinstance(log_level, int):
error('Please choose a correct python logging level')
sys.exit(1)
# When logging level is INFO or lower, print logs in terminal
# Otherwise log to a file
log_to_file = True if log_level > logging.INFO else False
FORMAT = "[%(levelname)s:%(filename)s:%(lineno)s:%(funcName)s()] %(message)s"
logging.basicConfig(level=log_level, format=FORMAT,
**({'filename': LOGGING_PATH} if log_to_file else {}))
if log_to_file:
logger = logging.getLogger()
handler = RotatingFileHandler(LOGGING_PATH, mode='a', maxBytes=1024*4, backupCount=1)
logger.addHandler(handler)
logging.info('logging enabled')
del args['log']
logging.info(f'{args}')
# Start main
args = parser.parse_args()
if any(vars(args).values()):
if any(args.values()):
main_cli(args)
else:
main_gui()
print('Program should not reach here.')
error('Program should not reach here.')
sys.exit(1)
def check_env(msg: Callable[[str], None]) -> None:
"""Check enveironments before start"""
"""Check environments before start"""
if os.environ.get('XDG_SESSION_TYPE', '').lower() == 'wayland':
msg("Currently Wayland is not supported")
sys.exit(1)
@ -94,6 +124,7 @@ def check_env(msg: Callable[[str], None]) -> None:
if not shutil.which('x11vnc'):
msg("x11vnc is not installed.")
sys.exit(1)
# Check if xrandr is correctly parsed.
try:
test = XRandR()
except RuntimeError as e:
@ -105,7 +136,7 @@ def main_gui():
app = QApplication(sys.argv)
loop = QEventLoop(app)
asyncio.set_event_loop(loop)
# Check environment first
from PyQt5.QtWidgets import QMessageBox, QSystemTrayIcon
def dialog(message: str) -> None:
@ -114,7 +145,7 @@ def main_gui():
dialog("Cannot detect system tray on this system.")
sys.exit(1)
check_env(dialog)
app.setApplicationName("VirtScreen")
app.setWindowIcon(QIcon(ICON_PATH))
os.environ["QT_QUICK_CONTROLS_STYLE"] = "Material"
@ -138,38 +169,36 @@ def main_gui():
def main_cli(args: argparse.Namespace):
loop = asyncio.get_event_loop()
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"
error("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.
# 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:
if not args['auto']:
args_virt = ['portrait', 'hidpi']
for prop in args_virt:
if vars(args)[prop]:
if args[prop]:
config['virt'][prop] = True
args_position = ['left', 'right', 'above', 'below']
tmp_args = {k: vars(args)[k] for k in args_position}
tmp_args = {k: 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)")
error("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
# Create virtscreen and Start VNC
def handle_error(msg):
print('Error: ', msg)
error(msg)
sys.exit(1)
backend.onError.connect(handle_error)
backend.createVirtScreen(config['virt']['device'], config['virt']['width'],

View file

@ -29,6 +29,7 @@ BASE_PATH = os.path.dirname(__file__) # Location of this script
X11VNC_LOG_PATH = HOME_PATH + "/x11vnc_log.txt"
X11VNC_PASSWORD_PATH = HOME_PATH + "/x11vnc_passwd"
CONFIG_PATH = HOME_PATH + "/config.json"
LOGGING_PATH = HOME_PATH + "/log.txt"
# Path in the program path
ICON_PATH = BASE_PATH + "/icon/full_256x256.png"
ASSETS_PATH = BASE_PATH + "/assets"

View file

@ -5,6 +5,7 @@ import asyncio
import signal
import shlex
import os
import logging
class SubprocessWrapper:
@ -30,7 +31,7 @@ class _Protocol(asyncio.SubprocessProtocol):
self.transport: asyncio.SubprocessTransport
def connection_made(self, transport):
print("connectionMade!")
logging.info("connectionMade!")
self.outer.connected()
self.transport = transport
transport.get_pipe_transport(0).close() # No more input
@ -47,14 +48,14 @@ class _Protocol(asyncio.SubprocessProtocol):
def pipe_connection_lost(self, fd, exc):
if fd == 0: # stdin
print("stdin is closed. (we probably did it)")
logging.info("stdin is closed. (we probably did it)")
elif fd == 1: # stdout
print("The child closed their stdout.")
logging.info("The child closed their stdout.")
elif fd == 2: # stderr
print("The child closed their stderr.")
logging.info("The child closed their stderr.")
def connection_lost(self, exc):
print("Subprocess connection lost.")
logging.info("Subprocess connection lost.")
def process_exited(self):
if self.outer.logfile is not None:
@ -62,10 +63,10 @@ class _Protocol(asyncio.SubprocessProtocol):
self.transport.close()
return_code = self.transport.get_returncode()
if return_code is None:
print("Unknown exit")
logging.error("Unknown exit")
self.outer.ended(1)
return
print("processEnded, status", return_code)
logging.info(f"processEnded, status {return_code}")
self.outer.ended(return_code)

View file

@ -7,6 +7,7 @@ import os
import shutil
import atexit
import time
import logging
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSlot, pyqtSignal, Q_ENUMS
from PyQt5.QtGui import QCursor
@ -74,7 +75,7 @@ class Backend(QObject):
p = SubprocessWrapper()
arg = 'x11vnc -opts'
ret = p.run(arg)
options = tuple(m.group(1) for m in re.finditer("\s*(-\w+)\s+", ret))
options = tuple(m.group(1) for m in re.finditer(r"\s*(-\w+)\s+", ret))
# Set/unset available x11vnc options flags in config
with open(CONFIG_PATH, 'r') as f, open(DATA_PATH, 'r') as f_data:
config = json.load(f)
@ -93,6 +94,10 @@ class Backend(QObject):
with open(CONFIG_PATH, 'w') as f:
f.write(json.dumps(config, indent=4, sort_keys=True))
def promptError(self, msg):
logging.error(msg)
self.onError.emit(msg)
# Qt properties
@pyqtProperty(str, constant=True)
def settings(self):
@ -118,7 +123,7 @@ class Backend(QObject):
try:
return QQmlListProperty(DisplayProperty, self, [DisplayProperty(x) for x in self.xrandr.screens])
except RuntimeError as e:
self.onError.emit(str(e))
self.promptError(str(e))
return QQmlListProperty(DisplayProperty, self, [])
@pyqtProperty(bool, notify=onVncUsePasswordChanged)
@ -148,28 +153,28 @@ class Backend(QObject):
@pyqtSlot(str, int, int, bool, bool)
def createVirtScreen(self, device, width, height, portrait, hidpi, pos=''):
self.xrandr.virt_name = device
print("Creating a Virtual Screen...")
logging.info("Creating a Virtual Screen...")
try:
self.xrandr.create_virtual_screen(width, height, portrait, hidpi, pos)
except subprocess.CalledProcessError as e:
self.onError.emit(str(e.cmd) + '\n' + e.stdout.decode('utf-8'))
self.promptError(str(e.cmd) + '\n' + e.stdout.decode('utf-8'))
return
except RuntimeError as e:
self.onError.emit(str(e))
self.promptError(str(e))
return
self.virtScreenCreated = True
@pyqtSlot()
def deleteVirtScreen(self):
print("Deleting the Virtual Screen...")
logging.info("Deleting the Virtual Screen...")
if self.vncState is not self.VNCState.OFF:
self.onError.emit("Turn off the VNC server first")
self.promptError("Turn off the VNC server first")
self.virtScreenCreated = True
return
try:
self.xrandr.delete_virtual_screen()
except RuntimeError as e:
self.onError.emit(str(e))
self.promptError(str(e))
return
self.virtScreenCreated = False
@ -181,11 +186,11 @@ class Backend(QObject):
try:
p.run(f"x11vnc -storepasswd {X11VNC_PASSWORD_PATH}", input=password, check=True)
except subprocess.CalledProcessError as e:
self.onError.emit(str(e.cmd) + '\n' + e.stdout.decode('utf-8'))
self.promptError(str(e.cmd) + '\n' + e.stdout.decode('utf-8'))
return
self.vncUsePassword = True
else:
self.onError.emit("Empty password")
self.promptError("Empty password")
@pyqtSlot()
def deleteVNCPassword(self):
@ -193,16 +198,16 @@ class Backend(QObject):
os.remove(X11VNC_PASSWORD_PATH)
self.vncUsePassword = False
else:
self.onError.emit("Failed deleting the password file")
self.promptError("Failed deleting the password file")
@pyqtSlot(int)
def startVNC(self, port):
# Check if a virtual screen created
if not self.virtScreenCreated:
self.onError.emit("Virtual Screen not crated.")
self.promptError("Virtual Screen not crated.")
return
if self.vncState is not self.VNCState.OFF:
self.onError.emit("VNC Server is already running.")
self.promptError("VNC Server is already running.")
return
# regex used in callbacks
patter_connected = re.compile(r"^.*Got connection from client.*$", re.M)
@ -210,27 +215,27 @@ class Backend(QObject):
# define callbacks
def _connected():
print("VNC started.")
logging.info("VNC started.")
self.vncState = self.VNCState.WAITING
def _received(data):
data = data.decode("utf-8")
if (self._vncState is not self.VNCState.CONNECTED) and patter_connected.search(data):
print("VNC connected.")
logging.info("VNC connected.")
self.vncState = self.VNCState.CONNECTED
if (self._vncState is self.VNCState.CONNECTED) and patter_disconnected.search(data):
print("VNC disconnected.")
logging.info("VNC disconnected.")
self.vncState = self.VNCState.WAITING
def _ended(exitCode):
if exitCode is not 0:
self.vncState = self.VNCState.ERROR
self.onError.emit('X11VNC: Error occurred.\n'
self.promptError('X11VNC: Error occurred.\n'
'Double check if the port is already used.')
self.vncState = self.VNCState.OFF # TODO: better handling error state
else:
self.vncState = self.VNCState.OFF
print("VNC Exited.")
logging.info("VNC Exited.")
atexit.unregister(self.stopVNC)
# load settings
with open(CONFIG_PATH, 'r') as f:
@ -250,7 +255,7 @@ class Backend(QObject):
try:
virt = self.xrandr.get_virtual_screen()
except RuntimeError as e:
self.onError.emit(str(e))
self.promptError(str(e))
return
clip = f"{virt.width}x{virt.height}+{virt.x_offset}+{virt.y_offset}"
arg = f"x11vnc -rfbport {port} -clip {clip} {options}"
@ -264,20 +269,20 @@ class Backend(QObject):
def openDisplaySetting(self, app: str = "arandr"):
# define callbacks
def _connected():
print("External Display Setting opened.")
logging.info("External Display Setting opened.")
def _received(data):
pass
def _ended(exitCode):
print("External Display Setting closed.")
logging.info("External Display Setting closed.")
self.onDisplaySettingClosed.emit()
if exitCode is not 0:
self.onError.emit(f'Error opening "{running_program}".')
self.promptError(f'Error opening "{running_program}".')
with open(DATA_PATH, 'r') as f:
data = json.load(f)['displaySettingApps']
if app not in data:
self.onError.emit('Wrong display settings program')
self.promptError('Wrong display settings program')
return
program_list = [data[app]['args'], "arandr"]
program = AsyncSubprocess(_connected, _received, _received, _ended, None)
@ -288,12 +293,12 @@ class Backend(QObject):
running_program = arg
program.run(arg)
return
self.onError.emit('Failed to find a display settings program.\n'
'Please install ARandR package.\n'
'(e.g. sudo apt-get install arandr)\n'
'Please issue a feature request\n'
'if you wish to add a display settings\n'
'program for your Desktop Environment.')
self.promptError('Failed to find a display settings program.\n'
'Please install ARandR package.\n'
'(e.g. sudo apt-get install arandr)\n'
'Please issue a feature request\n'
'if you wish to add a display settings\n'
'program for your Desktop Environment.')
@pyqtSlot()
def stopVNC(self, force=False):
@ -304,7 +309,7 @@ class Backend(QObject):
if self._vncState in (self.VNCState.WAITING, self.VNCState.CONNECTED):
self.vncServer.close()
else:
self.onError.emit("stopVNC called while it is not running")
self.promptError("stopVNC called while it is not running")
@pyqtSlot()
def clearCache(self):

View file

@ -3,6 +3,7 @@
import re
import atexit
import subprocess
import logging
from typing import List
from .display import Display
@ -53,9 +54,9 @@ class XRandR(SubprocessWrapper):
screen.height = int(match.group(7))
screen.x_offset = int(match.group(8))
screen.y_offset = int(match.group(9))
print("Display information:")
logging.info("Display information:")
for s in self.screens:
print("\t", s)
logging.info(f"\t{s}")
if self.primary_idx is None:
raise RuntimeError("There is no primary screen detected.\n"
"Go to display settings and set\n"
@ -108,7 +109,7 @@ class XRandR(SubprocessWrapper):
def create_virtual_screen(self, width, height, portrait=False, hidpi=False, pos='') -> None:
self._update_screens()
print("creating: ", self.virt)
logging.info(f"creating: {self.virt}")
self._add_screen_mode(width, height, portrait, hidpi)
arg_pos = ['left', 'right', 'above', 'below']
xrandr_pos = ['--left-of', '--right-of', '--above', '--below']