Browse Source

UI: Add intro startup page (windows)

Allows the ability to show a web page via CEF to the users on startup to
present and announce new features.
jp9000 7 years ago
parent
commit
a032bcc798

+ 4 - 0
UI/CMakeLists.txt

@@ -55,6 +55,8 @@ include_directories(${FFMPEG_INCLUDE_DIRS})
 include_directories(SYSTEM "obs-frontend-api")
 include_directories(SYSTEM "${CMAKE_SOURCE_DIR}/libobs")
 include_directories(SYSTEM "${CMAKE_SOURCE_DIR}/deps/libff")
+include_directories(SYSTEM "${CMAKE_SOURCE_DIR}/deps/json11")
+include_directories(SYSTEM "${CMAKE_SOURCE_DIR}/plugins/obs-browser/panel")
 
 find_package(Libcurl REQUIRED)
 include_directories(${LIBCURL_INCLUDE_DIRS})
@@ -127,6 +129,7 @@ endif()
 set(obs_SOURCES
 	${obs_PLATFORM_SOURCES}
 	${obs_libffutil_SOURCES}
+	../deps/json11/json11.cpp
 	obs-app.cpp
 	api-interface.cpp
 	window-basic-main.cpp
@@ -178,6 +181,7 @@ set(obs_SOURCES
 set(obs_HEADERS
 	${obs_PLATFORM_HEADERS}
 	${obs_libffutil_HEADERS}
+	../deps/json11/json11.hpp
 	obs-app.hpp
 	platform.hpp
 	window-main.hpp

+ 1 - 0
UI/obs-app.cpp

@@ -367,6 +367,7 @@ bool OBSApp::InitGlobalConfigDefaults()
 	config_set_default_string(globalConfig, "General", "Language",
 			DEFAULT_LANG);
 	config_set_default_uint(globalConfig, "General", "MaxLogs", 10);
+	config_set_default_int(globalConfig, "General", "InfoIncrement", -1);
 	config_set_default_string(globalConfig, "General", "ProcessPriority",
 			"Normal");
 	config_set_default_bool(globalConfig, "General", "EnableAutoUpdates",

+ 116 - 1
UI/win-update/win-update.cpp

@@ -27,11 +27,15 @@ using namespace std;
 #define WIN_MANIFEST_URL "https://obsproject.com/update_studio/manifest.json"
 #endif
 
+#ifndef WIN_WHATSNEW_URL
+#define WIN_WHATSNEW_URL "https://obsproject.com/update_studio/whatsnew.json"
+#endif
+
 #ifndef WIN_UPDATER_URL
 #define WIN_UPDATER_URL "https://obsproject.com/update_studio/updater.exe"
 #endif
 
-static HCRYPTPROV provider = 0;
+static __declspec(thread) HCRYPTPROV provider = 0;
 
 #pragma pack(push, r1, 1)
 
@@ -780,3 +784,114 @@ try {
 } catch (string text) {
 	blog(LOG_WARNING, "%s: %s", __FUNCTION__, text.c_str());
 }
+
+/* ------------------------------------------------------------------------ */
+
+void WhatsNewInfoThread::run()
+try {
+	long           responseCode;
+	vector<string> extraHeaders;
+	string         text;
+	string         error;
+	string         signature;
+	CryptProvider  localProvider;
+	BYTE           whatsnewHash[BLAKE2_HASH_LENGTH];
+	bool           success;
+
+	BPtr<char> whatsnewPath = GetConfigPathPtr(
+			"obs-studio\\updates\\whatsnew.json");
+
+	/* ----------------------------------- *
+	 * create signature provider           */
+
+	if (!CryptAcquireContext(&localProvider,
+	                         nullptr,
+	                         MS_ENH_RSA_AES_PROV,
+	                         PROV_RSA_AES,
+	                         CRYPT_VERIFYCONTEXT))
+		throw strprintf("CryptAcquireContext failed: %lu",
+				GetLastError());
+
+	provider = localProvider;
+
+	/* ----------------------------------- *
+	 * avoid downloading json again        */
+
+	if (CalculateFileHash(whatsnewPath, whatsnewHash)) {
+		char hashString[BLAKE2_HASH_STR_LENGTH];
+		HashToString(whatsnewHash, hashString);
+
+		string header = "If-None-Match: ";
+		header += hashString;
+		extraHeaders.push_back(move(header));
+	}
+
+	/* ----------------------------------- *
+	 * get current install GUID            */
+
+	const char *pguid = config_get_string(GetGlobalConfig(),
+			"General", "InstallGUID");
+	string guid;
+	if (pguid)
+		guid = pguid;
+
+	if (guid.empty()) {
+		GenerateGUID(guid);
+
+		if (!guid.empty())
+			config_set_string(GetGlobalConfig(),
+					"General", "InstallGUID",
+					guid.c_str());
+	}
+
+	if (!guid.empty()) {
+		string header = "X-OBS2-GUID: ";
+		header += guid;
+		extraHeaders.push_back(move(header));
+	}
+
+	/* ----------------------------------- *
+	 * get json from server                */
+
+	success = GetRemoteFile(WIN_WHATSNEW_URL, text, error, &responseCode,
+			nullptr, nullptr, extraHeaders, &signature);
+
+	if (!success || (responseCode != 200 && responseCode != 304)) {
+		if (responseCode == 404)
+			return;
+
+		throw strprintf("Failed to fetch whatsnew file: %s",
+				error.c_str());
+	}
+
+	/* ----------------------------------- *
+	 * verify file signature               */
+
+	if (responseCode == 200) {
+		success = CheckDataSignature(text, "whatsnew",
+				signature.data(), signature.size());
+		if (!success)
+			throw string("Invalid whatsnew signature");
+	}
+
+	/* ----------------------------------- *
+	 * write or load json                  */
+
+	if (responseCode == 200) {
+		if (!QuickWriteFile(whatsnewPath, text.data(), text.size()))
+			throw strprintf("Could not write file '%s'",
+					whatsnewPath.Get());
+	} else {
+		if (!QuickReadFile(whatsnewPath, text))
+			throw strprintf("Could not read file '%s'",
+					whatsnewPath.Get());
+	}
+
+	/* ----------------------------------- *
+	 * success                             */
+
+	emit Result(QString::fromUtf8(text.c_str()));
+
+} catch (string text) {
+	blog(LOG_WARNING, "%s: %s", __FUNCTION__, text.c_str());
+}

+ 12 - 0
UI/win-update/win-update.hpp

@@ -21,3 +21,15 @@ private slots:
 public:
 	AutoUpdateThread(bool manualUpdate_) : manualUpdate(manualUpdate_) {}
 };
+
+class WhatsNewInfoThread : public QThread {
+	Q_OBJECT
+
+	virtual void run() override;
+
+signals:
+	void Result(const QString &text);
+
+public:
+	inline WhatsNewInfoThread() {}
+};

+ 119 - 0
UI/window-basic-main.cpp

@@ -68,8 +68,19 @@
 #include <QScreen>
 #include <QWindow>
 
+#ifdef _WIN32
+#include <browser-panel.hpp>
+#endif
+
+#include <json11.hpp>
+
+using namespace json11;
 using namespace std;
 
+#ifdef _WIN32
+static CREATE_BROWSER_WIDGET_PROC create_browser_widget = nullptr;
+#endif
+
 namespace {
 
 template <typename OBSRef>
@@ -1429,6 +1440,10 @@ void OBSBasic::OBSInit()
 	blog(LOG_INFO, "---------------------------------");
 	obs_post_load_modules();
 
+#ifdef _WIN32
+	create_browser_widget = obs_browser_init_panel();
+#endif
+
 	CheckForSimpleModeX264Fallback();
 
 	blog(LOG_INFO, STARTUP_SEPARATOR);
@@ -1649,6 +1664,21 @@ void OBSBasic::OnFirstLoad()
 {
 	if (api)
 		api->on_event(OBS_FRONTEND_EVENT_FINISHED_LOADING);
+
+#ifdef _WIN32
+	/* Attempt to load init screen if available */
+	if (create_browser_widget) {
+		WhatsNewInfoThread *wnit = new WhatsNewInfoThread();
+		if (wnit) {
+			connect(wnit, &WhatsNewInfoThread::Result,
+					this, &OBSBasic::ReceivedIntroJson);
+		}
+		if (wnit) {
+			introCheckThread = wnit;
+			introCheckThread->start();
+		}
+	}
+#endif
 }
 
 void OBSBasic::DeferredLoad(const QString &file, int requeueCount)
@@ -1666,6 +1696,93 @@ void OBSBasic::DeferredLoad(const QString &file, int requeueCount)
 	OnFirstLoad();
 }
 
+/* shows a "what's new" page on startup of new versions using CEF */
+void OBSBasic::ReceivedIntroJson(const QString &text)
+{
+#ifdef _WIN32
+	std::string err;
+	Json json = Json::parse(QT_TO_UTF8(text), err);
+	if (!err.empty())
+		return;
+
+	std::string info_url;
+	int info_increment = -1;
+
+	/* check to see if there's an info page for this version */
+	const Json::array &items = json.array_items();
+	for (const Json &item : items) {
+		const std::string &version = item["version"].string_value();
+		const std::string &url = item["url"].string_value();
+		int increment = item["increment"].int_value();
+
+		int major = 0;
+		int minor = 0;
+
+		sscanf(version.c_str(), "%d.%d", &major, &minor);
+		if (major == LIBOBS_API_MAJOR_VER &&
+		    minor == LIBOBS_API_MINOR_VER) {
+			info_url = url;
+			info_increment = increment;
+		}
+	}
+
+	/* this version was not found, or no info for this version */
+	if (info_increment == -1) {
+		return;
+	}
+
+	uint32_t lastVersion = config_get_int(App()->GlobalConfig(), "General",
+			"LastVersion");
+
+	int current_version_increment = -1;
+
+	if (lastVersion < LIBOBS_API_VER) {
+		config_set_int(App()->GlobalConfig(), "General",
+				"InfoIncrement", -1);
+	} else {
+		current_version_increment = config_get_int(
+				App()->GlobalConfig(), "General",
+				"InfoIncrement");
+	}
+
+	if (info_increment <= current_version_increment) {
+		return;
+	}
+
+	config_set_int(App()->GlobalConfig(), "General",
+			"InfoIncrement", info_increment);
+
+	QDialog dlg(this);
+	dlg.setWindowTitle("What's New");
+	dlg.resize(600, 600);
+
+	QCefWidget *cefWidget = create_browser_widget(nullptr, info_url);
+	if (!cefWidget) {
+		return;
+	}
+
+	connect(cefWidget, SIGNAL(titleChanged(const QString &)),
+			&dlg, SLOT(setWindowTitle(const QString &)));
+
+	QPushButton *close = new QPushButton(QTStr("Close"));
+	connect(close, &QAbstractButton::clicked,
+			&dlg, &QDialog::accept);
+
+	QHBoxLayout *bottomLayout = new QHBoxLayout();
+	bottomLayout->addStretch();
+	bottomLayout->addWidget(close);
+	bottomLayout->addStretch();
+
+	QVBoxLayout *topLayout = new QVBoxLayout(&dlg);
+	topLayout->addWidget(cefWidget);
+	topLayout->addLayout(bottomLayout);
+
+	dlg.exec();
+#else
+	UNUSED_PARAMETER(text);
+#endif
+}
+
 void OBSBasic::UpdateMultiviewProjectorMenu()
 {
 	multiviewProjectorMenu->clear();
@@ -3389,6 +3506,8 @@ void OBSBasic::closeEvent(QCloseEvent *event)
 
 	blog(LOG_INFO, SHUTDOWN_SEPARATOR);
 
+	if (introCheckThread)
+		introCheckThread->wait();
 	if (updateCheckThread)
 		updateCheckThread->wait();
 	if (logUploadThread)

+ 3 - 0
UI/window-basic-main.hpp

@@ -139,6 +139,7 @@ private:
 	bool copyVisible = true;
 
 	QPointer<QThread> updateCheckThread;
+	QPointer<QThread> introCheckThread;
 	QPointer<QThread> logUploadThread;
 
 	QPointer<OBSBasicInteraction> interaction;
@@ -369,6 +370,8 @@ private:
 	obs_data_array_t *SaveProjectors();
 	void LoadSavedProjectors(obs_data_array_t *savedProjectors);
 
+	void ReceivedIntroJson(const QString &text);
+
 public slots:
 	void DeferSaveBegin();
 	void DeferSaveEnd();

+ 1 - 1
plugins/obs-browser

@@ -1 +1 @@
-Subproject commit f5083a1cc4294b540e9df94663eae88ab42a86fa
+Subproject commit 89aa4ee8b6c7ae12acbd90158feca1fe61dedf7d