diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index f5848116..467d6e81 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -17,23 +17,21 @@ jobs: quality: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v1 - - name: Set up Python 3.8 + - uses: actions/checkout@v2 + - name: Set up Python 3.x uses: actions/setup-python@v1 with: - python-version: 3.8 + python-version: 3.x - name: Install dependencies run: | - python -m pip install --upgrade pip + python -m pip install --upgrade pip wheel pip install -r requirements.txt + pip3 install black - name: Lint with flake8 run: | + black --check guiscrcpy --exclude guiscrcpy/ui pip install flake8 - # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 --per-file-ignores='setup.py:E501' - name: Test setup.py run: | diff --git a/.gitignore b/.gitignore index 74d9298a..396e23e7 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ guiscrcpy.AppDir.BUILD/ guiscrcpy.AppDir/ scrcpy-squashfs-root/ squashfs-root/ +/deb_dist/ diff --git a/guiscrcpy/__main__.py b/guiscrcpy/__main__.py index 17594876..755df510 100755 --- a/guiscrcpy/__main__.py +++ b/guiscrcpy/__main__.py @@ -19,4 +19,5 @@ """ from guiscrcpy.cli import cli + cli() diff --git a/guiscrcpy/cli.py b/guiscrcpy/cli.py index c8749450..257ff339 100644 --- a/guiscrcpy/cli.py +++ b/guiscrcpy/cli.py @@ -5,13 +5,16 @@ import click import colorama +from click import Context from qtpy.QtWidgets import QApplication from qtpy import QtCore, QtWidgets from .lib.utils import format_colors as fc, show_message_box from .launcher import bootstrap -from .lib.check import AndroidDebugBridge, ScrcpyNotFoundError, \ - AdbNotFoundError, ScrcpyServerNotFoundError +from .lib.bridge.exceptions import ScrcpyNotFoundError +from .lib.bridge.exceptions import AdbNotFoundError +from .lib.bridge.exceptions import ScrcpyServerNotFoundError +from .lib.bridge import AndroidDebugBridge from .lib.config import InterfaceConfig, InvalidConfigurationError from .version import VERSION from . import __doc__ as lic @@ -24,7 +27,7 @@ # srevin: bad snap! _sys_argv = list() for arg in sys.argv: - if not arg.startswith('/snap'): + if not arg.startswith("/snap"): _sys_argv.append(arg) sys.argv = _sys_argv @@ -33,25 +36,27 @@ def show_version(ctx, param, value): # noqa: """Prints the version of the utility""" if not value or ctx.resilient_parsing: return - click.echo(fc('{g}guiscrcpy {rst}v{v}', v=VERSION)) - if os.getenv('APPIMAGE'): + click.echo(fc("{g}guiscrcpy {rst}v{v}", v=VERSION)) + if os.getenv("APPIMAGE"): click.echo("Running from AppImage") - if os.getenv('APPDIR'): - click.echo("AppDir: {}".format(os.getenv('APPDIR'))) + if os.getenv("APPDIR"): + click.echo("AppDir: {}".format(os.getenv("APPDIR"))) import inspect from PyQt5 import Qt + _pyqt5_version = [ - '%s = %s' % (k, v) for k, v in - vars(Qt).items() if - k.lower().find('version') >= 0 and not inspect.isbuiltin(v) + "%s = %s" % (k, v) + for k, v in vars(Qt).items() + if k.lower().find("version") >= 0 and not inspect.isbuiltin(v) ] print() print("== PyQt5 Version ==") - print('\n'.join(sorted(_pyqt5_version))) + print("\n".join(sorted(_pyqt5_version))) print() if platform.system() == "Linux": print("== CairoSVG version ==") from cairosvg import VERSION as CAIRO_VERSION # noqa: + print("CairoSVG == {}".format(CAIRO_VERSION)) print() @@ -68,32 +73,55 @@ def show_license(ctx, param, value): # noqa: @click.group(invoke_without_command=True) @click.pass_context -@click.option('--version', is_flag=True, - callback=show_version, - expose_value=False, is_eager=True) -@click.option('--license', '--lic', is_flag=True, - callback=show_license, - expose_value=False, is_eager=True) -@click.option('-T', - '--theme', 'theme', - default='Breeze', - help="Set the default theme (based on PyQt5 " - "themes - Fusion, Breeze, Windows) " - "(stored in configuration, override by --theme-no-cfg)") -@click.option('-W', - '--hide-wm-frame/--show-wm-frame', - 'hide_wm_frame', - default=True, - help="Show window manager border frame.") -@click.option('--debug-disable-scrcpy', - 'debug__disable_scrcpy', - default=False, is_flag=True, - help="Do not launch scrcpy even when 'Start Scrcpy' is pressed") -@click.option('-A', '--always-on-top/--disable-always-on-top', 'aot', - default=True, help="Forces the panels to be always of top") -def cli(ctx, hide_wm_frame=True, aot=True, theme='Breeze', - debug__disable_scrcpy=False): - """ guiscrcpy: Graphical user interface for scrcpy""" +@click.option( + "--version", is_flag=True, callback=show_version, expose_value=False, is_eager=True +) +@click.option( + "--license", + "--lic", + is_flag=True, + callback=show_license, + expose_value=False, + is_eager=True, +) +@click.option( + "-T", + "--theme", + "theme", + default="Breeze", + help="Set the default theme (based on PyQt5 " + "themes - Fusion, Breeze, Windows) " + "(stored in configuration, override by --theme-no-cfg)", +) +@click.option( + "-W", + "--hide-wm-frame/--show-wm-frame", + "hide_wm_frame", + default=True, + help="Show window manager border frame.", +) +@click.option( + "--debug-disable-scrcpy", + "debug_disable_scrcpy", + default=False, + is_flag=True, + help="Do not launch scrcpy even when 'Start Scrcpy' is pressed", +) +@click.option( + "-A", + "--always-on-top/--disable-always-on-top", + "aot", + default=True, + help="Forces the panels to be always of top", +) +def cli( + ctx: Context, + hide_wm_frame: bool = True, + aot: bool = True, + theme: str = "Breeze", + debug_disable_scrcpy: bool = False, +): + """guiscrcpy: Graphical user interface for scrcpy""" print(fc("\n{b}guiscrcpy {v}{rst}", v=VERSION)) print(fc("by @srevinsaju")) print(fc("{x}https://github.com/srevinsaju/guiscrcpy{rst}\n\n")) @@ -110,33 +138,38 @@ def cli(ctx, hide_wm_frame=True, aot=True, theme='Breeze', QApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True) # init core app = QtWidgets.QApplication(sys.argv) - cfgmgr = InterfaceConfig() + config_manager = InterfaceConfig() bootstrap( - app, - cfgmgr, + app=app, + config_manager=config_manager, theme=theme, aot=aot, hide_wm_frame=hide_wm_frame, - debug__no_scrcpy=debug__disable_scrcpy) + debug_no_scrcpy=debug_disable_scrcpy, + ) except ScrcpyNotFoundError: _msg_box = show_message_box( text="Scrcpy not found", info_text="guiscrcpy could not find scrcpy. " - "Make sure you select scrcpy in the dialog box " - "or add scrcpy to PATH. You can get the latest release " - "of scrcpy from " - "Genymobile/scrcpy 's GitHub Releases " - "and select the
scrcpy{ext}
when you run " - "guiscrcpy next time".format( - ext=".exe" if platform.system() == "Windows" else "")) + "Make sure you select scrcpy in the dialog box " + "or add scrcpy to PATH. You can get the latest release " + "of scrcpy from " + "Genymobile/scrcpy 's GitHub Releases " + "and select the
scrcpy{ext}
when you run " + "guiscrcpy next time".format( + ext=".exe" if platform.system() == "Windows" else "" + ), + ) _msg_box.exec_() print("Aborting!") sys.exit(-1) except AdbNotFoundError: _msg_box = show_message_box( - text="ADB not found", info_text="guiscrcpy could not find adb. " - "Make sure you select adb in the dialog box or add adb to PATH") + text="ADB not found", + info_text="guiscrcpy could not find adb. " + "Make sure you select adb in the dialog box or add adb to PATH", + ) _msg_box.exec_() print("Aborting!") sys.exit(-1) @@ -144,12 +177,12 @@ def cli(ctx, hide_wm_frame=True, aot=True, theme='Breeze', _msg_box = show_message_box( text="Invalid configuration error", info_text="The configuration file for guiscrcpy is invalid.
" - "This is possibly because a new version of guiscrcpy " - "was installed, or the old paths to `adb` and `scrcpy` " - "as defined in the configuration file, no longer exists " - "in the same path. To fix this error," - "Run " - "
guiscrcpy config -r
on your terminal." + "This is possibly because a new version of guiscrcpy " + "was installed, or the old paths to `adb` and `scrcpy` " + "as defined in the configuration file, no longer exists " + "in the same path. To fix this error," + "Run " + "
guiscrcpy config -r
on your terminal.", ) _msg_box.exec_() print("Aborting!") @@ -158,12 +191,12 @@ def cli(ctx, hide_wm_frame=True, aot=True, theme='Breeze', _msg_box = show_message_box( text="Scrcpy server not found error", info_text="The configuration file for guiscrcpy is invalid.
" - "This is possibly because a new version of guiscrcpy " - "was installed, or the old paths to `adb` and `scrcpy` " - "as defined in the configuration file, no longer exists " - "in the same path. To fix this error," - "Run " - "
guiscrcpy config -r
on your terminal." + "This is possibly because a new version of guiscrcpy " + "was installed, or the old paths to `adb` and `scrcpy` " + "as defined in the configuration file, no longer exists " + "in the same path. To fix this error," + "Run " + "
guiscrcpy config -r
on your terminal.", ) _msg_box.exec_() print("Aborting!") @@ -174,10 +207,10 @@ def cli(ctx, hide_wm_frame=True, aot=True, theme='Breeze', _msg_box = show_message_box( text="Error: Unhandled exception", info_text="
{error_message}
" - "Please report this, if its a bug, to " - "guiscrcpy bug tracker as it will help to improve " - "the next release.".format(error_message=error_message) + "Please report this, if its a bug, to " + "guiscrcpy bug tracker as it will help to improve " + "the next release.".format(error_message=error_message), ) _msg_box.exec_() print("Aborting!") @@ -185,23 +218,32 @@ def cli(ctx, hide_wm_frame=True, aot=True, theme='Breeze', @cli.command() -@click.option('--device-id', 'device_id', - help="Sets the device-id for mapper to configure " - " (optional, needed for multiple devices)") -@click.option('-r', '--reset', 'reset', is_flag=True, - help="Reset guiscrcpy's mapper configuration file") +@click.option( + "--device-id", + "device_id", + help="Sets the device-id for mapper to configure " + " (optional, needed for multiple devices)", +) +@click.option( + "-r", + "--reset", + "reset", + is_flag=True, + help="Reset guiscrcpy's mapper configuration file", +) def mapper(device_id=None, reset=False): """Run the guiscrcpy mapper""" - cfgmgr = InterfaceConfig() - config = cfgmgr.get_config() + config_manager = InterfaceConfig() + config = config_manager.get_config() mapper_cfg_path = os.path.join( - cfgmgr.get_cfgpath(), 'guiscrcpy.mapper.json' + config_manager.get_cfgpath(), "guiscrcpy.mapper.json" ) if reset: # A ternary version of removing a file if it exists # https://stackoverflow.com/q/10840533/ - mapper_configuration_file_exists = os.remove(mapper_cfg_path) if \ - os.path.exists(mapper_cfg_path) else None + mapper_configuration_file_exists = ( + os.remove(mapper_cfg_path) if os.path.exists(mapper_cfg_path) else None + ) if mapper_configuration_file_exists: print("guiscrcpy mapper configuration file has been removed.") print("Removed {}".format(mapper_configuration_file_exists)) @@ -209,10 +251,10 @@ def mapper(device_id=None, reset=False): print("guiscrcpy mapper configuration is not created yet.") return - if os.getenv('GUISCRCPY_ADB'): - adb_path = os.getenv('GUISCRCPY_ADB') + if os.getenv("GUISCRCPY_ADB"): + adb_path = os.getenv("GUISCRCPY_ADB") else: - adb_path = config['adb'] + adb_path = config["adb"] adb = AndroidDebugBridge(adb_path) adb_devices_list = adb.devices() if len(adb_devices_list) == 0: @@ -221,18 +263,19 @@ def mapper(device_id=None, reset=False): elif len(adb_devices_list) == 1: mapper_device_id = adb_devices_list[0][0] elif not device_id: - print("Please pass the --device-id to initialize " - "the mapper") + print("Please pass the --device-id to initialize " "the mapper") sys.exit(1) else: mapper_device_id = device_id from guiscrcpy.lib.mapper.mapper import Mapper + # Initialize the mapper if it is called. - adb = AndroidDebugBridge(path=cfgmgr.get_config().get('adb')) + adb = AndroidDebugBridge(path=config_manager.get_config().get("adb")) mp = Mapper(mapper_device_id, adb=adb, config_path=mapper_cfg_path) if not os.path.exists( - os.path.join(cfgmgr.get_cfgpath(), 'guiscrcpy.mapper.json')): + os.path.join(config_manager.get_cfgpath(), "guiscrcpy.mapper.json") + ): print("guiscrcpy.mapper.json does not exist. ") print("Initializing Mapper Configuration for the first time use.") mp.initialize(initialize_qt=True) @@ -242,54 +285,55 @@ def mapper(device_id=None, reset=False): print("Your keyboard is being listened by guiscrcpy-mapper") print("pressing any key will trigger the position.") print() - print('If you would like to register new keys, pass --mapper-reset') + print("If you would like to register new keys, pass --mapper-reset") print("\nInitializing\n\n") mp.listen_keypress() print("Done!") -@cli.command('adb') -@click.argument('args', nargs=-1) +@cli.command("adb") +@click.argument("args", nargs=-1) def adb_cli(args): """Create an interface with the Android Debugging bridge""" - cfgmgr = InterfaceConfig() - config = cfgmgr.get_config() - if os.getenv('GUISCRCPY_ADB'): - adb_path = os.getenv('GUISCRCPY_ADB') + config_manager = InterfaceConfig() + config = config_manager.get_config() + if os.getenv("GUISCRCPY_ADB"): + adb_path = os.getenv("GUISCRCPY_ADB") else: - adb_path = config['adb'] + adb_path = config["adb"] print("Interfacing guiscrcpy-adb") - os.system('{} {}'.format(adb_path, ' '.join(args))) + os.system("{} {}".format(adb_path, " ".join(args))) pass @cli.command() -@click.argument('args', nargs=-1) +@click.argument("args", nargs=-1) def scrcpy(args): """Create an interface with scrcpy""" - cfgmgr = InterfaceConfig() - config = cfgmgr.get_config() - if os.getenv('GUISCRCPY_SCRCPY'): - scrcpy_path = os.getenv('GUISCRCPY_SCRCPY') + config_manager = InterfaceConfig() + config = config_manager.get_config() + if os.getenv("GUISCRCPY_SCRCPY"): + scrcpy_path = os.getenv("GUISCRCPY_SCRCPY") else: - scrcpy_path = config['scrcpy'] + scrcpy_path = config["scrcpy"] print("Interfacing guiscrcpy-scrcpy") - os.system('{} {}'.format(scrcpy_path, ' '.join(args))) + os.system("{} {}".format(scrcpy_path, " ".join(args))) pass -@cli.command('config') -@click.option('-r', '--reset', 'reset', is_flag=True, - help="Reset the configuration files") +@cli.command("config") +@click.option( + "-r", "--reset", "reset", is_flag=True, help="Reset the configuration files" +) def _config(reset=False): """View / Edit the configuration file""" - cfgmgr = InterfaceConfig(load=False) + config_manager = InterfaceConfig(load=False) if reset: - cfgmgr.reset_config() + config_manager.reset_config() click.echo("Configuration file resetted successfully.") sys.exit(0) - cfgmgr.load_config() - print(cfgmgr) + config_manager.load_config() + print(config_manager) @cli.command() diff --git a/guiscrcpy/constants.py b/guiscrcpy/constants.py index 59f1cc31..e8d6c779 100644 --- a/guiscrcpy/constants.py +++ b/guiscrcpy/constants.py @@ -1,8 +1,8 @@ FONTS = [ - 'NotoSans-BoldItalic.ttf', - 'NotoSans-Italic.ttf', - 'TitilliumWeb-Bold.ttf', - 'NotoSans-Bold.ttf', - 'NotoSans-Regular.ttf', - 'TitilliumWeb-Regular.ttf', + "NotoSans-BoldItalic.ttf", + "NotoSans-Italic.ttf", + "TitilliumWeb-Bold.ttf", + "NotoSans-Bold.ttf", + "NotoSans-Regular.ttf", + "TitilliumWeb-Regular.ttf", ] diff --git a/guiscrcpy/install/finder.py b/guiscrcpy/install/finder.py index 07c3461b..4a429bce 100644 --- a/guiscrcpy/install/finder.py +++ b/guiscrcpy/install/finder.py @@ -4,14 +4,13 @@ def open_exe_name_dialog(parent, appname): options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog - file_name, _ = \ - QFileDialog.getOpenFileName( - parent, - "{} could not be found. Please locate it manually".format(appname), - "", - "Valid {} executable (*);;".format(appname), - options=options - ) + file_name, _ = QFileDialog.getOpenFileName( + parent, + "{} could not be found. Please locate it manually".format(appname), + "", + "Valid {} executable (*);;".format(appname), + options=options, + ) if file_name: print(file_name) return file_name diff --git a/guiscrcpy/launcher.py b/guiscrcpy/launcher.py index fd63fcd3..37062741 100644 --- a/guiscrcpy/launcher.py +++ b/guiscrcpy/launcher.py @@ -45,6 +45,10 @@ from qtpy.QtGui import QPixmap, QIcon, QFont, QFontDatabase from qtpy.QtWidgets import QMainWindow, QListWidgetItem, QMenu from qtpy.QtWidgets import QMessageBox + +from .lib.bridge.audio.sndcpy import SndcpyBridge +from .lib.bridge.audio.usbaudio import USBAudioBridge +from .lib.config import InterfaceConfig from .lib.utils import format_colors as fc from .constants import FONTS from .install.finder import open_exe_name_dialog @@ -60,8 +64,9 @@ from .ux.swipe import SwipeUX from .ux.toolkit import InterfaceToolkit from .version import VERSION -from .lib.check import AndroidDebugBridge, ScrcpyBridge, \ - ScrcpyServerNotFoundError +from .lib.bridge import AndroidDebugBridge, ScrcpyBridge +from .lib.bridge.exceptions import ScrcpyServerNotFoundError + environment = platform.System() @@ -70,6 +75,7 @@ if environment.system() == "Linux": try: from cairosvg import svg2png # noqa: + has_cairo = True except Exception as e: print("Failed to load cairo:", e) @@ -84,19 +90,26 @@ class InterfaceGuiscrcpy(QMainWindow, Ui_MainWindow): """ # noinspection PyArgumentList - def __init__(self, cfgmgr, adb, scrcpy, force_window_frame=False, - panels_not_always_on_top=False, debug__no_scrcpy=False): + def __init__( + self, + config_manager: InterfaceConfig, + adb: AndroidDebugBridge, + scrcpy: ScrcpyBridge, + force_window_frame: bool = False, + panels_not_always_on_top: bool = False, + debug_no_scrcpy: bool = False, + ): QMainWindow.__init__(self) Ui_MainWindow.__init__(self) self.setupUi(self) self._adb = adb self._scrcpy = scrcpy - self.cfgmgr = cfgmgr - config = self.cfgmgr.get_config() + self.config_manager = config_manager + config = self.config_manager.get_config() self._config = config self.panels_not_always_on_top = panels_not_always_on_top self.force_window_frame = force_window_frame - self.debug__no_scrcpy = debug__no_scrcpy + self.debug__no_scrcpy = debug_no_scrcpy self.cmx = None self.sm = None self.mp = None @@ -106,48 +119,50 @@ def __init__(self, cfgmgr, adb, scrcpy, force_window_frame=False, self.side_instance = None self.child_windows = list() self.options = "" - log("Options received by class are : {} {} {} {} {} ".format( - config['bitrate'], - config['dimension'], - config['swtouches'], - config['dispRO'], - config['fullscreen'], - )) + log( + "Options received by class are : {} {} {} {} {} ".format( + config["bitrate"], + config["dimension"], + config["swtouches"], + config["dispRO"], + config["fullscreen"], + ) + ) # ==================================================================== # Rotation; read config, update UI self.device_rotation.setCurrentIndex(config.get("rotation", 0)) - self.dial.setValue(int(config['bitrate'])) - if config['swtouches']: + self.dial.setValue(int(config["bitrate"])) + if config["swtouches"]: self.showTouches.setChecked(True) else: self.showTouches.setChecked(False) - if config['dispRO']: + if config["dispRO"]: self.displayForceOn.setChecked(True) else: self.displayForceOn.setChecked(False) # panels - if config['panels'].get('swipe'): + if config["panels"].get("swipe"): self.check_swipe_panel.setChecked(True) else: self.check_swipe_panel.setChecked(False) - if config['panels'].get('tookit'): + if config["panels"].get("tookit"): self.check_side_panel.setChecked(True) else: self.check_side_panel.setChecked(False) - if config['panels'].get('bottom'): + if config["panels"].get("bottom"): self.check_bottom_panel.setChecked(True) else: self.check_bottom_panel.setChecked(False) # dimension - if config['dimension'] is not None: + if config["dimension"] is not None: self.dimensionDefaultCheckbox.setChecked(False) try: - self.dimensionSlider.setValue(config['dimension']) + self.dimensionSlider.setValue(config["dimension"]) except TypeError: self.dimensionDefaultCheckbox.setChecked(True) - if config['fullscreen']: + if config["fullscreen"]: self.fullscreen.setChecked(True) else: self.fullscreen.setChecked(False) @@ -159,8 +174,7 @@ def __init__(self, cfgmgr, adb, scrcpy, force_window_frame=False, self.display_public_message("SCRCPY SERVER NOT RUNNING") # CONNECT DIMENSION CHECK BOX TO STATE CHANGE - self.dimensionDefaultCheckbox.stateChanged.connect( - self.__dimension_change_cb) + self.dimensionDefaultCheckbox.stateChanged.connect(self.__dimension_change_cb) self.build_label.setText("Build {} by srevinsaju".format(VERSION)) # DIAL CTRL GRP @@ -171,15 +185,15 @@ def __init__(self, cfgmgr, adb, scrcpy, force_window_frame=False, # MAIN EXECUTE ACTION self.executeaction.clicked.connect(self.start_act) try: - if config['extra']: - self.flaglineedit.setText(config['extra']) + if config["extra"]: + self.flaglineedit.setText(config["extra"]) except Exception as err: log(f"Exception: flaglineedit.text(config[extra]) {err}") self.quit.clicked.connect(self.quit_window) self.dimensionText.setText("DEFAULT") - config['bitrate'] = int(self.dial.value()) - self.bitrateText.setText(" " + str(config['bitrate']) + "KB/s") + config["bitrate"] = int(self.dial.value()) + self.bitrateText.setText(" " + str(config["bitrate"]) + "KB/s") self.pushButton.setText("RESET") self.pushButton.clicked.connect(self.reset) self.abtgit.clicked.connect(self.launch_web_github) @@ -189,26 +203,23 @@ def __init__(self, cfgmgr, adb, scrcpy, force_window_frame=False, self.settings_button.clicked.connect(self.settings_mgr) # self.devices_view.itemChanged.connect(self.update_rotation_combo_cb) self.devices_view.itemClicked.connect(self.update_rotation_combo_cb) - self.refreshdevices.clicked.connect( - self.scan_devices_update_list_view - ) - self.restart_adb_server.clicked.connect( - self.restart_adb_server_guiscrcpy) + self.refreshdevices.clicked.connect(self.scan_devices_update_list_view) + self.restart_adb_server.clicked.connect(self.restart_adb_server_guiscrcpy) self.devices_view.itemClicked.connect(self.more_options_device_view) self.devices_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.scan_config_devices_update_list_view() self.refresh_devices() @property - def adb(self): + def adb(self) -> AndroidDebugBridge: return self._adb @property - def scrcpy(self): + def scrcpy(self) -> ScrcpyBridge: return self._scrcpy @property - def config(self): + def config(self) -> dict: return self._config def restart_adb_server_guiscrcpy(self): @@ -229,38 +240,46 @@ def update_rotation_combo_cb(self): """ if self.devices_view.currentItem(): _, device_id = self.current_device_identifier() - _rotation = self.config.get('device').get(device_id, dict()).get( - 'rotation', self.device_rotation.currentIndex()) + _rotation = ( + self.config.get("device") + .get(device_id, dict()) + .get("rotation", self.device_rotation.currentIndex()) + ) else: - _rotation = self.config.get("rotation", - self.device_rotation.currentIndex()) + _rotation = self.config.get("rotation", self.device_rotation.currentIndex()) self.device_rotation.setCurrentIndex(_rotation) def settings_mgr(self): from guiscrcpy.ux.settings import InterfaceSettings + self.sm = InterfaceSettings(self) self.sm.init() self.sm.show() def network_mgr(self): from guiscrcpy.ux.network import InterfaceNetwork + self.nm = InterfaceNetwork(self.adb) self.nm.init() self.nm.show() def bootstrap_mapper(self): - mapper_config_path = os.path.join(self.cfgmgr.get_cfgpath(), - "guiscrcpy.mapper.json") + mapper_config_path = os.path.join( + self.config_manager.get_cfgpath(), "guiscrcpy.mapper.json" + ) if os.path.exists(mapper_config_path): from guiscrcpy.lib.mapper.mapper import MapperAsync + _, identifier = self.current_device_identifier() - self.mp = MapperAsync(self, device_id=identifier, - config_path=mapper_config_path, - adb=self.adb, initialize=False) - self.mp.start() - self.private_message_box_adb.setText( - "guiscrcpy-mapper has started" + self.mp = MapperAsync( + self, + device_id=identifier, + config_path=mapper_config_path, + adb=self.adb, + initialize=False, ) + self.mp.start() + self.private_message_box_adb.setText("guiscrcpy-mapper has started") else: message_box = QMessageBox() message_box.setText( @@ -275,15 +294,16 @@ def bootstrap_mapper(self): user_message_box_response = message_box.exec() values_devices_list = self.scan_devices_update_list_view() self.check_devices_status_and_select_first_if_only_one( - values_devices_list=values_devices_list) + values_devices_list=values_devices_list + ) # TODO: allow enabling mapper from inside if user_message_box_response == QMessageBox.Yes: - self.private_message_box_adb.setText( - "Initializing mapper...") - print("Make sure your phone is connected and display is " - "switched on") - print("Reset mapper if you missed any " - "steps by 'guiscrcpy --mapper-reset'") + self.private_message_box_adb.setText("Initializing mapper...") + print("Make sure your phone is connected and display is " "switched on") + print( + "Reset mapper if you missed any " + "steps by 'guiscrcpy --mapper-reset'" + ) print() print( "If at first you don't succeed... " @@ -293,25 +313,26 @@ def bootstrap_mapper(self): _, identifier = self.current_device_identifier() executable = get_self() from .lib.utils import shellify as sx + subprocess.Popen( - sx('{} mapper'.format(executable)), + sx("{} mapper".format(executable)), stdout=sys.stdout, stdin=sys.stdin, stderr=sys.stderr, - cwd=os.getcwd()) + cwd=os.getcwd(), + ) print("Mapper started") - self.private_message_box_adb.setText( - "Mapper initialized") + self.private_message_box_adb.setText("Mapper initialized") - @staticmethod - def launch_usb_audio(): - for path in environment.paths(): - if os.path.exists(os.path.join(path, 'usbaudio')): - path_to_usbaudio = os.path.join(path, 'usbaudio') - break + def launch_usb_audio(self): + android_version = self.adb.get_target_android_version() + if android_version == 10: + audio_bridge = SndcpyBridge() else: - return - Popen(path_to_usbaudio, stdout=PIPE, stderr=PIPE) + audio_bridge = USBAudioBridge() + + # FIXME: provide the right device id + audio_bridge.run(device_id=None) @staticmethod def launch_web_github(): @@ -338,7 +359,7 @@ def reset(self): Remove configuration files; Reset the mapper and guiscrcpy.json :return: """ - self.cfgmgr.reset_config() + self.config_manager.reset_config() log("CONFIGURATION FILE REMOVED SUCCESSFULLY") log("RESTART") message_box = QMessageBox().window() @@ -370,16 +391,16 @@ def forget_paired_device(self): """ try: _, identifier = self.current_device_identifier() - popped_device = self.config['device'].pop(identifier) + popped_device = self.config["device"].pop(identifier) self.refresh_devices() - self.cfgmgr.update_config(self.config) - self.cfgmgr.write_file() + self.config_manager.update_config(self.config) + self.config_manager.write_file() return popped_device except KeyError: return False def more_options_device_view(self, button): - if 'Disconnect' in button.text(): + if "Disconnect" in button.text(): menu = QMenu("Menu", self) menu.addAction("Pair / Ping", self.ping_paired_device) menu.addAction("Attempt TCPIP on device", self.tcpip_paired_device) @@ -390,56 +411,56 @@ def more_options_device_view(self, button): menu.addAction("Attempt reconnection", self.ping_paired_device) menu.addAction("Refresh", self.refresh_devices) _, identifier = self.current_device_identifier() - if platform.System.system() == "Linux" and identifier.count('.') >= 3: + if platform.System.system() == "Linux" and identifier.count(".") >= 3: menu.addAction( "Add Desktop Shortcut to this device", - self.create_desktop_shortcut_linux_os + self.create_desktop_shortcut_linux_os, ) menu.exec_( self.devices_view.mapToGlobal( QPoint( self.devices_view.visualItemRect(button).x() + 22, - self.devices_view.visualItemRect(button).y() + 22 + self.devices_view.visualItemRect(button).y() + 22, ) ) ) - def create_desktop_shortcut_linux_os(self): + def create_desktop_shortcut_linux_os(self) -> bool: """ Creates a desktop shortcut for Linux OS - :return: + :return: bool """ # just a check before anything further happens because of an # unrelated OS if environment.system() != "Linux": - log("Tried to run create_desktop_shortcut_linux_os on an " - "unsupported OS.") + log( + "Tried to run create_desktop_shortcut_linux_os on an " "unsupported OS." + ) return False # get device specific configuration model, identifier = self.current_device_identifier() - picture_file_path = self.cfgmgr.get_cfgpath() - __sha_shift = self.config.get('sha_shift', 5) - sha = hashlib.sha256( - str(identifier).encode() - ).hexdigest()[__sha_shift:__sha_shift + 6] + picture_file_path = self.config_manager.get_cfgpath() + __sha_shift = self.config.get("sha_shift", 5) + sha = hashlib.sha256(str(identifier).encode()).hexdigest()[ + __sha_shift : __sha_shift + 6 + ] log(f"Creating desktop shortcut sha: {sha}") - path_to_image = os.path.join(picture_file_path, identifier + '.png') + path_to_image = os.path.join(picture_file_path, identifier + ".png") if has_cairo: svg2png( bytestring=desktop_device_shortcut_svg().format(f"#{sha}"), - write_to=path_to_image + write_to=path_to_image, ) else: - print("Trying to use Plain SVG as renderer" - " instead of cairo") - with open(path_to_image, 'w') as fp: + print("Trying to use Plain SVG as renderer" " instead of cairo") + with open(path_to_image, "w") as fp: svg_str = desktop_device_shortcut_svg().format(f"#{sha}") fp.write(svg_str) # go through all args; break when we find guiscrcpy for args_i in range(len(sys.argv)): - if 'guiscrcpy' in sys.argv[args_i]: + if "guiscrcpy" in sys.argv[args_i]: aend = args_i + 1 break else: @@ -451,75 +472,73 @@ def create_desktop_shortcut_linux_os(self): # experimental support for AppImages / snaps # I am not sure; if it would work indeed for i in sys_args_desktop: - if i.endswith('.py'): + if i.endswith(".py"): needs_python = True break else: needs_python = False if needs_python: - sys_args_desktop = ['python3'] + sys_args_desktop + sys_args_desktop = ["python3"] + sys_args_desktop # convert the list into a string - sys_args_desktop = ' '.join(sys_args_desktop) - auto_connect_run_command = \ - "{executable} --connect={ip} " \ + sys_args_desktop = " ".join(sys_args_desktop) + auto_connect_run_command = ( + "{executable} --connect={ip} " "--start --start-scrcpy-device-id={ip}".format( - executable=sys_args_desktop, - ip=identifier + executable=sys_args_desktop, ip=identifier ) + ) # create the desktop file using linux's desktop file gen method path_to_desktop_file = platform.System().create_desktop( desktop_file=GUISCRCPY_DEVICE.format( identifier=model, command=auto_connect_run_command, - icon_path=path_to_image + icon_path=path_to_image, ), - desktop_file_name=f'{model}.guiscrcpy.desktop' + desktop_file_name=f"{model}.guiscrcpy.desktop", ) # announce it to developers / users log(f"Path to desktop file : {path_to_desktop_file}") print("Desktop file generated successfully") self.display_public_message("Desktop file has been created") + return True def is_connection_success_handler(self, output: Popen, ip=None): out, err = output.communicate() - if 'failed' in out.decode() or 'failed' in err.decode(): + if "failed" in out.decode() or "failed" in err.decode(): self.display_public_message( "Failed to connect to {}. See the logs for more " "information".format(ip) ) print("adb:", out.decode(), err.decode()) else: - self.display_public_message( - "Connection command completed successfully" - ) + self.display_public_message("Connection command completed successfully") def ping_paired_device(self, device_id=None): # update the configuration file first if not device_id: _, identifier = self.current_device_identifier() - if identifier.count('.') == 3: + if identifier.count(".") == 3: wifi_device = True else: wifi_device = False try: - self.config['device'][identifier]['wifi'] = wifi_device + self.config["device"][identifier]["wifi"] = wifi_device except KeyError: - log(f"Failed writing the configuration " - f"'wifi' key to {identifier}") + log(f"Failed writing the configuration " f"'wifi' key to {identifier}") if wifi_device: ip = self.current_device_identifier()[1] - output = self.adb.command('connect {}'.format(ip)) + output = self.adb.command("connect {}".format(ip)) self.is_connection_success_handler(output, ip=ip) else: - self.adb.command('reconnect offline') + self.adb.command("reconnect offline") # As we have attempted to connect; refresh the panel self.refresh_devices() else: - output = self.adb.command('connect {}'.format(device_id)) + output = self.adb.command("connect {}".format(device_id)) self.is_connection_success_handler(output, ip=device_id) def tcpip_paired_device(self): @@ -530,15 +549,12 @@ def tcpip_paired_device(self): __exit_code = self.adb.tcpip(identifier=identifier) if __exit_code != 0: self.display_public_message( - "TCP/IP failed on device. " - "Please reconnect USB and try again" + "TCP/IP failed on device. " "Please reconnect USB and try again" ) else: - self.display_public_message( - "TCP/IP completed successfully." - ) + self.display_public_message("TCP/IP completed successfully.") time.sleep(0.1) # wait for everything to get settled - if identifier.count('.') >= 3: + if identifier.count(".") >= 3: self.ping_paired_device(device_id=identifier) else: self.ping_paired_device() @@ -546,14 +562,16 @@ def tcpip_paired_device(self): def current_device_identifier(self, need_status=False): if self.devices_view.currentItem(): if need_status: - return \ - self.devices_view.currentItem().text().split()[0], \ - self.devices_view.currentItem().text().split()[1], \ - self.devices_view.currentItem().text().split()[2] + return ( + self.devices_view.currentItem().text().split()[0], + self.devices_view.currentItem().text().split()[1], + self.devices_view.currentItem().text().split()[2], + ) else: - return \ - self.devices_view.currentItem().text().split()[0], \ - self.devices_view.currentItem().text().split()[1] + return ( + self.devices_view.currentItem().text().split()[0], + self.devices_view.currentItem().text().split()[1], + ) else: raise ValueError("No item is selected in QListView") @@ -563,31 +581,31 @@ def scan_config_devices_update_list_view(self): :return: """ self.devices_view.clear() - paired_devices = self.config['device'] + paired_devices = self.config["device"] for i in paired_devices: - if paired_devices[i].get('wifi'): - icon = ':/icons/icons/portrait_mobile_disconnect.svg' + if paired_devices[i].get("wifi"): + icon = ":/icons/icons/portrait_mobile_disconnect.svg" devices_view_list_item = QListWidgetItem( QIcon(icon), "{device}\n{mode}\n{status}".format( - device=paired_devices[i].get('model'), + device=paired_devices[i].get("model"), mode=i, - status='Disconnected' - ) + status="Disconnected", + ), ) - __sha_shift = self.config.get('sha_shift', 5) - __sha = hashlib.sha256( - str(i).encode()).hexdigest()[__sha_shift:__sha_shift + 6] + __sha_shift = self.config.get("sha_shift", 5) + __sha = hashlib.sha256(str(i).encode()).hexdigest()[ + __sha_shift : __sha_shift + 6 + ] devices_view_list_item.setToolTip( "Device: {d}\n" "Status: {s}".format( d=i, - s="Disconnected. Right click 'ping' to attempt " - "reconnect", - color=__sha + s="Disconnected. Right click 'ping' to attempt " "reconnect", + color=__sha, ) ) - devices_view_list_item.setFont(QFont('Noto Sans', 8)) + devices_view_list_item.setFont(QFont("Noto Sans", 8)) self.devices_view.addItem(devices_view_list_item) return paired_devices @@ -605,45 +623,44 @@ def scan_devices_update_list_view(self): __devices = self.adb.devices_detailed() log(__devices) for i in __devices: - device_is_wifi = \ - i['identifier'].count('.') >= 3 and (':' in i['identifier']) + device_is_wifi = i["identifier"].count(".") >= 3 and ( + ":" in i["identifier"] + ) - if i['identifier'] not in self.config['device'].keys(): + if i["identifier"] not in self.config["device"].keys(): device_paired_and_exists = False - self.config['device'][i['identifier']] = { - 'rotation': 0 - } + self.config["device"][i["identifier"]] = {"rotation": 0} else: device_paired_and_exists = True if device_is_wifi: - _icon_suffix = '_wifi' + _icon_suffix = "_wifi" else: - _icon_suffix = '_usb' + _icon_suffix = "_usb" - icon = ':/icons/icons/portrait_mobile_white{}.svg'.format( - _icon_suffix - ) + icon = ":/icons/icons/portrait_mobile_white{}.svg".format(_icon_suffix) - if i['status'] == 'offline': - icon = ':/icons/icons/portrait_mobile_error.svg' - elif i['status'] == 'unauthorized': - icon = ':/icons/icons/portrait_mobile_warning.svg' + if i["status"] == "offline": + icon = ":/icons/icons/portrait_mobile_error.svg" + elif i["status"] == "unauthorized": + icon = ":/icons/icons/portrait_mobile_warning.svg" - if i['status'] == 'no_permission': + if i["status"] == "no_permission": log("pairfilter: 5") # https://stackoverflow.com/questions/ # 53887322/adb-devices-no-permissions-user-in- # plugdev-group-are-your-udev-rules-wrong - udev_error = "Error connecting to device. Your udev rules are"\ - " incorrect. See https://stackoverflow.com/questions"\ - "/53887322/adb-devices-no-permissions-user-in-plugdev-"\ + udev_error = ( + "Error connecting to device. Your udev rules are" + " incorrect. See https://stackoverflow.com/questions" + "/53887322/adb-devices-no-permissions-user-in-plugdev-" "group-are-your-udev-rules-wrong" + ) self.display_public_message(udev_error) print(udev_error) return [] # Check if device is unauthorized - elif i['status'] == "unauthorized": + elif i["status"] == "unauthorized": log("unauthorized device detected: Click Allow on your device") log("pairfilter: 4") # The device is connected; and might/might't paired in the past @@ -660,8 +677,7 @@ def scan_devices_update_list_view(self): # Remove other devices with the same id and offline and # unauthorized self.remove_device_device_view( - i['identifier'], - statuses=['offline', 'unauthorized'] + i["identifier"], statuses=["offline", "unauthorized"] ) # Unauthorized device cannot be considered as a paired device devices_view_list_item = QListWidgetItem() @@ -673,7 +689,7 @@ def scan_devices_update_list_view(self): devices_view_list_item = QListWidgetItem() else: for paired_device in paired_devices: - if paired_device.text().split()[0] == i['model']: + if paired_device.text().split()[0] == i["model"]: log("pairfilter: 1") paired = True devices_view_list_item = paired_device @@ -685,16 +701,13 @@ def scan_devices_update_list_view(self): # identifier and remove them; based on this same # assumption self.remove_device_device_view( - i['identifier'], - statuses=['offline', 'unauthorized'] + i["identifier"], statuses=["offline", "unauthorized"] ) break - elif paired_device.text().split()[1] ==\ - i['identifier']: + elif paired_device.text().split()[1] == i["identifier"]: log("pairfilter: 2") self.remove_device_device_view( - i['identifier'], - statuses=['offline', 'unauthorized'] + i["identifier"], statuses=["offline", "unauthorized"] ) devices_view_list_item = QListWidgetItem() paired = False @@ -708,15 +721,13 @@ def scan_devices_update_list_view(self): devices_view_list_item.setText( "{device}\n{mode}\n{status}".format( - device=i['model'], - mode=i['identifier'], - status=i['status'] + device=i["model"], mode=i["identifier"], status=i["status"] ) ) - __sha_shift = self.config.get('sha_shift', 5) - __sha = hashlib.sha256( - str(i['identifier']).encode() - ).hexdigest()[__sha_shift:__sha_shift + 6] + __sha_shift = self.config.get("sha_shift", 5) + __sha = hashlib.sha256(str(i["identifier"]).encode()).hexdigest()[ + __sha_shift : __sha_shift + 6 + ] devices_view_list_item.setToolTip( "Device: " "" @@ -727,18 +738,18 @@ def scan_devices_update_list_view(self): "Status: {s}\n
" "Transport ID: {t}\n
" "Paired: {p}".format( - d=i['identifier'], - m=i['model'], - a=i['product'], - s=i['status'], - t=i['transport_id'], + d=i["identifier"], + m=i["model"], + a=i["product"], + s=i["status"], + t=i["transport_id"], p=paired, color=__sha, - inv_color=str(hex(0xFFFFFF - int(__sha, 16))[2:]) + inv_color=str(hex(0xFFFFFF - int(__sha, 16))[2:]), ) ) - devices_view_list_item.setFont(QFont('Noto Sans', 8)) + devices_view_list_item.setFont(QFont("Noto Sans", 8)) log(f"Pairing status: {device_paired_and_exists}") if device_paired_and_exists and device_is_wifi: # we need to only neglect wifi devices @@ -752,7 +763,7 @@ def scan_devices_update_list_view(self): self.devices_view.addItem(devices_view_list_item) return __devices - def remove_device_device_view(self, identifier: str = '', statuses=()): + def remove_device_device_view(self, identifier: str = "", statuses=()): """ Removes all QListWidgetItems from the device_view for all matching identifier @@ -763,36 +774,33 @@ def remove_device_device_view(self, identifier: str = '', statuses=()): for index in range(self.devices_view.count() - 1, -1, -1): for status in statuses: if self.devices_view.item(index): - if str(identifier) in self.devices_view.item(index).text()\ - and \ - str(status) in \ - self.devices_view.item(index).text(): + if ( + str(identifier) in self.devices_view.item(index).text() + and str(status) in self.devices_view.item(index).text() + ): self.devices_view.takeItem(index) return def __dimension_change_cb(self): if self.dimensionDefaultCheckbox.isChecked(): self.dimensionSlider.setEnabled(False) - self.config['dimension'] = None + self.config["dimension"] = None self.dimensionText.setInputMask("") self.dimensionText.setText("DEFAULT") else: self.dimensionSlider.setEnabled(True) - self.config['dimension'] = int(self.dimensionSlider.value()) - self.dimensionText.setText( - " " + str(self.config['dimension']) + "px") - self.dimensionSlider.sliderMoved.connect( - self.__slider_change_cb) - self.dimensionSlider.sliderReleased.connect( - self.__slider_change_cb) + self.config["dimension"] = int(self.dimensionSlider.value()) + self.dimensionText.setText(" " + str(self.config["dimension"]) + "px") + self.dimensionSlider.sliderMoved.connect(self.__slider_change_cb) + self.dimensionSlider.sliderReleased.connect(self.__slider_change_cb) def __slider_change_cb(self): - self.config['dimension'] = int(self.dimensionSlider.value()) - self.dimensionText.setText(str(self.config['dimension']) + "px") + self.config["dimension"] = int(self.dimensionSlider.value()) + self.dimensionText.setText(str(self.config["dimension"]) + "px") def __dial_change_cb(self): - self.config['bitrate'] = int(self.dial.value()) - self.bitrateText.setText(str(self.config['bitrate']) + "KB/s") + self.config["bitrate"] = int(self.dial.value()) + self.bitrateText.setText(str(self.config["bitrate"]) + "KB/s") def progress(self, val): self.progressBar.setValue(val) @@ -803,7 +811,7 @@ def progress(self, val): @staticmethod def is_device_unusable(status): - if any(('unauth' in status, 'offline' in status)): + if any(("unauth" in status, "offline" in status)): return True else: return False @@ -814,12 +822,13 @@ def show_device_status_failure(self, status): ) def __reset_message_box_stylesheet(self): - stylesheet = \ - "background-color: qlineargradient(" \ - "spread:pad, x1:0, y1:0, x2:1, y2:1, " \ - "stop:0 rgba(0, 255, 255, 255), " \ - "stop:1 rgba(0, 255, 152, 255)); " \ + stylesheet = ( + "background-color: qlineargradient(" + "spread:pad, x1:0, y1:0, x2:1, y2:1, " + "stop:0 rgba(0, 255, 255, 255), " + "stop:1 rgba(0, 255, 152, 255)); " "border-radius: 10px;" + ) self.private_message_box_adb.setStyleSheet(stylesheet) def __select_first_device(self): @@ -835,8 +844,7 @@ def display_public_message(self, message): """ self.private_message_box_adb.setText(message) - def check_devices_status_and_select_first_if_only_one( - self, values_devices_list): + def check_devices_status_and_select_first_if_only_one(self, values_devices_list): """ Checks the devices in the Grid View, and then checks if any device is available or offline accordingly display the error message. If @@ -853,8 +861,7 @@ def check_devices_status_and_select_first_if_only_one( # Could not detect any device self.display_public_message("Could not find any devices") return 0 - elif self.devices_view.currentIndex() is None and \ - len(values_devices_list) != 1: + elif self.devices_view.currentIndex() is None and len(values_devices_list) != 1: # No device is selected and more than one device found self.display_public_message("Please select a device below.") return 0 @@ -873,8 +880,9 @@ def check_devices_status_and_select_first_if_only_one( # get the status and identifier of the device; # return if device is not in a connectable state try: - _, device_id, _stat = \ - self.current_device_identifier(need_status=True) + _, device_id, _stat = self.current_device_identifier( + need_status=True + ) if self.is_device_unusable(_stat): self.show_device_status_failure(_stat) return 0 @@ -889,8 +897,7 @@ def check_devices_status_and_select_first_if_only_one( self.display_public_message("Please select a device below.") return 0 else: - _, device_id, _stat = self.current_device_identifier( - need_status=True) + _, device_id, _stat = self.current_device_identifier(need_status=True) if self.is_device_unusable(_stat): self.show_device_status_failure(_stat) return 0 @@ -921,8 +928,7 @@ def start_act(self): # ==================================================================== # 3: Check devices values_devices_list = self.scan_devices_update_list_view() - _e = self.check_devices_status_and_select_first_if_only_one( - values_devices_list) + _e = self.check_devices_status_and_select_first_if_only_one(values_devices_list) if _e is None or isinstance(_e, int): return _e device_id, more_devices, _stat = _e @@ -934,17 +940,17 @@ def start_act(self): if self.dimensionDefaultCheckbox.isChecked(): self.dimensionSlider.setEnabled(False) self.dimensionText.setText("DEFAULT") - self.config['dimension'] = None + self.config["dimension"] = None else: self.dimensionSlider.setEnabled(True) - self.config['dimension'] = int(self.dimensionSlider.value()) - self.dimensionSlider.setValue(self.config['dimension']) - self.dimensionText.setText(str(self.config['dimension']) + "px") + self.config["dimension"] = int(self.dimensionSlider.value()) + self.dimensionSlider.setValue(self.config["dimension"]) + self.dimensionText.setText(str(self.config["dimension"]) + "px") # edit configuration files to update dimension key - if self.config['dimension'] is None: + if self.config["dimension"] is None: self.options = " " - elif self.config['dimension'] is not None: - self.options = " -m " + str(self.config['dimension']) + elif self.config["dimension"] is not None: + self.options = " -m " + str(self.config["dimension"]) else: self.options = "" progress = self.progress(progress) @@ -955,18 +961,18 @@ def start_act(self): self.options += " --always-on-top" if self.fullscreen.isChecked(): self.options += " -f" - self.config['fullscreen'] = True + self.config["fullscreen"] = True else: - self.config['fullscreen'] = False + self.config["fullscreen"] = False progress = self.progress(progress) # ==================================================================== # 6: Check if show touches / recording are on if self.showTouches.isChecked(): self.options += " --show-touches" - self.config['swtouches'] = True + self.config["swtouches"] = True else: - self.config['swtouches'] = False + self.config["swtouches"] = False progress = self.progress(progress) # ==================================================================== @@ -979,23 +985,25 @@ def start_act(self): # 8: Check if the display is forced to be on if self.displayForceOn.isChecked(): self.options += " -S" - self.config['dispRO'] = True + self.config["dispRO"] = True else: - self.config['dispRO'] = False + self.config["dispRO"] = False progress = self.progress(progress) # ==================================================================== # 9: Parse bitrate # Bitrate is parsed, by editing the bitrate mask - if self.bitrateText.text().split()[1][0] in ['K', 'M', 'T']: + if self.bitrateText.text().split()[1][0] in ["K", "M", "T"]: bitrate_multiplier = str(self.bitrateText.text().split()[1][0]) elif self.bitrateText.text().split()[1][0] == "B": bitrate_multiplier = "B" else: # do not proceed. Invalid file size multiplier - multiplier_error = f"Invalid file size multiplier \ - '{str(self.bitrateText.text().split()[1][0])}'. " \ - f"Please use only K, M, T only" + multiplier_error = ( + f"Invalid file size multiplier \ + '{str(self.bitrateText.text().split()[1][0])}'. " + f"Please use only K, M, T only" + ) print(multiplier_error) self.display_public_message(multiplier_error) return False @@ -1004,7 +1012,7 @@ def start_act(self): else: bitrate_integer = 8000 self.options += " -b {}{}".format(bitrate_integer, bitrate_multiplier) - self.config['bitrate'] = bitrate_integer + self.config["bitrate"] = bitrate_integer progress = self.progress(progress) # ==================================================================== @@ -1014,83 +1022,88 @@ def start_act(self): self.progressBar.setValue(50) log("Flags passed to scrcpy engine : " + self.options) self.progressBar.setValue(60) - self.config['extra'] = self.flaglineedit.text() + self.config["extra"] = self.flaglineedit.text() progress = self.progress(progress) # ==================================================================== # 11: Initialize User Experience Mapper ux = UXMapper( - adb=self.adb, - device_id=device_id, - sha_shift=self.config.get('sha_shift', 5) + adb=self.adb, device_id=device_id, sha_shift=self.config.get("sha_shift", 5) ) progress = self.progress(progress) - always_on_top = \ - self.config.get('panels_always_on_top', False) or \ - not self.panels_not_always_on_top + always_on_top = ( + self.config.get("panels_always_on_top", False) + or not self.panels_not_always_on_top + ) # ==================================================================== # 12: Init side_panel if necessary if self.check_side_panel.isChecked(): - self.config['panels']['toolkit'] = True + self.config["panels"]["toolkit"] = True side_instance = InterfaceToolkit( parent=self, ux_mapper=ux, frame=self.force_window_frame, - always_on_top=always_on_top + always_on_top=always_on_top, ) for instance in self.child_windows: - if instance.ux.get_sha() == side_instance.ux.get_sha() and \ - instance.name == side_instance.name and \ - not instance.isHidden(): + if ( + instance.ux.get_sha() == side_instance.ux.get_sha() + and instance.name == side_instance.name + and not instance.isHidden() + ): break else: side_instance.init() self.child_windows.append(side_instance) else: - self.config['panels']['toolkit'] = False + self.config["panels"]["toolkit"] = False progress = self.progress(progress) # ==================================================================== # 13: Init bottom_panel if necessary if self.check_bottom_panel.isChecked(): - self.config['panels']['bottom'] = True + self.config["panels"]["bottom"] = True panel_instance = Panel( parent=self, ux_mapper=ux, frame=self.force_window_frame, - always_on_top=always_on_top + always_on_top=always_on_top, ) for instance in self.child_windows: - if instance.ux.get_sha() == panel_instance.ux.get_sha() and \ - instance.name == panel_instance.name and \ - not instance.isHidden(): + if ( + instance.ux.get_sha() == panel_instance.ux.get_sha() + and instance.name == panel_instance.name + and not instance.isHidden() + ): break else: panel_instance.init() self.child_windows.append(panel_instance) else: - self.config['panels']['bottom'] = False + self.config["panels"]["bottom"] = False progress = self.progress(progress) # ==================================================================== # 14: Init swipe panel if necessary if self.check_swipe_panel.isChecked(): - self.config['panels']['swipe'] = True + self.config["panels"]["swipe"] = True swipe_instance = SwipeUX( ux_wrapper=ux, frame=self.force_window_frame, - always_on_top=always_on_top + always_on_top=always_on_top, ) # Load swipe UI for instance in self.child_windows: - if instance.ux.get_sha() == swipe_instance.ux.get_sha() and \ - instance.name == swipe_instance.name and \ - not instance.isHidden(): + if ( + instance.ux.get_sha() == swipe_instance.ux.get_sha() + and instance.name == swipe_instance.name + and not instance.isHidden() + ): break else: swipe_instance.init() self.child_windows.append(swipe_instance) else: - self.config['panels']['swipe'] = False + self.config["panels"]["swipe"] = False progress = self.progress(progress) # ==================================================================== @@ -1112,26 +1125,23 @@ def start_act(self): rotation_parameter = "--rotation" if rotation_index != -1: self.options += " {} {}".format(rotation_parameter, rotation_index) - self.config['device'][identifier]['rotation'] = \ - rotation_index + 1 + self.config["device"][identifier]["rotation"] = rotation_index + 1 else: - self.config['device'][identifier]['rotation'] = 0 + self.config["device"][identifier]["rotation"] = 0 # ==================================================================== # 18: Update device specific configuration - if identifier.count('.') >= 3 and identifier[-1].isdigit(): - self.config['device'][identifier]['wifi'] = True - self.config['device'][identifier]['model'] = model + if identifier.count(".") >= 3 and identifier[-1].isdigit(): + self.config["device"][identifier]["wifi"] = True + self.config["device"][identifier]["model"] = model # ==================================================================== # 16: Parse scrcpy arguments if self.cmx is not None: - self.config['cmx'] = ' '.join(map(str, self.cmx)) + self.config["cmx"] = " ".join(map(str, self.cmx)) arguments_scrcpy = "{} {} {}".format( - self.options, - self.config['extra'], - self.config['cmx'] + self.options, self.config["extra"], self.config["cmx"] ) progress = self.progress(progress) @@ -1146,8 +1156,7 @@ def start_act(self): # tell end users that the color of the device is this self.display_public_message( - f"Device {device_id} is connected; (color id matches " - f"toolkit color)" + f"Device {device_id} is connected; (color id matches " f"toolkit color)" ) log("Device connection completed successfully.") log("Private message box updated successfully") @@ -1158,8 +1167,7 @@ def start_act(self): if not self.debug__no_scrcpy: # for debugging purposes, its important to not start scrcpy # every time - self.scrcpy.start(arguments_scrcpy, stdout=sys.stdout, - stderr=sys.stderr) + self.scrcpy.start(arguments_scrcpy, stdout=sys.stdout, stderr=sys.stderr) progress = self.progress(progress) # ==================================================================== @@ -1171,56 +1179,60 @@ def start_act(self): # ==================================================================== # 22: Update configuration - self.cfgmgr.update_config(self.config) - self.cfgmgr.write_file() + self.config_manager.update_config(self.config) + self.config_manager.write_file() progress = self.progress(progress) return self.progress(progress) def set_scrcpy_server_path(config): - scrcpy_server_path_env = os.getenv('SCRCPY_SERVER_PATH', None) + scrcpy_server_path_env = os.getenv("SCRCPY_SERVER_PATH", None) if scrcpy_server_path_env: if not os.path.exists(scrcpy_server_path_env): - server_path = open_exe_name_dialog(None, 'scrcpy-server') + server_path = open_exe_name_dialog(None, "scrcpy-server") if server_path is None: - raise ScrcpyServerNotFoundError( - "User did not select scrcpy server") - config['scrcpy-server'] = server_path - os.environ['SCRCPY_SERVER_PATH'] = server_path + raise ScrcpyServerNotFoundError("User did not select scrcpy server") + config["scrcpy-server"] = server_path + os.environ["SCRCPY_SERVER_PATH"] = server_path elif ( - (scrcpy_server_path_env is None) and ( - (isinstance(config.get('scrcpy-server'), str) and - not os.path.exists(config.get('scrcpy-server'))) or - config.get('scrcpy-server') is None) - ) and ( - platform.System().system() == 'Windows' - ): - server_path = open_exe_name_dialog(None, 'scrcpy-server') + (scrcpy_server_path_env is None) + and ( + ( + isinstance(config.get("scrcpy-server"), str) + and not os.path.exists(config.get("scrcpy-server")) + ) + or config.get("scrcpy-server") is None + ) + ) and (platform.System().system() == "Windows"): + server_path = open_exe_name_dialog(None, "scrcpy-server") if server_path is None: - raise ScrcpyServerNotFoundError( - "User did not select scrcpy server") - config['scrcpy-server'] = server_path - os.environ['SCRCPY_SERVER_PATH'] = server_path + raise ScrcpyServerNotFoundError("User did not select scrcpy server") + config["scrcpy-server"] = server_path + os.environ["SCRCPY_SERVER_PATH"] = server_path elif platform.System().system() == "Windows": - os.environ['SCRCPY_SERVER_PATH'] = config['scrcpy-server'] + os.environ["SCRCPY_SERVER_PATH"] = config["scrcpy-server"] return config -def bootstrap(app, cfgmgr, theme='Breeze', aot=True, debug__no_scrcpy=False, - hide_wm_frame=True): +def bootstrap( + app: QtWidgets.QApplication, + config_manager: InterfaceConfig, + theme: str = "Breeze", + aot: bool = True, + debug_no_scrcpy: bool = False, + hide_wm_frame: bool = True, +): """ Launch the guiscrcpy window :return: """ - config = cfgmgr.get_config() + config = config_manager.get_config() # load fonts font_database = QFontDatabase() for font in FONTS: - s = font_database.addApplicationFont(':/font/fonts/{ttf}'.format( - ttf=font - )) + s = font_database.addApplicationFont(":/font/fonts/{ttf}".format(ttf=font)) if s == -1: # loading the font failed # https://doc.qt.io/qt-5/qfontdatabase.html print(fc("{y}Failed to load {ttf} font.{rst}", ttf=font)) @@ -1228,7 +1240,7 @@ def bootstrap(app, cfgmgr, theme='Breeze', aot=True, debug__no_scrcpy=False, # set theme app.setStyle(theme) # apply stylesheet - if theme == 'Breeze': + if theme == "Breeze": # The Qdarkstylesheet is based on Breeze, lets load them on default app.setStyleSheet(dark_stylesheet()) @@ -1241,16 +1253,19 @@ def bootstrap(app, cfgmgr, theme='Breeze', aot=True, debug__no_scrcpy=False, # on windows, users are likely not to add the scrcpy-server to the # SCRCPY_SERVER_PATH - cfgmgr.update_config(set_scrcpy_server_path(config)) - cfgmgr.write_file() - adb = AndroidDebugBridge(cfgmgr.get_config().get('adb')) - scrcpy = ScrcpyBridge(cfgmgr.get_config().get('scrcpy')) - cfgmgr['adb'] = adb.get_path() - cfgmgr['scrcpy'] = scrcpy.get_path() + config_manager.update_config(set_scrcpy_server_path(config)) + config_manager.write_file() + adb = AndroidDebugBridge(config_manager.get_config().get("adb")) + scrcpy = ScrcpyBridge(config_manager.get_config().get("scrcpy")) + config_manager["adb"] = adb.get_path() + config_manager["scrcpy"] = scrcpy.get_path() guiscrcpy = InterfaceGuiscrcpy( - cfgmgr=cfgmgr, adb=adb, scrcpy=scrcpy, + config_manager=config_manager, + adb=adb, + scrcpy=scrcpy, force_window_frame=not hide_wm_frame, - panels_not_always_on_top=not aot, debug__no_scrcpy=debug__no_scrcpy + panels_not_always_on_top=not aot, + debug_no_scrcpy=debug_no_scrcpy, ) guiscrcpy.show() app.processEvents() diff --git a/guiscrcpy/lib/bridge/__init__.py b/guiscrcpy/lib/bridge/__init__.py new file mode 100644 index 00000000..e91082c7 --- /dev/null +++ b/guiscrcpy/lib/bridge/__init__.py @@ -0,0 +1,4 @@ +from .adb import AndroidDebugBridge +from .scrcpy import ScrcpyBridge + +__all__ = (AndroidDebugBridge, ScrcpyBridge) diff --git a/guiscrcpy/lib/bridge/adb.py b/guiscrcpy/lib/bridge/adb.py new file mode 100644 index 00000000..526d5eff --- /dev/null +++ b/guiscrcpy/lib/bridge/adb.py @@ -0,0 +1,149 @@ +import logging +import sys +from subprocess import Popen, PIPE, TimeoutExpired, call + +from .base import Bridge +from ..check import _get_dimension_raw_noexcept, get +from ..utils import decode_process, _ + + +class AndroidDebugBridge(Bridge): + name = "adb" + + def get_target_android_version(self, device_id=None): + api = 5 + _proc = self.shell("getprop ro.build.version.release", device_id=device_id) + _ecode = _proc.wait(10) + if not _ecode: + api = int(_proc.stdout.read().decode()) + return api + + def shell_input(self, command, device_id=None): + path = self.path + if device_id: + Popen( + _("{} -s {} shell input {}".format(path, device_id, command)), + stdout=PIPE, + stderr=PIPE, + ) + else: + Popen( + _("{} shell input {}".format(path, command)), + stdout=PIPE, + stderr=PIPE, + ) + + def kill_adb_server(self): + self.command(self.path, "kill-server") + + def get_dimensions(self, device_id=None): + shell_adb = _get_dimension_raw_noexcept(path=self.path, device_id=device_id) + try: + if shell_adb.wait(timeout=3) != 0: + print( + "E: Command 'adb shell wm size' exited with {}".format( + shell_adb.returncode + ) + ) + return False + except TimeoutExpired: + print("E: adb falied; timeout exceeded 10s, killing and " "respawining adb") + self.kill_adb_server() + if isinstance(device_id, str) and device_id.count(".") >= 3: + self.command(self.path, "connect {}".format(device_id)) + shell_adb = _get_dimension_raw_noexcept(path=self.path, device_id=device_id) + if shell_adb.wait(timeout=8) != 0: + print( + "E: Command 'adb shell wm size' exited with {}".format( + shell_adb.returncode + ) + ) + return False + raw_dimensions = shell_adb.stdout.read().decode().strip("\n") + for i in ["Override size", "Physical size"]: + if i in raw_dimensions: + out = raw_dimensions[raw_dimensions.find(i) :] + out_decoded = out.split(":")[1].strip() + dimension_values = out_decoded.split("x") + return dimension_values + + # As the for loop did not find any device; and hence we have reached + # this line. Announce to the user regarding the same + logging.error( + "AndroidDeviceError: adb shell wm size did not return " + "'Physical Size' or 'Override Size'" + ) + return False + + def shell(self, command, device_id=None): + if device_id: + po = Popen( + _("{} -s {} shell {}".format(self.path, device_id, command)), + stdout=PIPE, + stderr=PIPE, + ) + else: + po = Popen( + _("{} shell {}".format(self.path, command)), stdout=PIPE, stderr=PIPE + ) + return po + + def command(self, command, device_id=None): + if device_id: + adb_shell_output = Popen( + _("{} -s {} {}".format(self.path, device_id, command)), + stdout=PIPE, + stderr=PIPE, + ) + else: + adb_shell_output = Popen( + _("{} {}".format(self.path, command)), stdout=PIPE, stderr=PIPE + ) + return adb_shell_output + + def tcpip(self, port=5555, identifier=""): + if identifier: + command = "{path} -s {identifier} -d tcpip {port}" + else: + command = "{path} -d tcpip {port}" + exit_code = call( + _(command.format(path=self.path, port=port, identifier=identifier)), + stdout=sys.stdout, + stderr=sys.stdout, + ) + return exit_code + + def devices(self): + proc = Popen(_(self.path + " devices"), stdout=PIPE) + output = [[y.strip() for y in x.split("\t")] for x in decode_process(proc)[1:]][ + :-1 + ] + + logging.debug("ADB: {}".format(output)) + return output + + def devices_detailed(self): + proc = Popen(_(self.path + " devices -l"), stdout=PIPE) + output = [[y.strip() for y in x.split()] for x in decode_process(proc)[1:]][:-1] + devices_found = [] + for device in output: + # https://github.com/srevinsaju/guiscrcpy/issues/117 + if "udev" in device and "permission" in device: + # This is an error with some linux and Windows OSes + # This happens because the udev is not configured + # and linux adb does not have access to reading the device + # the status hence should be 'no_permission' + status = "no_permission" + else: + status = device[1] + description = { + "identifier": device[0], + "status": status, + "product": get(device, 2, ":").split(":")[-1], + "model": get(device, 3, ":").split(":")[-1], + "device": get(device, 4, ":").split(":")[-1], + "transport_id": get(device, 5, ":").split(":")[-1], + } + devices_found.append(description) + logging.debug("ADB: {}".format(devices_found)) + return devices_found diff --git a/guiscrcpy/lib/bridge/audio/__init__.py b/guiscrcpy/lib/bridge/audio/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/guiscrcpy/lib/bridge/audio/base.py b/guiscrcpy/lib/bridge/audio/base.py new file mode 100644 index 00000000..d40160bd --- /dev/null +++ b/guiscrcpy/lib/bridge/audio/base.py @@ -0,0 +1,6 @@ +from ..base import Bridge + + +class AudioBridge(Bridge): + def run(self): + raise NotImplementedError diff --git a/guiscrcpy/lib/bridge/audio/sndcpy.py b/guiscrcpy/lib/bridge/audio/sndcpy.py new file mode 100644 index 00000000..0324e2be --- /dev/null +++ b/guiscrcpy/lib/bridge/audio/sndcpy.py @@ -0,0 +1,47 @@ +import sys +import subprocess +import time + +from .base import AudioBridge +from ...utils import shellify as _, show_message_box + + +class SndcpyBridge(AudioBridge): + name = "sndcpy" + + def run(self, device_id=None): + if device_id is None: + command = "{sndcpy}" + else: + command = "{sndcpy} {device_id}" + _proc = subprocess.Popen( + _(command.format(sndcpy=self.get_path(), device_id=device_id)), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + stdin=subprocess.PIPE, + ) + waiting_since = 0 + while True: + if waiting_since > 30: + # we should stop now + _out = show_message_box( + text="sndcpy Failed to connect", + info_text="We couldn't establish a proper connection " + "to your device. Check if sndcpy is given stream permissions. " + "See https://github.com/rom1v/sndcpy for more information.", + ) + _out.exec_() + line = _proc.stdout.readline() + if not line: + break + if "Press Enter" in line.decode(): + _out = show_message_box( + text="Sndcpy", + info_text="Click Accept on your Android device, and then click 'ok'", + ) + _out.exec_() + _proc.stdin.write(b"\n\r\n\r") + _proc.stdin.close() + break + time.sleep(0.1) + waiting_since += 0.1 diff --git a/guiscrcpy/lib/bridge/audio/usbaudio.py b/guiscrcpy/lib/bridge/audio/usbaudio.py new file mode 100644 index 00000000..14cd64c2 --- /dev/null +++ b/guiscrcpy/lib/bridge/audio/usbaudio.py @@ -0,0 +1,21 @@ +import sys +import subprocess + + +from .base import AudioBridge +from ...utils import shellify as _ + + +class USBAudioBridge(AudioBridge): + name = "usbaudio" + + def run(self, device_id=None): + if device_id is None: + command = "{usbaudio}" + else: + command = "{usbaudio} --serial {device_id}" + subprocess.Popen( + _(command.format(usbaudio=self.get_path(), device_id=device_id)), + stdout=sys.stdout, + stderr=sys.stderr, + ) diff --git a/guiscrcpy/lib/bridge/base.py b/guiscrcpy/lib/bridge/base.py new file mode 100644 index 00000000..16af9841 --- /dev/null +++ b/guiscrcpy/lib/bridge/base.py @@ -0,0 +1,25 @@ +import shutil + + +from ...install.finder import open_exe_name_dialog + + +class Bridge: + name = None + + def __init__(self, path=None): + if path is not None: + self.path = path + elif shutil.which(self.name): + self.path = shutil.which(self.name) + else: + self.path = open_exe_name_dialog(None, self.name) + if self.path is None: + raise FileNotFoundError(f"Could not find '{self.name}' on $PATH.") + self.post_init() + + def post_init(self): + pass + + def get_path(self): + return self.path diff --git a/guiscrcpy/lib/bridge/exceptions.py b/guiscrcpy/lib/bridge/exceptions.py new file mode 100644 index 00000000..97f4ea25 --- /dev/null +++ b/guiscrcpy/lib/bridge/exceptions.py @@ -0,0 +1,14 @@ +class ScrcpyNotFoundError(FileNotFoundError): + pass + + +class ScrcpyServerNotFoundError(FileNotFoundError): + pass + + +class AdbNotFoundError(FileNotFoundError): + pass + + +class AdbRuntimeError(RuntimeError): + pass diff --git a/guiscrcpy/lib/bridge/scrcpy.py b/guiscrcpy/lib/bridge/scrcpy.py new file mode 100644 index 00000000..cba58024 --- /dev/null +++ b/guiscrcpy/lib/bridge/scrcpy.py @@ -0,0 +1,24 @@ +import os +from subprocess import Popen, PIPE + +from .base import Bridge +from ...lib.utils import shellify as _ + + +class ScrcpyBridge(Bridge): + name = "scrcpy" + + def post_init(self): + if os.getenv("SCRCPY_LDD"): + if os.getenv("LD_LIBRARY_PATH"): + os.environ["LD_LIBRARY_PATH"] += os.getenv("SCRCPY_LDD") + else: + os.environ["LD_LIBRARY_PATH"] = os.getenv("SCRCPY_LDD") + + def start(self, args, stdout=PIPE, stderr=PIPE): + proc = Popen( + _("{} {}".format(self.path, args)), + stdout=stdout, + stderr=stderr, + ) + return proc diff --git a/guiscrcpy/lib/check.py b/guiscrcpy/lib/check.py index dd2dfd76..f03e2fc5 100644 --- a/guiscrcpy/lib/check.py +++ b/guiscrcpy/lib/check.py @@ -17,16 +17,9 @@ along with this program. If not, see . """ -import os -import logging -import shutil -from subprocess import Popen, PIPE, call, TimeoutExpired +from subprocess import Popen, PIPE -from ..lib.utils import decode_process, shellify as _ -from ..platform.platform import System -from ..install.finder import open_exe_name_dialog - -environment = System() +from ..lib.utils import shellify as _ def get(ls, idx, default=""): @@ -40,205 +33,9 @@ def _get_dimension_raw_noexcept(path, device_id=None): if device_id: shell_adb = Popen( _("{} -s {} shell wm size".format(path, device_id)), - stdout=PIPE, stderr=PIPE) - else: - shell_adb = Popen(_("{} shell wm size".format(path)), - stdout=PIPE, stderr=PIPE) - return shell_adb - - -class ScrcpyNotFoundError(FileNotFoundError): - pass - - -class ScrcpyServerNotFoundError(FileNotFoundError): - pass - - -class ScrcpyBridge: - def __init__(self, path=None): - if path is not None: - self.path = path - elif shutil.which('scrcpy'): - self.path = shutil.which('scrcpy') - else: - self.path = open_exe_name_dialog(None, 'scrcpy') - if self.path is None: - raise ScrcpyNotFoundError("Could not find `scrcpy` on PATH. Make " - "sure scrcpy is installed and " - "accessible from the terminal.") - if os.getenv('SCRCPY_LDD'): - if os.getenv('LD_LIBRARY_PATH'): - os.environ['LD_LIBRARY_PATH'] += os.getenv('SCRCPY_LDD') - else: - os.environ['LD_LIBRARY_PATH'] = os.getenv('SCRCPY_LDD') - - def start(self, args, stdout=PIPE, stderr=PIPE): - proc = Popen( - _("{} {}".format(self.path, args)), - stdout=stdout, - stderr=stderr, - ) - return proc - - def get_path(self): - return self.path - - -class AdbNotFoundError(FileNotFoundError): - pass - - -class AdbRuntimeError(RuntimeError): - pass - - -class AndroidDebugBridge: - def __init__(self, path=None): - if path is not None: - self.path = path - elif shutil.which('adb') is not None: - self.path = shutil.which('adb') - else: - self.path = open_exe_name_dialog(None, 'adb') - if self.path is None: - AdbNotFoundError("Could not find `adb` on PATH. " - "Make sure adb is installed accessible " - "from the terminal") - - def get_target_android_version(self, device_id=None): - api = 5 - _proc = self.shell( - "getprop ro.build.version.release", device_id=device_id) - _ecode = _proc.wait(10) - if not _ecode: - api = int(_proc.stdout.read().decode()) - return api - - def shell_input(self, command, device_id=None): - path = self.path - if device_id: - Popen( - _("{} -s {} shell input {}".format(path, device_id, command)), - stdout=PIPE, - stderr=PIPE, - ) - else: - Popen( - _("{} shell input {}".format(path, command)), - stdout=PIPE, - stderr=PIPE, - ) - - def get_path(self): - return self.path - - def kill_adb_server(self): - self.command(self.path, "kill-server") - - def get_dimensions(self, device_id=None): - shell_adb = _get_dimension_raw_noexcept( - path=self.path, device_id=device_id - ) - try: - if shell_adb.wait(timeout=3) != 0: - print("E: Command 'adb shell wm size' exited with {}".format( - shell_adb.returncode)) - return False - except TimeoutExpired: - print("E: adb falied; timeout exceeded 10s, killing and " - "respawining adb") - self.kill_adb_server() - if isinstance(device_id, str) and device_id.count('.') >= 3: - self.command(self.path, "connect {}".format(device_id)) - shell_adb = _get_dimension_raw_noexcept( - path=self.path, device_id=device_id - ) - if shell_adb.wait(timeout=8) != 0: - print("E: Command 'adb shell wm size' exited with {}".format( - shell_adb.returncode)) - return False - raw_dimensions = shell_adb.stdout.read().decode().strip('\n') - for i in ['Override size', 'Physical size']: - if i in raw_dimensions: - out = raw_dimensions[raw_dimensions.find(i):] - out_decoded = out.split(':')[1].strip() - dimension_values = out_decoded.split('x') - return dimension_values - - # As the for loop did not find any device; and hence we have reached - # this line. Announce to the user regarding the same - logging.error( - "AndroidDeviceError: adb shell wm size did not return " - "'Physical Size' or 'Override Size'" - ) - return False - - def shell(self, command, device_id=None): - if device_id: - po = Popen(_("{} -s {} shell {}".format( - self.path, device_id, command)), - stdout=PIPE, stderr=PIPE) - else: - po = Popen(_("{} shell {}".format(self.path, command)), - stdout=PIPE, stderr=PIPE) - return po - - def command(self, command, device_id=None): - if device_id: - adb_shell_output = Popen( - _("{} -s {} {}".format(self.path, device_id, command)), - stdout=PIPE, - stderr=PIPE) - else: - adb_shell_output = Popen(_("{} {}".format(self.path, command)), - stdout=PIPE, stderr=PIPE) - return adb_shell_output - - def tcpip(self, port=5555, identifier=""): - if identifier: - command = "{path} -s {identifier} -d tcpip {port}" - else: - command = "{path} -d tcpip {port}" - exit_code = call( - _(command.format(path=self.path, port=port, - identifier=identifier)), stdout=PIPE, - stderr=PIPE + stderr=PIPE, ) - return exit_code - - def devices(self): - proc = Popen(_(self.path + " devices"), stdout=PIPE) - output = [[y.strip() for y in x.split('\t')] - for x in decode_process(proc)[1:]][:-1] - - logging.debug("ADB: {}".format(output)) - return output - - def devices_detailed(self): - proc = Popen(_(self.path + " devices -l"), stdout=PIPE) - output = [[y.strip() for y in x.split()] - for x in decode_process(proc)[1:]][:-1] - devices_found = [] - for device in output: - # https://github.com/srevinsaju/guiscrcpy/issues/117 - if 'udev' in device and 'permission' in device: - # This is an error with some linux and Windows OSes - # This happens because the udev is not configured - # and linux adb does not have access to reading the device - # the status hence should be 'no_permission' - status = 'no_permission' - else: - status = device[1] - description = { - 'identifier': device[0], - 'status': status, - 'product': get(device, 2, ':').split(':')[-1], - 'model': get(device, 3, ':').split(':')[-1], - 'device': get(device, 4, ':').split(':')[-1], - 'transport_id': get(device, 5, ':').split(':')[-1] - } - devices_found.append(description) - logging.debug("ADB: {}".format(devices_found)) - return devices_found + else: + shell_adb = Popen(_("{} shell wm size".format(path)), stdout=PIPE, stderr=PIPE) + return shell_adb diff --git a/guiscrcpy/lib/config.py b/guiscrcpy/lib/config.py index 91ab9080..39749bb9 100644 --- a/guiscrcpy/lib/config.py +++ b/guiscrcpy/lib/config.py @@ -36,28 +36,24 @@ def __init__(self, load=True): self.cfgpath = self.os.cfgpath() self.paths = self.os.paths() self.config = { - 'paths': self.paths, - 'scrcpy': None, - 'adb': None, - 'panels': { - 'swipe': True, - 'bottom': True, - 'toolkit': True - }, - 'mapper': '', - 'sha_shift': 5, - 'scrcpy-server': None, - 'dimension': None, - 'swtouches': False, - 'bitrate': 8000, - 'fullscreen': False, - 'dispRO': False, - 'extra': "", - 'cmx': "", - 'device': {}, - 'theme': 'Breeze' + "paths": self.paths, + "scrcpy": None, + "adb": None, + "panels": {"swipe": True, "bottom": True, "toolkit": True}, + "mapper": "", + "sha_shift": 5, + "scrcpy-server": None, + "dimension": None, + "swtouches": False, + "bitrate": 8000, + "fullscreen": False, + "dispRO": False, + "extra": "", + "cmx": "", + "device": {}, + "theme": "Breeze", } - self.json_file = 'guiscrcpy.json' + self.json_file = "guiscrcpy.json" if load: self.load_config() @@ -68,42 +64,41 @@ def load_config(self): def validate(self): # check scrcpy and adb are not None, else replace it with original # values - if os.getenv('APPIMAGE') is not None: + if os.getenv("APPIMAGE") is not None: # no need further configuration for adb, scrcpy and scrcpy_server - self.config['adb'] = os.getenv('GUISCRCPY_ADB') - self.config['scrcpy'] = os.getenv('GUISCRCPY_SCRCPY') + self.config["adb"] = os.getenv("GUISCRCPY_ADB") + self.config["scrcpy"] = os.getenv("GUISCRCPY_SCRCPY") return True - if self.config['adb'] is None: - adb_path = shutil.which('adb') - self.config['adb'] = adb_path + if self.config["adb"] is None: + adb_path = shutil.which("adb") + self.config["adb"] = adb_path else: - _adb_path = self.config['adb'] + _adb_path = self.config["adb"] if not os.path.exists(_adb_path): raise InvalidConfigurationError( "The configuration key 'adb' is " "invalid. {} does not exist. " "If you did not set it on purpose, " "run `guiscrcpy config -r` to reset " - "the configuration".format( - self.config['adb']) + "the configuration".format(self.config["adb"]) ) - if self.config['scrcpy'] is None: - scrcpy_path = shutil.which('scrcpy') - self.config['scrcpy'] = scrcpy_path + if self.config["scrcpy"] is None: + scrcpy_path = shutil.which("scrcpy") + self.config["scrcpy"] = scrcpy_path else: - _scrcpy_path = self.config['scrcpy'] + _scrcpy_path = self.config["scrcpy"] if not os.path.exists(_scrcpy_path): raise InvalidConfigurationError( "The configuration key 'scrcpy' is " "invalid. {} does not exist. " "If you did not set it on purpose, " "run `guiscrcpy config -r` to reset " - "the configuration".format( - self.config['scrcpy']) + "the configuration".format(self.config["scrcpy"]) ) - if (self.config['scrcpy-server'] is not None) and ( - platform.System() == "Windows"): - os.environ['SCRCPY_SERVER_PATH'] = self.config['scrcpy-server'] + if (self.config["scrcpy-server"] is not None) and ( + platform.System() == "Windows" + ): + os.environ["SCRCPY_SERVER_PATH"] = self.config["scrcpy-server"] return True def __setitem__(self, key, value): @@ -117,14 +112,14 @@ def get_config(self): return self.config def get_scrcpy(self): - if self.config['scrcpy'] is not None: - return self.config['scrcpy'] + if self.config["scrcpy"] is not None: + return self.config["scrcpy"] else: return None def get_adb(self): - if self.config['adb'] is not None: - return self.config['adb'] + if self.config["adb"] is not None: + return self.config["adb"] else: return None @@ -132,12 +127,12 @@ def get_cfgpath(self): return self.cfgpath def read_file(self): - with open(os.path.join(self.cfgpath, self.json_file), 'r') as f: + with open(os.path.join(self.cfgpath, self.json_file), "r") as f: config = json.load(f) self.update_config(config) def write_file(self): - with open(os.path.join(self.cfgpath, self.json_file), 'w') as f: + with open(os.path.join(self.cfgpath, self.json_file), "w") as f: json.dump(self.config, f, indent=4, sort_keys=True) def check_file(self): @@ -160,6 +155,5 @@ def reset_config(self): def __repr__(self): return 'GuiscrcpyConfig({}, "{}")'.format( - json.dumps(self.config, indent=4), - self.cfgpath + json.dumps(self.config, indent=4), self.cfgpath ) diff --git a/guiscrcpy/lib/mapper/mapper.py b/guiscrcpy/lib/mapper/mapper.py index 44c835d7..ee1009e0 100644 --- a/guiscrcpy/lib/mapper/mapper.py +++ b/guiscrcpy/lib/mapper/mapper.py @@ -34,7 +34,7 @@ fixed_pos = [0.0, 0.0] final_pos = [0.0, 0.0] -json_file = 'guiscrcpy.mapper.json' +json_file = "guiscrcpy.mapper.json" def log(category, message): @@ -48,7 +48,7 @@ def __init__(self, device_id, adb, config_path=None): self.app = None self.adb = adb log("mapper", "Waiting for device...") - self.adb.command('wait-for-any-device') + self.adb.command("wait-for-any-device") log("mapper", "Device connection established...") self.window = None self.guiscrcpy_mapper_json = config_path @@ -66,17 +66,21 @@ def check_orientation(self): e_code = proc.wait(5) except subprocess.TimeoutExpired: e_code = 0 - log("mapper", "Failed to detect orientation instantly. Expect" - "invalid orientations.") + log( + "mapper", + "Failed to detect orientation instantly. Expect" + "invalid orientations.", + ) if e_code != 0: # process failed raise AdbRuntimeError( - 'adb failed with {ecode} when trying to ' - 'execute command ' - '`adb shell dumpsys input`'.format(ecode=e_code)) + "adb failed with {ecode} when trying to " + "execute command " + "`adb shell dumpsys input`".format(ecode=e_code) + ) out, err = proc.communicate() out, err = out.decode(), err.decode() - if 'SurfaceOrientation' in out: + if "SurfaceOrientation" in out: # SurfaceOrientation gives the idea if the device is # landscape or portait. SurfaceOrientation: 1 mentions that # the mobile is oriented in the landscape orientation @@ -89,12 +93,12 @@ def check_orientation(self): print("Detected Landscape orientation...") return 1 else: - print("Failed to detect orientation from device. " - "Fallback to 0") + print("Failed to detect orientation from device. " "Fallback to 0") return 0 else: - print("Failed to detect Orientation. SurfaceOrientation" - " key was not found") + print( + "Failed to detect Orientation. SurfaceOrientation" " key was not found" + ) return 0 def set_device_id(self, device_id): @@ -124,32 +128,29 @@ def get_screenshot(self): print("Please wait. A full definition screenshot is being captured") adb_screencap_process = self.adb.command( - 'shell screencap -p /sdcard/{uid}.png'.format( - uid=uid - ), - device_id=self._device_id + "shell screencap -p /sdcard/{uid}.png".format(uid=uid), + device_id=self._device_id, ) adb_screencap_process_ecode = adb_screencap_process.wait(500) if adb_screencap_process_ecode != 0: print("Screenshot failed. Exiting") - print(adb_screencap_process.stdout.read().decode('utf-8')) + print(adb_screencap_process.stdout.read().decode("utf-8")) return # sleep for two seconds so that the image is processed time.sleep(2) # pull screenshot from android using `adb pull` adb_pull_process = self.adb.command( - 'pull /sdcard/{uid}.png {dest}'.format( - uid=uid, - dest=os.path.dirname(self.guiscrcpy_mapper_json) + "pull /sdcard/{uid}.png {dest}".format( + uid=uid, dest=os.path.dirname(self.guiscrcpy_mapper_json) ), - device_id=self._device_id + device_id=self._device_id, ) adb_pull_process_ecode = adb_pull_process.wait(500) if adb_pull_process_ecode != 0: print("Screenshot pull failed. Exiting") - print(adb_pull_process.stdout.read().decode('utf-8')) + print(adb_pull_process.stdout.read().decode("utf-8")) return # sleep for 1 second to get capture time @@ -157,13 +158,16 @@ def get_screenshot(self): # remove data from user sdcard self.adb.command( - "shell rm /sdcard/{uid}.png".format(uid=uid), - device_id=self._device_id + "shell rm /sdcard/{uid}.png".format(uid=uid), device_id=self._device_id + ) + print( + "[LOG] Screenshot captured. Saved to {cfgpath}".format( + cfgpath=os.path.dirname(self.guiscrcpy_mapper_json) + ) + ) + return os.path.join( + os.path.dirname(self.guiscrcpy_mapper_json), "{uid}.png".format(uid=uid) ) - print("[LOG] Screenshot captured. Saved to {cfgpath}".format( - cfgpath=os.path.dirname(self.guiscrcpy_mapper_json))) - return os.path.join(os.path.dirname(self.guiscrcpy_mapper_json), - '{uid}.png'.format(uid=uid)) # The following functions handle key events on the mapper def on_key_press(self, key): @@ -172,10 +176,10 @@ def on_key_press(self, key): print("[KEY] Hotkey command executing") position_to_tap = self.config.get(key.char) c = self.adb.command( - 'shell input tap {} {}'.format(*position_to_tap), - device_id=self.get_device_id() + "shell input tap {} {}".format(*position_to_tap), + device_id=self.get_device_id(), ) - print(c.stdout.read().decode('utf-8')) + print(c.stdout.read().decode("utf-8")) print("[KEY][COMPLETE]") except AttributeError: @@ -194,10 +198,7 @@ def listen_keypress(self): :return: :rtype: """ - print( - "[SERVER] LISTENING VALUES:" - "Your keys are being listened by server. " - ) + print("[SERVER] LISTENING VALUES:" "Your keys are being listened by server. ") try: with keyboard.Listener(on_press=self.on_key_press) as listener: listener.join() @@ -208,11 +209,11 @@ def listen_keypress(self): def read_configuration(self): if not os.path.exists(self.guiscrcpy_mapper_json): self.create_configuration() - with open(self.guiscrcpy_mapper_json, 'r', encoding='utf-8') as f: + with open(self.guiscrcpy_mapper_json, "r", encoding="utf-8") as f: self.config.update(json.load(f)) def create_configuration(self): - with open(self.guiscrcpy_mapper_json, 'w') as w: + with open(self.guiscrcpy_mapper_json, "w") as w: json.dump(self.config, w) print("Wrote configuration file.") @@ -228,7 +229,7 @@ def initialize(self, initialize_qt=False): """ print("Setting up guiscrcpy-mapper for the first time use...") print("Intializing GUI window") - if __name__ == '__main__' or initialize_qt: + if __name__ == "__main__" or initialize_qt: print("Creating QtCore window Application instance") self.app = QtWidgets.QApplication([]) self.window = MapperUI( @@ -236,15 +237,14 @@ def initialize(self, initialize_qt=False): self.get_screenshot(), self.dimensions, fixed_pos=fixed_pos, - final_pos=final_pos + final_pos=final_pos, ) self.app.processEvents() self.app.exec_() class MapperAsync(QThread): - def __init__(self, parent, device_id, adb, initialize=True, - config_path=None): + def __init__(self, parent, device_id, adb, initialize=True, config_path=None): QThread.__init__(self, parent) self.parent = parent self.adb = adb @@ -253,8 +253,7 @@ def __init__(self, parent, device_id, adb, initialize=True, self._config_path = config_path def run(self): - mp = Mapper(self.device_id, adb=self.adb, - config_path=self._config_path) + mp = Mapper(self.device_id, adb=self.adb, config_path=self._config_path) if self.initialize: mp.initialize(initialize_qt=False) else: diff --git a/guiscrcpy/lib/mapper/ux.py b/guiscrcpy/lib/mapper/ux.py index 7277bc62..b25cbde1 100644 --- a/guiscrcpy/lib/mapper/ux.py +++ b/guiscrcpy/lib/mapper/ux.py @@ -30,8 +30,15 @@ class MapperUI(QtWidgets.QWidget): configuration an button mapping """ - def __init__(self, core, screenshot_path, dimensions, - fixed_pos=[0.0, 0.0], final_pos=[0.0, 0.0]): + + def __init__( + self, + core, + screenshot_path, + dimensions, + fixed_pos=[0.0, 0.0], + final_pos=[0.0, 0.0], + ): self.fixed_pos = fixed_pos self.final_pos = final_pos self.core = core @@ -50,17 +57,16 @@ def build_user_interface(self): self.widget.setGeometry(QtCore.QRect(0, 0, 351, 34)) self.widget.setObjectName("widget") self.horizontalLayout = QtWidgets.QHBoxLayout(self.widget) - self.horizontalLayout.setSizeConstraint( - QtWidgets.QLayout.SetMaximumSize) + self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetMaximumSize) self.horizontalLayout.setContentsMargins(0, 0, 0, 0) self.horizontalLayout.setObjectName("horizontalLayout") self.lineEdit = QtWidgets.QLineEdit(self.widget) size_policy = QtWidgets.QSizePolicy( - QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed + ) size_policy.setHorizontalStretch(0) size_policy.setVerticalStretch(0) - size_policy.setHeightForWidth( - self.lineEdit.sizePolicy().hasHeightForWidth()) + size_policy.setHeightForWidth(self.lineEdit.sizePolicy().hasHeightForWidth()) self.lineEdit.setSizePolicy(size_policy) self.lineEdit.setMinimumSize(QtCore.QSize(25, 25)) self.lineEdit.setMaximumSize(QtCore.QSize(25, 16777215)) @@ -101,13 +107,9 @@ def set_screenshot_to_label(self, screenshot_path): """ self.pixmap = QtGui.QPixmap(screenshot_path) self.label.resize( - int(0.5 * self.pixmap.width()), - int(0.5 * self.pixmap.height()) - ) - self.resize( - int(0.5 * self.pixmap.width()), - int(0.5 * self.pixmap.height()) + int(0.5 * self.pixmap.width()), int(0.5 * self.pixmap.height()) ) + self.resize(int(0.5 * self.pixmap.width()), int(0.5 * self.pixmap.height())) self.show() self.resize(self.label.size()) @@ -117,8 +119,7 @@ def set_screenshot_to_label(self, screenshot_path): int(0.5 * self.pixmap.width()), int(0.5 * self.pixmap.height()) ) self.setMaximumSize( - int(0.5 * self.pixmap.width()), - int(0.5 * self.pixmap.height()) + int(0.5 * self.pixmap.width()), int(0.5 * self.pixmap.height()) ) self.label.installEventFilter(self) layout = QtWidgets.QVBoxLayout(self) @@ -126,8 +127,7 @@ def set_screenshot_to_label(self, screenshot_path): self.pushButton.setText("OK") self.label0.setWordWrap(True) self.label0.setText( - "Click the point, and enter char in textbox and " - "press OK to continue." + "Click the point, and enter char in textbox and " "press OK to continue." ) def register_key(self): @@ -136,8 +136,10 @@ def register_key(self): fixx = relx * int(self.dimensions[0]) fixy = rely * int(self.dimensions[1]) char = self.lineEdit.text()[:1] - print("Successfully registered {ch} " - "with position ({x}, {y})".format(ch=char, x=fixx, y=fixy)) + print( + "Successfully registered {ch} " + "with position ({x}, {y})".format(ch=char, x=fixx, y=fixy) + ) self.core.add_position(char, (fixx, fixy)) self.label0.setText( "SUCCESS! " @@ -148,8 +150,7 @@ def register_key(self): def eventFilter(self, source, event): if source is self.label and event.type() == QtCore.QEvent.Resize: self.label.setPixmap( - self.pixmap.scaled(self.label.size(), - QtCore.Qt.KeepAspectRatio) + self.pixmap.scaled(self.label.size(), QtCore.Qt.KeepAspectRatio) ) return super(MapperUI, self).eventFilter(source, event) @@ -160,7 +161,8 @@ def mousePressEvent(self, event): self.fixed_pos[1] = int(event.pos().y()) print(self.last_found_point, "LAST") self.last_found_point = self.label.mapFromParent( - event.pos()) # this is working fine now + event.pos() + ) # this is working fine now # self.label.setPixmap(QPixmap.fromImage(self.image)) def mouseMoveEvent(self, event): @@ -170,7 +172,8 @@ def mouseMoveEvent(self, event): # painter.drawLine( # self.label.mapFromParent(event.pos()),self.last_found_point) self.last_found_point = self.label.mapFromParent( - event.pos()) # this is working fine now + event.pos() + ) # this is working fine now print(self.last_found_point, "MOVE") self.fixed_pos[0] = int(event.pos().x()) self.fixed_pos[1] = int(event.pos().y()) @@ -184,17 +187,15 @@ def mouseReleaseEvent(self, event): def closeEvent(self, event): # do stuff message_box = QMessageBox() - message_box.setText( - "Save changes and exit?" - ) - vals = ["{} → {}".format( - x, self.core.config[x]) for x in self.core.config] + message_box.setText("Save changes and exit?") + vals = ["{} → {}".format(x, self.core.config[x]) for x in self.core.config] message_box.setInformativeText( "Mapper has unsaved mappings: {val}. Do you want to save the " - "current mappings?".format(val=', '.join(vals)) + "current mappings?".format(val=", ".join(vals)) ) message_box.setStandardButtons( - QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel) + QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel + ) user_message_box_response = message_box.exec() if user_message_box_response == QMessageBox.Yes: print("Registration process completed.") diff --git a/guiscrcpy/lib/process.py b/guiscrcpy/lib/process.py index c71ef6ee..ff5bfcc7 100644 --- a/guiscrcpy/lib/process.py +++ b/guiscrcpy/lib/process.py @@ -19,6 +19,7 @@ try: import psutil + psutil_present = True except ModuleNotFoundError: psutil_present = False @@ -32,10 +33,6 @@ def is_running(process_name): # Check if process name contains the given name string. if process_name.lower() in proc.name().lower(): return True - except ( - psutil.NoSuchProcess, - psutil.AccessDenied, - psutil.ZombieProcess - ): + except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): pass return False diff --git a/guiscrcpy/lib/toolkit.py b/guiscrcpy/lib/toolkit.py index 732edd2c..93a4fe3d 100644 --- a/guiscrcpy/lib/toolkit.py +++ b/guiscrcpy/lib/toolkit.py @@ -31,8 +31,7 @@ import pyautogui as auto from pygetwindow import getWindowsWithTitle except ModuleNotFoundError as e: - logging.debug("pygetwindow, pyautogui " - "failed with error code {}".format(e)) + logging.debug("pygetwindow, pyautogui " "failed with error code {}".format(e)) auto = None getWindowsWithTitle = None else: @@ -46,26 +45,29 @@ def wmctrl_xdotool_linux_send_key(key): wmctrl = shutil.which("wmctrl") xdotool = shutil.which("xdotool") if not wmctrl or not xdotool: - print("E: Could not find {} on PATH. Make sure " - "that a compatible package is installed " - "on your system for this function") + print( + "E: Could not find {} on PATH. Make sure " + "that a compatible package is installed " + "on your system for this function" + ) return - _proc = subprocess.Popen(shlex.split("wmctrl -x -a scrcpy"), - stdout=sys.stdout, - stderr=sys.stderr) + _proc = subprocess.Popen( + shlex.split("wmctrl -x -a scrcpy"), stdout=sys.stdout, stderr=sys.stderr + ) if _proc.wait() == 0: _xdotool_proc = subprocess.Popen( - shlex.split("xdotool key --clearmodifiers {}+{}".format( - os.getenv("GUISCRCPY_MODIFIER") or "alt", key)), + shlex.split( + "xdotool key --clearmodifiers {}+{}".format( + os.getenv("GUISCRCPY_MODIFIER") or "alt", key + ) + ), stdout=sys.stdout, stderr=sys.stderr, ) if _xdotool_proc.wait() != 0: - print("E (xdotool): Failed to send key {} to " - "scrcpy window.".format(key)) + print("E (xdotool): Failed to send key {} to " "scrcpy window.".format(key)) else: - print("E (wmctrl): Failed to get scrcpy window. " - "(Is scrcpy running?)") + print("E (wmctrl): Failed to get scrcpy window. " "(Is scrcpy running?)") class UXMapper: @@ -83,12 +85,14 @@ def __init__(self, adb, device_id=None, sha_shift=5): logging.debug("Calculating Screen Size") self.android_dimensions = adb.get_dimensions(device_id=device_id) if not self.android_dimensions: - print("E: guiscrcpy has crashed because of a failure in the " - "execution of `adb shell wm size`. This might be because " - "of an improper connection of adb. Please reconnect your " - "device (disconnect from WiFi / Reconnect USB) or try \n\n" - "guiscrcpy --killserver\n\nas a command line to restart adb " - "server") + print( + "E: guiscrcpy has crashed because of a failure in the " + "execution of `adb shell wm size`. This might be because " + "of an improper connection of adb. Please reconnect your " + "device (disconnect from WiFi / Reconnect USB) or try \n\n" + "guiscrcpy --killserver\n\nas a command line to restart adb " + "server" + ) self.deviceId = device_id # each device connected is uniquely identified by the tools by @@ -101,7 +105,7 @@ def get_sha(self): A method which returns the unique UUID of the the device :return: The hexdigest of a salted hash """ - return self.__sha[self.sha_shift:self.sha_shift + 6] + return self.__sha[self.sha_shift : self.sha_shift + 6] def do_swipe(self, x1=10, y1=10, x2=10, y2=10): """ @@ -112,8 +116,9 @@ def do_swipe(self, x1=10, y1=10, x2=10, y2=10): :param y2: y2 coordinate :return: Boolean True, in success """ - self.adb.shell_input("swipe {} {} {} {}".format(x1, y1, x2, y2), - device_id=self.deviceId) + self.adb.shell_input( + "swipe {} {} {} {}".format(x1, y1, x2, y2), device_id=self.deviceId + ) return True def do_keyevent(self, key): @@ -122,8 +127,7 @@ def do_keyevent(self, key): :param key: The ADB predefined keycode :return: """ - self.adb.shell_input("keyevent {}".format(key), - device_id=self.deviceId) + self.adb.shell_input("keyevent {}".format(key), device_id=self.deviceId) return True def copy_devpc(self): @@ -164,17 +168,17 @@ def key_switch(self): def reorient_portrait(self): logging.debug("Passing REORIENT [POTRAIT]") - self.adb.shell("settings put system accelerometer_rotation 0", - device_id=self.deviceId) - self.adb.shell("settings put system rotation 1", - device_id=self.deviceId) + self.adb.shell( + "settings put system accelerometer_rotation 0", device_id=self.deviceId + ) + self.adb.shell("settings put system rotation 1", device_id=self.deviceId) def reorient_landscape(self): logging.debug("Passing REORIENT [LANDSCAPE]") - self.adb.shell("settings put system accelerometer_rotation 0", - device_id=self.deviceId) - self.adb.shell("settings put system rotation 1", - device_id=self.deviceId) + self.adb.shell( + "settings put system accelerometer_rotation 0", device_id=self.deviceId + ) + self.adb.shell("settings put system rotation 1", device_id=self.deviceId) def expand_notifications(self): logging.debug("Passing NOTIF EXPAND") diff --git a/guiscrcpy/lib/utils.py b/guiscrcpy/lib/utils.py index c038b6ea..1194a83b 100644 --- a/guiscrcpy/lib/utils.py +++ b/guiscrcpy/lib/utils.py @@ -32,25 +32,25 @@ environment = System() COLORS = { - 'g': Fore.GREEN, - 'rst': Fore.RESET, - 'y': Fore.YELLOW, - 'r': Fore.RED, - 'b': Fore.BLUE, - 'B': Fore.LIGHTBLUE_EX, - 'x': Fore.LIGHTBLACK_EX + "g": Fore.GREEN, + "rst": Fore.RESET, + "y": Fore.YELLOW, + "r": Fore.RED, + "b": Fore.BLUE, + "B": Fore.LIGHTBLUE_EX, + "x": Fore.LIGHTBLACK_EX, } def log(*args, **kwargs): - if os.getenv('GUISCRCPY_DEBUG', False): + if os.getenv("GUISCRCPY_DEBUG", False): print(*args, **kwargs) else: logging.debug(str(args)) def shellify(args): - if environment.system() == 'Windows': + if environment.system() == "Windows": return args else: return shlex.split(args) @@ -63,7 +63,7 @@ def decode_process(process): try: output = process.stdout.readlines() for i in range(len(output)): - output[i] = output[i].decode('utf-8') + output[i] = output[i].decode("utf-8") except NameError: logging.error("No stdout in process.") output = "" @@ -77,13 +77,12 @@ def check_existence(paths, filename="", directory=True, path=False): if directory and os.path.isdir(j): return [j] else: - if environment.system() == 'Windows': - append = '.exe' + if environment.system() == "Windows": + append = ".exe" else: - append = '' + append = "" - if (isinstance(filename, list)) or \ - (isinstance(filename, tuple)): + if (isinstance(filename, list)) or (isinstance(filename, tuple)): for exe in filename: if os.path.exists(os.path.join(j, exe + append)): return [os.path.join(j, exe + append)] @@ -95,11 +94,12 @@ def check_existence(paths, filename="", directory=True, path=False): logging.debug("{} doesn't exist".format(i)) if path: - new_paths = os.getenv('PATH').split(os.pathsep) + new_paths = os.getenv("PATH").split(os.pathsep) found_path = check_existence( - new_paths, filename=filename, directory=directory, path=False) + new_paths, filename=filename, directory=directory, path=False + ) if found_path: - return found_path + ['path'] + return found_path + ["path"] else: return False else: @@ -112,26 +112,26 @@ def get_self(): :return: :rtype: """ - if os.getenv('APPIMAGE'): + if os.getenv("APPIMAGE"): # Running from AppImage - return os.getenv('APPIMAGE') - elif getattr(sys, 'frozen', False): + return os.getenv("APPIMAGE") + elif getattr(sys, "frozen", False): # running in precompiled bundle return sys.executable - elif shutil.which('guiscrcpy'): + elif shutil.which("guiscrcpy"): # guiscrcpy is added to PATH - return shutil.which('guiscrcpy') - elif any([os.path.exists(os.path.join(x, 'guiscrcpy')) - for x in sys.path]) and \ - any([sys.executable.endswith(py) for py in - ['python', 'python3']]): + return shutil.which("guiscrcpy") + elif any([os.path.exists(os.path.join(x, "guiscrcpy")) for x in sys.path]) and any( + [sys.executable.endswith(py) for py in ["python", "python3"]] + ): # guiscrcpy is installed as a pip package, but not added to PATH - return '{py} -m guiscrcpy'.format(py=sys.executable) - elif os.getenv('SNAP'): + return "{py} -m guiscrcpy".format(py=sys.executable) + elif os.getenv("SNAP"): # running from SNAP - return '/snap/bin/guiscrcpy' - raise RuntimeError("Could not detect if guiscrcpy was run from " - "snap, appimage or python wheel") + return "/snap/bin/guiscrcpy" + raise RuntimeError( + "Could not detect if guiscrcpy was run from " "snap, appimage or python wheel" + ) def format_colors(string, **kwargs): @@ -148,12 +148,15 @@ def show_message_box(text, info_text="", buttons=QMessageBox.Ok): """ message_box = QMessageBox() try: - message_box.setIconPixmap(QPixmap( - ":/res/ui/guiscrcpy_logo.png").scaledToHeight(100)) + message_box.setIconPixmap( + QPixmap(":/res/ui/guiscrcpy_logo.png").scaledToHeight(100) + ) except Exception as e: - print("WARN: {e}: loading guiscrcpy " - "message box pixmap failed. " - "Ignoring".format(e=e)) + print( + "WARN: {e}: loading guiscrcpy " + "message box pixmap failed. " + "Ignoring".format(e=e) + ) message_box.setText("{}".format(text)) message_box.setTextFormat(QtCore.Qt.RichText) message_box.setInformativeText(info_text) diff --git a/guiscrcpy/network/network.py b/guiscrcpy/network/network.py index f04e9032..b4a8b515 100644 --- a/guiscrcpy/network/network.py +++ b/guiscrcpy/network/network.py @@ -5,7 +5,6 @@ class NetworkManager: - @staticmethod def get_my_ip(): """ @@ -27,8 +26,8 @@ def map_network(self, pool_size=255): ip_list = list() # get my IP and compose a base like 192.168.1.xxx - ip_parts = self.get_my_ip().split('.') - base_ip = ip_parts[0] + '.' + ip_parts[1] + '.' + ip_parts[2] + '.' + ip_parts = self.get_my_ip().split(".") + base_ip = ip_parts[0] + "." + ip_parts[1] + "." + ip_parts[2] + "." max_threads = 50 @@ -62,8 +61,8 @@ def check_adb_port(ip): return ip_list -if __name__ == '__main__': - print('Mapping...') +if __name__ == "__main__": + print("Mapping...") nm = NetworkManager() lst = nm.map_network() print(lst) diff --git a/guiscrcpy/platform/darwin.py b/guiscrcpy/platform/darwin.py index e65b75a1..8f790046 100644 --- a/guiscrcpy/platform/darwin.py +++ b/guiscrcpy/platform/darwin.py @@ -24,8 +24,7 @@ class Darwin: def __init__(self): logging.error( - "MacOS is untested. " - "guiscrcpy is trying to use Linux config on Mac" + "MacOS is untested. " "guiscrcpy is trying to use Linux config on Mac" ) def cfgpath(self): @@ -33,33 +32,30 @@ def cfgpath(self): @staticmethod def make_config(): - if os.getenv('XDG_CONFIG_HOME') is None: + if os.getenv("XDG_CONFIG_HOME") is None: path = os.path.expanduser("~/.config/guiscrcpy/") else: - path = os.getenv('XDG_CONFIG_HOME').split(":")[0] + "/guiscrcpy" + path = os.getenv("XDG_CONFIG_HOME").split(":")[0] + "/guiscrcpy" if not os.path.exists(path): try: os.makedirs(path) except Exception as e: logging.error( "Error creating configuration filename in dir {path}. " - "Error code:{e}" - .format( - path=path, - e=e - )) + "Error code:{e}".format(path=path, e=e) + ) return path @staticmethod def system(): - return 'Darwin' + return "Darwin" def increment(self): pass @staticmethod def paths(): - return ['bin', '/usr/bin', '~/.local/bin', '~/bin', '/usr/local/bin'] + return ["bin", "/usr/bin", "~/.local/bin", "~/bin", "/usr/local/bin"] @staticmethod def install_fonts(): diff --git a/guiscrcpy/platform/linux.py b/guiscrcpy/platform/linux.py index d29758b8..6495dfc4 100644 --- a/guiscrcpy/platform/linux.py +++ b/guiscrcpy/platform/linux.py @@ -22,8 +22,7 @@ from guiscrcpy.version import VERSION -desktop = \ - """ +desktop = """ [Desktop Entry] Version={v} Name=guiscrcpy @@ -46,26 +45,22 @@ def cfgpath(self): @staticmethod def make_config(): - if os.getenv('XDG_CONFIG_HOME') is None: + if os.getenv("XDG_CONFIG_HOME") is None: path = os.path.expanduser("~/.config/guiscrcpy/") else: - path = os.getenv('XDG_CONFIG_HOME').split(":")[0] + "/guiscrcpy" + path = os.getenv("XDG_CONFIG_HOME").split(":")[0] + "/guiscrcpy" if not os.path.exists(path): try: os.makedirs(path) except Exception as e: logging.error( "Error creating configuration filename in dir {path}. " - "Error code:{e}" - .format( - path=path, - e=e - )) + "Error code:{e}".format(path=path, e=e) + ) return path @staticmethod - def create_desktop(desktop_file=None, - desktop_file_name='guiscrcpy.desktop'): + def create_desktop(desktop_file=None, desktop_file_name="guiscrcpy.desktop"): """ Create Desktop filename for Linux in ~/.local level :return: @@ -76,26 +71,23 @@ def create_desktop(desktop_file=None, desk = desktop.format( v=VERSION, icon_path=os.path.join( - os.path.abspath( - os.path.dirname( - os.path.dirname(__file__) - ) - ), - 'ui', 'ui', 'guiscrcpy_logo.png' - ) + os.path.abspath(os.path.dirname(os.path.dirname(__file__))), + "ui", + "ui", + "guiscrcpy_logo.png", + ), ) - if os.getenv('XDG_DESKTOP_DIR'): - desktop_dir = os.getenv('XDG_DESKTOP_DIR') + if os.getenv("XDG_DESKTOP_DIR"): + desktop_dir = os.getenv("XDG_DESKTOP_DIR") else: - if os.path.exists(os.path.expanduser('~/Desktop')): - desktop_dir = os.path.expanduser('~/Desktop') - elif os.path.exists(os.path.expanduser('~/desktop')): - desktop_dir = os.path.expanduser('~/desktop') + if os.path.exists(os.path.expanduser("~/Desktop")): + desktop_dir = os.path.expanduser("~/Desktop") + elif os.path.exists(os.path.expanduser("~/desktop")): + desktop_dir = os.path.expanduser("~/desktop") else: desktop_dir = False if desktop_dir: - with open(os.path.join(desktop_dir, desktop_file_name), - 'w') as w: + with open(os.path.join(desktop_dir, desktop_file_name), "w") as w: w.write(desk) return desktop_dir else: @@ -108,11 +100,11 @@ def install_fonts(): @staticmethod def system(): - return 'Linux' + return "Linux" def increment(self): pass @staticmethod def paths(): - return ['bin', '/usr/bin', '~/.local/bin', '~/bin', '/usr/local/bin'] + return ["bin", "/usr/bin", "~/.local/bin", "~/bin", "/usr/local/bin"] diff --git a/guiscrcpy/platform/platform.py b/guiscrcpy/platform/platform.py index d57d8053..d0ec972d 100644 --- a/guiscrcpy/platform/platform.py +++ b/guiscrcpy/platform/platform.py @@ -20,7 +20,7 @@ import logging import platform -if platform.system() == 'Windows': +if platform.system() == "Windows": # Windows OS > 7 from guiscrcpy.platform.windows import Windows as System elif platform.system() == "Linux": diff --git a/guiscrcpy/platform/windows.py b/guiscrcpy/platform/windows.py index f25d465d..5f1a45cc 100644 --- a/guiscrcpy/platform/windows.py +++ b/guiscrcpy/platform/windows.py @@ -29,8 +29,7 @@ def __init__(self): @staticmethod def make_config(): - path = os.path.expanduser(os.path.join( - "~", "AppData", "Local", "guiscrcpy")) + path = os.path.expanduser(os.path.join("~", "AppData", "Local", "guiscrcpy")) if not os.path.exists(path): try: os.makedirs(path) @@ -43,7 +42,7 @@ def make_config(): @staticmethod def system(): - return 'Windows' + return "Windows" def cfgpath(self): return self.make_config() @@ -53,7 +52,7 @@ def increment(self): @staticmethod def paths(): - return ['bin'] + return ["bin"] @staticmethod def install_fonts(): diff --git a/guiscrcpy/platform/windows_tools/tools.py b/guiscrcpy/platform/windows_tools/tools.py index 3777c406..491f45c8 100644 --- a/guiscrcpy/platform/windows_tools/tools.py +++ b/guiscrcpy/platform/windows_tools/tools.py @@ -12,15 +12,15 @@ UserFolders = namedtuple("UserFolders", ("home", "desktop", "startmenu")) -scut_ext = 'lnk' -ico_ext = 'ico' +scut_ext = "lnk" +ico_ext = "ico" def get_conda_active_env(): - '''Return name of active conda environment or empty string''' + """Return name of active conda environment or empty string""" conda_env = None try: - conda_env = os.environ['CONDA_DEFAULT_ENV'] + conda_env = os.environ["CONDA_DEFAULT_ENV"] except KeyError: print("No conda env active, defaulting to base") conda_env = "" @@ -36,7 +36,9 @@ def get_conda_active_env(): echo # run in conda environment "%CONDA_DEFAULT_ENV%": echo # %* %* -""".format(conda_env) +""".format( + conda_env +) _WSHELL = win32com.client.Dispatch("Wscript.Shell") @@ -44,23 +46,24 @@ def get_conda_active_env(): # Windows Special Folders # see: https://docs.microsoft.com/en-us/windows/win32/shell/csidl + def get_homedir(): - '''Return home directory: + """Return home directory: note that we return CSIDL_PROFILE, not CSIDL_APPDATA, CSIDL_LOCAL_APPDATA, or CSIDL_COMMON_APPDATA - ''' + """ return shell.SHGetFolderPath(0, shellcon.CSIDL_PROFILE, None, 0) def get_desktop(): - '''Return user Desktop folder''' + """Return user Desktop folder""" return shell.SHGetFolderPath(0, shellcon.CSIDL_DESKTOP, None, 0) def get_startmenu(): - '''Return user Start Menu Programs folder + """Return user Start Menu Programs folder note that we return CSIDL_PROGRAMS not CSIDL_COMMON_PROGRAMS - ''' + """ return shell.SHGetFolderPath(0, shellcon.CSIDL_PROGRAMS, None, 0) @@ -79,22 +82,28 @@ def make_shortcut(): userfolders = get_folders() desktop, startmenu = True, True - for (create, folder) in ((desktop, userfolders.desktop), - (startmenu, userfolders.startmenu)): + for (create, folder) in ( + (desktop, userfolders.desktop), + (startmenu, userfolders.startmenu), + ): if not os.path.exists(folder): os.makedirs(folder) dest = os.path.join(folder, "guiscrcpy.lnk") wscript = _WSHELL.CreateShortCut(dest) - wscript.Targetpath = 'guiscrcpy.exe' + wscript.Targetpath = "guiscrcpy.exe" wscript.WorkingDirectory = userfolders.home wscript.WindowStyle = 0 wscript.Description = "An Open Source Android Screen Mirroring System" - wscript.IconLocation = os.path.join(os.path.abspath( - os.path.dirname(os.path.dirname(os.path.dirname(__file__)))), - 'ui', 'icons', - 'guiscrcpy_logo_SRj_icon.ico') + wscript.IconLocation = os.path.join( + os.path.abspath( + os.path.dirname(os.path.dirname(os.path.dirname(__file__))) + ), + "ui", + "icons", + "guiscrcpy_logo_SRj_icon.ico", + ) wscript.save() return True diff --git a/guiscrcpy/theme/decorate.py b/guiscrcpy/theme/decorate.py index f093bc45..ee19411e 100644 --- a/guiscrcpy/theme/decorate.py +++ b/guiscrcpy/theme/decorate.py @@ -23,21 +23,22 @@ class Header: def __init__(self, commit): print( - ColorTerms.UNDERLINE + - " " + - ColorTerms.ENDC) + ColorTerms.UNDERLINE + + " " + + ColorTerms.ENDC + ) print() print("guiscrcpy") print("by srevinsaju") print(ColorTerms.OKBLUE + commit + ColorTerms.ENDC) print( - ColorTerms.OKBLUE + - "Licensed under GNU GPL v3 (c) 2020 " + - ColorTerms.ENDC) + ColorTerms.OKBLUE + "Licensed under GNU GPL v3 (c) 2020 " + ColorTerms.ENDC + ) print( - ColorTerms.UNDERLINE + - " " + - ColorTerms.ENDC) + ColorTerms.UNDERLINE + + " " + + ColorTerms.ENDC + ) print(ColorTerms.OKBLUE + "" + ColorTerms.ENDC) print() diff --git a/guiscrcpy/theme/desktop_shortcut.py b/guiscrcpy/theme/desktop_shortcut.py index 2fd474c6..721c3a42 100644 --- a/guiscrcpy/theme/desktop_shortcut.py +++ b/guiscrcpy/theme/desktop_shortcut.py @@ -1,7 +1,7 @@ #!/usr/bin/env/python\n # flake8: noqa def desktop_device_shortcut_svg(): - a=""" + a = """ 30: - self.nm_det.setText('Device connect failed: Timeout') + self.nm_det.setText("Device connect failed: Timeout") else: time.sleep(1) - self.nm_det.setText( - "Connected to IP:{}:{}".format(ip, self.spinBox.value())) + self.nm_det.setText("Connected to IP:{}:{}".format(ip, self.spinBox.value())) def refresh(self): self.listView.clear() diff --git a/guiscrcpy/ux/panel.py b/guiscrcpy/ux/panel.py index c14e93c9..0d8a90f3 100644 --- a/guiscrcpy/ux/panel.py +++ b/guiscrcpy/ux/panel.py @@ -28,8 +28,7 @@ class Panel(QMainWindow, Ui_HorizontalPanel): # there was a Dialog in the bracket - def __init__(self, parent=None, ux_mapper=None, frame=False, - always_on_top=True): + def __init__(self, parent=None, ux_mapper=None, frame=False, always_on_top=True): """ The bottom panel subwindow class for guiscrcpy :param parent: The caller of the function @@ -91,14 +90,18 @@ def quit_window(self): # This method checks if we are the last member of the windows # spawned and we ourselves are not a member of ourself by # checking the uuid generated on creation - if not instance.isHidden() \ - and instance.name != "swipe" and instance.uid != \ - self.uid: + if ( + not instance.isHidden() + and instance.name != "swipe" + and instance.uid != self.uid + ): self.hide() break else: for instance in self.parent.child_windows: # noqa - if instance.name == 'swipe' and instance.ux.get_sha() == \ - self.ux.get_sha(): + if ( + instance.name == "swipe" + and instance.ux.get_sha() == self.ux.get_sha() + ): instance.hide() self.hide() diff --git a/guiscrcpy/ux/settings.py b/guiscrcpy/ux/settings.py index 1cb470d7..d9f39618 100644 --- a/guiscrcpy/ux/settings.py +++ b/guiscrcpy/ux/settings.py @@ -32,18 +32,17 @@ def __init__(self, parent): self.parent = parent self.commands = [] self.checkboxes = { - self.a1: [[None], '--always-on-top', 0], - self.a2: [[self.a2c1, self.a2c2], '--crop {}:{}', 1], - self.a3: [[self.a3e1], '--max-fps {}'], - self.a4: [[None], '--prefer-text'], - self.a5: [[self.a5c1], '--push-target {}'], - self.a6: [[self.a6c1], '--record {}'], - self.a8: [[self.a8c1], '--serial {}'], - self.a9: [[None], '--window-borderless'], - self.b0: [[self.b0c1], '--window-title {}'], - self.b1: [[self.b1c1, self.b1c1], '--window-x {} --window-y {}'], - self.b2: [[self.b2c1, self.b2c2], - '--window-width {} --window-height {}'], + self.a1: [[None], "--always-on-top", 0], + self.a2: [[self.a2c1, self.a2c2], "--crop {}:{}", 1], + self.a3: [[self.a3e1], "--max-fps {}"], + self.a4: [[None], "--prefer-text"], + self.a5: [[self.a5c1], "--push-target {}"], + self.a6: [[self.a6c1], "--record {}"], + self.a8: [[self.a8c1], "--serial {}"], + self.a9: [[None], "--window-borderless"], + self.b0: [[self.b0c1], "--window-title {}"], + self.b1: [[self.b1c1, self.b1c1], "--window-x {} --window-y {}"], + self.b2: [[self.b2c1, self.b2c2], "--window-width {} --window-height {}"], } def init(self): @@ -54,9 +53,9 @@ def init(self): def file_chooser(self): dialog = QFileDialog() dialog.setFilter(dialog.filter() | QtCore.QDir.Hidden) - dialog.setDefaultSuffix('mp4') + dialog.setDefaultSuffix("mp4") dialog.setAcceptMode(QFileDialog.AcceptSave) - dialog.setNameFilters(['H.264 (*.mp4)', 'MKV (*.mkv)']) + dialog.setNameFilters(["H.264 (*.mp4)", "MKV (*.mkv)"]) if dialog.exec_() == QDialog.Accepted: self.a6c1.setText(dialog.selectedFiles()[0]) diff --git a/guiscrcpy/ux/swipe.py b/guiscrcpy/ux/swipe.py index 0f1ccb88..92ac8cf1 100644 --- a/guiscrcpy/ux/swipe.py +++ b/guiscrcpy/ux/swipe.py @@ -106,8 +106,7 @@ def __init__(self, ux_wrapper=None, frame=False, always_on_top=True): self.lol.setText("") self.lol.setObjectName("lol") self.lol.setStyleSheet( - f"background-color: #{hexdigest};" - f"border-radius: 12px; " + f"background-color: #{hexdigest};" f"border-radius: 12px; " ) self.swirt = QtWidgets.QPushButton(self.centralwidget) self.swirt.setGeometry(QtCore.QRect(40, 20, 30, 30)) diff --git a/guiscrcpy/ux/toolkit.py b/guiscrcpy/ux/toolkit.py index 24b3ffcd..c352c5db 100644 --- a/guiscrcpy/ux/toolkit.py +++ b/guiscrcpy/ux/toolkit.py @@ -29,11 +29,7 @@ class InterfaceToolkit(QMainWindow, Ui_ToolbarPanel): - def __init__(self, - ux_mapper=None, - parent=None, - frame=False, - always_on_top=True): + def __init__(self, ux_mapper=None, parent=None, frame=False, always_on_top=True): """ Side panel toolkit for guiscrcpy main window :param ux_mapper: @@ -61,9 +57,11 @@ def __init__(self, self.ux = UXMapper() def init(self): - if platform.system() != "Linux" or (shutil.which("wmctrl") - and shutil.which("xdotool") - and platform.system() == "Linux"): + if platform.system() != "Linux" or ( + shutil.which("wmctrl") + and shutil.which("xdotool") + and platform.system() == "Linux" + ): self.clipD2PC.clicked.connect(self.ux.copy_devpc) self.clipPC2D.clicked.connect(self.ux.copy_pc2dev) self.fullscreenUI.clicked.connect(self.ux.fullscreen) @@ -79,7 +77,8 @@ def init(self): 'This function is disabled because "wmctrl"' ' or "xdotool" is not found on your installation.' "Keyboard shortcut can be used instead" - ": {}".format(helper)) + ": {}".format(helper) + ) self.back.clicked.connect(self.ux.key_back) self.screenfreeze.clicked.connect(self.quit_window) @@ -119,13 +118,18 @@ def quit_window(self): # This method checks if we are the last member of the windows # spawned and we ourselves are not a member of ourself by # checking the uuid generated on creation - if (not instance.isHidden() and instance.name != "swipe" - and instance.uid != self.uid): + if ( + not instance.isHidden() + and instance.name != "swipe" + and instance.uid != self.uid + ): self.hide() break else: for instance in self.parent.child_windows: # noqa - if (instance.name == "swipe" - and instance.ux.get_sha() == self.ux.get_sha()): + if ( + instance.name == "swipe" + and instance.ux.get_sha() == self.ux.get_sha() + ): instance.hide() self.hide()