commit d7a4df8b312ddd66704283cad894e86dc86532be Author: Olivier 'reivilibre Date: Thu Aug 25 21:34:58 2022 +0100 Initial commit with focusorstart diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..5fde060 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +layout poetry diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9e3fdb6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ + +/.idea +__pycache__ + diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..eb51e39 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,158 @@ +[[package]] +name = "atomicwrites" +version = "1.4.1" +description = "Atomic file writes." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "attrs" +version = "22.1.0" +description = "Classes Without Boilerplate" +category = "dev" +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"] +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 = "colorama" +version = "0.4.5" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "more-itertools" +version = "8.14.0" +description = "More routines for operating on iterables, beyond itertools" +category = "dev" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "packaging" +version = "21.3" +description = "Core utilities for Python packages" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" + +[[package]] +name = "pluggy" +version = "0.13.1" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.extras] +dev = ["pre-commit", "tox"] + +[[package]] +name = "psutil" +version = "5.9.1" +description = "Cross-platform lib for process and system monitoring in Python." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.extras] +test = ["ipaddress", "mock", "enum34", "pywin32", "wmi"] + +[[package]] +name = "py" +version = "1.11.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "pyparsing" +version = "3.0.9" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +category = "dev" +optional = false +python-versions = ">=3.6.8" + +[package.extras] +diagrams = ["railroad-diagrams", "jinja2"] + +[[package]] +name = "pytest" +version = "5.4.3" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=17.4.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +more-itertools = ">=4.0.0" +packaging = "*" +pluggy = ">=0.12,<1.0" +py = ">=1.5.0" +wcwidth = "*" + +[package.extras] +checkqa-mypy = ["mypy (==v0.761)"] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + +[[package]] +name = "python-libxdo-ng" +version = "0.1.4" +description = "Python bindings for libxdo" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +six = "*" + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "wcwidth" +version = "0.2.5" +description = "Measures the displayed width of unicode strings in a terminal" +category = "dev" +optional = false +python-versions = "*" + +[metadata] +lock-version = "1.1" +python-versions = "^3.8" +content-hash = "ce2bd3695095025e5af2cf1f12ed4c584ae8e7ab7306c563cfb8288fbae071e4" + +[metadata.files] +atomicwrites = [] +attrs = [] +colorama = [] +more-itertools = [] +packaging = [] +pluggy = [] +psutil = [] +py = [] +pyparsing = [] +pytest = [] +python-libxdo-ng = [] +six = [] +wcwidth = [] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..1bdd64f --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,20 @@ +[tool.poetry] +name = "rei_dotools" +version = "0.1.0" +description = "Reivilibre's tools for managing windows." +authors = ["Olivier 'reivilibre' "] + +[tool.poetry.dependencies] +python = "^3.8" +python-libxdo-ng = "^0.1.4" +psutil = "^5.9.1" + +[tool.poetry.dev-dependencies] +pytest = "^5.2" + +[tool.poetry.scripts] +rdt_focusorstart = 'rei_dotools.focusorstart:main' + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/rei_dotools/__init__.py b/rei_dotools/__init__.py new file mode 100644 index 0000000..b794fd4 --- /dev/null +++ b/rei_dotools/__init__.py @@ -0,0 +1 @@ +__version__ = '0.1.0' diff --git a/rei_dotools/focusorstart.py b/rei_dotools/focusorstart.py new file mode 100644 index 0000000..8a329e4 --- /dev/null +++ b/rei_dotools/focusorstart.py @@ -0,0 +1,45 @@ +import subprocess +import sys + +import psutil +from xdo import Xdo + +""" +Focus an application if it's running or start it if it's not. + +Must have libxdo-dev installed on Ubuntu and it must be recent (3.2021+). +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) + if not window_ids: + raise ValueError("No windows for application!") + + if len(window_ids) > 1: + print("!!! more than one window for application") + + 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) + + +def main() -> None: + application_name = sys.argv[1] + + for process in psutil.process_iter(): + process_name = process.name().split()[0] + if process_name == application_name: + focus_window_of_process(process.pid) + break + + # Launch the process, since we didn't find it. + subprocess.Popen(application_name) + + +if __name__ == '__main__': + main() diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_rei_dotools.py b/tests/test_rei_dotools.py new file mode 100644 index 0000000..2390ed5 --- /dev/null +++ b/tests/test_rei_dotools.py @@ -0,0 +1,5 @@ +from rei_dotools import __version__ + + +def test_version(): + assert __version__ == '0.1.0'