-
-
Notifications
You must be signed in to change notification settings - Fork 177
/
Copy pathmain.py
executable file
·4541 lines (4163 loc) · 175 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python3
# This file is part of Xpra.
# Copyright (C) 2011 Serviware (Arthur Huillet, <[email protected]>)
# Copyright (C) 2010-2024 Antoine Martin <[email protected]>
# Copyright (C) 2008 Nathaniel Smith <[email protected]>
# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
# later version. See the file COPYING for details.
import re
import sys
import os.path
import stat
import glob
import socket
import time
import logging
from math import ceil
from time import monotonic
from importlib.util import find_spec
from shutil import rmtree, which
from subprocess import Popen, PIPE, TimeoutExpired, run
import signal
import shlex
import traceback
from typing import Any, NoReturn
from collections.abc import Callable, Iterable
from xpra.common import SocketState, noerr, noop
from xpra.util.objects import typedict
from xpra.util.str_fn import nonl, csv, print_nested_dict, pver, sorted_nicely, bytestostr, sort_human
from xpra.util.env import envint, envbool, osexpand, save_env, get_exec_env, OSEnvContext
from xpra.util.thread import set_main_thread
from xpra.exit_codes import ExitCode, ExitValue, RETRY_EXIT_CODES, exit_str
from xpra.os_util import (
getuid, getgid, get_username_for_uid, force_quit,
gi_import,
WIN32, OSX, POSIX
)
from xpra.util.io import is_socket, stderr_print, use_tty, info, warn, error
from xpra.util.system import is_Wayland, SIGNAMES, set_proc_title, is_systemd_pid1
from xpra.scripts.parsing import (
get_usage,
parse_display_name, parse_env,
fixup_defaults,
validated_encodings, validate_encryption, do_parse_cmdline, show_audio_codec_help,
MODE_ALIAS, REVERSE_MODE_ALIAS,
)
from xpra.scripts.config import (
XpraConfig,
OPTION_TYPES, TRUE_OPTIONS, FALSE_OPTIONS, OFF_OPTIONS, ALL_BOOLEAN_OPTIONS,
NON_COMMAND_LINE_OPTIONS, CLIENT_ONLY_OPTIONS, CLIENT_OPTIONS,
START_COMMAND_OPTIONS, BIND_OPTIONS, PROXY_START_OVERRIDABLE_OPTIONS, OPTIONS_ADDED_SINCE_V5, OPTIONS_COMPAT_NAMES,
InitException, InitInfo, InitExit,
fixup_options,
find_docs_path, find_html5_path,
dict_to_validated_config, get_xpra_defaults_dirs, get_defaults, read_xpra_conf,
make_defaults_struct, str_to_bool, parse_bool_or, has_audio_support, name_to_field,
)
from xpra.net.common import DEFAULT_PORTS, SOCKET_TYPES, AUTO_ABSTRACT_SOCKET, ABSTRACT_SOCKET_PREFIX
from xpra.log import is_debug_enabled, Logger, get_debug_args
assert callable(error), "used by modules importing this function from here"
NO_ROOT_WARNING: bool = envbool("XPRA_NO_ROOT_WARNING", False)
WAIT_SERVER_TIMEOUT: int = envint("WAIT_SERVER_TIMEOUT", 90)
CONNECT_TIMEOUT: int = envint("XPRA_CONNECT_TIMEOUT", 20)
OPENGL_PROBE_TIMEOUT: int = envint("XPRA_OPENGL_PROBE_TIMEOUT", 5)
SYSTEMD_RUN: bool = envbool("XPRA_SYSTEMD_RUN", True)
VERIFY_SOCKET_TIMEOUT: int = envint("XPRA_VERIFY_SOCKET_TIMEOUT", 1)
LIST_REPROBE_TIMEOUT: int = envint("XPRA_LIST_REPROBE_TIMEOUT", 10)
# pylint: disable=import-outside-toplevel
# noinspection PyBroadException
def nox() -> str:
DISPLAY = os.environ.get("DISPLAY")
if DISPLAY is not None:
del os.environ["DISPLAY"]
# This is an error on Fedora/RH, so make it an error everywhere
# to ensure that it will be noticed:
import warnings
warnings.filterwarnings("error", "could not open display")
return str(DISPLAY or "") or os.environ.get("WAYLAND_DISPLAY", "")
def werr(*msg) -> None:
for x in msg:
stderr_print(str(x))
def error_handler(*args) -> NoReturn:
raise InitException(*args)
def add_process(*args, **kwargs):
from xpra.util.child_reaper import getChildReaper
return getChildReaper().add_process(*args, **kwargs)
def get_logger() -> Logger:
return Logger("util")
def main(script_file: str, cmdline) -> ExitValue:
set_main_thread()
save_env()
ml = envint("XPRA_MEM_USAGE_LOGGER")
if ml > 0:
from xpra.util.pysystem import start_mem_watcher
start_mem_watcher(ml)
if sys.flags.optimize > 0: # pragma: no cover
stderr_print("************************************************************")
stderr_print(f"Warning: the python optimize flag is set to {sys.flags.optimize}")
stderr_print(" xpra is very likely to crash")
stderr_print("************************************************************")
time.sleep(5)
from xpra.platform import clean as platform_clean, command_error, command_info
if len(cmdline) == 1:
cmdline.append("gui")
def debug_exc(msg: str = "run_mode error") -> None:
get_logger().debug(msg, exc_info=True)
try:
defaults: XpraConfig = make_defaults_struct()
fixup_defaults(defaults)
options, args = do_parse_cmdline(cmdline, defaults)
# `set_proc_title` is set here so that we can override the cmdline later
# (don't ask me why this works)
set_proc_title(" ".join(cmdline))
if not args:
raise InitExit(-1, "xpra: need a mode")
mode = args.pop(0)
mode = MODE_ALIAS.get(mode, mode)
return run_mode(script_file, cmdline, error_handler, options, args, mode, defaults)
except SystemExit:
debug_exc()
raise
except InitExit as e:
debug_exc()
if str(e) and e.args and (e.args[0] or len(e.args) > 1):
command_info(str(e))
return e.status
except InitInfo as e:
debug_exc()
command_info(str(e))
return 0
except InitException as e:
debug_exc()
command_error(f"xpra initialization error:\n {e}")
return 1
except AssertionError as e:
debug_exc()
command_error(f"xpra initialization error:\n {e}")
traceback.print_tb(sys.exc_info()[2])
return 1
except Exception:
debug_exc()
command_error("xpra main error:\n%s" % traceback.format_exc())
return 1
finally:
platform_clean()
clean_std_pipes()
def clean_std_pipes() -> None:
if not envbool("XPRA_CLEAN_STD_PIPES", True):
return
def closestd(std) -> None:
if std:
try:
std.close()
except OSError: # pragma: no cover
pass
closestd(sys.stdout)
closestd(sys.stderr)
def configure_logging(options, mode) -> None:
if mode in (
"attach", "listen", "launcher",
"sessions", "mdns-gui",
"bug-report", "session-info", "docs", "documentation", "about", "license",
"recover",
"splash", "qrcode",
"opengl-test",
"desktop-greeter",
"show-menu", "show-about", "show-session-info",
"webcam",
"showconfig",
"root-size",
):
to = sys.stdout
else:
to = sys.stderr
# a bit naughty here, but it's easier to let xpra.log initialize
# the logging system every time, and just undo things here..
from xpra.log import (
setloghandler, enable_color, enable_format,
LOG_FORMAT, NOPREFIX_FORMAT,
SIGPIPEStreamHandler,
)
setloghandler(SIGPIPEStreamHandler(to))
if mode in (
"seamless", "desktop", "monitor", "expand",
"shadow", "shadow-screen",
"recover",
"attach", "listen", "proxy",
"version", "info", "id",
"_audio_record", "_audio_play",
"stop", "print", "showconfig", "configure",
"_dialog", "_pass",
"pinentry",
"opengl",
"example",
) or mode.startswith("upgrade") or mode.startswith("request-"):
if "help" in options.speaker_codec or "help" in options.microphone_codec:
server_mode = mode not in ("attach", "listen")
codec_help = show_audio_codec_help(server_mode, options.speaker_codec, options.microphone_codec)
raise InitInfo("\n".join(codec_help))
fmt = LOG_FORMAT
if mode in ("stop", "showconfig", "version", "info", "id"):
fmt = NOPREFIX_FORMAT
if envbool("XPRA_COLOR_LOG", hasattr(to, "fileno") and os.isatty(to.fileno())):
enable_color(to, fmt)
else:
enable_format(fmt)
from xpra.log import add_debug_category, add_disabled_category, enable_debug_for, disable_debug_for
if options.debug:
categories = options.debug.split(",")
for cat in categories:
if not cat:
continue
if cat[0] == "-":
add_disabled_category(cat[1:])
disable_debug_for(cat[1:])
else:
add_debug_category(cat)
enable_debug_for(cat)
# always log debug level, we just use it selectively (see above)
logging.root.setLevel(logging.INFO)
def configure_network(options) -> None:
from xpra.net import compression, packet_encoding
compression.init_compressors(*(list(options.compressors) + ["none"]))
ecs = compression.get_enabled_compressors()
if not ecs:
# force compression level to zero since we have no compressors available:
options.compression_level = 0
packet_encoding.init_encoders(*list(options.packet_encoders) + ["none"])
ees = set(packet_encoding.get_enabled_encoders())
try:
ees.remove("none")
except KeyError:
pass
# verify that at least one real encoder is available:
if not ees:
raise InitException("at least one valid packet encoder must be enabled")
def configure_env(env_str) -> None:
if env_str:
env = parse_env(env_str)
if POSIX and getuid() == 0:
# running as root!
# sanitize: only allow "safe" environment variables
# as these may have been specified by a non-root user
env = {k: v for k, v in env.items() if k.startswith("XPRA_")}
os.environ.update(env)
def systemd_run_command(mode, systemd_run_args=None, user: bool = True) -> list[str]:
cmd = ["systemd-run", "--description", "xpra-%s" % mode, "--scope"]
if user:
cmd.append("--user")
log_systemd_wrap = envbool("XPRA_LOG_SYSTEMD_WRAP", True)
if not log_systemd_wrap:
cmd.append("--quiet")
if systemd_run_args:
cmd += shlex.split(systemd_run_args)
return cmd
def systemd_run_wrap(mode: str, args, systemd_run_args=None, user: bool = True, **kwargs) -> int:
cmd = systemd_run_command(mode, systemd_run_args, user)
cmd += args
cmd.append("--systemd-run=no")
errwrite = getattr(sys.stderr, "write", noop)
log_systemd_wrap = envbool("XPRA_LOG_SYSTEMD_WRAP", True)
if log_systemd_wrap:
noerr(errwrite, f"using systemd-run to wrap {mode!r} xpra server subcommand\n")
log_systemd_wrap_command = envbool("XPRA_LOG_SYSTEMD_WRAP_COMMAND", False)
if log_systemd_wrap_command:
noerr(errwrite, "%s\n" % " ".join(["'%s'" % x for x in cmd]))
try:
with Popen(cmd, **kwargs) as p:
return p.wait()
except KeyboardInterrupt:
return 128 + signal.SIGINT
def isdisplaytype(args, *dtypes) -> bool:
if not args:
return False
d = args[0]
return any(d.startswith(f"{dtype}/") or d.startswith(f"{dtype}:") for dtype in dtypes)
def set_gdk_backend() -> None:
try:
from xpra.x11.bindings.xwayland import isX11, isxwayland
except ImportError:
pass
else:
if os.environ.get("DISPLAY") and isX11():
# we have an X11 display!
if isxwayland():
os.environ["GDK_BACKEND"] = "wayland"
else:
os.environ["GDK_BACKEND"] = "x11"
if is_Wayland():
os.environ["GDK_BACKEND"] = "wayland"
def set_pyopengl_platform() -> None:
gdk_backend = os.environ.get("GDK_BACKEND", "")
if gdk_backend == "x11":
os.environ["PYOPENGL_PLATFORM"] = "x11"
elif gdk_backend == "wayland":
os.environ["PYOPENGL_PLATFORM"] = "egl"
def check_gtk_client() -> None:
no_gtk()
if POSIX and not OSX and not os.environ.get("GDK_BACKEND"):
set_gdk_backend()
if not os.environ.get("PYOPENGL_PLATFORM"):
set_pyopengl_platform()
check_gtk()
if not (find_spec("xpra.client.gui") and find_spec("xpra.client.gtk3")):
raise InitExit(ExitCode.FILE_NOT_FOUND, "`xpra-client-gtk3` is not installed") from None
def check_gtk() -> None:
Gtk = gi_import("Gtk")
if Gtk._version[0] > "3":
r = Gtk.init_check()
else:
r = Gtk.init_check(argv=None)[0]
if not r:
raise InitExit(ExitCode.NO_DISPLAY, "failed to initialize Gtk, no display?")
check_display()
def check_display() -> None:
from xpra.platform.gui import can_access_display
if not can_access_display(): # pragma: no cover
raise InitExit(ExitCode.NO_DISPLAY, "cannot access display")
def use_systemd_run(s) -> bool:
if not SYSTEMD_RUN or not POSIX or OSX:
return False # pragma: no cover
systemd_run = parse_bool_or("systemd-run", s)
if systemd_run in (True, False):
return systemd_run
# detect if we should use it:
if os.environ.get("SSH_TTY") or os.environ.get("SSH_CLIENT"): # pragma: no cover
# would fail
return False
if not is_systemd_pid1():
return False # pragma: no cover
# test it:
cmd = ["systemd-run", "--quiet"]
if getuid() != 0:
cmd += ["--user"]
cmd += ["--scope", "--", "true"]
proc = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=False)
try:
proc.communicate(timeout=2)
r = proc.returncode
except TimeoutExpired: # pragma: no cover
r = None
if r is None:
try:
proc.terminate()
except Exception:
pass
try:
proc.communicate(timeout=1)
except TimeoutExpired: # pragma: no cover
r = None
return r == 0
def verify_gir():
try:
from gi import repository
assert repository
except ImportError as e:
raise InitExit(ExitCode.FAILURE, f"the python gobject introspection bindings are missing: \n{e}")
def run_mode(script_file: str, cmdline, error_cb, options, args, full_mode: str, defaults) -> ExitValue:
mode_parts = full_mode.split(",")
mode = MODE_ALIAS.get(mode_parts[0], mode_parts[0])
# configure default logging handler:
if POSIX and getuid() == options.uid == 0 and mode not in (
"proxy", "autostart", "showconfig", "setup-ssl", "show-ssl",
) and not NO_ROOT_WARNING:
warn("\nWarning: running as root\n")
display_is_remote = isdisplaytype(args, "ssh", "tcp", "ssl", "vsock", "quic")
if mode.startswith("shadow") and WIN32 and not envbool("XPRA_PAEXEC_WRAP", False):
# are we started from a non-interactive context?
from xpra.platform.win32.gui import get_desktop_name
if get_desktop_name() is None:
argv = list(cmdline)
exe = argv[0]
if argv[0].endswith("Xpra_cmd.exe"):
# we have to use the "interactive" version:
argv[0] = exe.split("Xpra_cmd.exe", 1)[0] + "Xpra.exe"
cmd = ["paexec", "-i", "1", "-s"] + argv
try:
with Popen(cmd) as p:
return p.wait()
except KeyboardInterrupt:
return 128 + signal.SIGINT
if mode in (
"seamless", "desktop", "shadow", "shadow-screen", "expand",
"upgrade", "upgrade-seamless", "upgrade-desktop",
) and not display_is_remote and options.daemon and use_systemd_run(options.systemd_run):
# make sure we run via the same interpreter,
# inject it into the command line if we have to:
argv = list(cmdline)
if argv[0].find("python") < 0:
major, minor = sys.version_info.major, sys.version_info.minor
python = which("python%i.%i" % (major, minor)) or which("python%i" % major) or which("python") or "python"
argv.insert(0, python)
return systemd_run_wrap(mode, argv, options.systemd_run_args, user=getuid() != 0)
configure_env(options.env)
configure_logging(options, mode)
if mode not in (
"showconfig", "splash", "root-size",
"list", "list-windows", "list-mdns", "mdns-gui",
"list-clients",
"list-sessions", "sessions", "displays",
"clean-displays", "clean-sockets", "clean",
"xwait", "wminfo", "wmname",
"desktop-greeter", "gui", "start-gui",
"docs", "documentation", "about", "html5",
"pinentry", "input_pass", "_dialog", "_pass",
"opengl", "opengl-probe", "opengl-test",
"autostart",
"encoding", "video",
"nvinfo", "webcam",
"keyboard", "gtk-info", "gui-info", "network-info",
"compression", "packet-encoding", "path-info",
"printing-info", "version-info", "toolbox",
"initenv", "setup-ssl", "show-ssl",
"auth", "showconfig", "showsetting",
"applications-menu", "sessions-menu",
"_proxy",
):
configure_network(options)
verify_gir()
xrd = os.environ.get("XDG_RUNTIME_DIR", "")
if mode not in ("showconfig", "splash") and POSIX and not OSX and not xrd and getuid() > 0:
xrd = "/run/user/%i" % getuid()
if os.path.exists(xrd):
warn(f"Warning: using {xrd!r} as XDG_RUNTIME_DIR")
os.environ["XDG_RUNTIME_DIR"] = xrd
else:
warn("Warning: XDG_RUNTIME_DIR is not defined")
warn(f" and {xrd!r} does not exist")
if os.path.exists("/tmp") and os.path.isdir("/tmp"):
xrd = "/tmp"
warn(f" using {xrd!r}")
os.environ["XDG_RUNTIME_DIR"] = xrd
if not mode.startswith("_audio_"):
# audio commands don't want to set the name
# (they do it later to prevent glib import conflicts)
# "attach" does it when it received the session name from the server
if mode not in (
"attach", "listen",
"seamless", "desktop", "shadow", "shadow-screen", "expand",
"proxy",
) and not mode.startswith("upgrade"):
from xpra.platform import set_name
set_name("Xpra", "Xpra %s" % mode.strip("_"))
if mode in (
"seamless", "desktop", "shadow", "shadow-screen", "expand",
"recover",
) or mode.startswith("upgrade") or mode.startswith("request-"):
options.encodings = validated_encodings(options.encodings)
try:
return do_run_mode(script_file, cmdline, error_cb, options, args, full_mode, defaults)
except ValueError as e:
info(f"{e}")
return ExitCode.UNSUPPORTED
except KeyboardInterrupt as e:
info(f"\ncaught {e!r}, exiting")
return 128 + signal.SIGINT
def is_connection_arg(mode, arg):
if POSIX and (arg.startswith(":") or arg.startswith("wayland-")):
return True
if any(arg.startswith(f"{mode}://") for mode in SOCKET_TYPES):
return True
if any(arg.startswith(f"{mode}:") for mode in SOCKET_TYPES):
return True
if any(arg.startswith(f"{mode}/") for mode in SOCKET_TYPES):
return True
return False
def is_terminal() -> bool:
from xpra.platform import is_terminal as ist
return ist()
def DotXpra(*args, **kwargs):
from xpra.platform import dotxpra
return dotxpra.DotXpra(*args, **kwargs)
def do_run_mode(script_file: str, cmdline, error_cb, options, args, full_mode: str, defaults) -> ExitValue:
mode_parts = full_mode.split(",", 1)
mode = MODE_ALIAS.get(mode_parts[0], mode_parts[0])
display_is_remote = isdisplaytype(args, "ssh", "tcp", "ssl", "vsock", "quic")
if args and mode in ("seamless", "desktop", "monitor"):
# all args that aren't specifying a connection will be interpreted as a start-child command:
# ie: "xpra" "start" "xterm"
# ie: "xpra" "start-desktop" "ssh://host/" "fluxbox"
commands = []
connargs = []
for arg in tuple(args):
if is_connection_arg(mode, arg):
# keep this one
connargs.append(arg)
else:
commands.append(arg)
if commands:
args = connargs
# figure out if we also auto-enable:
# * --exit-with-children:
if not any(x.startswith("--exit-with-children") or x == "--no-exit-with-children" for x in cmdline):
options.exit_with_children = True
# * --attach if we have a real display:
# but not if attach was specified on the command line
# and not if we have html=open
html_open = (options.html or "").lower() not in (list(ALL_BOOLEAN_OPTIONS) + ["auto", "none", None])
if not html_open and not any(x.startswith("--attach") or x == "--no-attach" for x in cmdline):
options.attach = OSX or WIN32 or any(os.environ.get(x) for x in (
"DISPLAY", "WAYLAND_DISPLAY", "SSH_CONNECTION",
))
for command in commands:
options.start_child.append(command)
if mode in ("seamless", "desktop", "monitor", "expand", "shadow", "shadow-screen"):
if display_is_remote:
# ie: "xpra start ssh://USER@HOST:SSHPORT/DISPLAY --start-child=xterm"
return run_remote_server(script_file, cmdline, error_cb, options, args, mode, defaults)
elif args and str_to_bool(options.attach, False):
# maybe the server is already running,
# and we don't need to bother trying to start it:
try:
display = pick_display(error_cb, options, args, cmdline)
except Exception:
pass
else:
dotxpra = DotXpra(options.socket_dir, options.socket_dirs)
display_name = display.get("display_name")
if display_name:
state = dotxpra.get_display_state(display_name)
if state == SocketState.LIVE:
get_logger().info(f"existing live display found on {display_name}, attaching")
# we're connecting locally, so no need for these:
options.csc_modules = ["none"]
options.video_decoders = ["none"]
return do_run_mode(script_file, cmdline, error_cb, options, args, "attach", defaults)
if mode in (
"seamless", "desktop", "monitor", "expand", "shadow", "shadow-screen",
"upgrade", "upgrade-seamless", "upgrade-desktop",
"proxy",
):
return run_server(script_file, cmdline, error_cb, options, args, full_mode, defaults)
if mode in (
"attach", "listen", "detach",
"screenshot", "version", "info", "id",
"control", "_monitor", "shell", "print",
"qrcode",
"show-menu", "show-about", "show-session-info",
"connect-test",
) or mode.startswith("request-"):
return run_client(script_file, cmdline, error_cb, options, args, mode)
if mode in ("stop", "exit"):
no_gtk()
return run_stopexit(mode, error_cb, options, args, cmdline)
if mode == "top":
no_gtk()
return run_top(error_cb, options, args, cmdline)
if mode == "list":
no_gtk()
return run_list(error_cb, options, args)
if mode == "list-windows":
no_gtk()
return run_list_windows(error_cb, options, args)
if mode == "list-clients":
no_gtk()
return run_list_clients(error_cb, options, args)
if mode == "list-mdns":
no_gtk()
return run_list_mdns(error_cb, args)
if mode == "mdns-gui":
check_gtk_client()
return run_mdns_gui(options)
if mode == "list-sessions":
no_gtk()
return run_list_sessions(args, options)
if mode == "sessions":
check_gtk_client()
return run_sessions_gui(options)
if mode == "displays":
no_gtk()
return run_displays(options, args)
if mode == "clean-displays":
no_gtk()
return run_clean_displays(options, args)
if mode == "clean-sockets":
no_gtk()
return run_clean_sockets(options, args)
if mode == "clean":
no_gtk()
return run_clean(options, args)
if mode == "recover":
return run_recover(script_file, cmdline, error_cb, options, args, defaults)
if mode == "xwait":
no_gtk()
return run_xwait(args)
if mode == "wminfo":
no_gtk()
return run_wminfo(args)
if mode == "wmname":
no_gtk()
return run_wmname(args)
if mode == "desktop-greeter":
check_gtk_client()
return run_desktop_greeter()
if mode == "launcher":
check_gtk_client()
from xpra.client.gtk3.launcher import main as launcher_main
return launcher_main(["xpra"] + args)
if mode == "gui":
try:
check_gtk_client()
except InitExit as e:
# the user likely called `xpra` from a non GUI session
if is_terminal():
stderr_print("Error: cannot show the xpra gui")
stderr_print(f" {e}")
stderr_print(" you must use this subcommand from a desktop environment")
stderr_print(" from a terminal session you can try `xpra list`, `xpra showconfig`, etc")
return 1
raise
from xpra.gtk.dialogs import gui
return gui.main(cmdline)
if mode == "start-gui":
check_gtk_client()
from xpra.gtk.dialogs import start_gui
return start_gui.main(options)
if mode == "bug-report":
check_gtk_client()
from xpra.gtk.dialogs import bug_report
return bug_report.main(["xpra"] + args)
if mode == "session-info":
return run_session_info(error_cb, options, args, cmdline)
if mode in ("docs", "documentation"):
return run_docs()
if mode == "about":
try:
check_gtk_client()
except InitExit:
if is_terminal():
from xpra.util.version import XPRA_VERSION
from xpra.gtk.dialogs import about
from xpra.scripts.config import get_build_info
stderr_print(f"Xpra {XPRA_VERSION}")
stderr_print()
for line in get_build_info():
stderr_print(line)
stderr_print()
stderr_print("Main authors:")
for author in about.MAIN_AUTHORS:
stderr_print(f"- {author}")
stderr_print()
stderr_print("License: GPL2+")
stderr_print("run `xpra license` to see the full license")
stderr_print()
stderr_print("For more information, see:")
stderr_print(f"{about.SITE_URL}")
return 0
raise
from xpra.gtk.dialogs import about
return about.main()
if mode == "license":
from xpra.gtk.dialogs.about import load_license
stderr_print(load_license())
return 0
if mode == "html5":
return run_html5()
if mode == "_proxy" or mode.startswith("_proxy_"):
nox()
return run_proxy(error_cb, options, script_file, cmdline, args, mode, defaults)
if mode in ("_audio_record", "_audio_play", "_audio_query"):
if not has_audio_support():
error_cb("no audio support!")
from xpra.audio.wrapper import run_audio
return run_audio(mode, error_cb, options, args)
if mode == "pinentry":
check_gtk_client()
from xpra.scripts.pinentry import run_pinentry
return run_pinentry(args)
if mode == "input_pass":
check_gtk_client()
from xpra.scripts.pinentry import input_pass
password = input_pass((args + ["password"])[0])
return len(password) > 0
if mode == "_dialog":
check_gtk_client()
return run_dialog(args)
if mode == "_pass":
check_gtk_client()
return run_pass(args)
if mode == "send-file":
check_gtk()
return run_send_file(args)
if mode == "splash":
check_gtk()
return run_splash(args)
if mode == "opengl":
return run_glcheck(options)
if mode == "opengl-probe":
check_gtk_client()
return run_glprobe(options)
if mode == "opengl-test":
check_gtk_client()
return run_glprobe(options, True)
if mode == "example":
check_gtk_client()
return run_example(args)
if mode == "autostart":
return run_autostart(script_file, args)
if mode == "encoding":
from xpra.codecs import loader
return loader.main([script_file] + args)
if mode in ("applications-menu", "sessions-menu"):
from xpra.server.menu_provider import MenuProvider
if mode == "applications-menu":
data = MenuProvider().get_menu_data(remove_icons=True)
else:
data = MenuProvider().get_desktop_sessions(remove_icons=True)
if not data:
print("no menu data available")
return ExitCode.FAILURE
print_nested_dict(data)
return ExitCode.OK
if mode == "video":
from xpra.codecs import video
return video.main()
if mode == "nvinfo":
from xpra.codecs.nvidia import util
return util.main()
if mode == "webcam":
check_gtk()
from xpra.gtk.dialogs import show_webcam
return show_webcam.main()
if mode == "keyboard":
from xpra.platform import keyboard
return keyboard.main()
if mode == "root-size":
from xpra.gtk.util import get_root_size
sys.stdout.write("%ix%i\n" % get_root_size((0, 0)))
return ExitCode.OK
if mode == "gtk-info":
check_gtk()
from xpra.gtk import info
return info.main()
if mode == "gui-info":
check_gtk()
from xpra.platform import gui as platform_gui
return platform_gui.main()
if mode == "network-info":
from xpra.net import net_util
return net_util.main()
if mode == "compression":
from xpra.net import compression
return compression.main()
if mode == "packet-encoding":
from xpra.net import packet_encoding
return packet_encoding.main()
if mode == "path-info":
from xpra.platform import paths
return paths.main()
if mode == "printing-info":
from xpra.platform import printing
return printing.main(args)
if mode == "version-info":
from xpra.scripts import version
return version.main()
if mode == "toolbox":
check_gtk_client()
from xpra.gtk.dialogs import toolbox
return toolbox.main(args)
if mode == "initenv":
if not POSIX:
raise InitExit(ExitCode.UNSUPPORTED, "initenv is not supported on this OS")
from xpra.server.util import xpra_runner_shell_script, write_runner_shell_scripts
script = xpra_runner_shell_script(script_file, os.getcwd())
write_runner_shell_scripts(script, False)
return ExitCode.OK
if mode == "setup-ssl":
return setup_ssl(options, args, cmdline)
if mode == "show-ssl":
return show_ssl(options, args, cmdline)
if mode == "auth":
return run_auth(options, args)
if mode == "configure":
from xpra.gtk.configure.main import main
return main(args)
if mode == "showconfig":
return run_showconfig(options, args)
if mode == "showsetting":
return run_showsetting(args)
# unknown subcommand:
if mode != "help":
print(f"Invalid subcommand {mode!r}")
print("Usage:")
if not POSIX or OSX:
print("(this xpra installation does not support starting local servers)")
cmd = os.path.basename(script_file)
for x in get_usage():
print(f"\t{cmd} {x}")
print()
print("see 'man xpra' or 'xpra --help' for more details")
return 1
def find_session_by_name(opts, session_name: str) -> str:
from xpra.platform.paths import get_nodock_command
dotxpra = DotXpra(opts.socket_dir, opts.socket_dirs)
socket_paths = dotxpra.socket_paths(check_uid=getuid(), matching_state=SocketState.LIVE)
if not socket_paths:
return ""
id_sessions = {}
for socket_path in socket_paths:
cmd = get_nodock_command() + ["id", f"socket://{socket_path}"]
proc = Popen(cmd, stdout=PIPE, stderr=PIPE)
id_sessions[socket_path] = proc
now = monotonic()
while any(proc.poll() is None for proc in id_sessions.values()) and monotonic() - now < 10:
time.sleep(0.5)
session_uuid_to_path = {}
for socket_path, proc in id_sessions.items():
if proc.poll() == 0:
out, err = proc.communicate()
d = {}
for line in bytestostr(out or err).splitlines():
try:
k, v = line.split("=", 1)
d[k] = v
except ValueError:
continue
name = d.get("session-name")
uuid = d.get("uuid")
if name == session_name and uuid:
session_uuid_to_path[uuid] = socket_path
if not session_uuid_to_path:
return ""
if len(session_uuid_to_path) > 1:
raise InitException(f"more than one session found matching {session_name!r}")
socket_path = tuple(session_uuid_to_path.values())[0]
return f"socket://{socket_path}"
def display_desc_to_uri(display_desc: dict[str, Any]) -> str:
dtype = display_desc.get("type")
if not dtype:
raise InitException("missing display type")
uri = f"{dtype}://"
username = display_desc.get("username")
if username is not None:
uri += username
password = display_desc.get("password")
if password is not None:
uri += ":" + password
if username is not None or password is not None:
uri += "@"
if dtype in ("ssh", "tcp", "ssl", "ws", "wss", "quic"):
# TODO: re-add 'proxy_host' arguments here
host = display_desc.get("host")
if not host:
raise InitException("missing host from display parameters")
uri += host
port = display_desc.get("port")
if port and port != DEFAULT_PORTS.get(dtype):
uri += f":{port:d}"
elif dtype == "vsock":
cid, iport = display_desc["vsock"]
uri += f"{cid}:{iport}"
else:
raise NotImplementedError(f"{dtype} is not implemented yet")
uri += "/" + display_desc_to_display_path(display_desc)
return uri
def display_desc_to_display_path(display_desc: dict[str, Any]) -> str:
uri = ""
display = display_desc.get("display")
if display:
uri += display.lstrip(":")
options_str = display_desc.get("options_str")
if options_str:
uri += f"?{options_str}"
return uri
def pick_vnc_display(error_cb, opts, extra_args) -> dict[str, Any]:
if extra_args and len(extra_args) == 1:
try:
display = extra_args[0].lstrip(":")
display_no = int(display)
except (ValueError, TypeError):
pass
else:
return {
"display": f":{display_no}",
"display_name": display,
"host": "localhost",
"port": 5900 + display_no,
"local": True,
"type": "tcp",
}
error_cb("cannot find vnc displays yet")
return {}
def pick_display(error_cb, opts, extra_args, cmdline=()):
if len(extra_args) == 1 and extra_args[0].startswith("vnc"):
# can't identify vnc displays with xpra sockets
# try the first N standard vnc ports:
# (we could use threads to port scan more quickly)
N = 100
from xpra.net.socket_util import socket_connect
for i in range(N):
if not os.path.exists(f"{X11_SOCKET_DIR}/X{i}"):
# no X11 socket, assume no VNC server
continue
port = 5900 + i
sock = socket_connect("localhost", port, timeout=0.1)
if sock:
return {
"type": "vnc",
"local": True,
"host": "localhost",
"port": port,
"display": f":{i}",
"display_name": f":{i}",
}
# if not, then fall through and hope that the xpra server supports vnc:
dotxpra = DotXpra(opts.socket_dir, opts.socket_dirs)
return do_pick_display(dotxpra, error_cb, opts, extra_args, cmdline)
def do_pick_display(dotxpra, error_cb, opts, extra_args, cmdline=()):
if not extra_args:
# Pick a default server
dir_servers = dotxpra.socket_details()
try:
sockdir, display, sockpath = single_display_match(dir_servers, error_cb)
except Exception: