From 99c7158b98c79dc0ff8909498084c16104dfeac2 Mon Sep 17 00:00:00 2001 From: Olivier Date: Thu, 25 Aug 2022 22:45:12 +0100 Subject: [PATCH] Fix up focusorstart, especially on systems without recent libxdo --- poetry.lock | 123 ++++++++++++++++++++++++++++++++++-- pyproject.toml | 4 ++ rei_dotools/__init__.py | 2 +- rei_dotools/focusorstart.py | 41 ++++++++++-- scripts-dev/lint.sh | 6 ++ tests/test_rei_dotools.py | 2 +- 6 files changed, 165 insertions(+), 13 deletions(-) create mode 100755 scripts-dev/lint.sh diff --git a/poetry.lock b/poetry.lock index eb51e39..ce22086 100644 --- a/poetry.lock +++ b/poetry.lock @@ -15,11 +15,44 @@ optional = false python-versions = ">=3.5" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] -docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope-interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] +docs = ["furo", "sphinx", "zope-interface", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope-interface", "cloudpickle"] tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "cloudpickle"] +[[package]] +name = "black" +version = "22.6.0" +description = "The uncompromising code formatter." +category = "dev" +optional = false +python-versions = ">=3.6.2" + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} +typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "click" +version = "8.1.3" +description = "Composable command line interface toolkit" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + [[package]] name = "colorama" version = "0.4.5" @@ -28,6 +61,20 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +[[package]] +name = "isort" +version = "5.10.1" +description = "A Python utility / library to sort Python imports." +category = "dev" +optional = false +python-versions = ">=3.6.1,<4.0" + +[package.extras] +pipfile_deprecated_finder = ["pipreqs", "requirementslib"] +requirements_deprecated_finder = ["pipreqs", "pip-api"] +colors = ["colorama (>=0.4.3,<0.5.0)"] +plugins = ["setuptools"] + [[package]] name = "more-itertools" version = "8.14.0" @@ -36,6 +83,14 @@ category = "dev" optional = false python-versions = ">=3.5" +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "packaging" version = "21.3" @@ -47,6 +102,26 @@ python-versions = ">=3.6" [package.dependencies] pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" +[[package]] +name = "pathspec" +version = "0.9.0" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[[package]] +name = "platformdirs" +version = "2.5.2" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"] +test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"] + [[package]] name = "pluggy" version = "0.13.1" @@ -129,6 +204,22 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "typing-extensions" +version = "4.3.0" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "dev" +optional = false +python-versions = ">=3.7" + [[package]] name = "wcwidth" version = "0.2.5" @@ -140,19 +231,39 @@ python-versions = "*" [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "ce2bd3695095025e5af2cf1f12ed4c584ae8e7ab7306c563cfb8288fbae071e4" +content-hash = "30f692b01424a60c3313628f24ff7782340fc55fd34bd6220fa878cb88c4f117" [metadata.files] atomicwrites = [] attrs = [] +black = [] +click = [] colorama = [] +isort = [] more-itertools = [] -packaging = [] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] +packaging = [ + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, +] +pathspec = [ + {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, + {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, +] +platformdirs = [] pluggy = [] psutil = [] py = [] pyparsing = [] pytest = [] python-libxdo-ng = [] -six = [] +six = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] +tomli = [] +typing-extensions = [] wcwidth = [] diff --git a/pyproject.toml b/pyproject.toml index 1bdd64f..3a60119 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,6 +9,10 @@ python = "^3.8" python-libxdo-ng = "^0.1.4" psutil = "^5.9.1" +[tool.poetry.group.dev.dependencies] +black = "^22.6.0" +isort = "^5.10.1" + [tool.poetry.dev-dependencies] pytest = "^5.2" diff --git a/rei_dotools/__init__.py b/rei_dotools/__init__.py index b794fd4..3dc1f76 100644 --- a/rei_dotools/__init__.py +++ b/rei_dotools/__init__.py @@ -1 +1 @@ -__version__ = '0.1.0' +__version__ = "0.1.0" diff --git a/rei_dotools/focusorstart.py b/rei_dotools/focusorstart.py index 8a329e4..81b8a09 100644 --- a/rei_dotools/focusorstart.py +++ b/rei_dotools/focusorstart.py @@ -1,5 +1,7 @@ import subprocess import sys +from time import sleep +from typing import List import psutil from xdo import Xdo @@ -14,7 +16,14 @@ In old versions, `search_windows` won't return anything here... def focus_window_of_process(pid: int) -> None: x = Xdo() - window_ids = x.search_windows(pid=pid) + + # Broken for old libxdos, use xdotool as workaround + # window_ids = x.search_windows(pid=pid) + window_ids = [ + wid + for wid in _workaround_get_windows_for_pid(pid) + if _check_window_not_dropdown(wid) + ] if not window_ids: raise ValueError("No windows for application!") @@ -24,8 +33,30 @@ def focus_window_of_process(pid: int) -> None: current_desktop = x.get_current_desktop() window_id = window_ids[0] x.set_desktop_for_window(window_id, current_desktop) - x.raise_window(window_id) - x.focus_window(window_id) + x.activate_window(window_id) + + +def _workaround_get_windows_for_pid(pid: int) -> List[int]: + return [ + int(line.strip()) + for line in subprocess.check_output( + ["xdotool", "search", "--pid", str(pid)] + ).splitlines() + if line.strip() + ] + + +def _check_window_not_dropdown(window_id: int) -> bool: + xprop_output = subprocess.check_output(["xprop", "-id", str(window_id)]) + + if ( + b"_WM_WINDOW_TYPE_DROPDOWN_MENU" in xprop_output + or b"_WM_WINDOW_TYPE_POPUP_MENU" in xprop_output + ): + print("Ignoring dropdown/popup type window") + return False + + return True def main() -> None: @@ -35,11 +66,11 @@ def main() -> None: process_name = process.name().split()[0] if process_name == application_name: focus_window_of_process(process.pid) - break + return # Launch the process, since we didn't find it. subprocess.Popen(application_name) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/scripts-dev/lint.sh b/scripts-dev/lint.sh new file mode 100755 index 0000000..9c874bf --- /dev/null +++ b/scripts-dev/lint.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +isort . +black . +#mypy + diff --git a/tests/test_rei_dotools.py b/tests/test_rei_dotools.py index 2390ed5..f0db558 100644 --- a/tests/test_rei_dotools.py +++ b/tests/test_rei_dotools.py @@ -2,4 +2,4 @@ from rei_dotools import __version__ def test_version(): - assert __version__ == '0.1.0' + assert __version__ == "0.1.0"