1 // Copyright (c) 2020 Michael Fabian Dirks <info@xaymar.com>
2 //
3 // Permission is hereby granted, free of charge, to any person obtaining a copy
4 // of this software and associated documentation files (the "Software"), to deal
5 // in the Software without restriction, including without limitation the rights
6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 // copies of the Software, and to permit persons to whom the Software is
8 // furnished to do so, subject to the following conditions:
9 //
10 // The above copyright notice and this permission notice shall be included in all
11 // copies or substantial portions of the Software.
12 //
13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 // SOFTWARE.
20
21 #include "ui-updater.hpp"
22 #include "common.hpp"
23
24 #define ST_PREFIX "<ui::updater> "
25 #define D_LOG_ERROR(...) DLOG_ERROR(ST_PREFIX __VA_ARGS__)
26 #define D_LOG_WARNING(...) DLOG_WARNING(ST_PREFIX __VA_ARGS__)
27 #define D_LOG_INFO(...) DLOG_INFO(ST_PREFIX __VA_ARGS__)
28 #ifdef _DEBUG
29 #define D_LOG_DEBUG(...) DLOG_DEBUG(ST_PREFIX __VA_ARGS__)
30 #else
31 #define D_LOG_DEBUG(...)
32 #endif
33
34 #define D_I18N_MENU_CHECKFORUPDATES "UI.Updater.Menu.CheckForUpdates"
35 #define D_I18N_MENU_CHECKFORUPDATES_AUTOMATICALLY "UI.Updater.Menu.CheckForUpdates.Automatically"
36 #define D_I18N_MENU_CHANNEL "UI.Updater.Menu.Channel"
37 #define D_I18N_MENU_CHANNEL_RELEASE "UI.Updater.Menu.Channel.Release"
38 #define D_I18N_MENU_CHANNEL_TESTING "UI.Updater.Menu.Channel.Testing"
39 #define D_I18N_DIALOG_TITLE "UI.Updater.Dialog.Title"
40 #define D_I18N_GITHUBPERMISSION_TITLE "UI.Updater.GitHubPermission.Title"
41 #define D_I18N_GITHUBPERMISSION_TEXT "UI.Updater.GitHubPermission.Text"
42
updater_dialog()43 streamfx::ui::updater_dialog::updater_dialog() : QDialog(reinterpret_cast<QWidget*>(obs_frontend_get_main_window()))
44 {
45 setupUi(this);
46 setWindowFlag(Qt::WindowContextHelpButtonHint, false);
47 setWindowFlag(Qt::WindowMinimizeButtonHint, false);
48 setWindowFlag(Qt::WindowMaximizeButtonHint, false);
49
50 connect(ok, &QPushButton::clicked, this, &streamfx::ui::updater_dialog::on_ok);
51 connect(cancel, &QPushButton::clicked, this, &streamfx::ui::updater_dialog::on_cancel);
52 }
53
~updater_dialog()54 streamfx::ui::updater_dialog::~updater_dialog() {}
55
show(streamfx::update_info current,streamfx::update_info update)56 void streamfx::ui::updater_dialog::show(streamfx::update_info current, streamfx::update_info update)
57 {
58 {
59 std::vector<char> buf;
60 if (current.version_type) {
61 buf.resize(static_cast<size_t>(snprintf(nullptr, 0, "%" PRIu16 ".%" PRIu16 ".%" PRIu16 "%.1s%" PRIu16,
62 current.version_major, current.version_minor, current.version_patch,
63 ¤t.version_type, current.version_index))
64 + 1);
65 snprintf(buf.data(), buf.size(), "%" PRIu16 ".%" PRIu16 ".%" PRIu16 "%.1s%" PRIu16, current.version_major,
66 current.version_minor, current.version_patch, ¤t.version_type, current.version_index);
67 } else {
68 buf.resize(
69 static_cast<size_t>(snprintf(nullptr, 0, "%" PRIu16 ".%" PRIu16 ".%" PRIu16, current.version_major,
70 current.version_minor, current.version_patch))
71 + 1);
72 snprintf(buf.data(), buf.size(), "%" PRIu16 ".%" PRIu16 ".%" PRIu16, current.version_major,
73 current.version_minor, current.version_patch);
74 }
75 currentVersion->setText(QString::fromUtf8(buf.data()));
76 }
77
78 {
79 std::vector<char> buf;
80 if (update.version_type) {
81 buf.resize(static_cast<size_t>(snprintf(nullptr, 0, "%" PRIu16 ".%" PRIu16 ".%" PRIu16 "%.1s%" PRIu16,
82 update.version_major, update.version_minor, update.version_patch,
83 &update.version_type, update.version_index))
84 + 1);
85 snprintf(buf.data(), buf.size(), "%" PRIu16 ".%" PRIu16 ".%" PRIu16 "%.1s%" PRIu16, update.version_major,
86 update.version_minor, update.version_patch, &update.version_type, update.version_index);
87 } else {
88 buf.resize(static_cast<size_t>(snprintf(nullptr, 0, "%" PRIu16 ".%" PRIu16 ".%" PRIu16,
89 update.version_major, update.version_minor, update.version_patch))
90 + 1);
91 snprintf(buf.data(), buf.size(), "%" PRIu16 ".%" PRIu16 ".%" PRIu16, update.version_major,
92 update.version_minor, update.version_patch);
93 }
94 latestVersion->setText(QString::fromUtf8(buf.data()));
95
96 {
97 std::vector<char> buf2;
98 buf2.resize(static_cast<size_t>(snprintf(nullptr, 0, D_TRANSLATE(D_I18N_DIALOG_TITLE), buf.data())) + 1);
99 snprintf(buf2.data(), buf2.size(), D_TRANSLATE(D_I18N_DIALOG_TITLE), buf.data());
100 setWindowTitle(QString::fromUtf8(buf2.data()));
101 }
102 }
103
104 _update_url = QUrl(QString::fromStdString(update.url));
105
106 this->setModal(true);
107 QDialog::show();
108 }
109
hide()110 void streamfx::ui::updater_dialog::hide()
111 {
112 QDialog::hide();
113 this->setModal(false);
114 }
115
on_ok()116 void streamfx::ui::updater_dialog::on_ok()
117 {
118 QDesktopServices::openUrl(_update_url);
119 hide();
120 }
121
on_cancel()122 void streamfx::ui::updater_dialog::on_cancel()
123 {
124 hide();
125 }
126
updater(QMenu * menu)127 streamfx::ui::updater::updater(QMenu* menu)
128 : _updater(), _dialog(nullptr), _gdpr(nullptr), _cfu(nullptr), _cfu_auto(nullptr), _channel(nullptr),
129 _channel_menu(nullptr), _channel_stable(nullptr), _channel_preview(nullptr), _channel_group(nullptr)
130 {
131 // Create dialog.
132 _dialog = new updater_dialog();
133
134 { // Create the necessary menu entries.
135 menu->addSeparator();
136
137 // Check for Updates
138 _cfu = menu->addAction(QString::fromUtf8(D_TRANSLATE(D_I18N_MENU_CHECKFORUPDATES)));
139 connect(_cfu, &QAction::triggered, this, &streamfx::ui::updater::on_cfu_triggered);
140
141 // Automatically check for Updates
142 _cfu_auto = menu->addAction(QString::fromUtf8(D_TRANSLATE(D_I18N_MENU_CHECKFORUPDATES_AUTOMATICALLY)));
143 _cfu_auto->setCheckable(true);
144 connect(_cfu_auto, &QAction::toggled, this, &streamfx::ui::updater::on_cfu_auto_toggled);
145
146 // Update Channel
147 _channel_menu = menu->addMenu(QString::fromUtf8(D_TRANSLATE(D_I18N_MENU_CHANNEL)));
148
149 _channel_stable = _channel_menu->addAction(QString::fromUtf8(D_TRANSLATE(D_I18N_MENU_CHANNEL_RELEASE)));
150 _channel_stable->setCheckable(true);
151
152 _channel_preview = _channel_menu->addAction(QString::fromUtf8(D_TRANSLATE(D_I18N_MENU_CHANNEL_TESTING)));
153 _channel_preview->setCheckable(true);
154
155 _channel_group = new QActionGroup(_channel_menu);
156 _channel_group->addAction(_channel_stable);
157 _channel_group->addAction(_channel_preview);
158 connect(_channel_group, &QActionGroup::triggered, this, &streamfx::ui::updater::on_channel_group_triggered);
159 }
160
161 // Connect internal signals.
162 connect(this, &streamfx::ui::updater::autoupdate_changed, this, &streamfx::ui::updater::on_autoupdate_changed,
163 Qt::QueuedConnection);
164 connect(this, &streamfx::ui::updater::channel_changed, this, &streamfx::ui::updater::on_channel_changed,
165 Qt::QueuedConnection);
166 connect(this, &streamfx::ui::updater::update_detected, this, &streamfx::ui::updater::on_update_detected,
167 Qt::QueuedConnection);
168 connect(this, &streamfx::ui::updater::check_active, this, &streamfx::ui::updater::on_check_active,
169 Qt::QueuedConnection);
170
171 { // Retrieve the updater object and listen to it.
172 _updater = streamfx::updater::instance();
173 _updater->events.automation_changed.add(std::bind(&streamfx::ui::updater::on_updater_automation_changed, this,
174 std::placeholders::_1, std::placeholders::_2));
175 _updater->events.channel_changed.add(std::bind(&streamfx::ui::updater::on_updater_channel_changed, this,
176 std::placeholders::_1, std::placeholders::_2));
177 _updater->events.refreshed.add(
178 std::bind(&streamfx::ui::updater::on_updater_refreshed, this, std::placeholders::_1));
179
180 // Sync with updater information.
181 emit autoupdate_changed(_updater->automation());
182 emit channel_changed(_updater->channel());
183 }
184 }
185
~updater()186 streamfx::ui::updater::~updater() {}
187
on_updater_automation_changed(streamfx::updater &,bool value)188 void streamfx::ui::updater::on_updater_automation_changed(streamfx::updater&, bool value)
189 {
190 emit autoupdate_changed(value);
191 }
192
on_updater_channel_changed(streamfx::updater &,streamfx::update_channel channel)193 void streamfx::ui::updater::on_updater_channel_changed(streamfx::updater&, streamfx::update_channel channel)
194 {
195 emit channel_changed(channel);
196 }
197
on_updater_refreshed(streamfx::updater &)198 void streamfx::ui::updater::on_updater_refreshed(streamfx::updater&)
199 {
200 emit check_active(false);
201
202 if (!_updater->have_update())
203 return;
204
205 emit update_detected();
206 }
207
obs_ready()208 void streamfx::ui::updater::obs_ready()
209 {
210 if (_updater->automation()) {
211 if (_updater->gdpr()) {
212 _updater->refresh();
213 } else {
214 create_gdpr_box();
215 _gdpr->exec();
216 }
217 }
218 }
219
on_channel_changed(streamfx::update_channel channel)220 void streamfx::ui::updater::on_channel_changed(streamfx::update_channel channel)
221 {
222 bool is_stable = channel == streamfx::update_channel::RELEASE;
223 _channel_stable->setChecked(is_stable);
224 _channel_preview->setChecked(!is_stable);
225 }
226
on_update_detected()227 void streamfx::ui::updater::on_update_detected()
228 {
229 _dialog->show(_updater->get_current_info(), _updater->get_update_info());
230 }
231
on_autoupdate_changed(bool enabled)232 void streamfx::ui::updater::on_autoupdate_changed(bool enabled)
233 {
234 _cfu_auto->setChecked(enabled);
235 }
236
on_gdpr_button(QAbstractButton * btn)237 void streamfx::ui::updater::on_gdpr_button(QAbstractButton* btn)
238 {
239 if (_gdpr->standardButton(btn) == QMessageBox::Ok) {
240 _updater->set_gdpr(true);
241 emit check_active(true);
242 _updater->refresh();
243 } else {
244 _updater->set_gdpr(false);
245 _updater->set_automation(false);
246 }
247 }
248
on_cfu_triggered(bool)249 void streamfx::ui::updater::on_cfu_triggered(bool)
250 {
251 if (!_updater->gdpr()) {
252 create_gdpr_box();
253 _gdpr->exec();
254 } else {
255 emit check_active(true);
256 _updater->refresh();
257 }
258 }
259
on_cfu_auto_toggled(bool flag)260 void streamfx::ui::updater::on_cfu_auto_toggled(bool flag)
261 {
262 _updater->set_automation(flag);
263 }
264
on_channel_group_triggered(QAction * action)265 void streamfx::ui::updater::on_channel_group_triggered(QAction* action)
266 {
267 if (action == _channel_stable) {
268 _updater->set_channel(update_channel::RELEASE);
269 } else {
270 _updater->set_channel(update_channel::TESTING);
271 }
272 }
273
instance(QMenu * menu)274 std::shared_ptr<streamfx::ui::updater> streamfx::ui::updater::instance(QMenu* menu)
275 {
276 static std::weak_ptr<streamfx::ui::updater> _instance;
277 static std::mutex _lock;
278
279 auto lock = std::lock_guard<std::mutex>(_lock);
280 if (_instance.expired() && menu) {
281 auto ptr = std::make_shared<streamfx::ui::updater>(menu);
282 _instance = ptr;
283 return ptr;
284 } else {
285 return _instance.lock();
286 }
287 }
288
on_check_active(bool active)289 void streamfx::ui::updater::on_check_active(bool active)
290 {
291 _cfu->setEnabled(!active);
292 _channel_group->setEnabled(!active);
293 _channel_preview->setEnabled(!active);
294 _channel_stable->setEnabled(!active);
295 _channel_menu->setEnabled(!active);
296 }
297
create_gdpr_box()298 void streamfx::ui::updater::create_gdpr_box()
299 {
300 if (_gdpr) {
301 _gdpr->deleteLater();
302 _gdpr = nullptr;
303 }
304
305 // Create GitHub message box.
306 _gdpr = new QMessageBox(reinterpret_cast<QWidget*>(obs_frontend_get_main_window()));
307 _gdpr->setWindowTitle(QString::fromUtf8(D_TRANSLATE(D_I18N_GITHUBPERMISSION_TITLE)));
308 _gdpr->setTextFormat(Qt::TextFormat::RichText);
309 _gdpr->setText(QString::fromUtf8(D_TRANSLATE(D_I18N_GITHUBPERMISSION_TEXT)));
310 _gdpr->setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
311 connect(_gdpr, &QMessageBox::buttonClicked, this, &streamfx::ui::updater::on_gdpr_button);
312 }
313