Line data Source code
1 : /*
2 : * Copyright (C) 2004-2026 Savoir-faire Linux Inc.
3 : *
4 : * This program is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 3 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * This program is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 : */
17 :
18 : #include <cassert>
19 :
20 : #pragma GCC diagnostic push
21 : #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
22 : #include <yaml-cpp/yaml.h>
23 : #pragma GCC diagnostic pop
24 :
25 : #include "manager.h"
26 : #include "media_const.h"
27 : #include "client/jami_signal.h"
28 : #include "logger.h"
29 : #include "video_device_monitor.h"
30 :
31 : namespace jami {
32 : namespace video {
33 :
34 : constexpr const char* const VideoDeviceMonitor::CONFIG_LABEL;
35 :
36 : using std::map;
37 : using std::string;
38 : using std::vector;
39 :
40 : vector<string>
41 1 : VideoDeviceMonitor::getDeviceList() const
42 : {
43 1 : std::lock_guard l(lock_);
44 1 : vector<string> ids;
45 1 : ids.reserve(devices_.size());
46 2 : for (const auto& dev : devices_) {
47 1 : if (dev.name != DEVICE_DESKTOP)
48 0 : ids.emplace_back(dev.getDeviceId());
49 : }
50 2 : return ids;
51 1 : }
52 :
53 : libjami::VideoCapabilities
54 0 : VideoDeviceMonitor::getCapabilities(const string& id) const
55 : {
56 0 : std::lock_guard l(lock_);
57 0 : const auto iter = findDeviceById(id);
58 0 : if (iter == devices_.end())
59 0 : return libjami::VideoCapabilities();
60 :
61 0 : return iter->getCapabilities();
62 0 : }
63 :
64 : VideoSettings
65 0 : VideoDeviceMonitor::getSettings(const string& id)
66 : {
67 0 : std::lock_guard l(lock_);
68 :
69 0 : const auto prefIter = findPreferencesById(id);
70 0 : if (prefIter == preferences_.end())
71 0 : return VideoSettings();
72 :
73 0 : return *prefIter;
74 0 : }
75 :
76 : void
77 0 : VideoDeviceMonitor::applySettings(const string& id, const VideoSettings& settings)
78 : {
79 0 : std::lock_guard l(lock_);
80 0 : const auto iter = findDeviceById(id);
81 :
82 0 : if (iter == devices_.end())
83 0 : return;
84 :
85 0 : iter->applySettings(settings);
86 0 : auto it = findPreferencesById(settings.unique_id);
87 0 : if (it != preferences_.end())
88 0 : (*it) = settings;
89 0 : }
90 :
91 : string
92 301 : VideoDeviceMonitor::getDefaultDevice() const
93 : {
94 301 : std::lock_guard l(lock_);
95 301 : const auto it = findDeviceById(defaultDevice_);
96 602 : if (it == std::end(devices_) || it->getDeviceId() == DEVICE_DESKTOP)
97 301 : return {};
98 0 : return it->getDeviceId();
99 301 : }
100 :
101 : std::string
102 360 : VideoDeviceMonitor::getMRLForDefaultDevice() const
103 : {
104 360 : std::lock_guard l(lock_);
105 360 : const auto it = findDeviceById(defaultDevice_);
106 720 : if (it == std::end(devices_) || it->getDeviceId() == DEVICE_DESKTOP)
107 360 : return {};
108 0 : static const std::string sep = libjami::Media::VideoProtocolPrefix::SEPARATOR;
109 0 : return libjami::Media::VideoProtocolPrefix::CAMERA + sep + it->getDeviceId();
110 360 : }
111 :
112 : bool
113 0 : VideoDeviceMonitor::setDefaultDevice(const std::string& id)
114 : {
115 0 : std::lock_guard l(lock_);
116 0 : const auto itDev = findDeviceById(id);
117 0 : if (itDev != devices_.end()) {
118 0 : if (defaultDevice_ == itDev->getDeviceId())
119 0 : return false;
120 0 : defaultDevice_ = itDev->getDeviceId();
121 :
122 : // place it at the begining of the prefs
123 0 : auto itPref = findPreferencesById(itDev->getDeviceId());
124 0 : if (itPref != preferences_.end()) {
125 0 : std::ranges::rotate(preferences_.begin(), itPref, itPref + 1);
126 : } else {
127 0 : preferences_.insert(preferences_.begin(), itDev->getSettings());
128 : }
129 0 : return true;
130 : }
131 0 : return false;
132 0 : }
133 :
134 : void
135 0 : VideoDeviceMonitor::setDeviceOrientation(const std::string& id, int angle)
136 : {
137 0 : std::lock_guard l(lock_);
138 0 : const auto itd = findDeviceById(id);
139 0 : if (itd != devices_.cend()) {
140 0 : itd->setOrientation(angle);
141 : } else {
142 0 : JAMI_WARNING("Unable to find device {} to set orientation {}", id, angle);
143 : }
144 0 : }
145 :
146 : DeviceParams
147 301 : VideoDeviceMonitor::getDeviceParams(const std::string& id) const
148 : {
149 301 : std::lock_guard l(lock_);
150 301 : const auto itd = findDeviceById(id);
151 301 : if (itd == devices_.cend())
152 0 : return DeviceParams();
153 301 : return itd->getDeviceParams();
154 301 : }
155 :
156 : static void
157 32 : giveUniqueName(VideoDevice& dev, const vector<VideoDevice>& devices)
158 : {
159 32 : std::string suffix;
160 32 : uint64_t number = 2;
161 32 : auto unique = true;
162 0 : for (;; unique = static_cast<bool>(++number)) {
163 32 : for (const auto& s : devices)
164 0 : unique &= static_cast<bool>(s.name.compare(dev.name + suffix));
165 32 : if (unique)
166 64 : return (void) (dev.name += suffix);
167 0 : suffix = " (" + std::to_string(number) + ")";
168 0 : }
169 32 : }
170 :
171 : static void
172 32 : notify()
173 : {
174 32 : if (Manager::initialized) {
175 32 : emitSignal<libjami::VideoSignal::DeviceEvent>();
176 : }
177 32 : }
178 :
179 : bool
180 32 : VideoDeviceMonitor::addDevice(const string& id, const std::vector<std::map<std::string, std::string>>& devInfo)
181 : {
182 : try {
183 32 : std::lock_guard l(lock_);
184 32 : if (findDeviceById(id) != devices_.end())
185 0 : return false;
186 :
187 : // instantiate a new unique device
188 32 : VideoDevice dev {id, devInfo};
189 :
190 32 : if (dev.getChannelList().empty())
191 0 : return false;
192 :
193 32 : giveUniqueName(dev, devices_);
194 :
195 : // restore its preferences if any, or store the defaults
196 32 : auto it = findPreferencesById(id);
197 32 : if (it != preferences_.end()) {
198 0 : dev.applySettings(*it);
199 : } else {
200 32 : dev.applySettings(dev.getDefaultSettings());
201 32 : preferences_.emplace_back(dev.getSettings());
202 : }
203 :
204 : // in case there is no default device on a fresh run
205 32 : if (defaultDevice_.empty() && id != DEVICE_DESKTOP)
206 0 : defaultDevice_ = dev.getDeviceId();
207 :
208 32 : devices_.emplace_back(std::move(dev));
209 32 : } catch (const std::exception& e) {
210 0 : JAMI_ERROR("Failed to add device {}: {}", id, e.what());
211 0 : return false;
212 0 : }
213 32 : notify();
214 32 : return true;
215 : }
216 :
217 : void
218 0 : VideoDeviceMonitor::removeDevice(const string& id)
219 : {
220 : {
221 0 : std::lock_guard l(lock_);
222 0 : const auto it = findDeviceById(id);
223 0 : if (it == devices_.end())
224 0 : return;
225 :
226 0 : devices_.erase(it);
227 0 : if (defaultDevice_.find(id) != std::string::npos) {
228 0 : defaultDevice_.clear();
229 0 : for (const auto& dev : devices_)
230 0 : if (dev.name != DEVICE_DESKTOP) {
231 0 : defaultDevice_ = dev.getDeviceId();
232 0 : break;
233 : }
234 : }
235 0 : }
236 0 : notify();
237 : }
238 :
239 : vector<VideoDevice>::iterator
240 90 : VideoDeviceMonitor::findDeviceById(const string& id)
241 : {
242 90 : for (auto it = devices_.begin(); it != devices_.end(); ++it)
243 58 : if (it->getDeviceId().find(id) != std::string::npos)
244 58 : return it;
245 32 : return devices_.end();
246 : }
247 :
248 : vector<VideoDevice>::const_iterator
249 962 : VideoDeviceMonitor::findDeviceById(const string& id) const
250 : {
251 962 : for (auto it = devices_.cbegin(); it != devices_.cend(); ++it)
252 962 : if (it->getDeviceId().find(id) != std::string::npos)
253 962 : return it;
254 0 : return devices_.end();
255 : }
256 :
257 : vector<VideoSettings>::iterator
258 61 : VideoDeviceMonitor::findPreferencesById(const string& id)
259 : {
260 61 : for (auto it = preferences_.begin(); it != preferences_.end(); ++it)
261 29 : if (it->unique_id.find(id) != std::string::npos)
262 29 : return it;
263 32 : return preferences_.end();
264 : }
265 :
266 : void
267 29 : VideoDeviceMonitor::overwritePreferences(const VideoSettings& settings)
268 : {
269 29 : auto it = findPreferencesById(settings.unique_id);
270 29 : if (it != preferences_.end())
271 29 : preferences_.erase(it);
272 29 : preferences_.emplace_back(settings);
273 29 : }
274 :
275 : void
276 2449 : VideoDeviceMonitor::serialize(YAML::Emitter& out) const
277 : {
278 2449 : std::lock_guard l(lock_);
279 2449 : out << YAML::Key << "devices" << YAML::Value << preferences_;
280 2449 : }
281 :
282 : void
283 29 : VideoDeviceMonitor::unserialize(const YAML::Node& in)
284 : {
285 29 : std::lock_guard l(lock_);
286 29 : const auto& node = in[CONFIG_LABEL];
287 :
288 : /* load the device list from the "video" YAML section */
289 29 : const auto& devices = node["devices"];
290 58 : for (const auto& dev : devices) {
291 29 : VideoSettings pref = dev.as<VideoSettings>();
292 29 : if (pref.unique_id.empty())
293 0 : continue; // discard malformed section
294 29 : overwritePreferences(pref);
295 29 : auto itd = findDeviceById(pref.unique_id);
296 29 : if (itd != devices_.end())
297 29 : itd->applySettings(pref);
298 58 : }
299 :
300 : // Restore the default device if present, or select the first one
301 29 : const string prefId = preferences_.empty() ? "" : preferences_[0].unique_id;
302 29 : const auto devIter = findDeviceById(prefId);
303 29 : if (devIter != devices_.end() && prefId != DEVICE_DESKTOP) {
304 0 : defaultDevice_ = devIter->getDeviceId();
305 : } else {
306 29 : defaultDevice_.clear();
307 58 : for (const auto& dev : devices_)
308 29 : if (dev.name != DEVICE_DESKTOP) {
309 0 : defaultDevice_ = dev.getDeviceId();
310 0 : break;
311 : }
312 : }
313 29 : }
314 :
315 : } // namespace video
316 : } // namespace jami
|