提交 c1c84e91 编写于 作者: J jp9000

UI: Add front-end auto-updater

上级 33e44940
......@@ -14,6 +14,8 @@ add_subdirectory(obs-frontend-api)
project(obs)
set(ENABLE_WIN_UPDATER FALSE CACHE BOOL "Enable the windows updater")
if(DEFINED QTDIR${_lib_suffix})
list(APPEND CMAKE_PREFIX_PATH "${QTDIR${_lib_suffix}}")
elseif(DEFINED QTDIR)
......@@ -52,9 +54,25 @@ include_directories(${LIBCURL_INCLUDE_DIRS})
add_definitions(${LIBCURL_DEFINITIONS})
if(WIN32)
include_directories(${OBS_JANSSON_INCLUDE_DIRS})
set(obs_PLATFORM_SOURCES
platform-windows.cpp
win-update/update-window.cpp
win-update/win-update.cpp
win-update/win-update-helpers.cpp
obs.rc)
set(obs_PLATFORM_HEADERS
win-update/update-window.hpp
win-update/win-update.hpp
win-update/win-update-helpers.hpp)
set(obs_PLATFORM_LIBRARIES
crypt32
${OBS_JANSSON_IMPORT})
if(ENABLE_WIN_UPDATER)
add_definitions(-DENABLE_WIN_UPDATER)
endif()
elseif(APPLE)
set(obs_PLATFORM_SOURCES
platform-osx.mm)
......@@ -132,6 +150,7 @@ set(obs_SOURCES
qt-wrappers.cpp)
set(obs_HEADERS
${obs_PLATFORM_HEADERS}
obs-app.hpp
platform.hpp
window-main.hpp
......@@ -184,6 +203,7 @@ set(obs_UI
forms/OBSBasicSettings.ui
forms/OBSBasicSourceSelect.ui
forms/OBSBasicInteraction.ui
forms/OBSUpdate.ui
forms/OBSRemux.ui)
set(obs_QRC
......
......@@ -62,6 +62,20 @@ ReplayBuffer="Replay Buffer"
Import="Import"
Export="Export"
# updater
Updater.Title="New update available"
Updater.Text="There is a new update available:"
Updater.UpdateNow="Update Now"
Updater.RemindMeLater="Remind me Later"
Updater.Skip="Skip Version"
Updater.Running.Title="Program currently active"
Updater.Running.Text="Outputs are currently active, please shut down any active outputs before attempting to update"
Updater.NoUpdatesAvailable.Title="No updates available"
Updater.NoUpdatesAvailable.Text="No updates are currently available"
Updater.FailedToLaunch="Failed to launch updater"
Updater.GameCaptureActive.Title="Game capture active"
Updater.GameCaptureActive.Text="Game capture hook library is currently in use. Please close any games/programs being captured (or restart windows) and try again."
# quick transitions
QuickTransitions.SwapScenes="Swap Preview/Output Scenes After Transitioning"
QuickTransitions.SwapScenesTT="Swaps the preview and output scenes after transitioning (if the output's original scene still exists).\nThis will not undo any changes that may have been made to the output's original scene."
......@@ -407,6 +421,7 @@ Basic.Settings.Confirm="You have unsaved changes. Save changes?"
Basic.Settings.General="General"
Basic.Settings.General.Theme="Theme"
Basic.Settings.General.Language="Language"
Basic.Settings.General.EnableAutoUpdates="Automatically check for updates on startup"
Basic.Settings.General.WarnBeforeStartingStream="Show confirmation dialog when starting streams"
Basic.Settings.General.WarnBeforeStoppingStream="Show confirmation dialog when stopping streams"
Basic.Settings.General.Projectors="Projectors"
......
......@@ -168,6 +168,9 @@
<property name="labelAlignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="topMargin">
<number>2</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
......@@ -194,6 +197,32 @@
<item row="1" column="1">
<widget class="QComboBox" name="theme"/>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="enableAutoUpdates">
<property name="text">
<string>Basic.Settings.General.EnableAutoUpdates</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>170</width>
<height>5</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
......@@ -203,6 +232,9 @@
<string>Basic.Settings.Output</string>
</property>
<layout class="QFormLayout" name="formLayout_2">
<property name="topMargin">
<number>2</number>
</property>
<item row="0" column="0">
<spacer name="horizontalSpacer_5">
<property name="orientation">
......@@ -211,7 +243,7 @@
<property name="sizeHint" stdset="0">
<size>
<width>170</width>
<height>11</height>
<height>5</height>
</size>
</property>
</spacer>
......
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>OBSUpdate</class>
<widget class="QDialog" name="OBSUpdate">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>611</width>
<height>526</height>
</rect>
</property>
<property name="windowTitle">
<string>Updater.Title</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Updater.Text</string>
</property>
</widget>
</item>
<item>
<widget class="QTextEdit" name="text">
<property name="readOnly">
<bool>true</bool>
</property>
<property name="html">
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="yes">
<property name="text">
<string>Updater.UpdateNow</string>
</property>
<property name="default">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="no">
<property name="text">
<string>Updater.RemindMeLater</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="skip">
<property name="text">
<string>Updater.Skip</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
......@@ -363,6 +363,8 @@ bool OBSApp::InitGlobalConfigDefaults()
config_set_default_uint(globalConfig, "General", "MaxLogs", 10);
config_set_default_string(globalConfig, "General", "ProcessPriority",
"Normal");
config_set_default_bool(globalConfig, "General", "EnableAutoUpdates",
true);
#if _WIN32
config_set_default_string(globalConfig, "Video", "Renderer",
......@@ -448,7 +450,13 @@ static bool MakeUserDirs()
return false;
if (!do_mkdir(path))
return false;
if (GetConfigPath(path, sizeof(path), "obs-studio/updates") <= 0)
return false;
if (!do_mkdir(path))
return false;
#endif
if (GetConfigPath(path, sizeof(path), "obs-studio/plugin_config") <= 0)
return false;
if (!do_mkdir(path))
......
#include "update-window.hpp"
#include "obs-app.hpp"
OBSUpdate::OBSUpdate(QWidget *parent, bool manualUpdate, const QString &text)
: QDialog (parent, Qt::WindowSystemMenuHint |
Qt::WindowTitleHint |
Qt::WindowCloseButtonHint),
ui (new Ui_OBSUpdate)
{
ui->setupUi(this);
ui->text->setHtml(text);
if (manualUpdate) {
delete ui->skip;
ui->skip = nullptr;
ui->no->setText(QTStr("Cancel"));
}
}
void OBSUpdate::on_yes_clicked()
{
done(OBSUpdate::Yes);
}
void OBSUpdate::on_no_clicked()
{
done(OBSUpdate::No);
}
void OBSUpdate::on_skip_clicked()
{
done(OBSUpdate::Skip);
}
void OBSUpdate::accept()
{
done(OBSUpdate::Yes);
}
void OBSUpdate::reject()
{
done(OBSUpdate::No);
}
#pragma once
#include <QDialog>
#include <memory>
#include "ui_OBSUpdate.h"
class OBSUpdate : public QDialog {
Q_OBJECT
public:
enum ReturnVal {
No,
Yes,
Skip
};
OBSUpdate(QWidget *parent, bool manualUpdate, const QString &text);
public slots:
void on_yes_clicked();
void on_no_clicked();
void on_skip_clicked();
virtual void accept() override;
virtual void reject() override;
private:
std::unique_ptr<Ui_OBSUpdate> ui;
};
#include "win-update-helpers.hpp"
void FreeProvider(HCRYPTPROV prov)
{
CryptReleaseContext(prov, 0);
}
void FreeHash(HCRYPTHASH hash)
{
CryptDestroyHash(hash);
}
void FreeKey(HCRYPTKEY key)
{
CryptDestroyKey(key);
}
std::string vstrprintf(const char *format, va_list args)
{
if (!format)
return std::string();
std::string str;
int size = (int)vsnprintf(nullptr, 0, format, args);
str.resize(size);
vsnprintf(&str[0], size, format, args);
return str;
}
std::string strprintf(const char *format, ...)
{
std::string str;
va_list args;
va_start(args, format);
str = vstrprintf(format, args);
va_end(args);
return str;
}
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <Wincrypt.h>
#include <jansson.h>
#include <cstdint>
#include <string>
/* ------------------------------------------------------------------------ */
template<typename T, void freefunc(T)> class CustomHandle {
T handle;
public:
inline CustomHandle() : handle(0) {}
inline CustomHandle(T in) : handle(in) {}
inline ~CustomHandle()
{
if (handle)
freefunc(handle);
}
inline T *operator&() {return &handle;}
inline operator T() const {return handle;}
inline T get() const {return handle;}
inline CustomHandle<T, freefunc> &operator=(T in)
{
if (handle)
freefunc(handle);
handle = in;
return *this;
}
inline bool operator!() const {return !handle;}
};
void FreeProvider(HCRYPTPROV prov);
void FreeHash(HCRYPTHASH hash);
void FreeKey(HCRYPTKEY key);
using CryptProvider = CustomHandle<HCRYPTPROV, FreeProvider>;
using CryptHash = CustomHandle<HCRYPTHASH, FreeHash>;
using CryptKey = CustomHandle<HCRYPTKEY, FreeKey>;
/* ------------------------------------------------------------------------ */
template<typename T> class LocalPtr {
T *ptr = nullptr;
public:
inline ~LocalPtr()
{
if (ptr)
LocalFree(ptr);
}
inline T **operator&() {return &ptr;}
inline operator T() const {return ptr;}
inline T *get() const {return ptr;}
inline bool operator!() const {return !ptr;}
inline T *operator->() {return ptr;}
};
/* ------------------------------------------------------------------------ */
class Json {
json_t *json;
public:
inline Json() : json(nullptr) {}
explicit inline Json(json_t *json_) : json(json_) {}
inline Json(const Json &from) : json(json_incref(from.json)) {}
inline Json(Json &&from) : json(from.json) {from.json = nullptr;}
inline ~Json() {
if (json)
json_decref(json);
}
inline Json &operator=(json_t *json_)
{
if (json)
json_decref(json);
json = json_;
return *this;
}
inline Json &operator=(const Json &from)
{
if (json)
json_decref(json);
json = json_incref(from.json);
return *this;
}
inline Json &operator=(Json &&from)
{
if (json)
json_decref(json);
json = from.json;
from.json = nullptr;
return *this;
}
inline operator json_t *() const {return json;}
inline bool operator!() const {return !json;}
inline const char *GetString(const char *name,
const char *def = nullptr) const
{
json_t *obj(json_object_get(json, name));
if (!obj)
return def;
return json_string_value(obj);
}
inline int64_t GetInt(const char *name, int def = 0) const
{
json_t *obj(json_object_get(json, name));
if (!obj)
return def;
return json_integer_value(obj);
}
inline json_t *GetObject(const char *name) const
{
return json_object_get(json, name);
}
inline json_t *get() const {return json;}
};
/* ------------------------------------------------------------------------ */
std::string vstrprintf(const char *format, va_list args);
std::string strprintf(const char *format, ...);
此差异已折叠。
#pragma once
#include <QThread>
#include <QString>
class AutoUpdateThread : public QThread {
Q_OBJECT
bool manualUpdate;
bool user_confirmed = false;
virtual void run() override;
void info(const QString &title, const QString &text);
int queryUpdate(bool manualUpdate, const char *text_utf8);
private slots:
void infoMsg(const QString &title, const QString &text);
int queryUpdateSlot(bool manualUpdate, const QString &text);
public:
AutoUpdateThread(bool manualUpdate_) : manualUpdate(manualUpdate_) {}
};
......@@ -52,6 +52,10 @@
#include "volume-control.hpp"
#include "remote-text.hpp"
#if defined(_WIN32) && defined(ENABLE_WIN_UPDATER)
#include "win-update/win-update.hpp"
#endif
#include "ui_OBSBasic.h"
#include <fstream>
......@@ -1585,6 +1589,9 @@ void OBSBasic::ClearHotkeys()
OBSBasic::~OBSBasic()
{
if (updateCheckThread && updateCheckThread->isRunning())
updateCheckThread->wait();
delete programOptions;
delete program;
......@@ -2123,10 +2130,14 @@ void trigger_sparkle_update();
void OBSBasic::TimedCheckForUpdates()
{
if (!config_get_bool(App()->GlobalConfig(), "General",
"EnableAutoUpdates"))
return;
#ifdef UPDATE_SPARKLE
init_sparkle_updater(config_get_bool(App()->GlobalConfig(), "General",
"UpdateToUndeployed"));
#else
#elif ENABLE_WIN_UPDATER
long long lastUpdate = config_get_int(App()->GlobalConfig(), "General",
"LastUpdateCheck");
uint32_t lastVersion = config_get_int(App()->GlobalConfig(), "General",
......@@ -2142,27 +2153,21 @@ void OBSBasic::TimedCheckForUpdates()
long long secs = t - lastUpdate;
if (secs > UPDATE_CHECK_INTERVAL)
CheckForUpdates();
CheckForUpdates(false);
#endif
}
void OBSBasic::CheckForUpdates()
void OBSBasic::CheckForUpdates(bool manualUpdate)
{
#ifdef UPDATE_SPARKLE
trigger_sparkle_update();
#else
#elif ENABLE_WIN_UPDATER
ui->actionCheckForUpdates->setEnabled(false);
if (updateCheckThread) {
updateCheckThread->wait();
delete updateCheckThread;
}
if (updateCheckThread && updateCheckThread->isRunning())
return;
RemoteTextThread *thread = new RemoteTextThread(
"https://obsproject.com/obs2_update/basic.json");
updateCheckThread = thread;
connect(thread, &RemoteTextThread::Result,
this, &OBSBasic::updateFileFinished);
updateCheckThread = new AutoUpdateThread(manualUpdate);
updateCheckThread->start();
#endif
}
......@@ -2175,57 +2180,9 @@ void OBSBasic::CheckForUpdates()
#define VERSION_ENTRY "other"
#endif
void OBSBasic::updateFileFinished(const QString &text, const QString &error)
void OBSBasic::updateCheckFinished()
{
ui->actionCheckForUpdates->setEnabled(true);
if (text.isEmpty()) {
blog(LOG_WARNING, "Update check failed: %s", QT_TO_UTF8(error));
return;
}
obs_data_t *returnData = obs_data_create_from_json(QT_TO_UTF8(text));
obs_data_t *versionData = obs_data_get_obj(returnData, VERSION_ENTRY);
const char *description = obs_data_get_string(returnData,
"description");
const char *download = obs_data_get_string(versionData, "download");
if (returnData && versionData && description && download) {
long major = obs_data_get_int(versionData, "major");
long minor = obs_data_get_int(versionData, "minor");
long patch = obs_data_get_int(versionData, "patch");
long version = MAKE_SEMANTIC_VERSION(major, minor, patch);
blog(LOG_INFO, "Update check: last known remote version "
"is %ld.%ld.%ld",
major, minor, patch);
if (version > LIBOBS_API_VER) {
QString str = QTStr("UpdateAvailable.Text");
QMessageBox messageBox(this);
str = str.arg(QString::number(major),
QString::number(minor),
QString::number(patch),
download);
messageBox.setWindowTitle(QTStr("UpdateAvailable"));
messageBox.setTextFormat(Qt::RichText);
messageBox.setText(str);
messageBox.setInformativeText(QT_UTF8(description));
messageBox.exec();
long long t = (long long)time(nullptr);
config_set_int(App()->GlobalConfig(), "General",
"LastUpdateCheck", t);
config_save_safe(App()->GlobalConfig(), "tmp", nullptr);
}
} else {
blog(LOG_WARNING, "Bad JSON file received from server");
}
obs_data_release(versionData);
obs_data_release(returnData);
}
void OBSBasic::DuplicateSelectedScene()
......@@ -3730,7 +3687,7 @@ void OBSBasic::on_actionViewCurrentLog_triggered()
void OBSBasic::on_actionCheckForUpdates_triggered()
{
CheckForUpdates();
CheckForUpdates(true);
}
void OBSBasic::logUploadFinished(const QString &text, const QString &error)
......
......@@ -200,7 +200,7 @@ private:
bool QueryRemoveSource(obs_source_t *source);
void TimedCheckForUpdates();
void CheckForUpdates();
void CheckForUpdates(bool manualUpdate);
void GetFPSCommon(uint32_t &num, uint32_t &den) const;
void GetFPSInteger(uint32_t &num, uint32_t &den) const;
......@@ -595,7 +595,7 @@ private slots:
void logUploadFinished(const QString &text, const QString &error);
void updateFileFinished(const QString &text, const QString &error);
void updateCheckFinished();
void AddSourceFromAction();
......
......@@ -273,6 +273,7 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent)
HookWidget(ui->language, COMBO_CHANGED, GENERAL_CHANGED);
HookWidget(ui->theme, COMBO_CHANGED, GENERAL_CHANGED);
HookWidget(ui->enableAutoUpdates, CHECK_CHANGED, GENERAL_CHANGED);
HookWidget(ui->warnBeforeStreamStart,CHECK_CHANGED, GENERAL_CHANGED);
HookWidget(ui->warnBeforeStreamStop, CHECK_CHANGED, GENERAL_CHANGED);
HookWidget(ui->hideProjectorCursor, CHECK_CHANGED, GENERAL_CHANGED);
......@@ -896,6 +897,10 @@ void OBSBasicSettings::LoadGeneralSettings()
LoadLanguageList();
LoadThemeList();
bool enableAutoUpdates = config_get_bool(GetGlobalConfig(),
"General", "EnableAutoUpdates");
ui->enableAutoUpdates->setChecked(enableAutoUpdates);
bool recordWhenStreaming = config_get_bool(GetGlobalConfig(),
"BasicWindow", "RecordWhenStreaming");
ui->recordWhenStreaming->setChecked(recordWhenStreaming);
......@@ -2351,6 +2356,10 @@ void OBSBasicSettings::SaveGeneralSettings()
App()->SetTheme(theme);
}
if (WidgetChanged(ui->enableAutoUpdates))
config_set_bool(GetGlobalConfig(), "General",
"EnableAutoUpdates",
ui->enableAutoUpdates->isChecked());
if (WidgetChanged(ui->snappingEnabled))
config_set_bool(GetGlobalConfig(), "BasicWindow",
"SnappingEnabled",
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册