elsewindow 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,18 @@
1
+ <!-- Copyright (c) 2026 kogeler. SPDX-License-Identifier: MIT. -->
2
+
3
+ # Changelog
4
+
5
+ All notable changes to Elsewindow are documented here.
6
+
7
+ ## [0.1.0] - 2026-08-30
8
+
9
+ ### Added
10
+
11
+ - Established Elsewindow as an independent Linux project with one public
12
+ distribution, import package, command, and repository identity.
13
+ - Preserved the one-authentication OpenSSH lifecycle, mux-only Xpra channels,
14
+ heartbeat-supervised remote process group, and selective cleanup behavior.
15
+ - Shipped reviewed Xpra command and network profiles as package data and kept
16
+ release-backed package installation and container acceptance tooling.
17
+ - Added CPython 3.13 and 3.14 governance, deterministic Python distributions,
18
+ Linux standalone artifacts, and fail-closed publication policy.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 kogeler
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,17 @@
1
+ include .version
2
+ include CHANGELOG.md
3
+ include LICENSE
4
+ include MANIFEST.in
5
+ include README.md
6
+ include pyproject.toml
7
+ recursive-include elsewindow *.py *.yml py.typed
8
+ prune .github
9
+ prune bin
10
+ prune containers
11
+ prune doc
12
+ prune tests
13
+ prune tmp
14
+ prune tools
15
+ global-exclude .DS_Store
16
+ global-exclude *.py[cod]
17
+ global-exclude __pycache__
@@ -0,0 +1,176 @@
1
+ Metadata-Version: 2.4
2
+ Name: elsewindow
3
+ Version: 0.1.0
4
+ Summary: Run one remote Linux GUI application through Xpra over one owned OpenSSH master
5
+ Author: kogeler
6
+ Maintainer: kogeler
7
+ License-Expression: MIT
8
+ Project-URL: Homepage, https://kogeler.github.io/elsewindow/
9
+ Project-URL: Documentation, https://kogeler.github.io/elsewindow/
10
+ Project-URL: Repository, https://github.com/kogeler/elsewindow
11
+ Project-URL: Issues, https://github.com/kogeler/elsewindow/issues
12
+ Project-URL: Changelog, https://github.com/kogeler/elsewindow/blob/main/CHANGELOG.md
13
+ Keywords: xpra,openssh,ssh,wayland,remote-desktop
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Environment :: X11 Applications
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Intended Audience :: System Administrators
18
+ Classifier: Operating System :: POSIX :: Linux
19
+ Classifier: Programming Language :: Python :: 3 :: Only
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Programming Language :: Python :: Implementation :: CPython
23
+ Classifier: Topic :: System :: Networking
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: <3.15,>=3.13
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: ssh-wrapper==0.1.0
29
+ Provides-Extra: quality
30
+ Requires-Dist: bandit[toml]==1.9.4; extra == "quality"
31
+ Requires-Dist: mypy==2.3.1; extra == "quality"
32
+ Requires-Dist: pip-audit==2.10.1; extra == "quality"
33
+ Requires-Dist: pip-licenses==5.5.5; extra == "quality"
34
+ Requires-Dist: ruff==0.16.5; extra == "quality"
35
+ Provides-Extra: test
36
+ Requires-Dist: pytest==9.1.1; extra == "test"
37
+ Requires-Dist: pytest-asyncio==1.4.0; extra == "test"
38
+ Requires-Dist: pytest-cov==7.1.0; extra == "test"
39
+ Requires-Dist: pytest-xdist==3.8.0; extra == "test"
40
+ Provides-Extra: package
41
+ Requires-Dist: build==1.6.0; extra == "package"
42
+ Requires-Dist: setuptools==84.0.0; extra == "package"
43
+ Requires-Dist: wheel==0.48.0; extra == "package"
44
+ Provides-Extra: standalone
45
+ Requires-Dist: pyinstaller==6.22.2; extra == "standalone"
46
+ Provides-Extra: docs
47
+ Requires-Dist: mkdocs-material==9.7.7; extra == "docs"
48
+ Dynamic: license-file
49
+
50
+ # Elsewindow
51
+
52
+ `elsewindow` starts one GUI application on a remote Linux host and shows
53
+ its windows through Xpra. One owned OpenSSH ControlMaster carries every Xpra
54
+ control channel; the command opens no forwarding or Xpra TCP listener and
55
+ cleans only the server, application process group, sockets, and runtime state
56
+ created by that invocation.
57
+
58
+ The application renders through the remote Wayland compositor and remote GPU.
59
+ Only Xpra picture, input, and control traffic crosses SSH.
60
+
61
+ ## Install And Run
62
+
63
+ The Python distribution requires Linux, CPython 3.13 or 3.14, OpenSSH,
64
+ `false`, a local graphical session, and compatible Xpra packages on both
65
+ systems. Install with pip or pipx after the first release:
66
+
67
+ ```bash
68
+ python3.14 -m pip install "elsewindow==0.1.0"
69
+ # or: pipx install "elsewindow==0.1.0"
70
+ elsewindow --ssh-alias agents-a -- xterm
71
+ ```
72
+
73
+ Check packaged versions, profile digests, Linux support, and local commands
74
+ without opening a connection:
75
+
76
+ ```bash
77
+ elsewindow --diagnose
78
+ ```
79
+
80
+ The published distribution resolves the exact reviewed
81
+ [`ssh-wrapper==0.1.0`](https://pypi.org/project/ssh-wrapper/0.1.0/) dependency.
82
+ For a source checkout, prepare the hash-locked runtime and use the repository
83
+ launcher from any working directory:
84
+
85
+ ```bash
86
+ make runtime-venv
87
+ ./bin/elsewindow --help
88
+ ```
89
+
90
+ Use a direct authority when an OpenSSH alias is not appropriate:
91
+
92
+ ```bash
93
+ elsewindow \
94
+ --host host.example \
95
+ --user desktop-user \
96
+ --port 2222 \
97
+ -- /opt/application/bin/application
98
+ ```
99
+
100
+ The default encoding and network profiles are `rgb` and `gigabit_lan`.
101
+ Production arguments come exclusively from the reviewed YAML mirrors shipped
102
+ inside the Python package. See [the Xpra guide](doc/xpra.md) for profile,
103
+ hardware, application, and lifecycle details.
104
+
105
+ ## Installing The Maintained Xpra Build
106
+
107
+ [`tools/install_xpra_release.py`](tools/install_xpra_release.py) is a standalone
108
+ standard-library installer for Debian 13 and Ubuntu 26.04. It resolves the
109
+ newest canonical package release from `kogeler/xpra:develop`, verifies archive
110
+ and DEB contents before mutation, prints the exact installed Xpra inventory,
111
+ and requires interactive confirmation before purging that inventory. It uses
112
+ APT for the validated local packages and dependencies.
113
+
114
+ Elsewindow deliberately uses this maintained fork rather than treating a
115
+ generic upstream Xpra build as interchangeable. Project development uncovered
116
+ dozens of blocking Xpra defects: some fixes were accepted as direct upstream
117
+ contributions, some were implemented by the upstream maintainer after issue
118
+ reports, and some were not accepted after technical disagreements. The last
119
+ category requires continued maintenance of additional fork patches. The
120
+ [Xpra guide](doc/xpra.md#why-the-maintained-xpra-fork-is-required) explains
121
+ the resulting user compatibility boundary.
122
+
123
+ Review the installer and the [security contract](doc/security.md). The streamed
124
+ route below is pending until the first reviewed `main` push; it must not be
125
+ treated as available before then:
126
+
127
+ ```bash
128
+ ( set -o pipefail; curl --proto '=https' --tlsv1.2 -fsSL 'https://raw.githubusercontent.com/kogeler/elsewindow/main/tools/install_xpra_release.py' | /usr/bin/python3 - )
129
+ ```
130
+
131
+ Do not prefix the pipeline or Python process with `sudo`. When privilege is
132
+ needed, the reviewed script reads purge confirmation from the controlling
133
+ terminal and invokes `/usr/bin/sudo` itself only for its root-owned staging and
134
+ APT transaction.
135
+
136
+ ## Standalone Linux Executables
137
+
138
+ After the first release, GitHub Releases provide `elsewindow-linux-amd64` and
139
+ `elsewindow-linux-arm64`. These native one-file ELF executables bundle
140
+ Elsewindow, CPython, `ssh-wrapper`, version metadata, and the reviewed YAML
141
+ profiles. They do not bundle OpenSSH, Xpra, Podman, GPU drivers, VA-API, or
142
+ distribution packages. Verify the release's `SHA256SUMS.txt`, make the selected
143
+ file executable, and run `./elsewindow-linux-amd64 --diagnose` (or the arm64
144
+ equivalent) before starting a session.
145
+
146
+ ## Documentation
147
+
148
+ - [Rendered documentation site](https://kogeler.github.io/elsewindow/)
149
+ (published by the first reviewed direct `main` push)
150
+ - [Getting started](doc/getting-started.md)
151
+ - [Complete Xpra behavior and options](doc/xpra.md)
152
+ - [Security model](doc/security.md)
153
+ - [Architecture](doc/architecture.md)
154
+ - [Development and validation](doc/development.md)
155
+ - [Contributing](doc/contributing.md)
156
+ - [Maintainer CI contract](doc/maintenance/CI.md)
157
+ - [Dependency maintenance](doc/maintenance/DEPENDENCIES.md)
158
+ - [Release maintenance](doc/maintenance/RELEASES.md)
159
+
160
+ The Pages workflow renders these same maintained Markdown files and validates
161
+ every generated route, local link, anchor, canonical URL, sitemap, crawler
162
+ file, and advertised `llms.txt` route without network access.
163
+
164
+ ## Automatic Live Validation
165
+
166
+ `make live-test` resolves the newest fork release, builds checksum-bound Ubuntu
167
+ target and Debian client images, and runs detach and abrupt-master-loss cases
168
+ on a private rootless Podman network. The payload excludes `elsewindow/` source
169
+ and clean-installs the already verified wheel. The harness generates a fresh
170
+ Ed25519 key, performs exactly one authentication per case, verifies a visible
171
+ window and picture updates through the production profile assembler, preserves
172
+ an unrelated session, and removes only its labelled containers and network.
173
+ All wheel, test, and key material enters through validated tar streams; the
174
+ test uses no host bind, data volume, or copy channel.
175
+
176
+ Released under the [MIT License](LICENSE).
@@ -0,0 +1,127 @@
1
+ # Elsewindow
2
+
3
+ `elsewindow` starts one GUI application on a remote Linux host and shows
4
+ its windows through Xpra. One owned OpenSSH ControlMaster carries every Xpra
5
+ control channel; the command opens no forwarding or Xpra TCP listener and
6
+ cleans only the server, application process group, sockets, and runtime state
7
+ created by that invocation.
8
+
9
+ The application renders through the remote Wayland compositor and remote GPU.
10
+ Only Xpra picture, input, and control traffic crosses SSH.
11
+
12
+ ## Install And Run
13
+
14
+ The Python distribution requires Linux, CPython 3.13 or 3.14, OpenSSH,
15
+ `false`, a local graphical session, and compatible Xpra packages on both
16
+ systems. Install with pip or pipx after the first release:
17
+
18
+ ```bash
19
+ python3.14 -m pip install "elsewindow==0.1.0"
20
+ # or: pipx install "elsewindow==0.1.0"
21
+ elsewindow --ssh-alias agents-a -- xterm
22
+ ```
23
+
24
+ Check packaged versions, profile digests, Linux support, and local commands
25
+ without opening a connection:
26
+
27
+ ```bash
28
+ elsewindow --diagnose
29
+ ```
30
+
31
+ The published distribution resolves the exact reviewed
32
+ [`ssh-wrapper==0.1.0`](https://pypi.org/project/ssh-wrapper/0.1.0/) dependency.
33
+ For a source checkout, prepare the hash-locked runtime and use the repository
34
+ launcher from any working directory:
35
+
36
+ ```bash
37
+ make runtime-venv
38
+ ./bin/elsewindow --help
39
+ ```
40
+
41
+ Use a direct authority when an OpenSSH alias is not appropriate:
42
+
43
+ ```bash
44
+ elsewindow \
45
+ --host host.example \
46
+ --user desktop-user \
47
+ --port 2222 \
48
+ -- /opt/application/bin/application
49
+ ```
50
+
51
+ The default encoding and network profiles are `rgb` and `gigabit_lan`.
52
+ Production arguments come exclusively from the reviewed YAML mirrors shipped
53
+ inside the Python package. See [the Xpra guide](doc/xpra.md) for profile,
54
+ hardware, application, and lifecycle details.
55
+
56
+ ## Installing The Maintained Xpra Build
57
+
58
+ [`tools/install_xpra_release.py`](tools/install_xpra_release.py) is a standalone
59
+ standard-library installer for Debian 13 and Ubuntu 26.04. It resolves the
60
+ newest canonical package release from `kogeler/xpra:develop`, verifies archive
61
+ and DEB contents before mutation, prints the exact installed Xpra inventory,
62
+ and requires interactive confirmation before purging that inventory. It uses
63
+ APT for the validated local packages and dependencies.
64
+
65
+ Elsewindow deliberately uses this maintained fork rather than treating a
66
+ generic upstream Xpra build as interchangeable. Project development uncovered
67
+ dozens of blocking Xpra defects: some fixes were accepted as direct upstream
68
+ contributions, some were implemented by the upstream maintainer after issue
69
+ reports, and some were not accepted after technical disagreements. The last
70
+ category requires continued maintenance of additional fork patches. The
71
+ [Xpra guide](doc/xpra.md#why-the-maintained-xpra-fork-is-required) explains
72
+ the resulting user compatibility boundary.
73
+
74
+ Review the installer and the [security contract](doc/security.md). The streamed
75
+ route below is pending until the first reviewed `main` push; it must not be
76
+ treated as available before then:
77
+
78
+ ```bash
79
+ ( set -o pipefail; curl --proto '=https' --tlsv1.2 -fsSL 'https://raw.githubusercontent.com/kogeler/elsewindow/main/tools/install_xpra_release.py' | /usr/bin/python3 - )
80
+ ```
81
+
82
+ Do not prefix the pipeline or Python process with `sudo`. When privilege is
83
+ needed, the reviewed script reads purge confirmation from the controlling
84
+ terminal and invokes `/usr/bin/sudo` itself only for its root-owned staging and
85
+ APT transaction.
86
+
87
+ ## Standalone Linux Executables
88
+
89
+ After the first release, GitHub Releases provide `elsewindow-linux-amd64` and
90
+ `elsewindow-linux-arm64`. These native one-file ELF executables bundle
91
+ Elsewindow, CPython, `ssh-wrapper`, version metadata, and the reviewed YAML
92
+ profiles. They do not bundle OpenSSH, Xpra, Podman, GPU drivers, VA-API, or
93
+ distribution packages. Verify the release's `SHA256SUMS.txt`, make the selected
94
+ file executable, and run `./elsewindow-linux-amd64 --diagnose` (or the arm64
95
+ equivalent) before starting a session.
96
+
97
+ ## Documentation
98
+
99
+ - [Rendered documentation site](https://kogeler.github.io/elsewindow/)
100
+ (published by the first reviewed direct `main` push)
101
+ - [Getting started](doc/getting-started.md)
102
+ - [Complete Xpra behavior and options](doc/xpra.md)
103
+ - [Security model](doc/security.md)
104
+ - [Architecture](doc/architecture.md)
105
+ - [Development and validation](doc/development.md)
106
+ - [Contributing](doc/contributing.md)
107
+ - [Maintainer CI contract](doc/maintenance/CI.md)
108
+ - [Dependency maintenance](doc/maintenance/DEPENDENCIES.md)
109
+ - [Release maintenance](doc/maintenance/RELEASES.md)
110
+
111
+ The Pages workflow renders these same maintained Markdown files and validates
112
+ every generated route, local link, anchor, canonical URL, sitemap, crawler
113
+ file, and advertised `llms.txt` route without network access.
114
+
115
+ ## Automatic Live Validation
116
+
117
+ `make live-test` resolves the newest fork release, builds checksum-bound Ubuntu
118
+ target and Debian client images, and runs detach and abrupt-master-loss cases
119
+ on a private rootless Podman network. The payload excludes `elsewindow/` source
120
+ and clean-installs the already verified wheel. The harness generates a fresh
121
+ Ed25519 key, performs exactly one authentication per case, verifies a visible
122
+ window and picture updates through the production profile assembler, preserves
123
+ an unrelated session, and removes only its labelled containers and network.
124
+ All wheel, test, and key material enters through validated tar streams; the
125
+ test uses no host bind, data volume, or copy channel.
126
+
127
+ Released under the [MIT License](LICENSE).
@@ -0,0 +1,33 @@
1
+ # Copyright (c) 2026 kogeler
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ """Xpra application runner over one owned SSH master."""
5
+
6
+ from __future__ import annotations
7
+
8
+
9
+ def _resolve_version() -> str:
10
+ """Resolve the source-tree or installed distribution version."""
11
+ try:
12
+ from pathlib import Path
13
+
14
+ root = Path(__file__).resolve().parent.parent
15
+ if (root / "pyproject.toml").is_file():
16
+ return (root / ".version").read_text(encoding="utf-8").strip()
17
+ bundled = Path(__file__).resolve().parent / ".version"
18
+ if bundled.is_file():
19
+ return bundled.read_text(encoding="utf-8").strip()
20
+ except OSError:
21
+ pass
22
+
23
+ try:
24
+ from importlib.metadata import PackageNotFoundError, version
25
+
26
+ return version("elsewindow")
27
+ except PackageNotFoundError:
28
+ return "0.0.0+unknown"
29
+
30
+
31
+ __version__ = _resolve_version()
32
+
33
+ __all__ = ["__version__"]
@@ -0,0 +1,8 @@
1
+ # Copyright (c) 2026 kogeler
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ """Module entry point for Elsewindow."""
5
+
6
+ from .cli import main
7
+
8
+ raise SystemExit(main())
@@ -0,0 +1,204 @@
1
+ # Copyright (c) 2026 kogeler
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ """Command-line interface for one owned remote Xpra application."""
5
+
6
+ from __future__ import annotations
7
+
8
+ import argparse
9
+ import asyncio
10
+ import hashlib
11
+ import shutil
12
+ import signal
13
+ import sys
14
+ from collections.abc import Sequence
15
+ from contextlib import suppress
16
+ from importlib.metadata import PackageNotFoundError
17
+ from importlib.metadata import version as distribution_version
18
+
19
+ from ssh_wrapper.errors import SSHError
20
+
21
+ from . import __version__
22
+ from .config import (
23
+ DEFAULT_CONNECT_TIMEOUT,
24
+ DEFAULT_ENCODING_PROFILE,
25
+ DEFAULT_GRACE_TIMEOUT,
26
+ DEFAULT_HEARTBEAT_INTERVAL,
27
+ DEFAULT_LEASE_TIMEOUT,
28
+ DEFAULT_NETWORK_PROFILE,
29
+ DEFAULT_POLL_INTERVAL,
30
+ DEFAULT_PROBE_TIMEOUT,
31
+ DEFAULT_READY_TIMEOUT,
32
+ SUPPORTED_ENCODING_PROFILES,
33
+ SUPPORTED_NETWORK_PROFILES,
34
+ XpraConfig,
35
+ )
36
+ from .session import XpraSession
37
+
38
+
39
+ def build_parser() -> argparse.ArgumentParser:
40
+ """Build the public command-line contract."""
41
+ parser = argparse.ArgumentParser(
42
+ prog="elsewindow",
43
+ description="Run one new remote GUI application through Xpra over owned SSH.",
44
+ )
45
+ parser.add_argument(
46
+ "--version", action="version", version=f"%(prog)s {__version__}"
47
+ )
48
+ parser.add_argument(
49
+ "--diagnose",
50
+ action="store_true",
51
+ help="report bundled versions, profile digests, and local prerequisites",
52
+ )
53
+ authority = parser.add_mutually_exclusive_group()
54
+ authority.add_argument("--ssh-alias", help="trusted OpenSSH host alias")
55
+ authority.add_argument("--host", help="direct SSH host or address")
56
+ parser.add_argument("--user", help="remote user required with --host")
57
+ parser.add_argument("--port", type=int, default=22, help="direct SSH port")
58
+ parser.add_argument(
59
+ "--encoding-profile",
60
+ choices=SUPPORTED_ENCODING_PROFILES,
61
+ default=DEFAULT_ENCODING_PROFILE,
62
+ help=(
63
+ "reviewed pixel transport profile; h264 uses native VA-API, "
64
+ "libyuv, OpenGL, and alpha-capable fallback"
65
+ ),
66
+ )
67
+ parser.add_argument(
68
+ "--network-profile",
69
+ choices=SUPPORTED_NETWORK_PROFILES,
70
+ default=DEFAULT_NETWORK_PROFILE,
71
+ help="reviewed client quality/network profile (default: %(default)s)",
72
+ )
73
+ parser.add_argument(
74
+ "--connect-timeout", type=float, default=DEFAULT_CONNECT_TIMEOUT
75
+ )
76
+ parser.add_argument("--ready-timeout", type=float, default=DEFAULT_READY_TIMEOUT)
77
+ parser.add_argument("--probe-timeout", type=float, default=DEFAULT_PROBE_TIMEOUT)
78
+ parser.add_argument("--poll-interval", type=float, default=DEFAULT_POLL_INTERVAL)
79
+ parser.add_argument(
80
+ "--heartbeat-interval", type=float, default=DEFAULT_HEARTBEAT_INTERVAL
81
+ )
82
+ parser.add_argument("--lease-timeout", type=float, default=DEFAULT_LEASE_TIMEOUT)
83
+ parser.add_argument("--cleanup-grace", type=float, default=DEFAULT_GRACE_TIMEOUT)
84
+ parser.add_argument(
85
+ "application",
86
+ nargs=argparse.REMAINDER,
87
+ metavar="APP [ARG ...]",
88
+ help="application argv after --",
89
+ )
90
+ return parser
91
+
92
+
93
+ def _diagnose() -> int:
94
+ """Report immutable bundled inputs and actionable host prerequisites."""
95
+ from .live_config import LIVE_CLI_PATH, NETWORK_PROFILES_PATH
96
+
97
+ failed = False
98
+ print(f"elsewindow: {__version__}")
99
+ try:
100
+ wrapper_version = distribution_version("ssh-wrapper")
101
+ except PackageNotFoundError:
102
+ wrapper_version = "missing"
103
+ failed = True
104
+ print(f"ssh-wrapper: {wrapper_version}")
105
+ for label, path in (
106
+ ("live-cli.yml", LIVE_CLI_PATH),
107
+ ("profiles.yml", NETWORK_PROFILES_PATH),
108
+ ):
109
+ try:
110
+ digest = hashlib.sha256(path.read_bytes()).hexdigest()
111
+ except OSError as error:
112
+ print(f"elsewindow: invalid_resource: {label}: {error}", file=sys.stderr)
113
+ failed = True
114
+ else:
115
+ print(f"{label}: sha256:{digest}")
116
+ if not sys.platform.startswith("linux"):
117
+ print(
118
+ f"elsewindow: unsupported_platform: Linux is required, found {sys.platform}",
119
+ file=sys.stderr,
120
+ )
121
+ failed = True
122
+ for command in ("ssh", "false", "xpra"):
123
+ resolved = shutil.which(command)
124
+ if resolved is None:
125
+ print(
126
+ "elsewindow: missing_dependency: "
127
+ f"required command not found on PATH: {command}",
128
+ file=sys.stderr,
129
+ )
130
+ failed = True
131
+ else:
132
+ print(f"{command}: {resolved}")
133
+ return int(failed)
134
+
135
+
136
+ async def _run_with_signals(config: XpraConfig) -> int:
137
+ session = XpraSession(config)
138
+ loop = asyncio.get_running_loop()
139
+ interrupted: asyncio.Future[int] = loop.create_future()
140
+
141
+ def stop(exit_code: int) -> None:
142
+ if not interrupted.done():
143
+ interrupted.set_result(exit_code)
144
+
145
+ installed: list[signal.Signals] = []
146
+ for selected in (signal.SIGINT, signal.SIGTERM):
147
+ try:
148
+ loop.add_signal_handler(selected, stop, 128 + selected)
149
+ except NotImplementedError:
150
+ continue
151
+ installed.append(selected)
152
+
153
+ run_task = asyncio.create_task(session.run())
154
+ try:
155
+ done, _pending = await asyncio.wait(
156
+ {run_task, interrupted}, return_when=asyncio.FIRST_COMPLETED
157
+ )
158
+ if interrupted in done:
159
+ run_task.cancel()
160
+ with suppress(asyncio.CancelledError):
161
+ await run_task
162
+ return interrupted.result()
163
+ return run_task.result()
164
+ finally:
165
+ if not interrupted.done():
166
+ interrupted.cancel()
167
+ for selected in installed:
168
+ loop.remove_signal_handler(selected)
169
+
170
+
171
+ def main(argv: Sequence[str] | None = None) -> int:
172
+ """Validate configuration, run one session, and return a stable exit code."""
173
+ parser = build_parser()
174
+ args = parser.parse_args(argv)
175
+ if args.diagnose:
176
+ if (
177
+ args.ssh_alias is not None
178
+ or args.host is not None
179
+ or args.user is not None
180
+ or args.port != 22
181
+ or args.application
182
+ ):
183
+ parser.error("--diagnose cannot be combined with a session authority")
184
+ return _diagnose()
185
+ if args.ssh_alias is None and args.host is None:
186
+ parser.error("provide either --ssh-alias or --host")
187
+ try:
188
+ config = XpraConfig.from_namespace(args)
189
+ except SSHError as error:
190
+ parser.error(error.message)
191
+
192
+ try:
193
+ return asyncio.run(_run_with_signals(config))
194
+ except KeyboardInterrupt:
195
+ return 130
196
+ except SSHError as error:
197
+ print(f"elsewindow: {error.code}: {error.message}", file=sys.stderr)
198
+ return 1
199
+ except Exception as error: # noqa: BLE001 - public diagnostics stay sanitized.
200
+ print(
201
+ f"elsewindow: session_failed: {type(error).__name__}",
202
+ file=sys.stderr,
203
+ )
204
+ return 1