Mercurial > libervia-backend
annotate libervia/cli/cmd_call.py @ 4242:8acf46ed7f36
frontends: remote control implementation:
This is the frontends common part of remote control implementation. It handle the creation
of WebRTC session, and management of inputs. For now the reception use freedesktop.org
Desktop portal, and works mostly with Wayland based Desktop Environments.
rel 436
author | Goffi <goffi@goffi.org> |
---|---|
date | Sat, 11 May 2024 13:52:43 +0200 |
parents | 79c8a70e1813 |
children | 0d7bb4df2343 |
rev | line source |
---|---|
4143 | 1 #!/usr/bin/env python3 |
2 | |
3 | |
4 # Libervia CLI | |
5 # Copyright (C) 2009-2021 Jérôme Poisson (goffi@goffi.org) | |
6 | |
7 # This program is free software: you can redistribute it and/or modify | |
8 # it under the terms of the GNU Affero General Public License as published by | |
9 # the Free Software Foundation, either version 3 of the License, or | |
10 # (at your option) any later version. | |
11 | |
12 # This program is distributed in the hope that it will be useful, | |
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 # GNU Affero General Public License for more details. | |
16 | |
17 # You should have received a copy of the GNU Affero General Public License | |
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 | |
20 | |
21 from functools import partial | |
4210
9218d4331bb2
cli (call): `tui` output implementation:
Goffi <goffi@goffi.org>
parents:
4206
diff
changeset
|
22 import importlib |
4143 | 23 import logging |
4233
d01b8d002619
cli (call, file), frontends: implement webRTC data channel transfer:
Goffi <goffi@goffi.org>
parents:
4210
diff
changeset
|
24 from typing import Any |
4143 | 25 |
26 | |
27 from libervia.backend.core.i18n import _ | |
28 from libervia.backend.tools.common import data_format | |
29 from libervia.cli.constants import Const as C | |
4210
9218d4331bb2
cli (call): `tui` output implementation:
Goffi <goffi@goffi.org>
parents:
4206
diff
changeset
|
30 from libervia.frontends.tools import jid |
4233
d01b8d002619
cli (call, file), frontends: implement webRTC data channel transfer:
Goffi <goffi@goffi.org>
parents:
4210
diff
changeset
|
31 from libervia.frontends.tools.webrtc_models import CallData |
4143 | 32 |
33 from . import base | |
34 | |
35 __commands__ = ["Call"] | |
36 | |
37 | |
38 class Common(base.CommandBase): | |
39 | |
4206 | 40 |
41 def __init__(self, *args, **kwargs): | |
42 super().__init__( | |
43 *args, | |
44 use_output=C.OUTPUT_CUSTOM, | |
45 extra_outputs={ | |
4210
9218d4331bb2
cli (call): `tui` output implementation:
Goffi <goffi@goffi.org>
parents:
4206
diff
changeset
|
46 #: automatically select best output for current platform |
4206 | 47 "default": self.auto_output, |
4210
9218d4331bb2
cli (call): `tui` output implementation:
Goffi <goffi@goffi.org>
parents:
4206
diff
changeset
|
48 #: simple output with GStreamer ``autovideosink`` |
9218d4331bb2
cli (call): `tui` output implementation:
Goffi <goffi@goffi.org>
parents:
4206
diff
changeset
|
49 "simple": partial(self.use_output, "simple"), |
9218d4331bb2
cli (call): `tui` output implementation:
Goffi <goffi@goffi.org>
parents:
4206
diff
changeset
|
50 #: Qt GUI |
9218d4331bb2
cli (call): `tui` output implementation:
Goffi <goffi@goffi.org>
parents:
4206
diff
changeset
|
51 "gui": partial(self.use_output, "gui"), |
9218d4331bb2
cli (call): `tui` output implementation:
Goffi <goffi@goffi.org>
parents:
4206
diff
changeset
|
52 #: experimental TUI output |
9218d4331bb2
cli (call): `tui` output implementation:
Goffi <goffi@goffi.org>
parents:
4206
diff
changeset
|
53 "tui": partial(self.use_output, "tui"), |
4206 | 54 }, |
55 **kwargs | |
56 ) | |
57 | |
4143 | 58 def add_parser_options(self): |
59 self.parser.add_argument( | |
60 "--no-ui", action="store_true", help=_("disable user interface") | |
61 ) | |
4233
d01b8d002619
cli (call, file), frontends: implement webRTC data channel transfer:
Goffi <goffi@goffi.org>
parents:
4210
diff
changeset
|
62 sources_group = self.parser.add_mutually_exclusive_group() |
d01b8d002619
cli (call, file), frontends: implement webRTC data channel transfer:
Goffi <goffi@goffi.org>
parents:
4210
diff
changeset
|
63 sources_group.add_argument( |
d01b8d002619
cli (call, file), frontends: implement webRTC data channel transfer:
Goffi <goffi@goffi.org>
parents:
4210
diff
changeset
|
64 "-s", "--sources", choices=['auto', 'test'], default='auto', |
d01b8d002619
cli (call, file), frontends: implement webRTC data channel transfer:
Goffi <goffi@goffi.org>
parents:
4210
diff
changeset
|
65 help='Well-known sources to use (default: "auto").' |
d01b8d002619
cli (call, file), frontends: implement webRTC data channel transfer:
Goffi <goffi@goffi.org>
parents:
4210
diff
changeset
|
66 ) |
d01b8d002619
cli (call, file), frontends: implement webRTC data channel transfer:
Goffi <goffi@goffi.org>
parents:
4210
diff
changeset
|
67 |
d01b8d002619
cli (call, file), frontends: implement webRTC data channel transfer:
Goffi <goffi@goffi.org>
parents:
4210
diff
changeset
|
68 def get_call_data_kw(self) -> dict[str, Any]: |
d01b8d002619
cli (call, file), frontends: implement webRTC data channel transfer:
Goffi <goffi@goffi.org>
parents:
4210
diff
changeset
|
69 """Get relevant keyword arguments for CallData""" |
d01b8d002619
cli (call, file), frontends: implement webRTC data channel transfer:
Goffi <goffi@goffi.org>
parents:
4210
diff
changeset
|
70 kwargs: dict[str, Any] = {} |
d01b8d002619
cli (call, file), frontends: implement webRTC data channel transfer:
Goffi <goffi@goffi.org>
parents:
4210
diff
changeset
|
71 if self.args.sources == "test": |
4240
79c8a70e1813
backend, frontend: prepare remote control:
Goffi <goffi@goffi.org>
parents:
4233
diff
changeset
|
72 from libervia.frontends.tools.webrtc_models import SourcesTest |
79c8a70e1813
backend, frontend: prepare remote control:
Goffi <goffi@goffi.org>
parents:
4233
diff
changeset
|
73 kwargs["sources_data"] = SourcesTest() |
4233
d01b8d002619
cli (call, file), frontends: implement webRTC data channel transfer:
Goffi <goffi@goffi.org>
parents:
4210
diff
changeset
|
74 return kwargs |
d01b8d002619
cli (call, file), frontends: implement webRTC data channel transfer:
Goffi <goffi@goffi.org>
parents:
4210
diff
changeset
|
75 |
4143 | 76 |
77 async def start(self): | |
78 root_logger = logging.getLogger() | |
79 # we don't want any formatting for messages from webrtc | |
80 for handler in root_logger.handlers: | |
81 handler.setFormatter(None) | |
82 if self.verbosity == 0: | |
83 root_logger.setLevel(logging.ERROR) | |
84 if self.verbosity >= 1: | |
85 root_logger.setLevel(logging.WARNING) | |
86 if self.verbosity >= 2: | |
87 root_logger.setLevel(logging.DEBUG) | |
88 | |
4210
9218d4331bb2
cli (call): `tui` output implementation:
Goffi <goffi@goffi.org>
parents:
4206
diff
changeset
|
89 async def auto_output(self, call_data: CallData) -> None: |
9218d4331bb2
cli (call): `tui` output implementation:
Goffi <goffi@goffi.org>
parents:
4206
diff
changeset
|
90 """Make a guess on the best output to use on current platform""" |
9218d4331bb2
cli (call): `tui` output implementation:
Goffi <goffi@goffi.org>
parents:
4206
diff
changeset
|
91 try: |
9218d4331bb2
cli (call): `tui` output implementation:
Goffi <goffi@goffi.org>
parents:
4206
diff
changeset
|
92 from .call_gui import AVCallUI |
9218d4331bb2
cli (call): `tui` output implementation:
Goffi <goffi@goffi.org>
parents:
4206
diff
changeset
|
93 except ImportError: |
9218d4331bb2
cli (call): `tui` output implementation:
Goffi <goffi@goffi.org>
parents:
4206
diff
changeset
|
94 # we can't import GUI, we may have missing modules |
9218d4331bb2
cli (call): `tui` output implementation:
Goffi <goffi@goffi.org>
parents:
4206
diff
changeset
|
95 await self.use_output("simple", call_data) |
4206 | 96 else: |
4210
9218d4331bb2
cli (call): `tui` output implementation:
Goffi <goffi@goffi.org>
parents:
4206
diff
changeset
|
97 if AVCallUI.can_run(): |
9218d4331bb2
cli (call): `tui` output implementation:
Goffi <goffi@goffi.org>
parents:
4206
diff
changeset
|
98 await self.use_output("gui", call_data) |
9218d4331bb2
cli (call): `tui` output implementation:
Goffi <goffi@goffi.org>
parents:
4206
diff
changeset
|
99 else: |
9218d4331bb2
cli (call): `tui` output implementation:
Goffi <goffi@goffi.org>
parents:
4206
diff
changeset
|
100 await self.use_output("simple", call_data) |
4206 | 101 |
4210
9218d4331bb2
cli (call): `tui` output implementation:
Goffi <goffi@goffi.org>
parents:
4206
diff
changeset
|
102 async def use_output(self, output_type, call_data: CallData): |
4206 | 103 try: |
4210
9218d4331bb2
cli (call): `tui` output implementation:
Goffi <goffi@goffi.org>
parents:
4206
diff
changeset
|
104 AVCall_module = importlib.import_module(f"libervia.cli.call_{output_type}") |
9218d4331bb2
cli (call): `tui` output implementation:
Goffi <goffi@goffi.org>
parents:
4206
diff
changeset
|
105 AVCallUI = AVCall_module.AVCallUI |
4206 | 106 except Exception as e: |
4210
9218d4331bb2
cli (call): `tui` output implementation:
Goffi <goffi@goffi.org>
parents:
4206
diff
changeset
|
107 self.disp(f"Error starting {output_type.upper()} UI: {e}", error=True) |
4206 | 108 self.host.quit(C.EXIT_ERROR) |
4210
9218d4331bb2
cli (call): `tui` output implementation:
Goffi <goffi@goffi.org>
parents:
4206
diff
changeset
|
109 else: |
9218d4331bb2
cli (call): `tui` output implementation:
Goffi <goffi@goffi.org>
parents:
4206
diff
changeset
|
110 try: |
9218d4331bb2
cli (call): `tui` output implementation:
Goffi <goffi@goffi.org>
parents:
4206
diff
changeset
|
111 await AVCallUI.run(self, call_data) |
9218d4331bb2
cli (call): `tui` output implementation:
Goffi <goffi@goffi.org>
parents:
4206
diff
changeset
|
112 except Exception as e: |
9218d4331bb2
cli (call): `tui` output implementation:
Goffi <goffi@goffi.org>
parents:
4206
diff
changeset
|
113 self.disp(f"Error running {output_type.upper()} UI: {e}", error=True) |
9218d4331bb2
cli (call): `tui` output implementation:
Goffi <goffi@goffi.org>
parents:
4206
diff
changeset
|
114 self.host.quit(C.EXIT_ERROR) |
4206 | 115 |
4143 | 116 |
117 class Make(Common): | |
118 def __init__(self, host): | |
119 super().__init__( | |
120 host, | |
121 "make", | |
122 use_verbose=True, | |
123 help=_("start a call"), | |
124 ) | |
125 | |
126 def add_parser_options(self): | |
127 super().add_parser_options() | |
128 self.parser.add_argument( | |
129 "entity", | |
130 metavar="JID", | |
131 help=_("JIDs of entity to call"), | |
132 ) | |
133 | |
134 async def start(self): | |
135 await super().start() | |
4206 | 136 await super().output(CallData( |
137 callee=jid.JID(self.args.entity), | |
4233
d01b8d002619
cli (call, file), frontends: implement webRTC data channel transfer:
Goffi <goffi@goffi.org>
parents:
4210
diff
changeset
|
138 kwargs=self.get_call_data_kw() |
4206 | 139 )) |
4143 | 140 |
141 | |
142 class Receive(Common): | |
143 def __init__(self, host): | |
144 super().__init__( | |
145 host, | |
146 "receive", | |
147 use_verbose=True, | |
148 help=_("wait for a call"), | |
149 ) | |
150 | |
151 def add_parser_options(self): | |
152 super().add_parser_options() | |
153 auto_accept_group = self.parser.add_mutually_exclusive_group() | |
154 auto_accept_group.add_argument( | |
155 "-a", | |
156 "--auto-accept", | |
157 action="append", | |
158 metavar="JID", | |
159 default=[], | |
160 help=_("automatically accept call from this jid (can be used multiple times)") | |
161 ) | |
162 auto_accept_group.add_argument( | |
163 "--auto-accept-all", | |
164 action="store_true", | |
165 help=_("automatically accept call from anybody") | |
166 ) | |
167 | |
168 async def on_action_new( | |
169 self, action_data_s: str, action_id: str, security_limit: int, profile: str | |
170 ) -> None: | |
171 if profile != self.profile: | |
172 return | |
173 action_data = data_format.deserialise(action_data_s) | |
174 if action_data.get("type") != C.META_TYPE_CALL: | |
175 return | |
176 peer_jid = jid.JID(action_data["from_jid"]).bare | |
177 caller = peer_jid.bare | |
178 if ( | |
179 not self.args.auto_accept_all | |
180 and caller not in self.args.auto_accept | |
181 and not await self.host.confirm( | |
182 _("📞 Incoming call from {caller}, do you accept?").format( | |
183 caller=caller | |
184 ) | |
185 ) | |
186 ): | |
187 await self.host.bridge.action_launch( | |
188 action_id, data_format.serialise({"cancelled": True}), profile | |
189 ) | |
190 return | |
191 | |
192 self.disp(_("✅ Incoming call from {caller} accepted.").format(caller=caller)) | |
193 | |
4206 | 194 await super().output(CallData( |
195 callee=peer_jid, | |
196 sid=action_data["session_id"], | |
4233
d01b8d002619
cli (call, file), frontends: implement webRTC data channel transfer:
Goffi <goffi@goffi.org>
parents:
4210
diff
changeset
|
197 action_id=action_id, |
d01b8d002619
cli (call, file), frontends: implement webRTC data channel transfer:
Goffi <goffi@goffi.org>
parents:
4210
diff
changeset
|
198 kwargs=self.get_call_data_kw() |
4206 | 199 )) |
4143 | 200 |
201 async def start(self): | |
202 await super().start() | |
203 self.host.bridge.register_signal("action_new", self.on_action_new, "core") | |
204 | |
205 | |
206 class Call(base.CommandBase): | |
207 subcommands = (Make, Receive) | |
208 | |
209 def __init__(self, host): | |
210 super().__init__(host, "call", use_profile=False, help=_("A/V calls and related")) |