mirror of
https://github.com/obsproject/obs-studio.git
synced 2026-10-22 22:15:21 -04:00
Merge pull request #11622 from PatTheMav/frontend-refactor
UI: Reorganize and refactor entire frontend codebase
This commit is contained in:
980 files changed
+28570
-26315
No files matched your search
@@ -39,7 +39,7 @@ runs:
|
||||
uses: ./.github/actions/check-changes
|
||||
id: checks
|
||||
with:
|
||||
checkGlob: 'UI/forms/**/*.ui'
|
||||
checkGlob: 'frontend/forms/**/*.ui'
|
||||
|
||||
- name: Validate XML 💯
|
||||
if: fromJSON(steps.checks.outputs.hasChangedFiles)
|
||||
@@ -56,6 +56,6 @@ runs:
|
||||
if (( ${#CHANGED_FILES[@]} )); then
|
||||
if [[ '${{ inputs.failCondition }}' == never ]]; then set +e; fi
|
||||
xmllint \
|
||||
--schema ${{ github.workspace }}/UI/forms/XML-Schema-Qt5.15.xsd \
|
||||
--schema ${{ github.workspace }}/frontend/forms/XML-Schema-Qt5.15.xsd \
|
||||
--noout "${CHANGED_FILES[@]}"
|
||||
fi
|
||||
@@ -201,7 +201,7 @@ build() {
|
||||
|
||||
rm -rf OBS.app
|
||||
mkdir OBS.app
|
||||
ditto UI/${config}/OBS.app OBS.app
|
||||
ditto frontend/${config}/OBS.app OBS.app
|
||||
}
|
||||
}
|
||||
popd
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
!/cmake
|
||||
!/deps
|
||||
!/docs
|
||||
!/frontend
|
||||
!/libobs*
|
||||
!/plugins
|
||||
!/shared
|
||||
|
||||
+1
-1
@@ -29,6 +29,6 @@ add_subdirectory(plugins)
|
||||
|
||||
add_subdirectory(test/test-input)
|
||||
|
||||
add_subdirectory(UI)
|
||||
add_subdirectory(frontend)
|
||||
|
||||
message_configuration()
|
||||
@@ -1,636 +0,0 @@
|
||||
#include <obs-frontend-internal.hpp>
|
||||
#include <qt-wrappers.hpp>
|
||||
#include "obs-app.hpp"
|
||||
#include "window-basic-main.hpp"
|
||||
#include "window-basic-main-outputs.hpp"
|
||||
|
||||
#include <functional>
|
||||
|
||||
using namespace std;
|
||||
|
||||
Q_DECLARE_METATYPE(OBSScene);
|
||||
Q_DECLARE_METATYPE(OBSSource);
|
||||
|
||||
template<typename T> static T GetOBSRef(QListWidgetItem *item)
|
||||
{
|
||||
return item->data(static_cast<int>(QtDataRole::OBSRef)).value<T>();
|
||||
}
|
||||
|
||||
extern volatile bool streaming_active;
|
||||
extern volatile bool recording_active;
|
||||
extern volatile bool recording_paused;
|
||||
extern volatile bool replaybuf_active;
|
||||
extern volatile bool virtualcam_active;
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
template<typename T> struct OBSStudioCallback {
|
||||
T callback;
|
||||
void *private_data;
|
||||
|
||||
inline OBSStudioCallback(T cb, void *p) : callback(cb), private_data(p) {}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
inline size_t GetCallbackIdx(vector<OBSStudioCallback<T>> &callbacks, T callback, void *private_data)
|
||||
{
|
||||
for (size_t i = 0; i < callbacks.size(); i++) {
|
||||
OBSStudioCallback<T> curCB = callbacks[i];
|
||||
if (curCB.callback == callback && curCB.private_data == private_data)
|
||||
return i;
|
||||
}
|
||||
|
||||
return (size_t)-1;
|
||||
}
|
||||
|
||||
struct OBSStudioAPI : obs_frontend_callbacks {
|
||||
OBSBasic *main;
|
||||
vector<OBSStudioCallback<obs_frontend_event_cb>> callbacks;
|
||||
vector<OBSStudioCallback<obs_frontend_save_cb>> saveCallbacks;
|
||||
vector<OBSStudioCallback<obs_frontend_save_cb>> preloadCallbacks;
|
||||
|
||||
inline OBSStudioAPI(OBSBasic *main_) : main(main_) {}
|
||||
|
||||
void *obs_frontend_get_main_window(void) override { return (void *)main; }
|
||||
|
||||
void *obs_frontend_get_main_window_handle(void) override { return (void *)main->winId(); }
|
||||
|
||||
void *obs_frontend_get_system_tray(void) override { return (void *)main->trayIcon.data(); }
|
||||
|
||||
void obs_frontend_get_scenes(struct obs_frontend_source_list *sources) override
|
||||
{
|
||||
for (int i = 0; i < main->ui->scenes->count(); i++) {
|
||||
QListWidgetItem *item = main->ui->scenes->item(i);
|
||||
OBSScene scene = GetOBSRef<OBSScene>(item);
|
||||
obs_source_t *source = obs_scene_get_source(scene);
|
||||
|
||||
if (obs_source_get_ref(source) != nullptr)
|
||||
da_push_back(sources->sources, &source);
|
||||
}
|
||||
}
|
||||
|
||||
obs_source_t *obs_frontend_get_current_scene(void) override
|
||||
{
|
||||
if (main->IsPreviewProgramMode()) {
|
||||
return obs_weak_source_get_source(main->programScene);
|
||||
} else {
|
||||
OBSSource source = main->GetCurrentSceneSource();
|
||||
return obs_source_get_ref(source);
|
||||
}
|
||||
}
|
||||
|
||||
void obs_frontend_set_current_scene(obs_source_t *scene) override
|
||||
{
|
||||
if (main->IsPreviewProgramMode()) {
|
||||
QMetaObject::invokeMethod(main, "TransitionToScene", WaitConnection(),
|
||||
Q_ARG(OBSSource, OBSSource(scene)));
|
||||
} else {
|
||||
QMetaObject::invokeMethod(main, "SetCurrentScene", WaitConnection(),
|
||||
Q_ARG(OBSSource, OBSSource(scene)), Q_ARG(bool, false));
|
||||
}
|
||||
}
|
||||
|
||||
void obs_frontend_get_transitions(struct obs_frontend_source_list *sources) override
|
||||
{
|
||||
for (int i = 0; i < main->ui->transitions->count(); i++) {
|
||||
OBSSource tr = main->ui->transitions->itemData(i).value<OBSSource>();
|
||||
|
||||
if (!tr)
|
||||
continue;
|
||||
|
||||
if (obs_source_get_ref(tr) != nullptr)
|
||||
da_push_back(sources->sources, &tr);
|
||||
}
|
||||
}
|
||||
|
||||
obs_source_t *obs_frontend_get_current_transition(void) override
|
||||
{
|
||||
OBSSource tr = main->GetCurrentTransition();
|
||||
return obs_source_get_ref(tr);
|
||||
}
|
||||
|
||||
void obs_frontend_set_current_transition(obs_source_t *transition) override
|
||||
{
|
||||
QMetaObject::invokeMethod(main, "SetTransition", Q_ARG(OBSSource, OBSSource(transition)));
|
||||
}
|
||||
|
||||
int obs_frontend_get_transition_duration(void) override { return main->ui->transitionDuration->value(); }
|
||||
|
||||
void obs_frontend_set_transition_duration(int duration) override
|
||||
{
|
||||
QMetaObject::invokeMethod(main->ui->transitionDuration, "setValue", Q_ARG(int, duration));
|
||||
}
|
||||
|
||||
void obs_frontend_release_tbar(void) override { QMetaObject::invokeMethod(main, "TBarReleased"); }
|
||||
|
||||
void obs_frontend_set_tbar_position(int position) override
|
||||
{
|
||||
QMetaObject::invokeMethod(main, "TBarChanged", Q_ARG(int, position));
|
||||
}
|
||||
|
||||
int obs_frontend_get_tbar_position(void) override { return main->tBar->value(); }
|
||||
|
||||
void obs_frontend_get_scene_collections(std::vector<std::string> &strings) override
|
||||
{
|
||||
for (auto &[collectionName, collection] : main->GetSceneCollectionCache()) {
|
||||
strings.emplace_back(collectionName);
|
||||
}
|
||||
}
|
||||
|
||||
char *obs_frontend_get_current_scene_collection(void) override
|
||||
{
|
||||
const OBSSceneCollection ¤tCollection = main->GetCurrentSceneCollection();
|
||||
return bstrdup(currentCollection.name.c_str());
|
||||
}
|
||||
|
||||
void obs_frontend_set_current_scene_collection(const char *collection) override
|
||||
{
|
||||
QList<QAction *> menuActions = main->ui->sceneCollectionMenu->actions();
|
||||
QString qstrCollection = QT_UTF8(collection);
|
||||
|
||||
for (int i = 0; i < menuActions.count(); i++) {
|
||||
QAction *action = menuActions[i];
|
||||
QVariant v = action->property("file_name");
|
||||
|
||||
if (v.typeName() != nullptr) {
|
||||
if (action->text() == qstrCollection) {
|
||||
action->trigger();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool obs_frontend_add_scene_collection(const char *name) override
|
||||
{
|
||||
bool success = false;
|
||||
QMetaObject::invokeMethod(main, "CreateNewSceneCollection", WaitConnection(),
|
||||
Q_RETURN_ARG(bool, success), Q_ARG(QString, QT_UTF8(name)));
|
||||
return success;
|
||||
}
|
||||
|
||||
void obs_frontend_get_profiles(std::vector<std::string> &strings) override
|
||||
{
|
||||
const OBSProfileCache &profiles = main->GetProfileCache();
|
||||
|
||||
for (auto &[profileName, profile] : profiles) {
|
||||
strings.emplace_back(profileName);
|
||||
}
|
||||
}
|
||||
|
||||
char *obs_frontend_get_current_profile(void) override
|
||||
{
|
||||
const OBSProfile &profile = main->GetCurrentProfile();
|
||||
return bstrdup(profile.name.c_str());
|
||||
}
|
||||
|
||||
char *obs_frontend_get_current_profile_path(void) override
|
||||
{
|
||||
const OBSProfile &profile = main->GetCurrentProfile();
|
||||
|
||||
return bstrdup(profile.path.u8string().c_str());
|
||||
}
|
||||
|
||||
void obs_frontend_set_current_profile(const char *profile) override
|
||||
{
|
||||
QList<QAction *> menuActions = main->ui->profileMenu->actions();
|
||||
QString qstrProfile = QT_UTF8(profile);
|
||||
|
||||
for (int i = 0; i < menuActions.count(); i++) {
|
||||
QAction *action = menuActions[i];
|
||||
QVariant v = action->property("file_name");
|
||||
|
||||
if (v.typeName() != nullptr) {
|
||||
if (action->text() == qstrProfile) {
|
||||
action->trigger();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void obs_frontend_create_profile(const char *name) override
|
||||
{
|
||||
QMetaObject::invokeMethod(main, "CreateNewProfile", Q_ARG(QString, name));
|
||||
}
|
||||
|
||||
void obs_frontend_duplicate_profile(const char *name) override
|
||||
{
|
||||
QMetaObject::invokeMethod(main, "CreateDuplicateProfile", Q_ARG(QString, name));
|
||||
}
|
||||
|
||||
void obs_frontend_delete_profile(const char *profile) override
|
||||
{
|
||||
QMetaObject::invokeMethod(main, "DeleteProfile", Q_ARG(QString, profile));
|
||||
}
|
||||
|
||||
void obs_frontend_streaming_start(void) override { QMetaObject::invokeMethod(main, "StartStreaming"); }
|
||||
|
||||
void obs_frontend_streaming_stop(void) override { QMetaObject::invokeMethod(main, "StopStreaming"); }
|
||||
|
||||
bool obs_frontend_streaming_active(void) override { return os_atomic_load_bool(&streaming_active); }
|
||||
|
||||
void obs_frontend_recording_start(void) override { QMetaObject::invokeMethod(main, "StartRecording"); }
|
||||
|
||||
void obs_frontend_recording_stop(void) override { QMetaObject::invokeMethod(main, "StopRecording"); }
|
||||
|
||||
bool obs_frontend_recording_active(void) override { return os_atomic_load_bool(&recording_active); }
|
||||
|
||||
void obs_frontend_recording_pause(bool pause) override
|
||||
{
|
||||
QMetaObject::invokeMethod(main, pause ? "PauseRecording" : "UnpauseRecording");
|
||||
}
|
||||
|
||||
bool obs_frontend_recording_paused(void) override { return os_atomic_load_bool(&recording_paused); }
|
||||
|
||||
bool obs_frontend_recording_split_file(void) override
|
||||
{
|
||||
if (os_atomic_load_bool(&recording_active) && !os_atomic_load_bool(&recording_paused)) {
|
||||
proc_handler_t *ph = obs_output_get_proc_handler(main->outputHandler->fileOutput);
|
||||
uint8_t stack[128];
|
||||
calldata cd;
|
||||
calldata_init_fixed(&cd, stack, sizeof(stack));
|
||||
proc_handler_call(ph, "split_file", &cd);
|
||||
bool result = calldata_bool(&cd, "split_file_enabled");
|
||||
return result;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool obs_frontend_recording_add_chapter(const char *name) override
|
||||
{
|
||||
if (!os_atomic_load_bool(&recording_active) || os_atomic_load_bool(&recording_paused))
|
||||
return false;
|
||||
|
||||
proc_handler_t *ph = obs_output_get_proc_handler(main->outputHandler->fileOutput);
|
||||
|
||||
calldata cd;
|
||||
calldata_init(&cd);
|
||||
calldata_set_string(&cd, "chapter_name", name);
|
||||
bool result = proc_handler_call(ph, "add_chapter", &cd);
|
||||
calldata_free(&cd);
|
||||
return result;
|
||||
}
|
||||
|
||||
void obs_frontend_replay_buffer_start(void) override { QMetaObject::invokeMethod(main, "StartReplayBuffer"); }
|
||||
|
||||
void obs_frontend_replay_buffer_save(void) override { QMetaObject::invokeMethod(main, "ReplayBufferSave"); }
|
||||
|
||||
void obs_frontend_replay_buffer_stop(void) override { QMetaObject::invokeMethod(main, "StopReplayBuffer"); }
|
||||
|
||||
bool obs_frontend_replay_buffer_active(void) override { return os_atomic_load_bool(&replaybuf_active); }
|
||||
|
||||
void *obs_frontend_add_tools_menu_qaction(const char *name) override
|
||||
{
|
||||
main->ui->menuTools->setEnabled(true);
|
||||
return (void *)main->ui->menuTools->addAction(QT_UTF8(name));
|
||||
}
|
||||
|
||||
void obs_frontend_add_tools_menu_item(const char *name, obs_frontend_cb callback, void *private_data) override
|
||||
{
|
||||
main->ui->menuTools->setEnabled(true);
|
||||
|
||||
auto func = [private_data, callback]() {
|
||||
callback(private_data);
|
||||
};
|
||||
|
||||
QAction *action = main->ui->menuTools->addAction(QT_UTF8(name));
|
||||
QObject::connect(action, &QAction::triggered, func);
|
||||
}
|
||||
|
||||
void *obs_frontend_add_dock(void *dock) override
|
||||
{
|
||||
QDockWidget *d = reinterpret_cast<QDockWidget *>(dock);
|
||||
|
||||
QString name = d->objectName();
|
||||
if (name.isEmpty() || main->IsDockObjectNameUsed(name)) {
|
||||
blog(LOG_WARNING, "The object name of the added dock is empty or already used,"
|
||||
" a temporary one will be set to avoid conflicts");
|
||||
|
||||
char *uuid = os_generate_uuid();
|
||||
name = QT_UTF8(uuid);
|
||||
bfree(uuid);
|
||||
name.append("_oldExtraDock");
|
||||
|
||||
d->setObjectName(name);
|
||||
}
|
||||
|
||||
return (void *)main->AddDockWidget(d);
|
||||
}
|
||||
|
||||
bool obs_frontend_add_dock_by_id(const char *id, const char *title, void *widget) override
|
||||
{
|
||||
if (main->IsDockObjectNameUsed(QT_UTF8(id))) {
|
||||
blog(LOG_WARNING,
|
||||
"Dock id '%s' already used! "
|
||||
"Duplicate library?",
|
||||
id);
|
||||
return false;
|
||||
}
|
||||
|
||||
OBSDock *dock = new OBSDock(main);
|
||||
dock->setWidget((QWidget *)widget);
|
||||
dock->setWindowTitle(QT_UTF8(title));
|
||||
dock->setObjectName(QT_UTF8(id));
|
||||
|
||||
main->AddDockWidget(dock, Qt::RightDockWidgetArea);
|
||||
|
||||
dock->setVisible(false);
|
||||
dock->setFloating(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void obs_frontend_remove_dock(const char *id) override { main->RemoveDockWidget(QT_UTF8(id)); }
|
||||
|
||||
bool obs_frontend_add_custom_qdock(const char *id, void *dock) override
|
||||
{
|
||||
if (main->IsDockObjectNameUsed(QT_UTF8(id))) {
|
||||
blog(LOG_WARNING,
|
||||
"Dock id '%s' already used! "
|
||||
"Duplicate library?",
|
||||
id);
|
||||
return false;
|
||||
}
|
||||
|
||||
QDockWidget *d = reinterpret_cast<QDockWidget *>(dock);
|
||||
d->setObjectName(QT_UTF8(id));
|
||||
|
||||
main->AddCustomDockWidget(d);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void obs_frontend_add_event_callback(obs_frontend_event_cb callback, void *private_data) override
|
||||
{
|
||||
size_t idx = GetCallbackIdx(callbacks, callback, private_data);
|
||||
if (idx == (size_t)-1)
|
||||
callbacks.emplace_back(callback, private_data);
|
||||
}
|
||||
|
||||
void obs_frontend_remove_event_callback(obs_frontend_event_cb callback, void *private_data) override
|
||||
{
|
||||
size_t idx = GetCallbackIdx(callbacks, callback, private_data);
|
||||
if (idx == (size_t)-1)
|
||||
return;
|
||||
|
||||
callbacks.erase(callbacks.begin() + idx);
|
||||
}
|
||||
|
||||
obs_output_t *obs_frontend_get_streaming_output(void) override
|
||||
{
|
||||
auto multitrackVideo = main->outputHandler->multitrackVideo.get();
|
||||
auto mtvOutput = multitrackVideo ? obs_output_get_ref(multitrackVideo->StreamingOutput()) : nullptr;
|
||||
if (mtvOutput)
|
||||
return mtvOutput;
|
||||
|
||||
OBSOutput output = main->outputHandler->streamOutput.Get();
|
||||
return obs_output_get_ref(output);
|
||||
}
|
||||
|
||||
obs_output_t *obs_frontend_get_recording_output(void) override
|
||||
{
|
||||
OBSOutput out = main->outputHandler->fileOutput.Get();
|
||||
return obs_output_get_ref(out);
|
||||
}
|
||||
|
||||
obs_output_t *obs_frontend_get_replay_buffer_output(void) override
|
||||
{
|
||||
OBSOutput out = main->outputHandler->replayBuffer.Get();
|
||||
return obs_output_get_ref(out);
|
||||
}
|
||||
|
||||
config_t *obs_frontend_get_profile_config(void) override { return main->activeConfiguration; }
|
||||
|
||||
config_t *obs_frontend_get_global_config(void) override
|
||||
{
|
||||
blog(LOG_WARNING,
|
||||
"DEPRECATION: obs_frontend_get_global_config is deprecated. Read from global or user configuration explicitly instead.");
|
||||
return App()->GetAppConfig();
|
||||
}
|
||||
|
||||
config_t *obs_frontend_get_app_config(void) override { return App()->GetAppConfig(); }
|
||||
|
||||
config_t *obs_frontend_get_user_config(void) override { return App()->GetUserConfig(); }
|
||||
|
||||
void obs_frontend_open_projector(const char *type, int monitor, const char *geometry, const char *name) override
|
||||
{
|
||||
SavedProjectorInfo proj = {
|
||||
ProjectorType::Preview,
|
||||
monitor,
|
||||
geometry ? geometry : "",
|
||||
name ? name : "",
|
||||
};
|
||||
if (type) {
|
||||
if (astrcmpi(type, "Source") == 0)
|
||||
proj.type = ProjectorType::Source;
|
||||
else if (astrcmpi(type, "Scene") == 0)
|
||||
proj.type = ProjectorType::Scene;
|
||||
else if (astrcmpi(type, "StudioProgram") == 0)
|
||||
proj.type = ProjectorType::StudioProgram;
|
||||
else if (astrcmpi(type, "Multiview") == 0)
|
||||
proj.type = ProjectorType::Multiview;
|
||||
}
|
||||
QMetaObject::invokeMethod(main, "OpenSavedProjector", WaitConnection(),
|
||||
Q_ARG(SavedProjectorInfo *, &proj));
|
||||
}
|
||||
|
||||
void obs_frontend_save(void) override { main->SaveProject(); }
|
||||
|
||||
void obs_frontend_defer_save_begin(void) override { QMetaObject::invokeMethod(main, "DeferSaveBegin"); }
|
||||
|
||||
void obs_frontend_defer_save_end(void) override { QMetaObject::invokeMethod(main, "DeferSaveEnd"); }
|
||||
|
||||
void obs_frontend_add_save_callback(obs_frontend_save_cb callback, void *private_data) override
|
||||
{
|
||||
size_t idx = GetCallbackIdx(saveCallbacks, callback, private_data);
|
||||
if (idx == (size_t)-1)
|
||||
saveCallbacks.emplace_back(callback, private_data);
|
||||
}
|
||||
|
||||
void obs_frontend_remove_save_callback(obs_frontend_save_cb callback, void *private_data) override
|
||||
{
|
||||
size_t idx = GetCallbackIdx(saveCallbacks, callback, private_data);
|
||||
if (idx == (size_t)-1)
|
||||
return;
|
||||
|
||||
saveCallbacks.erase(saveCallbacks.begin() + idx);
|
||||
}
|
||||
|
||||
void obs_frontend_add_preload_callback(obs_frontend_save_cb callback, void *private_data) override
|
||||
{
|
||||
size_t idx = GetCallbackIdx(preloadCallbacks, callback, private_data);
|
||||
if (idx == (size_t)-1)
|
||||
preloadCallbacks.emplace_back(callback, private_data);
|
||||
}
|
||||
|
||||
void obs_frontend_remove_preload_callback(obs_frontend_save_cb callback, void *private_data) override
|
||||
{
|
||||
size_t idx = GetCallbackIdx(preloadCallbacks, callback, private_data);
|
||||
if (idx == (size_t)-1)
|
||||
return;
|
||||
|
||||
preloadCallbacks.erase(preloadCallbacks.begin() + idx);
|
||||
}
|
||||
|
||||
void obs_frontend_push_ui_translation(obs_frontend_translate_ui_cb translate) override
|
||||
{
|
||||
App()->PushUITranslation(translate);
|
||||
}
|
||||
|
||||
void obs_frontend_pop_ui_translation(void) override { App()->PopUITranslation(); }
|
||||
|
||||
void obs_frontend_set_streaming_service(obs_service_t *service) override { main->SetService(service); }
|
||||
|
||||
obs_service_t *obs_frontend_get_streaming_service(void) override { return main->GetService(); }
|
||||
|
||||
void obs_frontend_save_streaming_service(void) override { main->SaveService(); }
|
||||
|
||||
bool obs_frontend_preview_program_mode_active(void) override { return main->IsPreviewProgramMode(); }
|
||||
|
||||
void obs_frontend_set_preview_program_mode(bool enable) override { main->SetPreviewProgramMode(enable); }
|
||||
|
||||
void obs_frontend_preview_program_trigger_transition(void) override
|
||||
{
|
||||
QMetaObject::invokeMethod(main, "TransitionClicked");
|
||||
}
|
||||
|
||||
bool obs_frontend_preview_enabled(void) override { return main->previewEnabled; }
|
||||
|
||||
void obs_frontend_set_preview_enabled(bool enable) override
|
||||
{
|
||||
if (main->previewEnabled != enable)
|
||||
main->EnablePreviewDisplay(enable);
|
||||
}
|
||||
|
||||
obs_source_t *obs_frontend_get_current_preview_scene(void) override
|
||||
{
|
||||
if (main->IsPreviewProgramMode()) {
|
||||
OBSSource source = main->GetCurrentSceneSource();
|
||||
return obs_source_get_ref(source);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void obs_frontend_set_current_preview_scene(obs_source_t *scene) override
|
||||
{
|
||||
if (main->IsPreviewProgramMode()) {
|
||||
QMetaObject::invokeMethod(main, "SetCurrentScene", Q_ARG(OBSSource, OBSSource(scene)),
|
||||
Q_ARG(bool, false));
|
||||
}
|
||||
}
|
||||
|
||||
void obs_frontend_take_screenshot(void) override { QMetaObject::invokeMethod(main, "Screenshot"); }
|
||||
|
||||
void obs_frontend_take_source_screenshot(obs_source_t *source) override
|
||||
{
|
||||
QMetaObject::invokeMethod(main, "Screenshot", Q_ARG(OBSSource, OBSSource(source)));
|
||||
}
|
||||
|
||||
obs_output_t *obs_frontend_get_virtualcam_output(void) override
|
||||
{
|
||||
OBSOutput output = main->outputHandler->virtualCam.Get();
|
||||
return obs_output_get_ref(output);
|
||||
}
|
||||
|
||||
void obs_frontend_start_virtualcam(void) override { QMetaObject::invokeMethod(main, "StartVirtualCam"); }
|
||||
|
||||
void obs_frontend_stop_virtualcam(void) override { QMetaObject::invokeMethod(main, "StopVirtualCam"); }
|
||||
|
||||
bool obs_frontend_virtualcam_active(void) override { return os_atomic_load_bool(&virtualcam_active); }
|
||||
|
||||
void obs_frontend_reset_video(void) override { main->ResetVideo(); }
|
||||
|
||||
void obs_frontend_open_source_properties(obs_source_t *source) override
|
||||
{
|
||||
QMetaObject::invokeMethod(main, "OpenProperties", Q_ARG(OBSSource, OBSSource(source)));
|
||||
}
|
||||
|
||||
void obs_frontend_open_source_filters(obs_source_t *source) override
|
||||
{
|
||||
QMetaObject::invokeMethod(main, "OpenFilters", Q_ARG(OBSSource, OBSSource(source)));
|
||||
}
|
||||
|
||||
void obs_frontend_open_source_interaction(obs_source_t *source) override
|
||||
{
|
||||
QMetaObject::invokeMethod(main, "OpenInteraction", Q_ARG(OBSSource, OBSSource(source)));
|
||||
}
|
||||
|
||||
void obs_frontend_open_sceneitem_edit_transform(obs_sceneitem_t *item) override
|
||||
{
|
||||
QMetaObject::invokeMethod(main, "OpenEditTransform", Q_ARG(OBSSceneItem, OBSSceneItem(item)));
|
||||
}
|
||||
|
||||
char *obs_frontend_get_current_record_output_path(void) override
|
||||
{
|
||||
const char *recordOutputPath = main->GetCurrentOutputPath();
|
||||
|
||||
return bstrdup(recordOutputPath);
|
||||
}
|
||||
|
||||
const char *obs_frontend_get_locale_string(const char *string) override { return Str(string); }
|
||||
|
||||
bool obs_frontend_is_theme_dark(void) override { return App()->IsThemeDark(); }
|
||||
|
||||
char *obs_frontend_get_last_recording(void) override
|
||||
{
|
||||
return bstrdup(main->outputHandler->lastRecordingPath.c_str());
|
||||
}
|
||||
|
||||
char *obs_frontend_get_last_screenshot(void) override { return bstrdup(main->lastScreenshot.c_str()); }
|
||||
|
||||
char *obs_frontend_get_last_replay(void) override { return bstrdup(main->lastReplay.c_str()); }
|
||||
|
||||
void obs_frontend_add_undo_redo_action(const char *name, const undo_redo_cb undo, const undo_redo_cb redo,
|
||||
const char *undo_data, const char *redo_data, bool repeatable) override
|
||||
{
|
||||
main->undo_s.add_action(
|
||||
name, [undo](const std::string &data) { undo(data.c_str()); },
|
||||
[redo](const std::string &data) { redo(data.c_str()); }, undo_data, redo_data, repeatable);
|
||||
}
|
||||
|
||||
void on_load(obs_data_t *settings) override
|
||||
{
|
||||
for (size_t i = saveCallbacks.size(); i > 0; i--) {
|
||||
auto cb = saveCallbacks[i - 1];
|
||||
cb.callback(settings, false, cb.private_data);
|
||||
}
|
||||
}
|
||||
|
||||
void on_preload(obs_data_t *settings) override
|
||||
{
|
||||
for (size_t i = preloadCallbacks.size(); i > 0; i--) {
|
||||
auto cb = preloadCallbacks[i - 1];
|
||||
cb.callback(settings, false, cb.private_data);
|
||||
}
|
||||
}
|
||||
|
||||
void on_save(obs_data_t *settings) override
|
||||
{
|
||||
for (size_t i = saveCallbacks.size(); i > 0; i--) {
|
||||
auto cb = saveCallbacks[i - 1];
|
||||
cb.callback(settings, true, cb.private_data);
|
||||
}
|
||||
}
|
||||
|
||||
void on_event(enum obs_frontend_event event) override
|
||||
{
|
||||
if (main->disableSaving && event != OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP &&
|
||||
event != OBS_FRONTEND_EVENT_EXIT)
|
||||
return;
|
||||
|
||||
for (size_t i = callbacks.size(); i > 0; i--) {
|
||||
auto cb = callbacks[i - 1];
|
||||
cb.callback(event, cb.private_data);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main)
|
||||
{
|
||||
obs_frontend_callbacks *api = new OBSStudioAPI(main);
|
||||
obs_frontend_set_callbacks_internal(api);
|
||||
return api;
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
if(TARGET OBS::browser-panels)
|
||||
target_enable_feature(obs-studio "Browser panels" BROWSER_AVAILABLE)
|
||||
|
||||
target_link_libraries(obs-studio PRIVATE OBS::browser-panels)
|
||||
|
||||
target_sources(
|
||||
obs-studio
|
||||
PRIVATE window-dock-browser.cpp window-dock-browser.hpp window-extra-browsers.cpp window-extra-browsers.hpp
|
||||
)
|
||||
endif()
|
||||
@@ -1,10 +0,0 @@
|
||||
target_sources(
|
||||
obs-studio
|
||||
PRIVATE
|
||||
importers/classic.cpp
|
||||
importers/importers.cpp
|
||||
importers/importers.hpp
|
||||
importers/sl.cpp
|
||||
importers/studio.cpp
|
||||
importers/xsplit.cpp
|
||||
)
|
||||
@@ -1,78 +0,0 @@
|
||||
if(NOT TARGET OBS::properties-view)
|
||||
add_subdirectory("${CMAKE_SOURCE_DIR}/shared/properties-view" "${CMAKE_BINARY_DIR}/shared/properties-view")
|
||||
endif()
|
||||
|
||||
if(NOT TARGET OBS::qt-plain-text-edit)
|
||||
add_subdirectory("${CMAKE_SOURCE_DIR}/shared/qt/plain-text-edit" "${CMAKE_BINARY_DIR}/shared/qt/plain-text-edit")
|
||||
endif()
|
||||
|
||||
if(NOT TARGET OBS::qt-slider-ignorewheel)
|
||||
add_subdirectory(
|
||||
"${CMAKE_SOURCE_DIR}/shared/qt/slider-ignorewheel"
|
||||
"${CMAKE_BINARY_DIR}/shared/qt/slider-ignorewheel"
|
||||
)
|
||||
endif()
|
||||
|
||||
if(NOT TARGET OBS::qt-vertical-scroll-area)
|
||||
add_subdirectory(
|
||||
"${CMAKE_SOURCE_DIR}/shared/qt/vertical-scroll-area"
|
||||
"${CMAKE_BINARY_DIR}/shared/qt/vertical-scroll-area"
|
||||
)
|
||||
endif()
|
||||
|
||||
target_link_libraries(
|
||||
obs-studio
|
||||
PRIVATE OBS::properties-view OBS::qt-plain-text-edit OBS::qt-slider-ignorewheel OBS::qt-vertical-scroll-area
|
||||
)
|
||||
|
||||
target_sources(
|
||||
obs-studio
|
||||
PRIVATE
|
||||
absolute-slider.cpp
|
||||
absolute-slider.hpp
|
||||
adv-audio-control.cpp
|
||||
adv-audio-control.hpp
|
||||
audio-encoders.cpp
|
||||
audio-encoders.hpp
|
||||
balance-slider.hpp
|
||||
basic-controls.cpp
|
||||
basic-controls.hpp
|
||||
clickable-label.hpp
|
||||
context-bar-controls.cpp
|
||||
context-bar-controls.hpp
|
||||
focus-list.cpp
|
||||
focus-list.hpp
|
||||
horizontal-scroll-area.cpp
|
||||
horizontal-scroll-area.hpp
|
||||
hotkey-edit.cpp
|
||||
hotkey-edit.hpp
|
||||
item-widget-helpers.cpp
|
||||
item-widget-helpers.hpp
|
||||
log-viewer.cpp
|
||||
log-viewer.hpp
|
||||
media-controls.cpp
|
||||
media-controls.hpp
|
||||
menu-button.cpp
|
||||
menu-button.hpp
|
||||
mute-checkbox.hpp
|
||||
noncheckable-button.hpp
|
||||
preview-controls.cpp
|
||||
preview-controls.hpp
|
||||
remote-text.cpp
|
||||
remote-text.hpp
|
||||
scene-tree.cpp
|
||||
scene-tree.hpp
|
||||
screenshot-obj.hpp
|
||||
source-label.cpp
|
||||
source-label.hpp
|
||||
source-tree.cpp
|
||||
source-tree.hpp
|
||||
undo-stack-obs.cpp
|
||||
undo-stack-obs.hpp
|
||||
url-push-button.cpp
|
||||
url-push-button.hpp
|
||||
visibility-item-widget.cpp
|
||||
visibility-item-widget.hpp
|
||||
volume-control.cpp
|
||||
volume-control.hpp
|
||||
)
|
||||
@@ -1,65 +0,0 @@
|
||||
find_package(Qt6 REQUIRED Widgets Network Svg Xml)
|
||||
|
||||
if(OS_LINUX OR OS_FREEBSD OR OS_OPENBSD)
|
||||
find_package(Qt6 REQUIRED Gui DBus)
|
||||
endif()
|
||||
|
||||
if(NOT TARGET OBS::qt-wrappers)
|
||||
add_subdirectory("${CMAKE_SOURCE_DIR}/shared/qt/wrappers" "${CMAKE_BINARY_DIR}/shared/qt/wrappers")
|
||||
endif()
|
||||
|
||||
target_link_libraries(
|
||||
obs-studio
|
||||
PRIVATE Qt::Widgets Qt::Svg Qt::Xml Qt::Network OBS::qt-wrappers
|
||||
)
|
||||
|
||||
set_target_properties(
|
||||
obs-studio
|
||||
PROPERTIES AUTOMOC ON AUTOUIC ON AUTORCC ON
|
||||
)
|
||||
|
||||
set_property(TARGET obs-studio APPEND PROPERTY AUTOUIC_SEARCH_PATHS forms forms/source-toolbar)
|
||||
|
||||
set(
|
||||
_qt_sources
|
||||
forms/AutoConfigFinishPage.ui
|
||||
forms/AutoConfigStartPage.ui
|
||||
forms/AutoConfigStartPage.ui
|
||||
forms/AutoConfigStreamPage.ui
|
||||
forms/AutoConfigTestPage.ui
|
||||
forms/AutoConfigVideoPage.ui
|
||||
forms/ColorSelect.ui
|
||||
forms/obs.qrc
|
||||
forms/OBSAbout.ui
|
||||
forms/OBSAdvAudio.ui
|
||||
forms/OBSBasic.ui
|
||||
forms/OBSBasicControls.ui
|
||||
forms/OBSBasicFilters.ui
|
||||
forms/OBSBasicInteraction.ui
|
||||
forms/OBSBasicProperties.ui
|
||||
forms/OBSBasicSettings.ui
|
||||
forms/OBSBasicSourceSelect.ui
|
||||
forms/OBSBasicTransform.ui
|
||||
forms/OBSBasicVCamConfig.ui
|
||||
forms/OBSExtraBrowsers.ui
|
||||
forms/OBSImporter.ui
|
||||
forms/OBSLogReply.ui
|
||||
forms/OBSLogViewer.ui
|
||||
forms/OBSMissingFiles.ui
|
||||
forms/OBSRemux.ui
|
||||
forms/OBSUpdate.ui
|
||||
forms/OBSYoutubeActions.ui
|
||||
forms/source-toolbar/browser-source-toolbar.ui
|
||||
forms/source-toolbar/color-source-toolbar.ui
|
||||
forms/source-toolbar/device-select-toolbar.ui
|
||||
forms/source-toolbar/game-capture-toolbar.ui
|
||||
forms/source-toolbar/image-source-toolbar.ui
|
||||
forms/source-toolbar/media-controls.ui
|
||||
forms/source-toolbar/text-source-toolbar.ui
|
||||
)
|
||||
|
||||
target_sources(obs-studio PRIVATE ${_qt_sources})
|
||||
|
||||
source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}/forms" PREFIX "UI Files" FILES ${_qt_sources})
|
||||
|
||||
unset(_qt_sources)
|
||||
@@ -1,63 +0,0 @@
|
||||
target_sources(
|
||||
obs-studio
|
||||
PRIVATE
|
||||
window-basic-about.cpp
|
||||
window-basic-about.hpp
|
||||
window-basic-adv-audio.cpp
|
||||
window-basic-adv-audio.hpp
|
||||
window-basic-auto-config-test.cpp
|
||||
window-basic-auto-config.cpp
|
||||
window-basic-auto-config.hpp
|
||||
window-basic-filters.cpp
|
||||
window-basic-filters.hpp
|
||||
window-basic-interaction.cpp
|
||||
window-basic-interaction.hpp
|
||||
window-basic-main-browser.cpp
|
||||
window-basic-main-dropfiles.cpp
|
||||
window-basic-main-icons.cpp
|
||||
window-basic-main-outputs.cpp
|
||||
window-basic-main-outputs.hpp
|
||||
window-basic-main-profiles.cpp
|
||||
window-basic-main-scene-collections.cpp
|
||||
window-basic-main-screenshot.cpp
|
||||
window-basic-main-transitions.cpp
|
||||
window-basic-main.cpp
|
||||
window-basic-main.hpp
|
||||
window-basic-preview.cpp
|
||||
window-basic-preview.hpp
|
||||
window-basic-properties.cpp
|
||||
window-basic-properties.hpp
|
||||
window-basic-settings-a11y.cpp
|
||||
window-basic-settings-appearance.cpp
|
||||
window-basic-settings-stream.cpp
|
||||
window-basic-settings.cpp
|
||||
window-basic-settings.hpp
|
||||
window-basic-source-select.cpp
|
||||
window-basic-source-select.hpp
|
||||
window-basic-stats.cpp
|
||||
window-basic-stats.hpp
|
||||
window-basic-status-bar.cpp
|
||||
window-basic-status-bar.hpp
|
||||
window-basic-transform.cpp
|
||||
window-basic-transform.hpp
|
||||
window-basic-vcam-config.cpp
|
||||
window-basic-vcam-config.hpp
|
||||
window-basic-vcam.hpp
|
||||
window-dock.cpp
|
||||
window-dock.hpp
|
||||
window-importer.cpp
|
||||
window-importer.hpp
|
||||
window-log-reply.cpp
|
||||
window-log-reply.hpp
|
||||
window-main.hpp
|
||||
window-missing-files.cpp
|
||||
window-missing-files.hpp
|
||||
window-namedialog.cpp
|
||||
window-namedialog.hpp
|
||||
window-projector.cpp
|
||||
window-projector.hpp
|
||||
window-remux.cpp
|
||||
window-remux.hpp
|
||||
window-whats-new.cpp
|
||||
window-whats-new.hpp
|
||||
)
|
||||
@@ -1,707 +0,0 @@
|
||||
#include "window-basic-main.hpp"
|
||||
#include "moc_context-bar-controls.cpp"
|
||||
#include "obs-app.hpp"
|
||||
|
||||
#include <qt-wrappers.hpp>
|
||||
#include <QStandardItemModel>
|
||||
#include <QColorDialog>
|
||||
#include <QFontDialog>
|
||||
|
||||
#include "ui_browser-source-toolbar.h"
|
||||
#include "ui_device-select-toolbar.h"
|
||||
#include "ui_game-capture-toolbar.h"
|
||||
#include "ui_image-source-toolbar.h"
|
||||
#include "ui_color-source-toolbar.h"
|
||||
#include "ui_text-source-toolbar.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define get_os_module(win, mac, linux) obs_get_module(win)
|
||||
#define get_os_text(mod, win, mac, linux) obs_module_get_locale_text(mod, win)
|
||||
#elif __APPLE__
|
||||
#define get_os_module(win, mac, linux) obs_get_module(mac)
|
||||
#define get_os_text(mod, win, mac, linux) obs_module_get_locale_text(mod, mac)
|
||||
#else
|
||||
#define get_os_module(win, mac, linux) obs_get_module(linux)
|
||||
#define get_os_text(mod, win, mac, linux) obs_module_get_locale_text(mod, linux)
|
||||
#endif
|
||||
|
||||
/* ========================================================================= */
|
||||
|
||||
SourceToolbar::SourceToolbar(QWidget *parent, OBSSource source)
|
||||
: QWidget(parent),
|
||||
weakSource(OBSGetWeakRef(source)),
|
||||
props(obs_source_properties(source), obs_properties_destroy)
|
||||
{
|
||||
}
|
||||
|
||||
void SourceToolbar::SaveOldProperties(obs_source_t *source)
|
||||
{
|
||||
oldData = obs_data_create();
|
||||
|
||||
OBSDataAutoRelease oldSettings = obs_source_get_settings(source);
|
||||
obs_data_apply(oldData, oldSettings);
|
||||
obs_data_set_string(oldData, "undo_suuid", obs_source_get_uuid(source));
|
||||
}
|
||||
|
||||
void SourceToolbar::SetUndoProperties(obs_source_t *source, bool repeatable)
|
||||
{
|
||||
if (!oldData) {
|
||||
blog(LOG_ERROR, "%s: somehow oldData was null.", __FUNCTION__);
|
||||
return;
|
||||
}
|
||||
|
||||
OBSBasic *main = reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
|
||||
|
||||
OBSSource currentSceneSource = main->GetCurrentSceneSource();
|
||||
if (!currentSceneSource)
|
||||
return;
|
||||
std::string scene_uuid = obs_source_get_uuid(currentSceneSource);
|
||||
auto undo_redo = [scene_uuid = std::move(scene_uuid), main](const std::string &data) {
|
||||
OBSDataAutoRelease settings = obs_data_create_from_json(data.c_str());
|
||||
OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(settings, "undo_suuid"));
|
||||
obs_source_reset_settings(source, settings);
|
||||
|
||||
OBSSourceAutoRelease scene_source = obs_get_source_by_uuid(scene_uuid.c_str());
|
||||
main->SetCurrentScene(scene_source.Get(), true);
|
||||
|
||||
main->UpdateContextBar();
|
||||
};
|
||||
|
||||
OBSDataAutoRelease new_settings = obs_data_create();
|
||||
OBSDataAutoRelease curr_settings = obs_source_get_settings(source);
|
||||
obs_data_apply(new_settings, curr_settings);
|
||||
obs_data_set_string(new_settings, "undo_suuid", obs_source_get_uuid(source));
|
||||
|
||||
std::string undo_data(obs_data_get_json(oldData));
|
||||
std::string redo_data(obs_data_get_json(new_settings));
|
||||
|
||||
if (undo_data.compare(redo_data) != 0)
|
||||
main->undo_s.add_action(QTStr("Undo.Properties").arg(obs_source_get_name(source)), undo_redo, undo_redo,
|
||||
undo_data, redo_data, repeatable);
|
||||
|
||||
oldData = nullptr;
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
|
||||
BrowserToolbar::BrowserToolbar(QWidget *parent, OBSSource source)
|
||||
: SourceToolbar(parent, source),
|
||||
ui(new Ui_BrowserSourceToolbar)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
}
|
||||
|
||||
BrowserToolbar::~BrowserToolbar() {}
|
||||
|
||||
void BrowserToolbar::on_refresh_clicked()
|
||||
{
|
||||
OBSSource source = GetSource();
|
||||
if (!source) {
|
||||
return;
|
||||
}
|
||||
|
||||
obs_property_t *p = obs_properties_get(props.get(), "refreshnocache");
|
||||
obs_property_button_clicked(p, source.Get());
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
|
||||
ComboSelectToolbar::ComboSelectToolbar(QWidget *parent, OBSSource source)
|
||||
: SourceToolbar(parent, source),
|
||||
ui(new Ui_DeviceSelectToolbar)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
}
|
||||
|
||||
ComboSelectToolbar::~ComboSelectToolbar() {}
|
||||
|
||||
static int FillPropertyCombo(QComboBox *c, obs_property_t *p, const std::string &cur_id, bool is_int = false)
|
||||
{
|
||||
size_t count = obs_property_list_item_count(p);
|
||||
int cur_idx = -1;
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
const char *name = obs_property_list_item_name(p, i);
|
||||
std::string id;
|
||||
|
||||
if (is_int) {
|
||||
id = std::to_string(obs_property_list_item_int(p, i));
|
||||
} else {
|
||||
const char *val = obs_property_list_item_string(p, i);
|
||||
id = val ? val : "";
|
||||
}
|
||||
|
||||
if (cur_id == id)
|
||||
cur_idx = (int)i;
|
||||
|
||||
c->addItem(name, id.c_str());
|
||||
}
|
||||
|
||||
return cur_idx;
|
||||
}
|
||||
|
||||
void UpdateSourceComboToolbarProperties(QComboBox *combo, OBSSource source, obs_properties_t *props,
|
||||
const char *prop_name, bool is_int)
|
||||
{
|
||||
std::string cur_id;
|
||||
|
||||
OBSDataAutoRelease settings = obs_source_get_settings(source);
|
||||
if (is_int) {
|
||||
cur_id = std::to_string(obs_data_get_int(settings, prop_name));
|
||||
} else {
|
||||
cur_id = obs_data_get_string(settings, prop_name);
|
||||
}
|
||||
|
||||
combo->blockSignals(true);
|
||||
|
||||
obs_property_t *p = obs_properties_get(props, prop_name);
|
||||
int cur_idx = FillPropertyCombo(combo, p, cur_id, is_int);
|
||||
|
||||
if (cur_idx == -1 || obs_property_list_item_disabled(p, cur_idx)) {
|
||||
if (cur_idx == -1) {
|
||||
combo->insertItem(0, QTStr("Basic.Settings.Audio.UnknownAudioDevice"));
|
||||
cur_idx = 0;
|
||||
}
|
||||
|
||||
SetComboItemEnabled(combo, cur_idx, false);
|
||||
}
|
||||
|
||||
combo->setCurrentIndex(cur_idx);
|
||||
combo->blockSignals(false);
|
||||
}
|
||||
|
||||
void ComboSelectToolbar::Init()
|
||||
{
|
||||
OBSSource source = GetSource();
|
||||
if (!source) {
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateSourceComboToolbarProperties(ui->device, source, props.get(), prop_name, is_int);
|
||||
}
|
||||
|
||||
void UpdateSourceComboToolbarValue(QComboBox *combo, OBSSource source, int idx, const char *prop_name, bool is_int)
|
||||
{
|
||||
QString id = combo->itemData(idx).toString();
|
||||
|
||||
OBSDataAutoRelease settings = obs_data_create();
|
||||
if (is_int) {
|
||||
obs_data_set_int(settings, prop_name, id.toInt());
|
||||
} else {
|
||||
obs_data_set_string(settings, prop_name, QT_TO_UTF8(id));
|
||||
}
|
||||
obs_source_update(source, settings);
|
||||
}
|
||||
|
||||
void ComboSelectToolbar::on_device_currentIndexChanged(int idx)
|
||||
{
|
||||
OBSSource source = GetSource();
|
||||
if (idx == -1 || !source) {
|
||||
return;
|
||||
}
|
||||
|
||||
SaveOldProperties(source);
|
||||
UpdateSourceComboToolbarValue(ui->device, source, idx, prop_name, is_int);
|
||||
SetUndoProperties(source);
|
||||
}
|
||||
|
||||
AudioCaptureToolbar::AudioCaptureToolbar(QWidget *parent, OBSSource source) : ComboSelectToolbar(parent, source) {}
|
||||
|
||||
void AudioCaptureToolbar::Init()
|
||||
{
|
||||
delete ui->activateButton;
|
||||
ui->activateButton = nullptr;
|
||||
|
||||
obs_module_t *mod = get_os_module("win-wasapi", "mac-capture", "linux-pulseaudio");
|
||||
if (!mod)
|
||||
return;
|
||||
|
||||
const char *device_str = get_os_text(mod, "Device", "CoreAudio.Device", "Device");
|
||||
ui->deviceLabel->setText(device_str);
|
||||
|
||||
prop_name = "device_id";
|
||||
|
||||
ComboSelectToolbar::Init();
|
||||
}
|
||||
|
||||
WindowCaptureToolbar::WindowCaptureToolbar(QWidget *parent, OBSSource source) : ComboSelectToolbar(parent, source) {}
|
||||
|
||||
void WindowCaptureToolbar::Init()
|
||||
{
|
||||
delete ui->activateButton;
|
||||
ui->activateButton = nullptr;
|
||||
|
||||
obs_module_t *mod = get_os_module("win-capture", "mac-capture", "linux-capture");
|
||||
if (!mod)
|
||||
return;
|
||||
|
||||
const char *device_str = get_os_text(mod, "WindowCapture.Window", "WindowUtils.Window", "Window");
|
||||
ui->deviceLabel->setText(device_str);
|
||||
|
||||
#if !defined(_WIN32) && !defined(__APPLE__) //linux
|
||||
prop_name = "capture_window";
|
||||
#else
|
||||
prop_name = "window";
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
is_int = true;
|
||||
#endif
|
||||
|
||||
ComboSelectToolbar::Init();
|
||||
}
|
||||
|
||||
ApplicationAudioCaptureToolbar::ApplicationAudioCaptureToolbar(QWidget *parent, OBSSource source)
|
||||
: ComboSelectToolbar(parent, source)
|
||||
{
|
||||
}
|
||||
|
||||
void ApplicationAudioCaptureToolbar::Init()
|
||||
{
|
||||
delete ui->activateButton;
|
||||
ui->activateButton = nullptr;
|
||||
|
||||
obs_module_t *mod = obs_get_module("win-wasapi");
|
||||
const char *device_str = obs_module_get_locale_text(mod, "Window");
|
||||
ui->deviceLabel->setText(device_str);
|
||||
|
||||
prop_name = "window";
|
||||
|
||||
ComboSelectToolbar::Init();
|
||||
}
|
||||
|
||||
DisplayCaptureToolbar::DisplayCaptureToolbar(QWidget *parent, OBSSource source) : ComboSelectToolbar(parent, source) {}
|
||||
|
||||
void DisplayCaptureToolbar::Init()
|
||||
{
|
||||
delete ui->activateButton;
|
||||
ui->activateButton = nullptr;
|
||||
|
||||
obs_module_t *mod = get_os_module("win-capture", "mac-capture", "linux-capture");
|
||||
if (!mod)
|
||||
return;
|
||||
|
||||
const char *device_str = get_os_text(mod, "Monitor", "DisplayCapture.Display", "Screen");
|
||||
ui->deviceLabel->setText(device_str);
|
||||
|
||||
#ifdef _WIN32
|
||||
prop_name = "monitor_id";
|
||||
#elif __APPLE__
|
||||
prop_name = "display_uuid";
|
||||
#else
|
||||
is_int = true;
|
||||
prop_name = "screen";
|
||||
#endif
|
||||
|
||||
ComboSelectToolbar::Init();
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
|
||||
DeviceCaptureToolbar::DeviceCaptureToolbar(QWidget *parent, OBSSource source)
|
||||
: QWidget(parent),
|
||||
weakSource(OBSGetWeakRef(source)),
|
||||
ui(new Ui_DeviceSelectToolbar)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
delete ui->deviceLabel;
|
||||
delete ui->device;
|
||||
ui->deviceLabel = nullptr;
|
||||
ui->device = nullptr;
|
||||
|
||||
OBSDataAutoRelease settings = obs_source_get_settings(source);
|
||||
active = obs_data_get_bool(settings, "active");
|
||||
|
||||
obs_module_t *mod = obs_get_module("win-dshow");
|
||||
if (!mod)
|
||||
return;
|
||||
|
||||
activateText = obs_module_get_locale_text(mod, "Activate");
|
||||
deactivateText = obs_module_get_locale_text(mod, "Deactivate");
|
||||
|
||||
ui->activateButton->setText(active ? deactivateText : activateText);
|
||||
}
|
||||
|
||||
DeviceCaptureToolbar::~DeviceCaptureToolbar() {}
|
||||
|
||||
void DeviceCaptureToolbar::on_activateButton_clicked()
|
||||
{
|
||||
OBSSource source = OBSGetStrongRef(weakSource);
|
||||
if (!source) {
|
||||
return;
|
||||
}
|
||||
|
||||
OBSDataAutoRelease settings = obs_source_get_settings(source);
|
||||
bool now_active = obs_data_get_bool(settings, "active");
|
||||
|
||||
bool desyncedSetting = now_active != active;
|
||||
|
||||
active = !active;
|
||||
|
||||
const char *text = active ? deactivateText : activateText;
|
||||
ui->activateButton->setText(text);
|
||||
|
||||
if (desyncedSetting) {
|
||||
return;
|
||||
}
|
||||
|
||||
calldata_t cd = {};
|
||||
calldata_set_bool(&cd, "active", active);
|
||||
proc_handler_t *ph = obs_source_get_proc_handler(source);
|
||||
proc_handler_call(ph, "activate", &cd);
|
||||
calldata_free(&cd);
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
|
||||
GameCaptureToolbar::GameCaptureToolbar(QWidget *parent, OBSSource source)
|
||||
: SourceToolbar(parent, source),
|
||||
ui(new Ui_GameCaptureToolbar)
|
||||
{
|
||||
obs_property_t *p;
|
||||
int cur_idx;
|
||||
|
||||
ui->setupUi(this);
|
||||
|
||||
obs_module_t *mod = obs_get_module("win-capture");
|
||||
if (!mod)
|
||||
return;
|
||||
|
||||
ui->modeLabel->setText(obs_module_get_locale_text(mod, "Mode"));
|
||||
ui->windowLabel->setText(obs_module_get_locale_text(mod, "WindowCapture.Window"));
|
||||
|
||||
OBSDataAutoRelease settings = obs_source_get_settings(source);
|
||||
std::string cur_mode = obs_data_get_string(settings, "capture_mode");
|
||||
std::string cur_window = obs_data_get_string(settings, "window");
|
||||
|
||||
ui->mode->blockSignals(true);
|
||||
p = obs_properties_get(props.get(), "capture_mode");
|
||||
cur_idx = FillPropertyCombo(ui->mode, p, cur_mode);
|
||||
ui->mode->setCurrentIndex(cur_idx);
|
||||
ui->mode->blockSignals(false);
|
||||
|
||||
ui->window->blockSignals(true);
|
||||
p = obs_properties_get(props.get(), "window");
|
||||
cur_idx = FillPropertyCombo(ui->window, p, cur_window);
|
||||
ui->window->setCurrentIndex(cur_idx);
|
||||
ui->window->blockSignals(false);
|
||||
|
||||
if (cur_idx != -1 && obs_property_list_item_disabled(p, cur_idx)) {
|
||||
SetComboItemEnabled(ui->window, cur_idx, false);
|
||||
}
|
||||
|
||||
UpdateWindowVisibility();
|
||||
}
|
||||
|
||||
GameCaptureToolbar::~GameCaptureToolbar() {}
|
||||
|
||||
void GameCaptureToolbar::UpdateWindowVisibility()
|
||||
{
|
||||
QString mode = ui->mode->currentData().toString();
|
||||
bool is_window = (mode == "window");
|
||||
ui->windowLabel->setVisible(is_window);
|
||||
ui->window->setVisible(is_window);
|
||||
}
|
||||
|
||||
void GameCaptureToolbar::on_mode_currentIndexChanged(int idx)
|
||||
{
|
||||
OBSSource source = GetSource();
|
||||
if (idx == -1 || !source) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString id = ui->mode->itemData(idx).toString();
|
||||
|
||||
SaveOldProperties(source);
|
||||
OBSDataAutoRelease settings = obs_data_create();
|
||||
obs_data_set_string(settings, "capture_mode", QT_TO_UTF8(id));
|
||||
obs_source_update(source, settings);
|
||||
SetUndoProperties(source);
|
||||
|
||||
UpdateWindowVisibility();
|
||||
}
|
||||
|
||||
void GameCaptureToolbar::on_window_currentIndexChanged(int idx)
|
||||
{
|
||||
OBSSource source = GetSource();
|
||||
if (idx == -1 || !source) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString id = ui->window->itemData(idx).toString();
|
||||
|
||||
SaveOldProperties(source);
|
||||
OBSDataAutoRelease settings = obs_data_create();
|
||||
obs_data_set_string(settings, "window", QT_TO_UTF8(id));
|
||||
obs_source_update(source, settings);
|
||||
SetUndoProperties(source);
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
|
||||
ImageSourceToolbar::ImageSourceToolbar(QWidget *parent, OBSSource source)
|
||||
: SourceToolbar(parent, source),
|
||||
ui(new Ui_ImageSourceToolbar)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
obs_module_t *mod = obs_get_module("image-source");
|
||||
ui->pathLabel->setText(obs_module_get_locale_text(mod, "File"));
|
||||
|
||||
OBSDataAutoRelease settings = obs_source_get_settings(source);
|
||||
std::string file = obs_data_get_string(settings, "file");
|
||||
|
||||
ui->path->setText(file.c_str());
|
||||
}
|
||||
|
||||
ImageSourceToolbar::~ImageSourceToolbar() {}
|
||||
|
||||
void ImageSourceToolbar::on_browse_clicked()
|
||||
{
|
||||
OBSSource source = GetSource();
|
||||
if (!source) {
|
||||
return;
|
||||
}
|
||||
|
||||
obs_property_t *p = obs_properties_get(props.get(), "file");
|
||||
const char *desc = obs_property_description(p);
|
||||
const char *filter = obs_property_path_filter(p);
|
||||
const char *default_path = obs_property_path_default_path(p);
|
||||
|
||||
QString startDir = ui->path->text();
|
||||
if (startDir.isEmpty())
|
||||
startDir = default_path;
|
||||
|
||||
QString path = OpenFile(this, desc, startDir, filter);
|
||||
if (path.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ui->path->setText(path);
|
||||
|
||||
SaveOldProperties(source);
|
||||
OBSDataAutoRelease settings = obs_data_create();
|
||||
obs_data_set_string(settings, "file", QT_TO_UTF8(path));
|
||||
obs_source_update(source, settings);
|
||||
SetUndoProperties(source);
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
|
||||
static inline QColor color_from_int(long long val)
|
||||
{
|
||||
return QColor(val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff, (val >> 24) & 0xff);
|
||||
}
|
||||
|
||||
static inline long long color_to_int(QColor color)
|
||||
{
|
||||
auto shift = [&](unsigned val, int shift) {
|
||||
return ((val & 0xff) << shift);
|
||||
};
|
||||
|
||||
return shift(color.red(), 0) | shift(color.green(), 8) | shift(color.blue(), 16) | shift(color.alpha(), 24);
|
||||
}
|
||||
|
||||
ColorSourceToolbar::ColorSourceToolbar(QWidget *parent, OBSSource source)
|
||||
: SourceToolbar(parent, source),
|
||||
ui(new Ui_ColorSourceToolbar)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
OBSDataAutoRelease settings = obs_source_get_settings(source);
|
||||
unsigned int val = (unsigned int)obs_data_get_int(settings, "color");
|
||||
|
||||
color = color_from_int(val);
|
||||
UpdateColor();
|
||||
}
|
||||
|
||||
ColorSourceToolbar::~ColorSourceToolbar() {}
|
||||
|
||||
void ColorSourceToolbar::UpdateColor()
|
||||
{
|
||||
QPalette palette = QPalette(color);
|
||||
ui->color->setFrameStyle(QFrame::Sunken | QFrame::Panel);
|
||||
ui->color->setText(color.name(QColor::HexRgb));
|
||||
ui->color->setPalette(palette);
|
||||
ui->color->setStyleSheet(QString("background-color :%1; color: %2;")
|
||||
.arg(palette.color(QPalette::Window).name(QColor::HexRgb))
|
||||
.arg(palette.color(QPalette::WindowText).name(QColor::HexRgb)));
|
||||
ui->color->setAutoFillBackground(true);
|
||||
ui->color->setAlignment(Qt::AlignCenter);
|
||||
}
|
||||
|
||||
void ColorSourceToolbar::on_choose_clicked()
|
||||
{
|
||||
OBSSource source = GetSource();
|
||||
if (!source) {
|
||||
return;
|
||||
}
|
||||
|
||||
obs_property_t *p = obs_properties_get(props.get(), "color");
|
||||
const char *desc = obs_property_description(p);
|
||||
|
||||
QColorDialog::ColorDialogOptions options;
|
||||
|
||||
options |= QColorDialog::ShowAlphaChannel;
|
||||
#ifdef __linux__
|
||||
// TODO: Revisit hang on Ubuntu with native dialog
|
||||
options |= QColorDialog::DontUseNativeDialog;
|
||||
#endif
|
||||
|
||||
QColor newColor = QColorDialog::getColor(color, this, desc, options);
|
||||
if (!newColor.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
color = newColor;
|
||||
UpdateColor();
|
||||
|
||||
SaveOldProperties(source);
|
||||
|
||||
OBSDataAutoRelease settings = obs_data_create();
|
||||
obs_data_set_int(settings, "color", color_to_int(color));
|
||||
obs_source_update(source, settings);
|
||||
|
||||
SetUndoProperties(source);
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
|
||||
extern void MakeQFont(obs_data_t *font_obj, QFont &font, bool limit = false);
|
||||
|
||||
TextSourceToolbar::TextSourceToolbar(QWidget *parent, OBSSource source)
|
||||
: SourceToolbar(parent, source),
|
||||
ui(new Ui_TextSourceToolbar)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
OBSDataAutoRelease settings = obs_source_get_settings(source);
|
||||
|
||||
const char *id = obs_source_get_unversioned_id(source);
|
||||
bool ft2 = strcmp(id, "text_ft2_source") == 0;
|
||||
bool read_from_file = obs_data_get_bool(settings, ft2 ? "from_file" : "read_from_file");
|
||||
|
||||
OBSDataAutoRelease font_obj = obs_data_get_obj(settings, "font");
|
||||
MakeQFont(font_obj, font);
|
||||
|
||||
// Use "color1" if it's a freetype source and "color" elsewise
|
||||
unsigned int val = (unsigned int)obs_data_get_int(
|
||||
settings, (strncmp(obs_source_get_id(source), "text_ft2_source", 15) == 0) ? "color1" : "color");
|
||||
|
||||
color = color_from_int(val);
|
||||
|
||||
const char *text = obs_data_get_string(settings, "text");
|
||||
|
||||
bool single_line = !read_from_file && (!text || (strchr(text, '\n') == nullptr));
|
||||
ui->emptySpace->setVisible(!single_line);
|
||||
ui->text->setVisible(single_line);
|
||||
if (single_line)
|
||||
ui->text->setText(text);
|
||||
}
|
||||
|
||||
TextSourceToolbar::~TextSourceToolbar() {}
|
||||
|
||||
void TextSourceToolbar::on_selectFont_clicked()
|
||||
{
|
||||
OBSSource source = GetSource();
|
||||
if (!source) {
|
||||
return;
|
||||
}
|
||||
|
||||
QFontDialog::FontDialogOptions options;
|
||||
uint32_t flags;
|
||||
bool success;
|
||||
|
||||
#ifndef _WIN32
|
||||
options = QFontDialog::DontUseNativeDialog;
|
||||
#endif
|
||||
|
||||
font = QFontDialog::getFont(&success, font, this, QTStr("Basic.PropertiesWindow.SelectFont.WindowTitle"),
|
||||
options);
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
|
||||
OBSDataAutoRelease font_obj = obs_data_create();
|
||||
|
||||
obs_data_set_string(font_obj, "face", QT_TO_UTF8(font.family()));
|
||||
obs_data_set_string(font_obj, "style", QT_TO_UTF8(font.styleName()));
|
||||
obs_data_set_int(font_obj, "size", font.pointSize());
|
||||
flags = font.bold() ? OBS_FONT_BOLD : 0;
|
||||
flags |= font.italic() ? OBS_FONT_ITALIC : 0;
|
||||
flags |= font.underline() ? OBS_FONT_UNDERLINE : 0;
|
||||
flags |= font.strikeOut() ? OBS_FONT_STRIKEOUT : 0;
|
||||
obs_data_set_int(font_obj, "flags", flags);
|
||||
|
||||
SaveOldProperties(source);
|
||||
|
||||
OBSDataAutoRelease settings = obs_data_create();
|
||||
|
||||
obs_data_set_obj(settings, "font", font_obj);
|
||||
|
||||
obs_source_update(source, settings);
|
||||
|
||||
SetUndoProperties(source);
|
||||
}
|
||||
|
||||
void TextSourceToolbar::on_selectColor_clicked()
|
||||
{
|
||||
OBSSource source = GetSource();
|
||||
if (!source) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool freetype = strncmp(obs_source_get_id(source), "text_ft2_source", 15) == 0;
|
||||
|
||||
obs_property_t *p = obs_properties_get(props.get(), freetype ? "color1" : "color");
|
||||
|
||||
const char *desc = obs_property_description(p);
|
||||
|
||||
QColorDialog::ColorDialogOptions options;
|
||||
|
||||
options |= QColorDialog::ShowAlphaChannel;
|
||||
#ifdef __linux__
|
||||
// TODO: Revisit hang on Ubuntu with native dialog
|
||||
options |= QColorDialog::DontUseNativeDialog;
|
||||
#endif
|
||||
|
||||
QColor newColor = QColorDialog::getColor(color, this, desc, options);
|
||||
if (!newColor.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
color = newColor;
|
||||
|
||||
SaveOldProperties(source);
|
||||
|
||||
OBSDataAutoRelease settings = obs_data_create();
|
||||
if (freetype) {
|
||||
obs_data_set_int(settings, "color1", color_to_int(color));
|
||||
obs_data_set_int(settings, "color2", color_to_int(color));
|
||||
} else {
|
||||
obs_data_set_int(settings, "color", color_to_int(color));
|
||||
}
|
||||
obs_source_update(source, settings);
|
||||
|
||||
SetUndoProperties(source);
|
||||
}
|
||||
|
||||
void TextSourceToolbar::on_text_textChanged()
|
||||
{
|
||||
OBSSource source = GetSource();
|
||||
if (!source) {
|
||||
return;
|
||||
}
|
||||
std::string newText = QT_TO_UTF8(ui->text->text());
|
||||
OBSDataAutoRelease settings = obs_source_get_settings(source);
|
||||
if (newText == obs_data_get_string(settings, "text")) {
|
||||
return;
|
||||
}
|
||||
SaveOldProperties(source);
|
||||
|
||||
obs_data_set_string(settings, "text", newText.c_str());
|
||||
obs_source_update(source, nullptr);
|
||||
|
||||
SetUndoProperties(source, true);
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <obs.hpp>
|
||||
#include <QWidget>
|
||||
|
||||
class Ui_BrowserSourceToolbar;
|
||||
class Ui_DeviceSelectToolbar;
|
||||
class Ui_GameCaptureToolbar;
|
||||
class Ui_ImageSourceToolbar;
|
||||
class Ui_ColorSourceToolbar;
|
||||
class Ui_TextSourceToolbar;
|
||||
|
||||
class SourceToolbar : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
OBSWeakSource weakSource;
|
||||
|
||||
protected:
|
||||
using properties_delete_t = decltype(&obs_properties_destroy);
|
||||
using properties_t = std::unique_ptr<obs_properties_t, properties_delete_t>;
|
||||
|
||||
properties_t props;
|
||||
OBSDataAutoRelease oldData;
|
||||
|
||||
void SaveOldProperties(obs_source_t *source);
|
||||
void SetUndoProperties(obs_source_t *source, bool repeatable = false);
|
||||
|
||||
public:
|
||||
SourceToolbar(QWidget *parent, OBSSource source);
|
||||
|
||||
OBSSource GetSource() { return OBSGetStrongRef(weakSource); }
|
||||
|
||||
public slots:
|
||||
virtual void Update() {}
|
||||
};
|
||||
|
||||
class BrowserToolbar : public SourceToolbar {
|
||||
Q_OBJECT
|
||||
|
||||
std::unique_ptr<Ui_BrowserSourceToolbar> ui;
|
||||
|
||||
public:
|
||||
BrowserToolbar(QWidget *parent, OBSSource source);
|
||||
~BrowserToolbar();
|
||||
|
||||
public slots:
|
||||
void on_refresh_clicked();
|
||||
};
|
||||
|
||||
class ComboSelectToolbar : public SourceToolbar {
|
||||
Q_OBJECT
|
||||
|
||||
protected:
|
||||
std::unique_ptr<Ui_DeviceSelectToolbar> ui;
|
||||
const char *prop_name;
|
||||
bool is_int = false;
|
||||
|
||||
public:
|
||||
ComboSelectToolbar(QWidget *parent, OBSSource source);
|
||||
~ComboSelectToolbar();
|
||||
virtual void Init();
|
||||
|
||||
public slots:
|
||||
void on_device_currentIndexChanged(int idx);
|
||||
};
|
||||
|
||||
class AudioCaptureToolbar : public ComboSelectToolbar {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AudioCaptureToolbar(QWidget *parent, OBSSource source);
|
||||
void Init() override;
|
||||
};
|
||||
|
||||
class WindowCaptureToolbar : public ComboSelectToolbar {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
WindowCaptureToolbar(QWidget *parent, OBSSource source);
|
||||
void Init() override;
|
||||
};
|
||||
|
||||
class ApplicationAudioCaptureToolbar : public ComboSelectToolbar {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ApplicationAudioCaptureToolbar(QWidget *parent, OBSSource source);
|
||||
void Init() override;
|
||||
};
|
||||
|
||||
class DisplayCaptureToolbar : public ComboSelectToolbar {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DisplayCaptureToolbar(QWidget *parent, OBSSource source);
|
||||
void Init() override;
|
||||
};
|
||||
|
||||
class DeviceCaptureToolbar : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
OBSWeakSource weakSource;
|
||||
|
||||
std::unique_ptr<Ui_DeviceSelectToolbar> ui;
|
||||
const char *activateText;
|
||||
const char *deactivateText;
|
||||
bool active;
|
||||
|
||||
public:
|
||||
DeviceCaptureToolbar(QWidget *parent, OBSSource source);
|
||||
~DeviceCaptureToolbar();
|
||||
|
||||
public slots:
|
||||
void on_activateButton_clicked();
|
||||
};
|
||||
|
||||
class GameCaptureToolbar : public SourceToolbar {
|
||||
Q_OBJECT
|
||||
|
||||
std::unique_ptr<Ui_GameCaptureToolbar> ui;
|
||||
|
||||
void UpdateWindowVisibility();
|
||||
|
||||
public:
|
||||
GameCaptureToolbar(QWidget *parent, OBSSource source);
|
||||
~GameCaptureToolbar();
|
||||
|
||||
public slots:
|
||||
void on_mode_currentIndexChanged(int idx);
|
||||
void on_window_currentIndexChanged(int idx);
|
||||
};
|
||||
|
||||
class ImageSourceToolbar : public SourceToolbar {
|
||||
Q_OBJECT
|
||||
|
||||
std::unique_ptr<Ui_ImageSourceToolbar> ui;
|
||||
|
||||
public:
|
||||
ImageSourceToolbar(QWidget *parent, OBSSource source);
|
||||
~ImageSourceToolbar();
|
||||
|
||||
public slots:
|
||||
void on_browse_clicked();
|
||||
};
|
||||
|
||||
class ColorSourceToolbar : public SourceToolbar {
|
||||
Q_OBJECT
|
||||
|
||||
std::unique_ptr<Ui_ColorSourceToolbar> ui;
|
||||
QColor color;
|
||||
|
||||
void UpdateColor();
|
||||
|
||||
public:
|
||||
ColorSourceToolbar(QWidget *parent, OBSSource source);
|
||||
~ColorSourceToolbar();
|
||||
|
||||
public slots:
|
||||
void on_choose_clicked();
|
||||
};
|
||||
|
||||
class TextSourceToolbar : public SourceToolbar {
|
||||
Q_OBJECT
|
||||
|
||||
std::unique_ptr<Ui_TextSourceToolbar> ui;
|
||||
QFont font;
|
||||
QColor color;
|
||||
|
||||
public:
|
||||
TextSourceToolbar(QWidget *parent, OBSSource source);
|
||||
~TextSourceToolbar();
|
||||
|
||||
public slots:
|
||||
void on_selectFont_clicked();
|
||||
void on_selectColor_clicked();
|
||||
void on_text_textChanged();
|
||||
};
|
||||
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<description>OBS Studio</description>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel
|
||||
level="asInvoker"
|
||||
uiAccess="false"
|
||||
/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- Windows 10 and Windows 11 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
</assembly>
|
||||
@@ -1,26 +0,0 @@
|
||||
IDI_ICON1 ICON DISCARDABLE "../cmake/bundle/windows/obs-studio.ico"
|
||||
|
||||
1 VERSIONINFO
|
||||
FILEVERSION ${UI_VERSION_MAJOR},${UI_VERSION_MINOR},${UI_VERSION_PATCH},0
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904B0"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "OBS"
|
||||
VALUE "FileDescription", "OBS Studio"
|
||||
VALUE "FileVersion", "${UI_VERSION}"
|
||||
VALUE "InternalName", "obs"
|
||||
VALUE "OriginalFilename", "obs"
|
||||
VALUE "ProductName", "OBS Studio"
|
||||
VALUE "ProductVersion", "${UI_VERSION}"
|
||||
VALUE "Comments", "Free and open source software for video recording and live streaming"
|
||||
VALUE "LegalCopyright", "(C) Lain Bailey"
|
||||
END
|
||||
END
|
||||
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x0409, 0x04B0
|
||||
END
|
||||
END
|
||||
-1613
File diff suppressed because it is too large.
Load diff
@@ -1,202 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QList>
|
||||
#include <QVector>
|
||||
#include <QPointer>
|
||||
#include <QListView>
|
||||
#include <QCheckBox>
|
||||
#include <QStaticText>
|
||||
#include <QSvgRenderer>
|
||||
#include <QAbstractListModel>
|
||||
#include <QStyledItemDelegate>
|
||||
#include <obs.hpp>
|
||||
#include <obs-frontend-api.h>
|
||||
|
||||
class QLabel;
|
||||
class OBSSourceLabel;
|
||||
class QCheckBox;
|
||||
class QLineEdit;
|
||||
class SourceTree;
|
||||
class QSpacerItem;
|
||||
class QHBoxLayout;
|
||||
class VisibilityItemWidget;
|
||||
|
||||
class SourceTreeItem : public QFrame {
|
||||
Q_OBJECT
|
||||
|
||||
friend class SourceTree;
|
||||
friend class SourceTreeModel;
|
||||
|
||||
void mouseDoubleClickEvent(QMouseEvent *event) override;
|
||||
void enterEvent(QEnterEvent *event) override;
|
||||
void leaveEvent(QEvent *event) override;
|
||||
|
||||
virtual bool eventFilter(QObject *object, QEvent *event) override;
|
||||
|
||||
void Update(bool force);
|
||||
|
||||
enum class Type {
|
||||
Unknown,
|
||||
Item,
|
||||
Group,
|
||||
SubItem,
|
||||
};
|
||||
|
||||
void DisconnectSignals();
|
||||
void ReconnectSignals();
|
||||
|
||||
Type type = Type::Unknown;
|
||||
|
||||
public:
|
||||
explicit SourceTreeItem(SourceTree *tree, OBSSceneItem sceneitem);
|
||||
bool IsEditing();
|
||||
|
||||
private:
|
||||
QSpacerItem *spacer = nullptr;
|
||||
QCheckBox *expand = nullptr;
|
||||
QLabel *iconLabel = nullptr;
|
||||
QCheckBox *vis = nullptr;
|
||||
QCheckBox *lock = nullptr;
|
||||
QHBoxLayout *boxLayout = nullptr;
|
||||
OBSSourceLabel *label = nullptr;
|
||||
|
||||
QLineEdit *editor = nullptr;
|
||||
|
||||
std::string newName;
|
||||
|
||||
SourceTree *tree;
|
||||
OBSSceneItem sceneitem;
|
||||
std::vector<OBSSignal> sigs;
|
||||
|
||||
virtual void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
void ExitEditModeInternal(bool save);
|
||||
|
||||
private slots:
|
||||
void Clear();
|
||||
|
||||
void EnterEditMode();
|
||||
void ExitEditMode(bool save);
|
||||
|
||||
void VisibilityChanged(bool visible);
|
||||
void LockedChanged(bool locked);
|
||||
|
||||
void ExpandClicked(bool checked);
|
||||
|
||||
void Select();
|
||||
void Deselect();
|
||||
};
|
||||
|
||||
class SourceTreeModel : public QAbstractListModel {
|
||||
Q_OBJECT
|
||||
|
||||
friend class SourceTree;
|
||||
friend class SourceTreeItem;
|
||||
|
||||
SourceTree *st;
|
||||
QVector<OBSSceneItem> items;
|
||||
bool hasGroups = false;
|
||||
|
||||
static void OBSFrontendEvent(enum obs_frontend_event event, void *ptr);
|
||||
void Clear();
|
||||
void SceneChanged();
|
||||
void ReorderItems();
|
||||
|
||||
void Add(obs_sceneitem_t *item);
|
||||
void Remove(obs_sceneitem_t *item);
|
||||
OBSSceneItem Get(int idx);
|
||||
QString GetNewGroupName();
|
||||
void AddGroup();
|
||||
|
||||
void GroupSelectedItems(QModelIndexList &indices);
|
||||
void UngroupSelectedGroups(QModelIndexList &indices);
|
||||
|
||||
void ExpandGroup(obs_sceneitem_t *item);
|
||||
void CollapseGroup(obs_sceneitem_t *item);
|
||||
|
||||
void UpdateGroupState(bool update);
|
||||
|
||||
public:
|
||||
explicit SourceTreeModel(SourceTree *st);
|
||||
|
||||
virtual int rowCount(const QModelIndex &parent) const override;
|
||||
virtual QVariant data(const QModelIndex &index, int role) const override;
|
||||
|
||||
virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
virtual Qt::DropActions supportedDropActions() const override;
|
||||
};
|
||||
|
||||
class SourceTree : public QListView {
|
||||
Q_OBJECT
|
||||
|
||||
bool ignoreReorder = false;
|
||||
|
||||
friend class SourceTreeModel;
|
||||
friend class SourceTreeItem;
|
||||
|
||||
bool textPrepared = false;
|
||||
QStaticText textNoSources;
|
||||
QSvgRenderer iconNoSources;
|
||||
|
||||
OBSData undoSceneData;
|
||||
|
||||
bool iconsVisible = true;
|
||||
|
||||
void UpdateNoSourcesMessage();
|
||||
|
||||
void ResetWidgets();
|
||||
void UpdateWidget(const QModelIndex &idx, obs_sceneitem_t *item);
|
||||
void UpdateWidgets(bool force = false);
|
||||
|
||||
inline SourceTreeModel *GetStm() const { return reinterpret_cast<SourceTreeModel *>(model()); }
|
||||
|
||||
public:
|
||||
inline SourceTreeItem *GetItemWidget(int idx)
|
||||
{
|
||||
QWidget *widget = indexWidget(GetStm()->createIndex(idx, 0));
|
||||
return reinterpret_cast<SourceTreeItem *>(widget);
|
||||
}
|
||||
|
||||
explicit SourceTree(QWidget *parent = nullptr);
|
||||
|
||||
inline bool IgnoreReorder() const { return ignoreReorder; }
|
||||
inline void Clear() { GetStm()->Clear(); }
|
||||
|
||||
inline void Add(obs_sceneitem_t *item) { GetStm()->Add(item); }
|
||||
inline OBSSceneItem Get(int idx) { return GetStm()->Get(idx); }
|
||||
inline QString GetNewGroupName() { return GetStm()->GetNewGroupName(); }
|
||||
|
||||
void SelectItem(obs_sceneitem_t *sceneitem, bool select);
|
||||
|
||||
bool MultipleBaseSelected() const;
|
||||
bool GroupsSelected() const;
|
||||
bool GroupedItemsSelected() const;
|
||||
|
||||
void UpdateIcons();
|
||||
void SetIconsVisible(bool visible);
|
||||
|
||||
public slots:
|
||||
inline void ReorderItems() { GetStm()->ReorderItems(); }
|
||||
inline void RefreshItems() { GetStm()->SceneChanged(); }
|
||||
void Remove(OBSSceneItem item, OBSScene scene);
|
||||
void GroupSelectedItems();
|
||||
void UngroupSelectedGroups();
|
||||
void AddGroup();
|
||||
bool Edit(int idx);
|
||||
void NewGroupEdit(int idx);
|
||||
|
||||
protected:
|
||||
virtual void mouseDoubleClickEvent(QMouseEvent *event) override;
|
||||
virtual void dropEvent(QDropEvent *event) override;
|
||||
virtual void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
virtual void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) override;
|
||||
};
|
||||
|
||||
class SourceTreeDelegate : public QStyledItemDelegate {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SourceTreeDelegate(QObject *parent);
|
||||
virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
||||
};
|
||||
@@ -1,51 +0,0 @@
|
||||
#ifndef MAC_UPDATER_H
|
||||
#define MAC_UPDATER_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <QThread>
|
||||
#include <QString>
|
||||
#include <QObject>
|
||||
|
||||
class QAction;
|
||||
|
||||
class MacUpdateThread : public QThread {
|
||||
Q_OBJECT
|
||||
|
||||
bool manualUpdate;
|
||||
|
||||
virtual void run() override;
|
||||
|
||||
void info(const QString &title, const QString &text);
|
||||
|
||||
signals:
|
||||
void Result(const QString &branch, bool manual);
|
||||
|
||||
private slots:
|
||||
void infoMsg(const QString &title, const QString &text);
|
||||
|
||||
public:
|
||||
MacUpdateThread(bool manual) : manualUpdate(manual) {}
|
||||
};
|
||||
|
||||
#ifdef __OBJC__
|
||||
@class OBSUpdateDelegate;
|
||||
#endif
|
||||
|
||||
class OBSSparkle : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
OBSSparkle(const char *branch, QAction *checkForUpdatesAction);
|
||||
void setBranch(const char *branch);
|
||||
void checkForUpdates(bool manualCheck);
|
||||
|
||||
private:
|
||||
#ifdef __OBJC__
|
||||
OBSUpdateDelegate *updaterDelegate;
|
||||
#else
|
||||
void *updaterDelegate;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,79 +0,0 @@
|
||||
#include "mac-update.hpp"
|
||||
|
||||
#include <qaction.h>
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <Sparkle/Sparkle.h>
|
||||
|
||||
@interface OBSUpdateDelegate : NSObject <SPUUpdaterDelegate> {
|
||||
}
|
||||
@property (copy) NSString *branch;
|
||||
@property (nonatomic) SPUStandardUpdaterController *updaterController;
|
||||
@end
|
||||
|
||||
@implementation OBSUpdateDelegate {
|
||||
}
|
||||
|
||||
@synthesize branch;
|
||||
|
||||
- (nonnull NSSet<NSString *> *)allowedChannelsForUpdater:(nonnull SPUUpdater *)updater
|
||||
{
|
||||
return [NSSet setWithObject:branch];
|
||||
}
|
||||
|
||||
- (void)observeCanCheckForUpdatesWithAction:(QAction *)action
|
||||
{
|
||||
[_updaterController.updater addObserver:self forKeyPath:NSStringFromSelector(@selector(canCheckForUpdates))
|
||||
options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew)
|
||||
context:(void *) action];
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath
|
||||
ofObject:(id)object
|
||||
change:(NSDictionary<NSKeyValueChangeKey, id> *)change
|
||||
context:(void *)context
|
||||
{
|
||||
if ([keyPath isEqualToString:NSStringFromSelector(@selector(canCheckForUpdates))]) {
|
||||
QAction *menuAction = (QAction *) context;
|
||||
menuAction->setEnabled(_updaterController.updater.canCheckForUpdates);
|
||||
} else {
|
||||
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
@autoreleasepool {
|
||||
[_updaterController.updater removeObserver:self forKeyPath:NSStringFromSelector(@selector(canCheckForUpdates))];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
OBSSparkle::OBSSparkle(const char *branch, QAction *checkForUpdatesAction)
|
||||
{
|
||||
@autoreleasepool {
|
||||
updaterDelegate = [[OBSUpdateDelegate alloc] init];
|
||||
updaterDelegate.branch = [NSString stringWithUTF8String:branch];
|
||||
updaterDelegate.updaterController =
|
||||
[[SPUStandardUpdaterController alloc] initWithStartingUpdater:YES updaterDelegate:updaterDelegate
|
||||
userDriverDelegate:nil];
|
||||
[updaterDelegate observeCanCheckForUpdatesWithAction:checkForUpdatesAction];
|
||||
}
|
||||
}
|
||||
|
||||
void OBSSparkle::setBranch(const char *branch)
|
||||
{
|
||||
updaterDelegate.branch = [NSString stringWithUTF8String:branch];
|
||||
}
|
||||
|
||||
void OBSSparkle::checkForUpdates(bool manualCheck)
|
||||
{
|
||||
@autoreleasepool {
|
||||
if (manualCheck) {
|
||||
[updaterDelegate.updaterController checkForUpdates:nil];
|
||||
} else {
|
||||
[updaterDelegate.updaterController.updater checkForUpdatesInBackground];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
#include "moc_visibility-item-widget.cpp"
|
||||
#include "obs-app.hpp"
|
||||
#include "source-label.hpp"
|
||||
|
||||
#include <qt-wrappers.hpp>
|
||||
#include <QListWidget>
|
||||
#include <QLineEdit>
|
||||
#include <QHBoxLayout>
|
||||
#include <QMessageBox>
|
||||
#include <QLabel>
|
||||
#include <QKeyEvent>
|
||||
#include <QCheckBox>
|
||||
|
||||
VisibilityItemWidget::VisibilityItemWidget(obs_source_t *source_)
|
||||
: source(source_),
|
||||
enabledSignal(obs_source_get_signal_handler(source), "enable", OBSSourceEnabled, this)
|
||||
{
|
||||
bool enabled = obs_source_enabled(source);
|
||||
|
||||
vis = new QCheckBox();
|
||||
vis->setProperty("class", "checkbox-icon indicator-visibility");
|
||||
vis->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
|
||||
vis->setChecked(enabled);
|
||||
|
||||
label = new OBSSourceLabel(source);
|
||||
label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||
|
||||
QHBoxLayout *itemLayout = new QHBoxLayout();
|
||||
itemLayout->addWidget(vis);
|
||||
itemLayout->addWidget(label);
|
||||
itemLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
setLayout(itemLayout);
|
||||
|
||||
connect(vis, &QCheckBox::clicked, [this](bool visible) { obs_source_set_enabled(source, visible); });
|
||||
}
|
||||
|
||||
void VisibilityItemWidget::OBSSourceEnabled(void *param, calldata_t *data)
|
||||
{
|
||||
VisibilityItemWidget *window = reinterpret_cast<VisibilityItemWidget *>(param);
|
||||
bool enabled = calldata_bool(data, "enabled");
|
||||
|
||||
QMetaObject::invokeMethod(window, "SourceEnabled", Q_ARG(bool, enabled));
|
||||
}
|
||||
|
||||
void VisibilityItemWidget::SourceEnabled(bool enabled)
|
||||
{
|
||||
if (vis->isChecked() != enabled)
|
||||
vis->setChecked(enabled);
|
||||
}
|
||||
|
||||
void VisibilityItemWidget::SetColor(const QColor &color, bool active_, bool selected_)
|
||||
{
|
||||
/* Do not update unless the state has actually changed */
|
||||
if (active_ == active && selected_ == selected)
|
||||
return;
|
||||
|
||||
QPalette pal = vis->palette();
|
||||
pal.setColor(QPalette::WindowText, color);
|
||||
vis->setPalette(pal);
|
||||
|
||||
label->setStyleSheet(QString("color: %1;").arg(color.name()));
|
||||
|
||||
active = active_;
|
||||
selected = selected_;
|
||||
}
|
||||
|
||||
VisibilityItemDelegate::VisibilityItemDelegate(QObject *parent) : QStyledItemDelegate(parent) {}
|
||||
|
||||
void VisibilityItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index) const
|
||||
{
|
||||
QStyledItemDelegate::paint(painter, option, index);
|
||||
|
||||
QObject *parentObj = parent();
|
||||
QListWidget *list = qobject_cast<QListWidget *>(parentObj);
|
||||
if (!list)
|
||||
return;
|
||||
|
||||
QListWidgetItem *item = list->item(index.row());
|
||||
VisibilityItemWidget *widget = qobject_cast<VisibilityItemWidget *>(list->itemWidget(item));
|
||||
if (!widget)
|
||||
return;
|
||||
|
||||
bool selected = option.state.testFlag(QStyle::State_Selected);
|
||||
bool active = option.state.testFlag(QStyle::State_Active);
|
||||
|
||||
QPalette palette = list->palette();
|
||||
#if defined(_WIN32) || defined(__APPLE__)
|
||||
QPalette::ColorGroup group = active ? QPalette::Active : QPalette::Inactive;
|
||||
#else
|
||||
QPalette::ColorGroup group = QPalette::Active;
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
QPalette::ColorRole highlightRole = QPalette::WindowText;
|
||||
#else
|
||||
QPalette::ColorRole highlightRole = QPalette::HighlightedText;
|
||||
#endif
|
||||
|
||||
QPalette::ColorRole role;
|
||||
|
||||
if (selected && active)
|
||||
role = highlightRole;
|
||||
else
|
||||
role = QPalette::WindowText;
|
||||
|
||||
widget->SetColor(palette.color(group, role), active, selected);
|
||||
}
|
||||
|
||||
bool VisibilityItemDelegate::eventFilter(QObject *object, QEvent *event)
|
||||
{
|
||||
QWidget *editor = qobject_cast<QWidget *>(object);
|
||||
if (!editor)
|
||||
return false;
|
||||
|
||||
if (event->type() == QEvent::KeyPress) {
|
||||
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
|
||||
|
||||
if (keyEvent->key() == Qt::Key_Tab || keyEvent->key() == Qt::Key_Backtab) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return QStyledItemDelegate::eventFilter(object, event);
|
||||
}
|
||||
|
||||
void SetupVisibilityItem(QListWidget *list, QListWidgetItem *item, obs_source_t *source)
|
||||
{
|
||||
VisibilityItemWidget *baseWidget = new VisibilityItemWidget(source);
|
||||
|
||||
list->setItemWidget(item, baseWidget);
|
||||
}
|
||||
@@ -1,288 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWizard>
|
||||
#include <QPointer>
|
||||
#include <QFormLayout>
|
||||
#include <QWizardPage>
|
||||
|
||||
#include <condition_variable>
|
||||
#include <utility>
|
||||
#include <thread>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
|
||||
class Ui_AutoConfigStartPage;
|
||||
class Ui_AutoConfigVideoPage;
|
||||
class Ui_AutoConfigStreamPage;
|
||||
class Ui_AutoConfigTestPage;
|
||||
|
||||
class AutoConfigStreamPage;
|
||||
class Auth;
|
||||
|
||||
class AutoConfig : public QWizard {
|
||||
Q_OBJECT
|
||||
|
||||
friend class AutoConfigStartPage;
|
||||
friend class AutoConfigVideoPage;
|
||||
friend class AutoConfigStreamPage;
|
||||
friend class AutoConfigTestPage;
|
||||
|
||||
enum class Type {
|
||||
Invalid,
|
||||
Streaming,
|
||||
Recording,
|
||||
VirtualCam,
|
||||
};
|
||||
|
||||
enum class Service {
|
||||
Twitch,
|
||||
YouTube,
|
||||
AmazonIVS,
|
||||
Other,
|
||||
};
|
||||
|
||||
enum class Encoder {
|
||||
x264,
|
||||
NVENC,
|
||||
QSV,
|
||||
AMD,
|
||||
Apple,
|
||||
Stream,
|
||||
};
|
||||
|
||||
enum class Quality {
|
||||
Stream,
|
||||
High,
|
||||
};
|
||||
|
||||
enum class FPSType : int {
|
||||
PreferHighFPS,
|
||||
PreferHighRes,
|
||||
UseCurrent,
|
||||
fps30,
|
||||
fps60,
|
||||
};
|
||||
|
||||
struct StreamServer {
|
||||
std::string name;
|
||||
std::string address;
|
||||
};
|
||||
|
||||
static inline const char *GetEncoderId(Encoder enc);
|
||||
|
||||
AutoConfigStreamPage *streamPage = nullptr;
|
||||
|
||||
Service service = Service::Other;
|
||||
Quality recordingQuality = Quality::Stream;
|
||||
Encoder recordingEncoder = Encoder::Stream;
|
||||
Encoder streamingEncoder = Encoder::x264;
|
||||
Type type = Type::Streaming;
|
||||
FPSType fpsType = FPSType::PreferHighFPS;
|
||||
int idealBitrate = 2500;
|
||||
struct {
|
||||
std::optional<int> targetBitrate;
|
||||
std::optional<int> bitrate;
|
||||
bool testSuccessful = false;
|
||||
} multitrackVideo;
|
||||
int baseResolutionCX = 1920;
|
||||
int baseResolutionCY = 1080;
|
||||
int idealResolutionCX = 1280;
|
||||
int idealResolutionCY = 720;
|
||||
int idealFPSNum = 60;
|
||||
int idealFPSDen = 1;
|
||||
std::string serviceName;
|
||||
std::string serverName;
|
||||
std::string server;
|
||||
std::vector<StreamServer> serviceConfigServers;
|
||||
std::string key;
|
||||
|
||||
bool hardwareEncodingAvailable = false;
|
||||
bool nvencAvailable = false;
|
||||
bool qsvAvailable = false;
|
||||
bool vceAvailable = false;
|
||||
bool appleAvailable = false;
|
||||
|
||||
int startingBitrate = 2500;
|
||||
bool customServer = false;
|
||||
bool bandwidthTest = false;
|
||||
bool testMultitrackVideo = false;
|
||||
bool testRegions = true;
|
||||
bool twitchAuto = false;
|
||||
bool amazonIVSAuto = false;
|
||||
bool regionUS = true;
|
||||
bool regionEU = true;
|
||||
bool regionAsia = true;
|
||||
bool regionOther = true;
|
||||
bool preferHighFPS = false;
|
||||
bool preferHardware = false;
|
||||
int specificFPSNum = 0;
|
||||
int specificFPSDen = 0;
|
||||
|
||||
void TestHardwareEncoding();
|
||||
bool CanTestServer(const char *server);
|
||||
|
||||
virtual void done(int result) override;
|
||||
|
||||
void SaveStreamSettings();
|
||||
void SaveSettings();
|
||||
|
||||
public:
|
||||
AutoConfig(QWidget *parent);
|
||||
~AutoConfig();
|
||||
|
||||
enum Page {
|
||||
StartPage,
|
||||
VideoPage,
|
||||
StreamPage,
|
||||
TestPage,
|
||||
};
|
||||
};
|
||||
|
||||
class AutoConfigStartPage : public QWizardPage {
|
||||
Q_OBJECT
|
||||
|
||||
friend class AutoConfig;
|
||||
|
||||
std::unique_ptr<Ui_AutoConfigStartPage> ui;
|
||||
|
||||
public:
|
||||
AutoConfigStartPage(QWidget *parent = nullptr);
|
||||
~AutoConfigStartPage();
|
||||
|
||||
virtual int nextId() const override;
|
||||
|
||||
public slots:
|
||||
void on_prioritizeStreaming_clicked();
|
||||
void on_prioritizeRecording_clicked();
|
||||
void PrioritizeVCam();
|
||||
};
|
||||
|
||||
class AutoConfigVideoPage : public QWizardPage {
|
||||
Q_OBJECT
|
||||
|
||||
friend class AutoConfig;
|
||||
|
||||
std::unique_ptr<Ui_AutoConfigVideoPage> ui;
|
||||
|
||||
public:
|
||||
AutoConfigVideoPage(QWidget *parent = nullptr);
|
||||
~AutoConfigVideoPage();
|
||||
|
||||
virtual int nextId() const override;
|
||||
virtual bool validatePage() override;
|
||||
};
|
||||
|
||||
class AutoConfigStreamPage : public QWizardPage {
|
||||
Q_OBJECT
|
||||
|
||||
friend class AutoConfig;
|
||||
|
||||
enum class Section : int {
|
||||
Connect,
|
||||
StreamKey,
|
||||
};
|
||||
|
||||
std::shared_ptr<Auth> auth;
|
||||
|
||||
std::unique_ptr<Ui_AutoConfigStreamPage> ui;
|
||||
QString lastService;
|
||||
bool ready = false;
|
||||
|
||||
void LoadServices(bool showAll);
|
||||
inline bool IsCustomService() const;
|
||||
|
||||
public:
|
||||
AutoConfigStreamPage(QWidget *parent = nullptr);
|
||||
~AutoConfigStreamPage();
|
||||
|
||||
virtual bool isComplete() const override;
|
||||
virtual int nextId() const override;
|
||||
virtual bool validatePage() override;
|
||||
|
||||
void OnAuthConnected();
|
||||
void OnOAuthStreamKeyConnected();
|
||||
|
||||
public slots:
|
||||
void on_show_clicked();
|
||||
void on_connectAccount_clicked();
|
||||
void on_disconnectAccount_clicked();
|
||||
void on_useStreamKey_clicked();
|
||||
void on_preferHardware_clicked();
|
||||
void ServiceChanged();
|
||||
void UpdateKeyLink();
|
||||
void UpdateMoreInfoLink();
|
||||
void UpdateServerList();
|
||||
void UpdateCompleted();
|
||||
|
||||
void reset_service_ui_fields(std::string &service);
|
||||
};
|
||||
|
||||
class AutoConfigTestPage : public QWizardPage {
|
||||
Q_OBJECT
|
||||
|
||||
friend class AutoConfig;
|
||||
|
||||
QPointer<QFormLayout> results;
|
||||
|
||||
std::unique_ptr<Ui_AutoConfigTestPage> ui;
|
||||
std::thread testThread;
|
||||
std::condition_variable cv;
|
||||
std::mutex m;
|
||||
bool cancel = false;
|
||||
bool started = false;
|
||||
|
||||
enum class Stage {
|
||||
Starting,
|
||||
BandwidthTest,
|
||||
StreamEncoder,
|
||||
RecordingEncoder,
|
||||
Finished,
|
||||
};
|
||||
|
||||
Stage stage = Stage::Starting;
|
||||
bool softwareTested = false;
|
||||
|
||||
void StartBandwidthStage();
|
||||
void StartStreamEncoderStage();
|
||||
void StartRecordingEncoderStage();
|
||||
|
||||
void FindIdealHardwareResolution();
|
||||
bool TestSoftwareEncoding();
|
||||
|
||||
void TestBandwidthThread();
|
||||
void TestStreamEncoderThread();
|
||||
void TestRecordingEncoderThread();
|
||||
|
||||
void FinalizeResults();
|
||||
|
||||
struct ServerInfo {
|
||||
std::string name;
|
||||
std::string address;
|
||||
int bitrate = 0;
|
||||
int ms = -1;
|
||||
|
||||
inline ServerInfo() {}
|
||||
|
||||
inline ServerInfo(const char *name_, const char *address_) : name(name_), address(address_) {}
|
||||
};
|
||||
|
||||
void GetServers(std::vector<ServerInfo> &servers);
|
||||
|
||||
public:
|
||||
AutoConfigTestPage(QWidget *parent = nullptr);
|
||||
~AutoConfigTestPage();
|
||||
|
||||
virtual void initializePage() override;
|
||||
virtual void cleanupPage() override;
|
||||
virtual bool isComplete() const override;
|
||||
virtual int nextId() const override;
|
||||
|
||||
public slots:
|
||||
void NextStage();
|
||||
void UpdateMessage(QString message);
|
||||
void Failure(QString message);
|
||||
void Progress(int percentage);
|
||||
};
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,720 +0,0 @@
|
||||
/******************************************************************************
|
||||
Copyright (C) 2023 by Lain Bailey <lain@obsproject.com>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
#include <obs.hpp>
|
||||
#include <util/util.hpp>
|
||||
#include <QMessageBox>
|
||||
#include <QVariant>
|
||||
#include <QFileDialog>
|
||||
#include <QStandardPaths>
|
||||
#include <qt-wrappers.hpp>
|
||||
#include "item-widget-helpers.hpp"
|
||||
#include "window-basic-main.hpp"
|
||||
#include "window-importer.hpp"
|
||||
#include "window-namedialog.hpp"
|
||||
|
||||
// MARK: Constant Expressions
|
||||
|
||||
constexpr std::string_view OBSSceneCollectionPath = "/obs-studio/basic/scenes/";
|
||||
|
||||
// MARK: - Anonymous Namespace
|
||||
namespace {
|
||||
QList<QString> sortedSceneCollections{};
|
||||
|
||||
void updateSortedSceneCollections(const OBSSceneCollectionCache &collections)
|
||||
{
|
||||
const QLocale locale = QLocale::system();
|
||||
QList<QString> newList{};
|
||||
|
||||
for (auto [collectionName, _] : collections) {
|
||||
QString entry = QString::fromStdString(collectionName);
|
||||
newList.append(entry);
|
||||
}
|
||||
|
||||
std::sort(newList.begin(), newList.end(), [&locale](const QString &lhs, const QString &rhs) -> bool {
|
||||
int result = QString::localeAwareCompare(locale.toLower(lhs), locale.toLower(rhs));
|
||||
|
||||
return (result < 0);
|
||||
});
|
||||
|
||||
sortedSceneCollections.swap(newList);
|
||||
}
|
||||
|
||||
void cleanBackupCollision(const OBSSceneCollection &collection)
|
||||
{
|
||||
std::filesystem::path backupFilePath = collection.collectionFile;
|
||||
backupFilePath.replace_extension(".json.bak");
|
||||
|
||||
if (std::filesystem::exists(backupFilePath)) {
|
||||
try {
|
||||
std::filesystem::remove(backupFilePath);
|
||||
} catch (std::filesystem::filesystem_error &) {
|
||||
throw std::logic_error("Failed to remove pre-existing scene collection backup file: " +
|
||||
backupFilePath.u8string());
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// MARK: - Main Scene Collection Management Functions
|
||||
|
||||
void OBSBasic::SetupNewSceneCollection(const std::string &collectionName)
|
||||
{
|
||||
const OBSSceneCollection &newCollection = CreateSceneCollection(collectionName);
|
||||
|
||||
OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING);
|
||||
|
||||
cleanBackupCollision(newCollection);
|
||||
ActivateSceneCollection(newCollection);
|
||||
|
||||
blog(LOG_INFO, "Created scene collection '%s' (clean, %s)", newCollection.name.c_str(),
|
||||
newCollection.fileName.c_str());
|
||||
blog(LOG_INFO, "------------------------------------------------");
|
||||
}
|
||||
|
||||
void OBSBasic::SetupDuplicateSceneCollection(const std::string &collectionName)
|
||||
{
|
||||
const OBSSceneCollection &newCollection = CreateSceneCollection(collectionName);
|
||||
const OBSSceneCollection ¤tCollection = GetCurrentSceneCollection();
|
||||
|
||||
SaveProjectNow();
|
||||
|
||||
const auto copyOptions = std::filesystem::copy_options::overwrite_existing;
|
||||
|
||||
try {
|
||||
std::filesystem::copy(currentCollection.collectionFile, newCollection.collectionFile, copyOptions);
|
||||
} catch (const std::filesystem::filesystem_error &error) {
|
||||
blog(LOG_DEBUG, "%s", error.what());
|
||||
throw std::logic_error("Failed to copy file for cloned scene collection: " + newCollection.name);
|
||||
}
|
||||
|
||||
OBSDataAutoRelease collection = obs_data_create_from_json_file(newCollection.collectionFile.u8string().c_str());
|
||||
|
||||
obs_data_set_string(collection, "name", newCollection.name.c_str());
|
||||
|
||||
OBSDataArrayAutoRelease sources = obs_data_get_array(collection, "sources");
|
||||
|
||||
if (sources) {
|
||||
obs_data_erase(collection, "sources");
|
||||
|
||||
obs_data_array_enum(
|
||||
sources,
|
||||
[](obs_data_t *data, void *) -> void {
|
||||
const char *uuid = os_generate_uuid();
|
||||
|
||||
obs_data_set_string(data, "uuid", uuid);
|
||||
|
||||
bfree((void *)uuid);
|
||||
},
|
||||
nullptr);
|
||||
|
||||
obs_data_set_array(collection, "sources", sources);
|
||||
}
|
||||
|
||||
obs_data_save_json_safe(collection, newCollection.collectionFile.u8string().c_str(), "tmp", nullptr);
|
||||
|
||||
cleanBackupCollision(newCollection);
|
||||
ActivateSceneCollection(newCollection);
|
||||
|
||||
blog(LOG_INFO, "Created scene collection '%s' (duplicate, %s)", newCollection.name.c_str(),
|
||||
newCollection.fileName.c_str());
|
||||
blog(LOG_INFO, "------------------------------------------------");
|
||||
}
|
||||
|
||||
void OBSBasic::SetupRenameSceneCollection(const std::string &collectionName)
|
||||
{
|
||||
const OBSSceneCollection &newCollection = CreateSceneCollection(collectionName);
|
||||
const OBSSceneCollection currentCollection = GetCurrentSceneCollection();
|
||||
|
||||
SaveProjectNow();
|
||||
|
||||
const auto copyOptions = std::filesystem::copy_options::overwrite_existing;
|
||||
|
||||
try {
|
||||
std::filesystem::copy(currentCollection.collectionFile, newCollection.collectionFile, copyOptions);
|
||||
} catch (const std::filesystem::filesystem_error &error) {
|
||||
blog(LOG_DEBUG, "%s", error.what());
|
||||
throw std::logic_error("Failed to copy file for scene collection: " + currentCollection.name);
|
||||
}
|
||||
|
||||
collections.erase(currentCollection.name);
|
||||
|
||||
OBSDataAutoRelease collection = obs_data_create_from_json_file(newCollection.collectionFile.u8string().c_str());
|
||||
|
||||
obs_data_set_string(collection, "name", newCollection.name.c_str());
|
||||
|
||||
obs_data_save_json_safe(collection, newCollection.collectionFile.u8string().c_str(), "tmp", nullptr);
|
||||
|
||||
cleanBackupCollision(newCollection);
|
||||
ActivateSceneCollection(newCollection);
|
||||
RemoveSceneCollection(currentCollection);
|
||||
|
||||
blog(LOG_INFO, "Renamed scene collection '%s' to '%s' (%s)", currentCollection.name.c_str(),
|
||||
newCollection.name.c_str(), newCollection.fileName.c_str());
|
||||
blog(LOG_INFO, "------------------------------------------------");
|
||||
|
||||
OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_RENAMED);
|
||||
}
|
||||
|
||||
// MARK: - Scene Collection File Management Functions
|
||||
|
||||
const OBSSceneCollection &OBSBasic::CreateSceneCollection(const std::string &collectionName)
|
||||
{
|
||||
if (const auto &foundCollection = GetSceneCollectionByName(collectionName)) {
|
||||
throw std::invalid_argument("Scene collection already exists: " + collectionName);
|
||||
}
|
||||
|
||||
std::string fileName;
|
||||
if (!GetFileSafeName(collectionName.c_str(), fileName)) {
|
||||
throw std::invalid_argument("Failed to create safe directory for new scene collection: " +
|
||||
collectionName);
|
||||
}
|
||||
|
||||
std::string collectionFile;
|
||||
collectionFile.reserve(App()->userScenesLocation.u8string().size() + OBSSceneCollectionPath.size() +
|
||||
fileName.size());
|
||||
collectionFile.append(App()->userScenesLocation.u8string()).append(OBSSceneCollectionPath).append(fileName);
|
||||
|
||||
if (!GetClosestUnusedFileName(collectionFile, "json")) {
|
||||
throw std::invalid_argument("Failed to get closest file name for new scene collection: " + fileName);
|
||||
}
|
||||
|
||||
const std::filesystem::path collectionFilePath = std::filesystem::u8path(collectionFile);
|
||||
|
||||
auto [iterator, success] = collections.try_emplace(
|
||||
collectionName,
|
||||
OBSSceneCollection{collectionName, collectionFilePath.filename().u8string(), collectionFilePath});
|
||||
|
||||
return iterator->second;
|
||||
}
|
||||
|
||||
void OBSBasic::RemoveSceneCollection(OBSSceneCollection collection)
|
||||
{
|
||||
try {
|
||||
std::filesystem::remove(collection.collectionFile);
|
||||
} catch (const std::filesystem::filesystem_error &error) {
|
||||
blog(LOG_DEBUG, "%s", error.what());
|
||||
throw std::logic_error("Failed to remove scene collection file: " + collection.fileName);
|
||||
}
|
||||
|
||||
blog(LOG_INFO, "Removed scene collection '%s' (%s)", collection.name.c_str(), collection.fileName.c_str());
|
||||
blog(LOG_INFO, "------------------------------------------------");
|
||||
}
|
||||
|
||||
// MARK: - Scene Collection UI Handling Functions
|
||||
|
||||
bool OBSBasic::CreateNewSceneCollection(const QString &name)
|
||||
{
|
||||
try {
|
||||
SetupNewSceneCollection(name.toStdString());
|
||||
return true;
|
||||
} catch (const std::invalid_argument &error) {
|
||||
blog(LOG_ERROR, "%s", error.what());
|
||||
return false;
|
||||
} catch (const std::logic_error &error) {
|
||||
blog(LOG_ERROR, "%s", error.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool OBSBasic::CreateDuplicateSceneCollection(const QString &name)
|
||||
{
|
||||
try {
|
||||
SetupDuplicateSceneCollection(name.toStdString());
|
||||
return true;
|
||||
} catch (const std::invalid_argument &error) {
|
||||
blog(LOG_ERROR, "%s", error.what());
|
||||
return false;
|
||||
} catch (const std::logic_error &error) {
|
||||
blog(LOG_ERROR, "%s", error.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void OBSBasic::DeleteSceneCollection(const QString &name)
|
||||
{
|
||||
const std::string_view currentCollectionName{
|
||||
config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection")};
|
||||
|
||||
if (currentCollectionName == name.toStdString()) {
|
||||
on_actionRemoveSceneCollection_triggered();
|
||||
return;
|
||||
}
|
||||
|
||||
OBSSceneCollection currentCollection = GetCurrentSceneCollection();
|
||||
|
||||
RemoveSceneCollection(currentCollection);
|
||||
|
||||
collections.erase(name.toStdString());
|
||||
|
||||
RefreshSceneCollections();
|
||||
|
||||
OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED);
|
||||
}
|
||||
|
||||
void OBSBasic::ChangeSceneCollection()
|
||||
{
|
||||
QAction *action = reinterpret_cast<QAction *>(sender());
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string_view currentCollectionName{
|
||||
config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection")};
|
||||
const QVariant qCollectionName = action->property("collection_name");
|
||||
const std::string selectedCollectionName{qCollectionName.toString().toStdString()};
|
||||
|
||||
if (currentCollectionName == selectedCollectionName) {
|
||||
action->setChecked(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const std::optional<OBSSceneCollection> foundCollection = GetSceneCollectionByName(selectedCollectionName);
|
||||
|
||||
if (!foundCollection) {
|
||||
const std::string errorMessage{"Selected scene collection not found: "};
|
||||
|
||||
throw std::invalid_argument(errorMessage + currentCollectionName.data());
|
||||
}
|
||||
|
||||
const OBSSceneCollection &selectedCollection = foundCollection.value();
|
||||
|
||||
OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING);
|
||||
|
||||
ActivateSceneCollection(selectedCollection);
|
||||
|
||||
blog(LOG_INFO, "Switched to scene collection '%s' (%s)", selectedCollection.name.c_str(),
|
||||
selectedCollection.fileName.c_str());
|
||||
blog(LOG_INFO, "------------------------------------------------");
|
||||
}
|
||||
|
||||
void OBSBasic::RefreshSceneCollections(bool refreshCache)
|
||||
{
|
||||
std::string_view currentCollectionName{config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection")};
|
||||
|
||||
QList<QAction *> menuActions = ui->sceneCollectionMenu->actions();
|
||||
|
||||
for (auto &action : menuActions) {
|
||||
QVariant variant = action->property("file_name");
|
||||
if (variant.typeName() != nullptr) {
|
||||
delete action;
|
||||
}
|
||||
}
|
||||
|
||||
if (refreshCache) {
|
||||
RefreshSceneCollectionCache();
|
||||
}
|
||||
|
||||
updateSortedSceneCollections(collections);
|
||||
|
||||
size_t numAddedCollections = 0;
|
||||
for (auto &name : sortedSceneCollections) {
|
||||
const std::string collectionName = name.toStdString();
|
||||
try {
|
||||
const OBSSceneCollection &collection = collections.at(collectionName);
|
||||
const QString qCollectionName = QString().fromStdString(collectionName);
|
||||
|
||||
QAction *action = new QAction(qCollectionName, this);
|
||||
action->setProperty("collection_name", qCollectionName);
|
||||
action->setProperty("file_name", QString().fromStdString(collection.fileName));
|
||||
connect(action, &QAction::triggered, this, &OBSBasic::ChangeSceneCollection);
|
||||
action->setCheckable(true);
|
||||
action->setChecked(collectionName == currentCollectionName);
|
||||
|
||||
ui->sceneCollectionMenu->addAction(action);
|
||||
|
||||
numAddedCollections += 1;
|
||||
} catch (const std::out_of_range &error) {
|
||||
blog(LOG_ERROR, "No scene collection with name %s found in scene collection cache.\n%s",
|
||||
collectionName.c_str(), error.what());
|
||||
}
|
||||
}
|
||||
|
||||
ui->actionRemoveSceneCollection->setEnabled(numAddedCollections > 1);
|
||||
|
||||
OBSBasic *main = reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
|
||||
|
||||
main->ui->actionPasteFilters->setEnabled(false);
|
||||
main->ui->actionPasteRef->setEnabled(false);
|
||||
main->ui->actionPasteDup->setEnabled(false);
|
||||
}
|
||||
|
||||
// MARK: - Scene Collection Cache Functions
|
||||
|
||||
void OBSBasic::RefreshSceneCollectionCache()
|
||||
{
|
||||
OBSSceneCollectionCache foundCollections{};
|
||||
|
||||
const std::filesystem::path collectionsPath =
|
||||
App()->userScenesLocation / std::filesystem::u8path(OBSSceneCollectionPath.substr(1));
|
||||
|
||||
if (!std::filesystem::exists(collectionsPath)) {
|
||||
blog(LOG_WARNING, "Failed to get scene collections config path");
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto &entry : std::filesystem::directory_iterator(collectionsPath)) {
|
||||
if (entry.is_directory()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.path().extension().u8string() != ".json") {
|
||||
continue;
|
||||
}
|
||||
|
||||
OBSDataAutoRelease collectionData =
|
||||
obs_data_create_from_json_file_safe(entry.path().u8string().c_str(), "bak");
|
||||
|
||||
std::string candidateName;
|
||||
const char *collectionName = obs_data_get_string(collectionData, "name");
|
||||
|
||||
if (!collectionName) {
|
||||
candidateName = entry.path().filename().u8string();
|
||||
} else {
|
||||
candidateName = collectionName;
|
||||
}
|
||||
|
||||
foundCollections.try_emplace(candidateName,
|
||||
OBSSceneCollection{candidateName, entry.path().filename().u8string(),
|
||||
entry.path()});
|
||||
}
|
||||
|
||||
collections.swap(foundCollections);
|
||||
}
|
||||
|
||||
const OBSSceneCollection &OBSBasic::GetCurrentSceneCollection() const
|
||||
{
|
||||
std::string currentCollectionName{config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection")};
|
||||
|
||||
if (currentCollectionName.empty()) {
|
||||
throw std::invalid_argument("No valid scene collection name in configuration Basic->SceneCollection");
|
||||
}
|
||||
|
||||
const auto &foundCollection = collections.find(currentCollectionName);
|
||||
|
||||
if (foundCollection != collections.end()) {
|
||||
return foundCollection->second;
|
||||
} else {
|
||||
throw std::invalid_argument("Scene collection not found in collection list: " + currentCollectionName);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<OBSSceneCollection> OBSBasic::GetSceneCollectionByName(const std::string &collectionName) const
|
||||
{
|
||||
auto foundCollection = collections.find(collectionName);
|
||||
|
||||
if (foundCollection == collections.end()) {
|
||||
return {};
|
||||
} else {
|
||||
return foundCollection->second;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<OBSSceneCollection> OBSBasic::GetSceneCollectionByFileName(const std::string &fileName) const
|
||||
{
|
||||
for (auto &[iterator, collection] : collections) {
|
||||
if (collection.fileName == fileName) {
|
||||
return collection;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// MARK: - Qt Slot Functions
|
||||
|
||||
void OBSBasic::on_actionNewSceneCollection_triggered()
|
||||
{
|
||||
const OBSPromptCallback sceneCollectionCallback = [this](const OBSPromptResult &result) {
|
||||
if (GetSceneCollectionByName(result.promptValue)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const OBSPromptRequest request{Str("Basic.Main.AddSceneCollection.Title"),
|
||||
Str("Basic.Main.AddSceneCollection.Text")};
|
||||
|
||||
OBSPromptResult result = PromptForName(request, sceneCollectionCallback);
|
||||
|
||||
if (!result.success) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
SetupNewSceneCollection(result.promptValue);
|
||||
} catch (const std::invalid_argument &error) {
|
||||
blog(LOG_ERROR, "%s", error.what());
|
||||
} catch (const std::logic_error &error) {
|
||||
blog(LOG_ERROR, "%s", error.what());
|
||||
}
|
||||
}
|
||||
|
||||
void OBSBasic::on_actionDupSceneCollection_triggered()
|
||||
{
|
||||
const OBSPromptCallback sceneCollectionCallback = [this](const OBSPromptResult &result) {
|
||||
if (GetSceneCollectionByName(result.promptValue)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const OBSPromptRequest request{Str("Basic.Main.AddSceneCollection.Title"),
|
||||
Str("Basic.Main.AddSceneCollection.Text")};
|
||||
|
||||
OBSPromptResult result = PromptForName(request, sceneCollectionCallback);
|
||||
|
||||
if (!result.success) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
SetupDuplicateSceneCollection(result.promptValue);
|
||||
} catch (const std::invalid_argument &error) {
|
||||
blog(LOG_ERROR, "%s", error.what());
|
||||
} catch (const std::logic_error &error) {
|
||||
blog(LOG_ERROR, "%s", error.what());
|
||||
}
|
||||
}
|
||||
|
||||
void OBSBasic::on_actionRenameSceneCollection_triggered()
|
||||
{
|
||||
const OBSSceneCollection ¤tCollection = GetCurrentSceneCollection();
|
||||
|
||||
const OBSPromptCallback sceneCollectionCallback = [this](const OBSPromptResult &result) {
|
||||
if (GetSceneCollectionByName(result.promptValue)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const OBSPromptRequest request{Str("Basic.Main.RenameSceneCollection.Title"),
|
||||
Str("Basic.Main.AddSceneCollection.Text"), currentCollection.name};
|
||||
|
||||
OBSPromptResult result = PromptForName(request, sceneCollectionCallback);
|
||||
|
||||
if (!result.success) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
SetupRenameSceneCollection(result.promptValue);
|
||||
} catch (const std::invalid_argument &error) {
|
||||
blog(LOG_ERROR, "%s", error.what());
|
||||
} catch (const std::logic_error &error) {
|
||||
blog(LOG_ERROR, "%s", error.what());
|
||||
}
|
||||
}
|
||||
|
||||
void OBSBasic::on_actionRemoveSceneCollection_triggered(bool skipConfirmation)
|
||||
{
|
||||
if (collections.size() < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
OBSSceneCollection currentCollection;
|
||||
|
||||
try {
|
||||
currentCollection = GetCurrentSceneCollection();
|
||||
|
||||
if (!skipConfirmation) {
|
||||
const QString confirmationText =
|
||||
QTStr("ConfirmRemove.Text").arg(QString::fromStdString(currentCollection.name));
|
||||
const QMessageBox::StandardButton button =
|
||||
OBSMessageBox::question(this, QTStr("ConfirmRemove.Title"), confirmationText);
|
||||
|
||||
if (button == QMessageBox::No) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING);
|
||||
|
||||
collections.erase(currentCollection.name);
|
||||
} catch (const std::invalid_argument &error) {
|
||||
blog(LOG_ERROR, "%s", error.what());
|
||||
} catch (const std::logic_error &error) {
|
||||
blog(LOG_ERROR, "%s", error.what());
|
||||
}
|
||||
|
||||
const OBSSceneCollection &newCollection = collections.begin()->second;
|
||||
|
||||
ActivateSceneCollection(newCollection);
|
||||
RemoveSceneCollection(currentCollection);
|
||||
|
||||
blog(LOG_INFO, "Switched to scene collection '%s' (%s)", newCollection.name.c_str(),
|
||||
newCollection.fileName.c_str());
|
||||
blog(LOG_INFO, "------------------------------------------------");
|
||||
}
|
||||
|
||||
void OBSBasic::on_actionImportSceneCollection_triggered()
|
||||
{
|
||||
OBSImporter imp(this);
|
||||
imp.exec();
|
||||
|
||||
RefreshSceneCollections(true);
|
||||
}
|
||||
|
||||
void OBSBasic::on_actionExportSceneCollection_triggered()
|
||||
{
|
||||
SaveProjectNow();
|
||||
|
||||
const OBSSceneCollection ¤tCollection = GetCurrentSceneCollection();
|
||||
|
||||
const QString home = QDir::homePath();
|
||||
|
||||
const QString destinationFileName = SaveFile(this, QTStr("Basic.MainMenu.SceneCollection.Export"),
|
||||
home + "/" + currentCollection.fileName.c_str(),
|
||||
"JSON Files (*.json)");
|
||||
|
||||
if (!destinationFileName.isEmpty() && !destinationFileName.isNull()) {
|
||||
const std::filesystem::path sourceFile = currentCollection.collectionFile;
|
||||
const std::filesystem::path destinationFile =
|
||||
std::filesystem::u8path(destinationFileName.toStdString());
|
||||
|
||||
OBSDataAutoRelease collection = obs_data_create_from_json_file(sourceFile.u8string().c_str());
|
||||
|
||||
OBSDataArrayAutoRelease sources = obs_data_get_array(collection, "sources");
|
||||
if (!sources) {
|
||||
blog(LOG_WARNING, "No sources in exported scene collection");
|
||||
return;
|
||||
}
|
||||
|
||||
obs_data_erase(collection, "sources");
|
||||
|
||||
using OBSDataVector = std::vector<OBSData>;
|
||||
|
||||
OBSDataVector sourceItems;
|
||||
obs_data_array_enum(
|
||||
sources,
|
||||
[](obs_data_t *data, void *vector) -> void {
|
||||
OBSDataVector &sourceItems{*static_cast<OBSDataVector *>(vector)};
|
||||
sourceItems.push_back(data);
|
||||
},
|
||||
&sourceItems);
|
||||
|
||||
std::sort(sourceItems.begin(), sourceItems.end(), [](const OBSData &a, const OBSData &b) {
|
||||
return astrcmpi(obs_data_get_string(a, "name"), obs_data_get_string(b, "name")) < 0;
|
||||
});
|
||||
|
||||
OBSDataArrayAutoRelease newSources = obs_data_array_create();
|
||||
for (auto &item : sourceItems) {
|
||||
obs_data_array_push_back(newSources, item);
|
||||
}
|
||||
|
||||
obs_data_set_array(collection, "sources", newSources);
|
||||
obs_data_save_json_pretty_safe(collection, destinationFile.u8string().c_str(), "tmp", "bak");
|
||||
}
|
||||
}
|
||||
|
||||
void OBSBasic::on_actionRemigrateSceneCollection_triggered()
|
||||
{
|
||||
if (Active()) {
|
||||
OBSMessageBox::warning(this, QTStr("Basic.Main.RemigrateSceneCollection.Title"),
|
||||
QTStr("Basic.Main.RemigrateSceneCollection.CannotMigrate.Active"));
|
||||
return;
|
||||
}
|
||||
|
||||
OBSDataAutoRelease priv = obs_get_private_data();
|
||||
|
||||
if (!usingAbsoluteCoordinates && !migrationBaseResolution) {
|
||||
OBSMessageBox::warning(
|
||||
this, QTStr("Basic.Main.RemigrateSceneCollection.Title"),
|
||||
QTStr("Basic.Main.RemigrateSceneCollection.CannotMigrate.UnknownBaseResolution"));
|
||||
return;
|
||||
}
|
||||
|
||||
obs_video_info ovi;
|
||||
obs_get_video_info(&ovi);
|
||||
|
||||
if (!usingAbsoluteCoordinates && migrationBaseResolution->first == ovi.base_width &&
|
||||
migrationBaseResolution->second == ovi.base_height) {
|
||||
OBSMessageBox::warning(
|
||||
this, QTStr("Basic.Main.RemigrateSceneCollection.Title"),
|
||||
QTStr("Basic.Main.RemigrateSceneCollection.CannotMigrate.BaseResolutionMatches"));
|
||||
return;
|
||||
}
|
||||
|
||||
const OBSSceneCollection ¤tCollection = GetCurrentSceneCollection();
|
||||
|
||||
QString name = QString::fromStdString(currentCollection.name);
|
||||
QString message =
|
||||
QTStr("Basic.Main.RemigrateSceneCollection.Text").arg(name).arg(ovi.base_width).arg(ovi.base_height);
|
||||
|
||||
auto answer = OBSMessageBox::question(this, QTStr("Basic.Main.RemigrateSceneCollection.Title"), message);
|
||||
|
||||
if (answer == QMessageBox::No)
|
||||
return;
|
||||
|
||||
lastOutputResolution = {ovi.base_width, ovi.base_height};
|
||||
|
||||
if (!usingAbsoluteCoordinates) {
|
||||
/* Temporarily change resolution to migration resolution */
|
||||
ovi.base_width = migrationBaseResolution->first;
|
||||
ovi.base_height = migrationBaseResolution->second;
|
||||
|
||||
if (obs_reset_video(&ovi) != OBS_VIDEO_SUCCESS) {
|
||||
OBSMessageBox::critical(
|
||||
this, QTStr("Basic.Main.RemigrateSceneCollection.Title"),
|
||||
QTStr("Basic.Main.RemigrateSceneCollection.CannotMigrate.FailedVideoReset"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING);
|
||||
|
||||
/* Save and immediately reload to (re-)run migrations. */
|
||||
SaveProjectNow();
|
||||
/* Reset video if we potentially changed to a temporary resolution */
|
||||
if (!usingAbsoluteCoordinates) {
|
||||
ResetVideo();
|
||||
}
|
||||
|
||||
ActivateSceneCollection(currentCollection);
|
||||
}
|
||||
|
||||
// MARK: - Scene Collection Management Helper Functions
|
||||
|
||||
void OBSBasic::ActivateSceneCollection(const OBSSceneCollection &collection)
|
||||
{
|
||||
const std::string currentCollectionName{config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection")};
|
||||
|
||||
if (auto foundCollection = GetSceneCollectionByName(currentCollectionName)) {
|
||||
if (collection.name != foundCollection.value().name) {
|
||||
SaveProjectNow();
|
||||
}
|
||||
}
|
||||
|
||||
config_set_string(App()->GetUserConfig(), "Basic", "SceneCollection", collection.name.c_str());
|
||||
config_set_string(App()->GetUserConfig(), "Basic", "SceneCollectionFile", collection.fileName.c_str());
|
||||
|
||||
Load(collection.collectionFile.u8string().c_str());
|
||||
|
||||
RefreshSceneCollections();
|
||||
|
||||
UpdateTitleBar();
|
||||
|
||||
OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED);
|
||||
OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED);
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,562 +0,0 @@
|
||||
#include "moc_window-extra-browsers.cpp"
|
||||
#include "window-dock-browser.hpp"
|
||||
#include "window-basic-main.hpp"
|
||||
|
||||
#include <qt-wrappers.hpp>
|
||||
#include <QLineEdit>
|
||||
#include <QHBoxLayout>
|
||||
#include <QUuid>
|
||||
|
||||
#include <json11.hpp>
|
||||
|
||||
#include "ui_OBSExtraBrowsers.h"
|
||||
|
||||
using namespace json11;
|
||||
|
||||
#define OBJ_NAME_SUFFIX "_extraBrowser"
|
||||
|
||||
enum class Column : int {
|
||||
Title,
|
||||
Url,
|
||||
Delete,
|
||||
|
||||
Count,
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
void ExtraBrowsersModel::Reset()
|
||||
{
|
||||
items.clear();
|
||||
|
||||
OBSBasic *main = OBSBasic::Get();
|
||||
|
||||
for (int i = 0; i < main->extraBrowserDocks.size(); i++) {
|
||||
Item item;
|
||||
item.prevIdx = i;
|
||||
item.title = main->extraBrowserDockNames[i];
|
||||
item.url = main->extraBrowserDockTargets[i];
|
||||
items.push_back(item);
|
||||
}
|
||||
}
|
||||
|
||||
int ExtraBrowsersModel::rowCount(const QModelIndex &) const
|
||||
{
|
||||
int count = items.size() + 1;
|
||||
return count;
|
||||
}
|
||||
|
||||
int ExtraBrowsersModel::columnCount(const QModelIndex &) const
|
||||
{
|
||||
return (int)Column::Count;
|
||||
}
|
||||
|
||||
QVariant ExtraBrowsersModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
int column = index.column();
|
||||
int idx = index.row();
|
||||
int count = items.size();
|
||||
bool validRole = role == Qt::DisplayRole || role == Qt::AccessibleTextRole;
|
||||
|
||||
if (!validRole)
|
||||
return QVariant();
|
||||
|
||||
if (idx >= 0 && idx < count) {
|
||||
switch (column) {
|
||||
case (int)Column::Title:
|
||||
return items[idx].title;
|
||||
case (int)Column::Url:
|
||||
return items[idx].url;
|
||||
}
|
||||
} else if (idx == count) {
|
||||
switch (column) {
|
||||
case (int)Column::Title:
|
||||
return newTitle;
|
||||
case (int)Column::Url:
|
||||
return newURL;
|
||||
}
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QVariant ExtraBrowsersModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
bool validRole = role == Qt::DisplayRole || role == Qt::AccessibleTextRole;
|
||||
|
||||
if (validRole && orientation == Qt::Orientation::Horizontal) {
|
||||
switch (section) {
|
||||
case (int)Column::Title:
|
||||
return QTStr("ExtraBrowsers.DockName");
|
||||
case (int)Column::Url:
|
||||
return QStringLiteral("URL");
|
||||
}
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
Qt::ItemFlags ExtraBrowsersModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
Qt::ItemFlags flags = QAbstractTableModel::flags(index);
|
||||
|
||||
if (index.column() != (int)Column::Delete)
|
||||
flags |= Qt::ItemIsEditable;
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
class DelButton : public QPushButton {
|
||||
public:
|
||||
inline DelButton(QModelIndex index_) : QPushButton(), index(index_) {}
|
||||
|
||||
QPersistentModelIndex index;
|
||||
};
|
||||
|
||||
class EditWidget : public QLineEdit {
|
||||
public:
|
||||
inline EditWidget(QWidget *parent, QModelIndex index_) : QLineEdit(parent), index(index_) {}
|
||||
|
||||
QPersistentModelIndex index;
|
||||
};
|
||||
|
||||
void ExtraBrowsersModel::AddDeleteButton(int idx)
|
||||
{
|
||||
QTableView *widget = reinterpret_cast<QTableView *>(parent());
|
||||
|
||||
QModelIndex index = createIndex(idx, (int)Column::Delete, nullptr);
|
||||
|
||||
QPushButton *del = new DelButton(index);
|
||||
del->setProperty("class", "icon-trash");
|
||||
del->setObjectName("extraPanelDelete");
|
||||
del->setMinimumSize(QSize(20, 20));
|
||||
connect(del, &QPushButton::clicked, this, &ExtraBrowsersModel::DeleteItem);
|
||||
|
||||
widget->setIndexWidget(index, del);
|
||||
widget->setRowHeight(idx, 20);
|
||||
widget->setColumnWidth(idx, 20);
|
||||
}
|
||||
|
||||
void ExtraBrowsersModel::CheckToAdd()
|
||||
{
|
||||
if (newTitle.isEmpty() || newURL.isEmpty())
|
||||
return;
|
||||
|
||||
int idx = items.size() + 1;
|
||||
beginInsertRows(QModelIndex(), idx, idx);
|
||||
|
||||
Item item;
|
||||
item.prevIdx = -1;
|
||||
item.title = newTitle;
|
||||
item.url = newURL;
|
||||
items.push_back(item);
|
||||
|
||||
newTitle = "";
|
||||
newURL = "";
|
||||
|
||||
endInsertRows();
|
||||
|
||||
AddDeleteButton(idx - 1);
|
||||
}
|
||||
|
||||
void ExtraBrowsersModel::UpdateItem(Item &item)
|
||||
{
|
||||
int idx = item.prevIdx;
|
||||
|
||||
OBSBasic *main = OBSBasic::Get();
|
||||
BrowserDock *dock = reinterpret_cast<BrowserDock *>(main->extraBrowserDocks[idx].get());
|
||||
dock->setWindowTitle(item.title);
|
||||
dock->setObjectName(item.title + OBJ_NAME_SUFFIX);
|
||||
|
||||
if (main->extraBrowserDockNames[idx] != item.title) {
|
||||
main->extraBrowserDockNames[idx] = item.title;
|
||||
dock->toggleViewAction()->setText(item.title);
|
||||
dock->setTitle(item.title);
|
||||
}
|
||||
|
||||
if (main->extraBrowserDockTargets[idx] != item.url) {
|
||||
dock->cefWidget->setURL(QT_TO_UTF8(item.url));
|
||||
main->extraBrowserDockTargets[idx] = item.url;
|
||||
}
|
||||
}
|
||||
|
||||
void ExtraBrowsersModel::DeleteItem()
|
||||
{
|
||||
QTableView *widget = reinterpret_cast<QTableView *>(parent());
|
||||
|
||||
DelButton *del = reinterpret_cast<DelButton *>(sender());
|
||||
int row = del->index.row();
|
||||
|
||||
/* there's some sort of internal bug in Qt and deleting certain index
|
||||
* widgets or "editors" that can cause a crash inside Qt if the widget
|
||||
* is not manually removed, at least on 5.7 */
|
||||
widget->setIndexWidget(del->index, nullptr);
|
||||
del->deleteLater();
|
||||
|
||||
/* --------- */
|
||||
|
||||
beginRemoveRows(QModelIndex(), row, row);
|
||||
|
||||
int prevIdx = items[row].prevIdx;
|
||||
items.removeAt(row);
|
||||
|
||||
if (prevIdx != -1) {
|
||||
int i = 0;
|
||||
for (; i < deleted.size() && deleted[i] < prevIdx; i++)
|
||||
;
|
||||
deleted.insert(i, prevIdx);
|
||||
}
|
||||
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
void ExtraBrowsersModel::Apply()
|
||||
{
|
||||
OBSBasic *main = OBSBasic::Get();
|
||||
|
||||
for (Item &item : items) {
|
||||
if (item.prevIdx != -1) {
|
||||
UpdateItem(item);
|
||||
} else {
|
||||
QString uuid = QUuid::createUuid().toString();
|
||||
uuid.replace(QRegularExpression("[{}-]"), "");
|
||||
main->AddExtraBrowserDock(item.title, item.url, uuid, true);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = deleted.size() - 1; i >= 0; i--) {
|
||||
int idx = deleted[i];
|
||||
main->extraBrowserDockTargets.removeAt(idx);
|
||||
main->extraBrowserDockNames.removeAt(idx);
|
||||
main->extraBrowserDocks.removeAt(idx);
|
||||
}
|
||||
|
||||
if (main->extraBrowserDocks.empty())
|
||||
main->extraBrowserMenuDocksSeparator.clear();
|
||||
|
||||
deleted.clear();
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
void ExtraBrowsersModel::TabSelection(bool forward)
|
||||
{
|
||||
QListView *widget = reinterpret_cast<QListView *>(parent());
|
||||
QItemSelectionModel *selModel = widget->selectionModel();
|
||||
|
||||
QModelIndex sel = selModel->currentIndex();
|
||||
int row = sel.row();
|
||||
int col = sel.column();
|
||||
|
||||
switch (sel.column()) {
|
||||
case (int)Column::Title:
|
||||
if (!forward) {
|
||||
if (row == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
row -= 1;
|
||||
}
|
||||
|
||||
col += 1;
|
||||
break;
|
||||
|
||||
case (int)Column::Url:
|
||||
if (forward) {
|
||||
if (row == items.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
row += 1;
|
||||
}
|
||||
|
||||
col -= 1;
|
||||
}
|
||||
|
||||
sel = createIndex(row, col, nullptr);
|
||||
selModel->setCurrentIndex(sel, QItemSelectionModel::Clear);
|
||||
}
|
||||
|
||||
void ExtraBrowsersModel::Init()
|
||||
{
|
||||
for (int i = 0; i < items.count(); i++)
|
||||
AddDeleteButton(i);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
QWidget *ExtraBrowsersDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &,
|
||||
const QModelIndex &index) const
|
||||
{
|
||||
QLineEdit *text = new EditWidget(parent, index);
|
||||
text->installEventFilter(const_cast<ExtraBrowsersDelegate *>(this));
|
||||
text->setSizePolicy(QSizePolicy(QSizePolicy::Policy::Expanding, QSizePolicy::Policy::Expanding,
|
||||
QSizePolicy::ControlType::LineEdit));
|
||||
return text;
|
||||
}
|
||||
|
||||
void ExtraBrowsersDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
|
||||
{
|
||||
QLineEdit *text = reinterpret_cast<QLineEdit *>(editor);
|
||||
text->blockSignals(true);
|
||||
text->setText(index.data().toString());
|
||||
text->blockSignals(false);
|
||||
}
|
||||
|
||||
bool ExtraBrowsersDelegate::eventFilter(QObject *object, QEvent *event)
|
||||
{
|
||||
QLineEdit *edit = qobject_cast<QLineEdit *>(object);
|
||||
if (!edit)
|
||||
return false;
|
||||
|
||||
if (LineEditCanceled(event)) {
|
||||
RevertText(edit);
|
||||
}
|
||||
if (LineEditChanged(event)) {
|
||||
UpdateText(edit);
|
||||
|
||||
if (event->type() == QEvent::KeyPress) {
|
||||
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
|
||||
if (keyEvent->key() == Qt::Key_Tab) {
|
||||
model->TabSelection(true);
|
||||
} else if (keyEvent->key() == Qt::Key_Backtab) {
|
||||
model->TabSelection(false);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ExtraBrowsersDelegate::ValidName(const QString &name) const
|
||||
{
|
||||
for (auto &item : model->items) {
|
||||
if (name.compare(item.title, Qt::CaseInsensitive) == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ExtraBrowsersDelegate::RevertText(QLineEdit *edit_)
|
||||
{
|
||||
EditWidget *edit = reinterpret_cast<EditWidget *>(edit_);
|
||||
int row = edit->index.row();
|
||||
int col = edit->index.column();
|
||||
bool newItem = (row == model->items.size());
|
||||
|
||||
QString oldText;
|
||||
if (col == (int)Column::Title) {
|
||||
oldText = newItem ? model->newTitle : model->items[row].title;
|
||||
} else {
|
||||
oldText = newItem ? model->newURL : model->items[row].url;
|
||||
}
|
||||
|
||||
edit->setText(oldText);
|
||||
}
|
||||
|
||||
bool ExtraBrowsersDelegate::UpdateText(QLineEdit *edit_)
|
||||
{
|
||||
EditWidget *edit = reinterpret_cast<EditWidget *>(edit_);
|
||||
int row = edit->index.row();
|
||||
int col = edit->index.column();
|
||||
bool newItem = (row == model->items.size());
|
||||
|
||||
QString text = edit->text().trimmed();
|
||||
|
||||
if (!newItem && text.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (col == (int)Column::Title) {
|
||||
QString oldText = newItem ? model->newTitle : model->items[row].title;
|
||||
bool same = oldText.compare(text, Qt::CaseInsensitive) == 0;
|
||||
|
||||
if (!same && !ValidName(text)) {
|
||||
edit->setText(oldText);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!newItem) {
|
||||
/* if edited existing item, update it*/
|
||||
switch (col) {
|
||||
case (int)Column::Title:
|
||||
model->items[row].title = text;
|
||||
break;
|
||||
case (int)Column::Url:
|
||||
model->items[row].url = text;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
/* if both new values filled out, create new one */
|
||||
switch (col) {
|
||||
case (int)Column::Title:
|
||||
model->newTitle = text;
|
||||
break;
|
||||
case (int)Column::Url:
|
||||
model->newURL = text;
|
||||
break;
|
||||
}
|
||||
|
||||
model->CheckToAdd();
|
||||
}
|
||||
|
||||
emit commitData(edit);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
OBSExtraBrowsers::OBSExtraBrowsers(QWidget *parent) : QDialog(parent), ui(new Ui::OBSExtraBrowsers)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
model = new ExtraBrowsersModel(ui->table);
|
||||
|
||||
ui->table->setModel(model);
|
||||
ui->table->setItemDelegateForColumn((int)Column::Title, new ExtraBrowsersDelegate(model));
|
||||
ui->table->setItemDelegateForColumn((int)Column::Url, new ExtraBrowsersDelegate(model));
|
||||
ui->table->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeMode::Stretch);
|
||||
ui->table->horizontalHeader()->setSectionResizeMode((int)Column::Delete, QHeaderView::ResizeMode::Fixed);
|
||||
ui->table->setEditTriggers(QAbstractItemView::EditTrigger::CurrentChanged);
|
||||
}
|
||||
|
||||
OBSExtraBrowsers::~OBSExtraBrowsers() {}
|
||||
|
||||
void OBSExtraBrowsers::closeEvent(QCloseEvent *event)
|
||||
{
|
||||
QDialog::closeEvent(event);
|
||||
model->Apply();
|
||||
}
|
||||
|
||||
void OBSExtraBrowsers::on_apply_clicked()
|
||||
{
|
||||
model->Apply();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
void OBSBasic::ClearExtraBrowserDocks()
|
||||
{
|
||||
extraBrowserDockTargets.clear();
|
||||
extraBrowserDockNames.clear();
|
||||
extraBrowserDocks.clear();
|
||||
}
|
||||
|
||||
void OBSBasic::LoadExtraBrowserDocks()
|
||||
{
|
||||
const char *jsonStr = config_get_string(App()->GetUserConfig(), "BasicWindow", "ExtraBrowserDocks");
|
||||
|
||||
std::string err;
|
||||
Json json = Json::parse(jsonStr, err);
|
||||
if (!err.empty())
|
||||
return;
|
||||
|
||||
Json::array array = json.array_items();
|
||||
if (!array.empty())
|
||||
extraBrowserMenuDocksSeparator = ui->menuDocks->addSeparator();
|
||||
|
||||
for (Json &item : array) {
|
||||
std::string title = item["title"].string_value();
|
||||
std::string url = item["url"].string_value();
|
||||
std::string uuid = item["uuid"].string_value();
|
||||
|
||||
AddExtraBrowserDock(title.c_str(), url.c_str(), uuid.c_str(), false);
|
||||
}
|
||||
}
|
||||
|
||||
void OBSBasic::SaveExtraBrowserDocks()
|
||||
{
|
||||
Json::array array;
|
||||
for (int i = 0; i < extraBrowserDocks.size(); i++) {
|
||||
QDockWidget *dock = extraBrowserDocks[i].get();
|
||||
QString title = extraBrowserDockNames[i];
|
||||
QString url = extraBrowserDockTargets[i];
|
||||
QString uuid = dock->property("uuid").toString();
|
||||
Json::object obj{
|
||||
{"title", QT_TO_UTF8(title)},
|
||||
{"url", QT_TO_UTF8(url)},
|
||||
{"uuid", QT_TO_UTF8(uuid)},
|
||||
};
|
||||
array.push_back(obj);
|
||||
}
|
||||
|
||||
std::string output = Json(array).dump();
|
||||
config_set_string(App()->GetUserConfig(), "BasicWindow", "ExtraBrowserDocks", output.c_str());
|
||||
}
|
||||
|
||||
void OBSBasic::ManageExtraBrowserDocks()
|
||||
{
|
||||
if (!extraBrowsers.isNull()) {
|
||||
extraBrowsers->show();
|
||||
extraBrowsers->raise();
|
||||
return;
|
||||
}
|
||||
|
||||
extraBrowsers = new OBSExtraBrowsers(this);
|
||||
extraBrowsers->show();
|
||||
}
|
||||
|
||||
void OBSBasic::AddExtraBrowserDock(const QString &title, const QString &url, const QString &uuid, bool firstCreate)
|
||||
{
|
||||
static int panel_version = -1;
|
||||
if (panel_version == -1) {
|
||||
panel_version = obs_browser_qcef_version();
|
||||
}
|
||||
|
||||
BrowserDock *dock = new BrowserDock(title);
|
||||
QString bId(uuid.isEmpty() ? QUuid::createUuid().toString() : uuid);
|
||||
bId.replace(QRegularExpression("[{}-]"), "");
|
||||
dock->setProperty("uuid", bId);
|
||||
dock->setObjectName(title + OBJ_NAME_SUFFIX);
|
||||
dock->resize(460, 600);
|
||||
dock->setMinimumSize(80, 80);
|
||||
dock->setWindowTitle(title);
|
||||
dock->setAllowedAreas(Qt::AllDockWidgetAreas);
|
||||
|
||||
QCefWidget *browser = cef->create_widget(dock, QT_TO_UTF8(url), nullptr);
|
||||
if (browser && panel_version >= 1)
|
||||
browser->allowAllPopups(true);
|
||||
|
||||
dock->SetWidget(browser);
|
||||
|
||||
/* Add support for Twitch Dashboard panels */
|
||||
if (url.contains("twitch.tv/popout") && url.contains("dashboard/live")) {
|
||||
QRegularExpression re("twitch.tv\\/popout\\/([^/]+)\\/");
|
||||
QRegularExpressionMatch match = re.match(url);
|
||||
QString username = match.captured(1);
|
||||
if (username.length() > 0) {
|
||||
std::string script;
|
||||
script = "Object.defineProperty(document, 'referrer', { get: () => '";
|
||||
script += "https://twitch.tv/";
|
||||
script += QT_TO_UTF8(username);
|
||||
script += "/dashboard/live";
|
||||
script += "'});";
|
||||
browser->setStartupScript(script);
|
||||
}
|
||||
}
|
||||
|
||||
AddDockWidget(dock, Qt::RightDockWidgetArea, true);
|
||||
extraBrowserDocks.push_back(std::shared_ptr<QDockWidget>(dock));
|
||||
extraBrowserDockNames.push_back(title);
|
||||
extraBrowserDockTargets.push_back(url);
|
||||
|
||||
if (firstCreate) {
|
||||
dock->setFloating(true);
|
||||
|
||||
QPoint curPos = pos();
|
||||
QSize wSizeD2 = size() / 2;
|
||||
QSize dSizeD2 = dock->size() / 2;
|
||||
|
||||
curPos.setX(curPos.x() + wSizeD2.width() - dSizeD2.width());
|
||||
curPos.setY(curPos.y() + wSizeD2.height() - dSizeD2.height());
|
||||
|
||||
dock->move(curPos);
|
||||
dock->setVisible(true);
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
#include <QScopedPointer>
|
||||
#include <QAbstractTableModel>
|
||||
#include <QStyledItemDelegate>
|
||||
#include <memory>
|
||||
|
||||
class Ui_OBSExtraBrowsers;
|
||||
class ExtraBrowsersModel;
|
||||
|
||||
class QCefWidget;
|
||||
|
||||
class OBSExtraBrowsers : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
std::unique_ptr<Ui_OBSExtraBrowsers> ui;
|
||||
ExtraBrowsersModel *model;
|
||||
|
||||
public:
|
||||
OBSExtraBrowsers(QWidget *parent);
|
||||
~OBSExtraBrowsers();
|
||||
|
||||
void closeEvent(QCloseEvent *event) override;
|
||||
|
||||
public slots:
|
||||
void on_apply_clicked();
|
||||
};
|
||||
|
||||
class ExtraBrowsersModel : public QAbstractTableModel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
inline ExtraBrowsersModel(QObject *parent = nullptr) : QAbstractTableModel(parent)
|
||||
{
|
||||
Reset();
|
||||
QMetaObject::invokeMethod(this, "Init", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
|
||||
struct Item {
|
||||
int prevIdx;
|
||||
QString title;
|
||||
QString url;
|
||||
};
|
||||
|
||||
void TabSelection(bool forward);
|
||||
|
||||
void AddDeleteButton(int idx);
|
||||
void Reset();
|
||||
void CheckToAdd();
|
||||
void UpdateItem(Item &item);
|
||||
void DeleteItem();
|
||||
void Apply();
|
||||
|
||||
QVector<Item> items;
|
||||
QVector<int> deleted;
|
||||
|
||||
QString newTitle;
|
||||
QString newURL;
|
||||
|
||||
public slots:
|
||||
void Init();
|
||||
};
|
||||
|
||||
class ExtraBrowsersDelegate : public QStyledItemDelegate {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
inline ExtraBrowsersDelegate(ExtraBrowsersModel *model_) : QStyledItemDelegate(nullptr), model(model_) {}
|
||||
|
||||
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index) const override;
|
||||
|
||||
void setEditorData(QWidget *editor, const QModelIndex &index) const override;
|
||||
|
||||
bool eventFilter(QObject *object, QEvent *event) override;
|
||||
void RevertText(QLineEdit *edit);
|
||||
bool UpdateText(QLineEdit *edit);
|
||||
bool ValidName(const QString &text) const;
|
||||
|
||||
ExtraBrowsersModel *model;
|
||||
};
|
||||
@@ -1,570 +0,0 @@
|
||||
/******************************************************************************
|
||||
Copyright (C) 2019-2020 by Dillon Pentz <dillon@vodbox.io>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
#include "moc_window-importer.cpp"
|
||||
|
||||
#include "obs-app.hpp"
|
||||
|
||||
#include <QPushButton>
|
||||
#include <QLineEdit>
|
||||
#include <QToolButton>
|
||||
#include <QMimeData>
|
||||
#include <QStyledItemDelegate>
|
||||
#include <QDirIterator>
|
||||
#include <QDropEvent>
|
||||
#include <qt-wrappers.hpp>
|
||||
|
||||
#include "importers/importers.hpp"
|
||||
|
||||
extern bool SceneCollectionExists(const char *findName);
|
||||
|
||||
enum ImporterColumn {
|
||||
Selected,
|
||||
Name,
|
||||
Path,
|
||||
Program,
|
||||
|
||||
Count
|
||||
};
|
||||
|
||||
enum ImporterEntryRole { EntryStateRole = Qt::UserRole, NewPath, AutoPath, CheckEmpty };
|
||||
|
||||
/**********************************************************
|
||||
Delegate - Presents cells in the grid.
|
||||
**********************************************************/
|
||||
|
||||
ImporterEntryPathItemDelegate::ImporterEntryPathItemDelegate() : QStyledItemDelegate() {}
|
||||
|
||||
QWidget *ImporterEntryPathItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem & /* option */,
|
||||
const QModelIndex &index) const
|
||||
{
|
||||
bool empty = index.model()
|
||||
->index(index.row(), ImporterColumn::Path)
|
||||
.data(ImporterEntryRole::CheckEmpty)
|
||||
.value<bool>();
|
||||
|
||||
QSizePolicy buttonSizePolicy(QSizePolicy::Policy::Minimum, QSizePolicy::Policy::Expanding,
|
||||
QSizePolicy::ControlType::PushButton);
|
||||
|
||||
QWidget *container = new QWidget(parent);
|
||||
|
||||
auto browseCallback = [this, container]() {
|
||||
const_cast<ImporterEntryPathItemDelegate *>(this)->handleBrowse(container);
|
||||
};
|
||||
|
||||
auto clearCallback = [this, container]() {
|
||||
const_cast<ImporterEntryPathItemDelegate *>(this)->handleClear(container);
|
||||
};
|
||||
|
||||
QHBoxLayout *layout = new QHBoxLayout();
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setSpacing(0);
|
||||
|
||||
QLineEdit *text = new QLineEdit();
|
||||
text->setObjectName(QStringLiteral("text"));
|
||||
text->setSizePolicy(QSizePolicy(QSizePolicy::Policy::Expanding, QSizePolicy::Policy::Expanding,
|
||||
QSizePolicy::ControlType::LineEdit));
|
||||
layout->addWidget(text);
|
||||
|
||||
QObject::connect(text, &QLineEdit::editingFinished, this, &ImporterEntryPathItemDelegate::updateText);
|
||||
|
||||
QToolButton *browseButton = new QToolButton();
|
||||
browseButton->setText("...");
|
||||
browseButton->setSizePolicy(buttonSizePolicy);
|
||||
layout->addWidget(browseButton);
|
||||
|
||||
container->connect(browseButton, &QToolButton::clicked, browseCallback);
|
||||
|
||||
// The "clear" button is not shown in output cells
|
||||
// or the insertion point's input cell.
|
||||
if (!empty) {
|
||||
QToolButton *clearButton = new QToolButton();
|
||||
clearButton->setText("X");
|
||||
clearButton->setSizePolicy(buttonSizePolicy);
|
||||
layout->addWidget(clearButton);
|
||||
|
||||
container->connect(clearButton, &QToolButton::clicked, clearCallback);
|
||||
}
|
||||
|
||||
container->setLayout(layout);
|
||||
container->setFocusProxy(text);
|
||||
return container;
|
||||
}
|
||||
|
||||
void ImporterEntryPathItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
|
||||
{
|
||||
QLineEdit *text = editor->findChild<QLineEdit *>();
|
||||
text->setText(index.data().toString());
|
||||
editor->setProperty(PATH_LIST_PROP, QVariant());
|
||||
}
|
||||
|
||||
void ImporterEntryPathItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
|
||||
const QModelIndex &index) const
|
||||
{
|
||||
// We use the PATH_LIST_PROP property to pass a list of
|
||||
// path strings from the editor widget into the model's
|
||||
// NewPathsToProcessRole. This is only used when paths
|
||||
// are selected through the "browse" or "delete" buttons
|
||||
// in the editor. If the user enters new text in the
|
||||
// text box, we simply pass that text on to the model
|
||||
// as normal text data in the default role.
|
||||
QVariant pathListProp = editor->property(PATH_LIST_PROP);
|
||||
if (pathListProp.isValid()) {
|
||||
QStringList list = editor->property(PATH_LIST_PROP).toStringList();
|
||||
model->setData(index, list, ImporterEntryRole::NewPath);
|
||||
} else {
|
||||
QLineEdit *lineEdit = editor->findChild<QLineEdit *>();
|
||||
model->setData(index, lineEdit->text());
|
||||
}
|
||||
}
|
||||
|
||||
void ImporterEntryPathItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index) const
|
||||
{
|
||||
QStyleOptionViewItem localOption = option;
|
||||
initStyleOption(&localOption, index);
|
||||
|
||||
QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &localOption, painter);
|
||||
}
|
||||
|
||||
void ImporterEntryPathItemDelegate::handleBrowse(QWidget *container)
|
||||
{
|
||||
QString Pattern = "(*.json *.bpres *.xml *.xconfig)";
|
||||
|
||||
QLineEdit *text = container->findChild<QLineEdit *>();
|
||||
|
||||
QString currentPath = text->text();
|
||||
|
||||
bool isSet = false;
|
||||
QStringList paths = OpenFiles(container, QTStr("Importer.SelectCollection"), currentPath,
|
||||
QTStr("Importer.Collection") + QString(" ") + Pattern);
|
||||
|
||||
if (!paths.empty()) {
|
||||
container->setProperty(PATH_LIST_PROP, paths);
|
||||
isSet = true;
|
||||
}
|
||||
|
||||
if (isSet)
|
||||
emit commitData(container);
|
||||
}
|
||||
|
||||
void ImporterEntryPathItemDelegate::handleClear(QWidget *container)
|
||||
{
|
||||
// An empty string list will indicate that the entry is being
|
||||
// blanked and should be deleted.
|
||||
container->setProperty(PATH_LIST_PROP, QStringList());
|
||||
|
||||
emit commitData(container);
|
||||
}
|
||||
|
||||
void ImporterEntryPathItemDelegate::updateText()
|
||||
{
|
||||
QLineEdit *lineEdit = dynamic_cast<QLineEdit *>(sender());
|
||||
QWidget *editor = lineEdit->parentWidget();
|
||||
emit commitData(editor);
|
||||
}
|
||||
|
||||
/**
|
||||
Model
|
||||
**/
|
||||
|
||||
int ImporterModel::rowCount(const QModelIndex &) const
|
||||
{
|
||||
return options.length() + 1;
|
||||
}
|
||||
|
||||
int ImporterModel::columnCount(const QModelIndex &) const
|
||||
{
|
||||
return ImporterColumn::Count;
|
||||
}
|
||||
|
||||
QVariant ImporterModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
QVariant result = QVariant();
|
||||
|
||||
if (index.row() >= options.length()) {
|
||||
if (role == ImporterEntryRole::CheckEmpty)
|
||||
result = true;
|
||||
else
|
||||
return QVariant();
|
||||
} else if (role == Qt::DisplayRole) {
|
||||
switch (index.column()) {
|
||||
case ImporterColumn::Path:
|
||||
result = options[index.row()].path;
|
||||
break;
|
||||
case ImporterColumn::Program:
|
||||
result = options[index.row()].program;
|
||||
break;
|
||||
case ImporterColumn::Name:
|
||||
result = options[index.row()].name;
|
||||
}
|
||||
} else if (role == Qt::EditRole) {
|
||||
if (index.column() == ImporterColumn::Name) {
|
||||
result = options[index.row()].name;
|
||||
}
|
||||
} else if (role == Qt::CheckStateRole) {
|
||||
switch (index.column()) {
|
||||
case ImporterColumn::Selected:
|
||||
if (options[index.row()].program != "")
|
||||
result = options[index.row()].selected ? Qt::Checked : Qt::Unchecked;
|
||||
else
|
||||
result = Qt::Unchecked;
|
||||
}
|
||||
} else if (role == ImporterEntryRole::CheckEmpty) {
|
||||
result = options[index.row()].empty;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Qt::ItemFlags ImporterModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
Qt::ItemFlags flags = QAbstractTableModel::flags(index);
|
||||
|
||||
if (index.column() == ImporterColumn::Selected && index.row() != options.length()) {
|
||||
flags |= Qt::ItemIsUserCheckable;
|
||||
} else if (index.column() == ImporterColumn::Path ||
|
||||
(index.column() == ImporterColumn::Name && index.row() != options.length())) {
|
||||
flags |= Qt::ItemIsEditable;
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
void ImporterModel::checkInputPath(int row)
|
||||
{
|
||||
ImporterEntry &entry = options[row];
|
||||
|
||||
if (entry.path.isEmpty()) {
|
||||
entry.program = "";
|
||||
entry.empty = true;
|
||||
entry.selected = false;
|
||||
entry.name = "";
|
||||
} else {
|
||||
entry.empty = false;
|
||||
|
||||
std::string program = DetectProgram(entry.path.toStdString());
|
||||
entry.program = QTStr(program.c_str());
|
||||
|
||||
if (program.empty()) {
|
||||
entry.selected = false;
|
||||
} else {
|
||||
std::string name = GetSCName(entry.path.toStdString(), program);
|
||||
entry.name = name.c_str();
|
||||
}
|
||||
}
|
||||
|
||||
emit dataChanged(index(row, 0), index(row, ImporterColumn::Count));
|
||||
}
|
||||
|
||||
bool ImporterModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
||||
{
|
||||
if (role == ImporterEntryRole::NewPath) {
|
||||
QStringList list = value.toStringList();
|
||||
|
||||
if (list.size() == 0) {
|
||||
if (index.row() < options.size()) {
|
||||
beginRemoveRows(QModelIndex(), index.row(), index.row());
|
||||
options.removeAt(index.row());
|
||||
endRemoveRows();
|
||||
}
|
||||
} else {
|
||||
if (list.size() > 0 && index.row() < options.length()) {
|
||||
options[index.row()].path = list[0];
|
||||
checkInputPath(index.row());
|
||||
|
||||
list.removeAt(0);
|
||||
}
|
||||
|
||||
if (list.size() > 0) {
|
||||
int row = index.row();
|
||||
int lastRow = row + list.size() - 1;
|
||||
beginInsertRows(QModelIndex(), row, lastRow);
|
||||
|
||||
for (QString path : list) {
|
||||
ImporterEntry entry;
|
||||
entry.path = path;
|
||||
|
||||
options.insert(row, entry);
|
||||
|
||||
row++;
|
||||
}
|
||||
|
||||
endInsertRows();
|
||||
|
||||
for (row = index.row(); row <= lastRow; row++) {
|
||||
checkInputPath(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (index.row() == options.length()) {
|
||||
QString path = value.toString();
|
||||
|
||||
if (!path.isEmpty()) {
|
||||
ImporterEntry entry;
|
||||
entry.path = path;
|
||||
entry.selected = role != ImporterEntryRole::AutoPath;
|
||||
entry.empty = false;
|
||||
|
||||
beginInsertRows(QModelIndex(), options.length() + 1, options.length() + 1);
|
||||
options.append(entry);
|
||||
endInsertRows();
|
||||
|
||||
checkInputPath(index.row());
|
||||
}
|
||||
} else if (index.column() == ImporterColumn::Selected) {
|
||||
bool select = value.toBool();
|
||||
|
||||
options[index.row()].selected = select;
|
||||
} else if (index.column() == ImporterColumn::Path) {
|
||||
QString path = value.toString();
|
||||
options[index.row()].path = path;
|
||||
|
||||
checkInputPath(index.row());
|
||||
} else if (index.column() == ImporterColumn::Name) {
|
||||
QString name = value.toString();
|
||||
options[index.row()].name = name;
|
||||
}
|
||||
|
||||
emit dataChanged(index, index);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QVariant ImporterModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
QVariant result = QVariant();
|
||||
|
||||
if (role == Qt::DisplayRole && orientation == Qt::Orientation::Horizontal) {
|
||||
switch (section) {
|
||||
case ImporterColumn::Path:
|
||||
result = QTStr("Importer.Path");
|
||||
break;
|
||||
case ImporterColumn::Program:
|
||||
result = QTStr("Importer.Program");
|
||||
break;
|
||||
case ImporterColumn::Name:
|
||||
result = QTStr("Name");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
Window
|
||||
**/
|
||||
|
||||
OBSImporter::OBSImporter(QWidget *parent) : QDialog(parent), optionsModel(new ImporterModel), ui(new Ui::OBSImporter)
|
||||
{
|
||||
setAcceptDrops(true);
|
||||
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
ui->setupUi(this);
|
||||
|
||||
ui->tableView->setModel(optionsModel);
|
||||
ui->tableView->setItemDelegateForColumn(ImporterColumn::Path, new ImporterEntryPathItemDelegate());
|
||||
ui->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeMode::ResizeToContents);
|
||||
ui->tableView->horizontalHeader()->setSectionResizeMode(ImporterColumn::Path, QHeaderView::ResizeMode::Stretch);
|
||||
|
||||
connect(optionsModel, &ImporterModel::dataChanged, this, &OBSImporter::dataChanged);
|
||||
|
||||
ui->tableView->setEditTriggers(QAbstractItemView::EditTrigger::CurrentChanged);
|
||||
|
||||
ui->buttonBox->button(QDialogButtonBox::Ok)->setText(QTStr("Import"));
|
||||
ui->buttonBox->button(QDialogButtonBox::Open)->setText(QTStr("Add"));
|
||||
|
||||
connect(ui->buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, this,
|
||||
&OBSImporter::importCollections);
|
||||
connect(ui->buttonBox->button(QDialogButtonBox::Open), &QPushButton::clicked, this, &OBSImporter::browseImport);
|
||||
connect(ui->buttonBox->button(QDialogButtonBox::Close), &QPushButton::clicked, this, &OBSImporter::close);
|
||||
|
||||
ImportersInit();
|
||||
|
||||
bool autoSearchPrompt = config_get_bool(App()->GetUserConfig(), "General", "AutoSearchPrompt");
|
||||
|
||||
if (!autoSearchPrompt) {
|
||||
QMessageBox::StandardButton button = OBSMessageBox::question(
|
||||
parent, QTStr("Importer.AutomaticCollectionPrompt"), QTStr("Importer.AutomaticCollectionText"));
|
||||
|
||||
if (button == QMessageBox::Yes) {
|
||||
config_set_bool(App()->GetUserConfig(), "General", "AutomaticCollectionSearch", true);
|
||||
} else {
|
||||
config_set_bool(App()->GetUserConfig(), "General", "AutomaticCollectionSearch", false);
|
||||
}
|
||||
|
||||
config_set_bool(App()->GetUserConfig(), "General", "AutoSearchPrompt", true);
|
||||
}
|
||||
|
||||
bool autoSearch = config_get_bool(App()->GetUserConfig(), "General", "AutomaticCollectionSearch");
|
||||
|
||||
OBSImporterFiles f;
|
||||
if (autoSearch)
|
||||
f = ImportersFindFiles();
|
||||
|
||||
for (size_t i = 0; i < f.size(); i++) {
|
||||
QString path = f[i].c_str();
|
||||
path.replace("\\", "/");
|
||||
addImportOption(path, true);
|
||||
}
|
||||
|
||||
f.clear();
|
||||
|
||||
ui->tableView->resizeColumnsToContents();
|
||||
|
||||
QModelIndex index = optionsModel->createIndex(optionsModel->rowCount() - 1, 2);
|
||||
QMetaObject::invokeMethod(ui->tableView, "setCurrentIndex", Qt::QueuedConnection,
|
||||
Q_ARG(const QModelIndex &, index));
|
||||
}
|
||||
|
||||
void OBSImporter::addImportOption(QString path, bool automatic)
|
||||
{
|
||||
QStringList list;
|
||||
|
||||
list.append(path);
|
||||
|
||||
QModelIndex insertIndex = optionsModel->index(optionsModel->rowCount() - 1, ImporterColumn::Path);
|
||||
|
||||
optionsModel->setData(insertIndex, list, automatic ? ImporterEntryRole::AutoPath : ImporterEntryRole::NewPath);
|
||||
}
|
||||
|
||||
void OBSImporter::dropEvent(QDropEvent *ev)
|
||||
{
|
||||
for (QUrl url : ev->mimeData()->urls()) {
|
||||
QFileInfo fileInfo(url.toLocalFile());
|
||||
if (fileInfo.isDir()) {
|
||||
|
||||
QDirIterator dirIter(fileInfo.absoluteFilePath(), QDir::Files);
|
||||
|
||||
while (dirIter.hasNext()) {
|
||||
addImportOption(dirIter.next(), false);
|
||||
}
|
||||
} else {
|
||||
addImportOption(fileInfo.canonicalFilePath(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OBSImporter::dragEnterEvent(QDragEnterEvent *ev)
|
||||
{
|
||||
if (ev->mimeData()->hasUrls())
|
||||
ev->accept();
|
||||
}
|
||||
|
||||
void OBSImporter::browseImport()
|
||||
{
|
||||
QString Pattern = "(*.json *.bpres *.xml *.xconfig)";
|
||||
|
||||
QStringList paths = OpenFiles(this, QTStr("Importer.SelectCollection"), "",
|
||||
QTStr("Importer.Collection") + QString(" ") + Pattern);
|
||||
|
||||
if (!paths.empty()) {
|
||||
for (int i = 0; i < paths.count(); i++) {
|
||||
addImportOption(paths[i], false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool GetUnusedName(std::string &name)
|
||||
{
|
||||
OBSBasic *basic = reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
|
||||
|
||||
if (!basic->GetSceneCollectionByName(name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string newName;
|
||||
int inc = 2;
|
||||
do {
|
||||
newName = name;
|
||||
newName += " ";
|
||||
newName += std::to_string(inc++);
|
||||
} while (basic->GetSceneCollectionByName(newName));
|
||||
|
||||
name = newName;
|
||||
return true;
|
||||
}
|
||||
|
||||
constexpr std::string_view OBSSceneCollectionPath = "obs-studio/basic/scenes/";
|
||||
|
||||
void OBSImporter::importCollections()
|
||||
{
|
||||
setEnabled(false);
|
||||
|
||||
const std::filesystem::path sceneCollectionLocation =
|
||||
App()->userScenesLocation / std::filesystem::u8path(OBSSceneCollectionPath);
|
||||
|
||||
for (int i = 0; i < optionsModel->rowCount() - 1; i++) {
|
||||
int selected = optionsModel->index(i, ImporterColumn::Selected).data(Qt::CheckStateRole).value<int>();
|
||||
|
||||
if (selected == Qt::Unchecked)
|
||||
continue;
|
||||
|
||||
std::string pathStr = optionsModel->index(i, ImporterColumn::Path)
|
||||
.data(Qt::DisplayRole)
|
||||
.value<QString>()
|
||||
.toStdString();
|
||||
std::string nameStr = optionsModel->index(i, ImporterColumn::Name)
|
||||
.data(Qt::DisplayRole)
|
||||
.value<QString>()
|
||||
.toStdString();
|
||||
|
||||
json11::Json res;
|
||||
ImportSC(pathStr, nameStr, res);
|
||||
|
||||
if (res != json11::Json()) {
|
||||
json11::Json::object out = res.object_items();
|
||||
std::string name = res["name"].string_value();
|
||||
std::string file;
|
||||
|
||||
if (GetUnusedName(name)) {
|
||||
json11::Json::object newOut = out;
|
||||
newOut["name"] = name;
|
||||
out = newOut;
|
||||
}
|
||||
|
||||
std::string fileName;
|
||||
if (!GetFileSafeName(name.c_str(), fileName)) {
|
||||
blog(LOG_WARNING, "Failed to create safe file name for '%s'", fileName.c_str());
|
||||
}
|
||||
|
||||
std::string collectionFile;
|
||||
collectionFile.reserve(sceneCollectionLocation.u8string().size() + fileName.size());
|
||||
collectionFile.append(sceneCollectionLocation.u8string()).append(fileName);
|
||||
|
||||
if (!GetClosestUnusedFileName(collectionFile, "json")) {
|
||||
blog(LOG_WARNING, "Failed to get closest file name for %s", fileName.c_str());
|
||||
}
|
||||
|
||||
std::string out_str = json11::Json(out).dump();
|
||||
|
||||
bool success = os_quick_write_utf8_file(collectionFile.c_str(), out_str.c_str(), out_str.size(),
|
||||
false);
|
||||
|
||||
blog(LOG_INFO, "Import Scene Collection: %s (%s) - %s", name.c_str(), fileName.c_str(),
|
||||
success ? "SUCCESS" : "FAILURE");
|
||||
}
|
||||
}
|
||||
|
||||
close();
|
||||
}
|
||||
|
||||
void OBSImporter::dataChanged()
|
||||
{
|
||||
ui->tableView->resizeColumnToContents(ImporterColumn::Name);
|
||||
}
|
||||
@@ -1,542 +0,0 @@
|
||||
/******************************************************************************
|
||||
Copyright (C) 2019 by Dillon Pentz <dillon@vodbox.io>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
#include "moc_window-missing-files.cpp"
|
||||
#include "window-basic-main.hpp"
|
||||
|
||||
#include "obs-app.hpp"
|
||||
|
||||
#include <QLineEdit>
|
||||
#include <QToolButton>
|
||||
#include <QFileDialog>
|
||||
|
||||
#include <qt-wrappers.hpp>
|
||||
|
||||
enum MissingFilesColumn {
|
||||
Source,
|
||||
OriginalPath,
|
||||
NewPath,
|
||||
State,
|
||||
|
||||
Count
|
||||
};
|
||||
|
||||
enum MissingFilesRole { EntryStateRole = Qt::UserRole, NewPathsToProcessRole };
|
||||
|
||||
/**********************************************************
|
||||
Delegate - Presents cells in the grid.
|
||||
**********************************************************/
|
||||
|
||||
MissingFilesPathItemDelegate::MissingFilesPathItemDelegate(bool isOutput, const QString &defaultPath)
|
||||
: QStyledItemDelegate(),
|
||||
isOutput(isOutput),
|
||||
defaultPath(defaultPath)
|
||||
{
|
||||
}
|
||||
|
||||
QWidget *MissingFilesPathItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem & /* option */,
|
||||
const QModelIndex &) const
|
||||
{
|
||||
QSizePolicy buttonSizePolicy(QSizePolicy::Policy::Minimum, QSizePolicy::Policy::Expanding,
|
||||
QSizePolicy::ControlType::PushButton);
|
||||
|
||||
QWidget *container = new QWidget(parent);
|
||||
|
||||
auto browseCallback = [this, container]() {
|
||||
const_cast<MissingFilesPathItemDelegate *>(this)->handleBrowse(container);
|
||||
};
|
||||
|
||||
auto clearCallback = [this, container]() {
|
||||
const_cast<MissingFilesPathItemDelegate *>(this)->handleClear(container);
|
||||
};
|
||||
|
||||
QHBoxLayout *layout = new QHBoxLayout();
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setSpacing(0);
|
||||
|
||||
QLineEdit *text = new QLineEdit();
|
||||
text->setObjectName(QStringLiteral("text"));
|
||||
text->setSizePolicy(QSizePolicy(QSizePolicy::Policy::Expanding, QSizePolicy::Policy::Expanding,
|
||||
QSizePolicy::ControlType::LineEdit));
|
||||
layout->addWidget(text);
|
||||
|
||||
QToolButton *browseButton = new QToolButton();
|
||||
browseButton->setText("...");
|
||||
browseButton->setSizePolicy(buttonSizePolicy);
|
||||
layout->addWidget(browseButton);
|
||||
|
||||
container->connect(browseButton, &QToolButton::clicked, browseCallback);
|
||||
|
||||
// The "clear" button is not shown in input cells
|
||||
if (isOutput) {
|
||||
QToolButton *clearButton = new QToolButton();
|
||||
clearButton->setText("X");
|
||||
clearButton->setSizePolicy(buttonSizePolicy);
|
||||
layout->addWidget(clearButton);
|
||||
|
||||
container->connect(clearButton, &QToolButton::clicked, clearCallback);
|
||||
}
|
||||
|
||||
container->setLayout(layout);
|
||||
container->setFocusProxy(text);
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
void MissingFilesPathItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
|
||||
{
|
||||
QLineEdit *text = editor->findChild<QLineEdit *>();
|
||||
text->setText(index.data().toString());
|
||||
|
||||
editor->setProperty(PATH_LIST_PROP, QVariant());
|
||||
}
|
||||
|
||||
void MissingFilesPathItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
|
||||
const QModelIndex &index) const
|
||||
{
|
||||
// We use the PATH_LIST_PROP property to pass a list of
|
||||
// path strings from the editor widget into the model's
|
||||
// NewPathsToProcessRole. This is only used when paths
|
||||
// are selected through the "browse" or "delete" buttons
|
||||
// in the editor. If the user enters new text in the
|
||||
// text box, we simply pass that text on to the model
|
||||
// as normal text data in the default role.
|
||||
QVariant pathListProp = editor->property(PATH_LIST_PROP);
|
||||
if (pathListProp.isValid()) {
|
||||
QStringList list = editor->property(PATH_LIST_PROP).toStringList();
|
||||
if (isOutput) {
|
||||
model->setData(index, list);
|
||||
} else
|
||||
model->setData(index, list, MissingFilesRole::NewPathsToProcessRole);
|
||||
} else {
|
||||
QLineEdit *lineEdit = editor->findChild<QLineEdit *>();
|
||||
model->setData(index, lineEdit->text(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
void MissingFilesPathItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index) const
|
||||
{
|
||||
QStyleOptionViewItem localOption = option;
|
||||
initStyleOption(&localOption, index);
|
||||
|
||||
QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &localOption, painter);
|
||||
}
|
||||
|
||||
void MissingFilesPathItemDelegate::handleBrowse(QWidget *container)
|
||||
{
|
||||
|
||||
QLineEdit *text = container->findChild<QLineEdit *>();
|
||||
|
||||
QString currentPath = text->text();
|
||||
if (currentPath.isEmpty() || currentPath.compare(QTStr("MissingFiles.Clear")) == 0)
|
||||
currentPath = defaultPath;
|
||||
|
||||
bool isSet = false;
|
||||
if (isOutput) {
|
||||
QString newPath =
|
||||
QFileDialog::getOpenFileName(container, QTStr("MissingFiles.SelectFile"), currentPath, nullptr);
|
||||
|
||||
#ifdef __APPLE__
|
||||
// TODO: Revisit when QTBUG-42661 is fixed
|
||||
container->window()->raise();
|
||||
#endif
|
||||
|
||||
if (!newPath.isEmpty()) {
|
||||
container->setProperty(PATH_LIST_PROP, QStringList() << newPath);
|
||||
isSet = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isSet)
|
||||
emit commitData(container);
|
||||
}
|
||||
|
||||
void MissingFilesPathItemDelegate::handleClear(QWidget *container)
|
||||
{
|
||||
// An empty string list will indicate that the entry is being
|
||||
// blanked and should be deleted.
|
||||
container->setProperty(PATH_LIST_PROP, QStringList() << QTStr("MissingFiles.Clear"));
|
||||
container->findChild<QLineEdit *>()->clearFocus();
|
||||
((QWidget *)container->parent())->setFocus();
|
||||
emit commitData(container);
|
||||
}
|
||||
|
||||
/**
|
||||
Model
|
||||
**/
|
||||
|
||||
MissingFilesModel::MissingFilesModel(QObject *parent) : QAbstractTableModel(parent)
|
||||
{
|
||||
QStyle *style = QApplication::style();
|
||||
|
||||
warningIcon = style->standardIcon(QStyle::SP_MessageBoxWarning);
|
||||
}
|
||||
|
||||
int MissingFilesModel::rowCount(const QModelIndex &) const
|
||||
{
|
||||
return files.length();
|
||||
}
|
||||
|
||||
int MissingFilesModel::columnCount(const QModelIndex &) const
|
||||
{
|
||||
return MissingFilesColumn::Count;
|
||||
}
|
||||
|
||||
int MissingFilesModel::found() const
|
||||
{
|
||||
int res = 0;
|
||||
|
||||
for (int i = 0; i < files.length(); i++) {
|
||||
if (files[i].state != Missing && files[i].state != Cleared)
|
||||
res++;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
QVariant MissingFilesModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
QVariant result = QVariant();
|
||||
|
||||
if (index.row() >= files.length()) {
|
||||
return QVariant();
|
||||
} else if (role == Qt::DisplayRole) {
|
||||
QFileInfo fi(files[index.row()].originalPath);
|
||||
|
||||
switch (index.column()) {
|
||||
case MissingFilesColumn::Source:
|
||||
result = files[index.row()].source;
|
||||
break;
|
||||
case MissingFilesColumn::OriginalPath:
|
||||
result = fi.fileName();
|
||||
break;
|
||||
case MissingFilesColumn::NewPath:
|
||||
result = files[index.row()].newPath;
|
||||
break;
|
||||
case MissingFilesColumn::State:
|
||||
switch (files[index.row()].state) {
|
||||
case MissingFilesState::Missing:
|
||||
result = QTStr("MissingFiles.Missing");
|
||||
break;
|
||||
|
||||
case MissingFilesState::Replaced:
|
||||
result = QTStr("MissingFiles.Replaced");
|
||||
break;
|
||||
|
||||
case MissingFilesState::Found:
|
||||
result = QTStr("MissingFiles.Found");
|
||||
break;
|
||||
|
||||
case MissingFilesState::Cleared:
|
||||
result = QTStr("MissingFiles.Cleared");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else if (role == Qt::DecorationRole && index.column() == MissingFilesColumn::Source) {
|
||||
OBSBasic *main = reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
|
||||
OBSSourceAutoRelease source = obs_get_source_by_name(files[index.row()].source.toStdString().c_str());
|
||||
|
||||
if (source) {
|
||||
result = main->GetSourceIcon(obs_source_get_id(source));
|
||||
}
|
||||
} else if (role == Qt::FontRole && index.column() == MissingFilesColumn::State) {
|
||||
QFont font = QFont();
|
||||
font.setBold(true);
|
||||
|
||||
result = font;
|
||||
} else if (role == Qt::ToolTipRole && index.column() == MissingFilesColumn::State) {
|
||||
switch (files[index.row()].state) {
|
||||
case MissingFilesState::Missing:
|
||||
result = QTStr("MissingFiles.Missing");
|
||||
break;
|
||||
|
||||
case MissingFilesState::Replaced:
|
||||
result = QTStr("MissingFiles.Replaced");
|
||||
break;
|
||||
|
||||
case MissingFilesState::Found:
|
||||
result = QTStr("MissingFiles.Found");
|
||||
break;
|
||||
|
||||
case MissingFilesState::Cleared:
|
||||
result = QTStr("MissingFiles.Cleared");
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if (role == Qt::ToolTipRole) {
|
||||
switch (index.column()) {
|
||||
case MissingFilesColumn::OriginalPath:
|
||||
result = files[index.row()].originalPath;
|
||||
break;
|
||||
case MissingFilesColumn::NewPath:
|
||||
result = files[index.row()].newPath;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Qt::ItemFlags MissingFilesModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
Qt::ItemFlags flags = QAbstractTableModel::flags(index);
|
||||
|
||||
if (index.column() == MissingFilesColumn::OriginalPath) {
|
||||
flags &= ~Qt::ItemIsEditable;
|
||||
} else if (index.column() == MissingFilesColumn::NewPath && index.row() != files.length()) {
|
||||
flags |= Qt::ItemIsEditable;
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
void MissingFilesModel::fileCheckLoop(QList<MissingFileEntry> files, QString path, bool skipPrompt)
|
||||
{
|
||||
loop = false;
|
||||
QUrl url = QUrl().fromLocalFile(path);
|
||||
QString dir = url.toDisplayString(QUrl::RemoveScheme | QUrl::RemoveFilename | QUrl::PreferLocalFile);
|
||||
|
||||
bool prompted = skipPrompt;
|
||||
|
||||
for (int i = 0; i < files.length(); i++) {
|
||||
if (files[i].state != MissingFilesState::Missing)
|
||||
continue;
|
||||
|
||||
QUrl origFile = QUrl().fromLocalFile(files[i].originalPath);
|
||||
QString filename = origFile.fileName();
|
||||
QString testFile = dir + filename;
|
||||
|
||||
if (os_file_exists(testFile.toStdString().c_str())) {
|
||||
if (!prompted) {
|
||||
QMessageBox::StandardButton button =
|
||||
QMessageBox::question(nullptr, QTStr("MissingFiles.AutoSearch"),
|
||||
QTStr("MissingFiles.AutoSearchText"));
|
||||
|
||||
if (button == QMessageBox::No)
|
||||
break;
|
||||
|
||||
prompted = true;
|
||||
}
|
||||
QModelIndex in = index(i, MissingFilesColumn::NewPath);
|
||||
setData(in, testFile, 0);
|
||||
}
|
||||
}
|
||||
loop = true;
|
||||
}
|
||||
|
||||
bool MissingFilesModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
||||
{
|
||||
bool success = false;
|
||||
|
||||
if (role == MissingFilesRole::NewPathsToProcessRole) {
|
||||
QStringList list = value.toStringList();
|
||||
|
||||
int row = index.row() + 1;
|
||||
beginInsertRows(QModelIndex(), row, row);
|
||||
|
||||
MissingFileEntry entry;
|
||||
entry.originalPath = list[0].replace("\\", "/");
|
||||
entry.source = list[1];
|
||||
|
||||
files.insert(row, entry);
|
||||
row++;
|
||||
|
||||
endInsertRows();
|
||||
|
||||
success = true;
|
||||
} else {
|
||||
QString path = value.toString();
|
||||
if (index.column() == MissingFilesColumn::NewPath) {
|
||||
files[index.row()].newPath = value.toString();
|
||||
QString fileName = QUrl(path).fileName();
|
||||
QString origFileName = QUrl(files[index.row()].originalPath).fileName();
|
||||
|
||||
if (path.isEmpty()) {
|
||||
files[index.row()].state = MissingFilesState::Missing;
|
||||
} else if (path.compare(QTStr("MissingFiles.Clear")) == 0) {
|
||||
files[index.row()].state = MissingFilesState::Cleared;
|
||||
} else if (fileName.compare(origFileName) == 0) {
|
||||
files[index.row()].state = MissingFilesState::Found;
|
||||
|
||||
if (loop)
|
||||
fileCheckLoop(files, path, false);
|
||||
} else {
|
||||
files[index.row()].state = MissingFilesState::Replaced;
|
||||
|
||||
if (loop)
|
||||
fileCheckLoop(files, path, false);
|
||||
}
|
||||
|
||||
emit dataChanged(index, index);
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
QVariant MissingFilesModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
QVariant result = QVariant();
|
||||
|
||||
if (role == Qt::DisplayRole && orientation == Qt::Orientation::Horizontal) {
|
||||
switch (section) {
|
||||
case MissingFilesColumn::State:
|
||||
result = QTStr("MissingFiles.State");
|
||||
break;
|
||||
case MissingFilesColumn::Source:
|
||||
result = QTStr("Basic.Main.Source");
|
||||
break;
|
||||
case MissingFilesColumn::OriginalPath:
|
||||
result = QTStr("MissingFiles.MissingFile");
|
||||
break;
|
||||
case MissingFilesColumn::NewPath:
|
||||
result = QTStr("MissingFiles.NewFile");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
OBSMissingFiles::OBSMissingFiles(obs_missing_files_t *files, QWidget *parent)
|
||||
: QDialog(parent),
|
||||
filesModel(new MissingFilesModel),
|
||||
ui(new Ui::OBSMissingFiles)
|
||||
{
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
ui->setupUi(this);
|
||||
|
||||
ui->tableView->setModel(filesModel);
|
||||
ui->tableView->setItemDelegateForColumn(MissingFilesColumn::OriginalPath,
|
||||
new MissingFilesPathItemDelegate(false, ""));
|
||||
ui->tableView->setItemDelegateForColumn(MissingFilesColumn::NewPath,
|
||||
new MissingFilesPathItemDelegate(true, ""));
|
||||
ui->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeMode::Stretch);
|
||||
ui->tableView->horizontalHeader()->setSectionResizeMode(MissingFilesColumn::Source,
|
||||
QHeaderView::ResizeMode::ResizeToContents);
|
||||
ui->tableView->horizontalHeader()->setMaximumSectionSize(width() / 3);
|
||||
ui->tableView->horizontalHeader()->setSectionResizeMode(MissingFilesColumn::State,
|
||||
QHeaderView::ResizeMode::ResizeToContents);
|
||||
ui->tableView->setEditTriggers(QAbstractItemView::EditTrigger::CurrentChanged);
|
||||
|
||||
ui->warningIcon->setPixmap(filesModel->warningIcon.pixmap(QSize(32, 32)));
|
||||
|
||||
for (size_t i = 0; i < obs_missing_files_count(files); i++) {
|
||||
obs_missing_file_t *f = obs_missing_files_get_file(files, (int)i);
|
||||
|
||||
const char *oldPath = obs_missing_file_get_path(f);
|
||||
const char *name = obs_missing_file_get_source_name(f);
|
||||
|
||||
addMissingFile(oldPath, name);
|
||||
}
|
||||
|
||||
QString found = QTStr("MissingFiles.NumFound").arg("0", QString::number(obs_missing_files_count(files)));
|
||||
|
||||
ui->found->setText(found);
|
||||
|
||||
fileStore = files;
|
||||
|
||||
connect(ui->doneButton, &QPushButton::clicked, this, &OBSMissingFiles::saveFiles);
|
||||
connect(ui->browseButton, &QPushButton::clicked, this, &OBSMissingFiles::browseFolders);
|
||||
connect(ui->cancelButton, &QPushButton::clicked, this, &OBSMissingFiles::close);
|
||||
connect(filesModel, &MissingFilesModel::dataChanged, this, &OBSMissingFiles::dataChanged);
|
||||
|
||||
QModelIndex index = filesModel->createIndex(0, 1);
|
||||
QMetaObject::invokeMethod(ui->tableView, "setCurrentIndex", Qt::QueuedConnection,
|
||||
Q_ARG(const QModelIndex &, index));
|
||||
}
|
||||
|
||||
OBSMissingFiles::~OBSMissingFiles()
|
||||
{
|
||||
obs_missing_files_destroy(fileStore);
|
||||
}
|
||||
|
||||
void OBSMissingFiles::addMissingFile(const char *originalPath, const char *sourceName)
|
||||
{
|
||||
QStringList list;
|
||||
|
||||
list.append(originalPath);
|
||||
list.append(sourceName);
|
||||
|
||||
QModelIndex insertIndex = filesModel->index(filesModel->rowCount() - 1, MissingFilesColumn::Source);
|
||||
|
||||
filesModel->setData(insertIndex, list, MissingFilesRole::NewPathsToProcessRole);
|
||||
}
|
||||
|
||||
void OBSMissingFiles::saveFiles()
|
||||
{
|
||||
for (int i = 0; i < filesModel->files.length(); i++) {
|
||||
MissingFilesState state = filesModel->files[i].state;
|
||||
if (state != MissingFilesState::Missing) {
|
||||
obs_missing_file_t *f = obs_missing_files_get_file(fileStore, i);
|
||||
|
||||
QString path = filesModel->files[i].newPath;
|
||||
|
||||
if (state == MissingFilesState::Cleared) {
|
||||
obs_missing_file_issue_callback(f, "");
|
||||
} else {
|
||||
char *p = bstrdup(path.toStdString().c_str());
|
||||
obs_missing_file_issue_callback(f, p);
|
||||
bfree(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
void OBSMissingFiles::browseFolders()
|
||||
{
|
||||
QString dir = QFileDialog::getExistingDirectory(this, QTStr("MissingFiles.SelectDir"), "",
|
||||
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
|
||||
|
||||
if (dir != "") {
|
||||
dir += "/";
|
||||
filesModel->fileCheckLoop(filesModel->files, dir, true);
|
||||
}
|
||||
}
|
||||
|
||||
void OBSMissingFiles::dataChanged()
|
||||
{
|
||||
QString found =
|
||||
QTStr("MissingFiles.NumFound")
|
||||
.arg(QString::number(filesModel->found()), QString::number(obs_missing_files_count(fileStore)));
|
||||
|
||||
ui->found->setText(found);
|
||||
|
||||
ui->tableView->resizeColumnToContents(MissingFilesColumn::State);
|
||||
ui->tableView->resizeColumnToContents(MissingFilesColumn::Source);
|
||||
}
|
||||
|
||||
QIcon OBSMissingFiles::GetWarningIcon()
|
||||
{
|
||||
return filesModel->warningIcon;
|
||||
}
|
||||
|
||||
void OBSMissingFiles::SetWarningIcon(const QIcon &icon)
|
||||
{
|
||||
ui->warningIcon->setPixmap(icon.pixmap(QSize(32, 32)));
|
||||
filesModel->warningIcon = icon;
|
||||
}
|
||||
@@ -1,924 +0,0 @@
|
||||
/******************************************************************************
|
||||
Copyright (C) 2014 by Ruwen Hahn <palana@stunned.de>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
#include "moc_window-remux.cpp"
|
||||
|
||||
#include "obs-app.hpp"
|
||||
|
||||
#include <QCloseEvent>
|
||||
#include <QDirIterator>
|
||||
#include <QItemDelegate>
|
||||
#include <QLineEdit>
|
||||
#include <QMessageBox>
|
||||
#include <QMimeData>
|
||||
#include <QPainter>
|
||||
#include <QPushButton>
|
||||
#include <QStandardItemModel>
|
||||
#include <QStyledItemDelegate>
|
||||
#include <QToolButton>
|
||||
#include <QTimer>
|
||||
#include <qt-wrappers.hpp>
|
||||
|
||||
#include "window-basic-main.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <cmath>
|
||||
|
||||
using namespace std;
|
||||
|
||||
enum RemuxEntryColumn {
|
||||
State,
|
||||
InputPath,
|
||||
OutputPath,
|
||||
|
||||
Count
|
||||
};
|
||||
|
||||
enum RemuxEntryRole { EntryStateRole = Qt::UserRole, NewPathsToProcessRole };
|
||||
|
||||
/**********************************************************
|
||||
Delegate - Presents cells in the grid.
|
||||
**********************************************************/
|
||||
|
||||
RemuxEntryPathItemDelegate::RemuxEntryPathItemDelegate(bool isOutput, const QString &defaultPath)
|
||||
: QStyledItemDelegate(),
|
||||
isOutput(isOutput),
|
||||
defaultPath(defaultPath)
|
||||
{
|
||||
}
|
||||
|
||||
QWidget *RemuxEntryPathItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem & /* option */,
|
||||
const QModelIndex &index) const
|
||||
{
|
||||
RemuxEntryState state = index.model()
|
||||
->index(index.row(), RemuxEntryColumn::State)
|
||||
.data(RemuxEntryRole::EntryStateRole)
|
||||
.value<RemuxEntryState>();
|
||||
if (state == RemuxEntryState::Pending || state == RemuxEntryState::InProgress) {
|
||||
// Never allow modification of rows that are
|
||||
// in progress.
|
||||
return Q_NULLPTR;
|
||||
} else if (isOutput && state != RemuxEntryState::Ready) {
|
||||
// Do not allow modification of output rows
|
||||
// that aren't associated with a valid input.
|
||||
return Q_NULLPTR;
|
||||
} else if (!isOutput && state == RemuxEntryState::Complete) {
|
||||
// Don't allow modification of rows that are
|
||||
// already complete.
|
||||
return Q_NULLPTR;
|
||||
} else {
|
||||
QSizePolicy buttonSizePolicy(QSizePolicy::Policy::Minimum, QSizePolicy::Policy::Expanding,
|
||||
QSizePolicy::ControlType::PushButton);
|
||||
|
||||
QWidget *container = new QWidget(parent);
|
||||
|
||||
auto browseCallback = [this, container]() {
|
||||
const_cast<RemuxEntryPathItemDelegate *>(this)->handleBrowse(container);
|
||||
};
|
||||
|
||||
auto clearCallback = [this, container]() {
|
||||
const_cast<RemuxEntryPathItemDelegate *>(this)->handleClear(container);
|
||||
};
|
||||
|
||||
QHBoxLayout *layout = new QHBoxLayout();
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setSpacing(0);
|
||||
|
||||
QLineEdit *text = new QLineEdit();
|
||||
text->setObjectName(QStringLiteral("text"));
|
||||
text->setSizePolicy(QSizePolicy(QSizePolicy::Policy::Expanding, QSizePolicy::Policy::Expanding,
|
||||
QSizePolicy::ControlType::LineEdit));
|
||||
layout->addWidget(text);
|
||||
|
||||
QObject::connect(text, &QLineEdit::editingFinished, this, &RemuxEntryPathItemDelegate::updateText);
|
||||
|
||||
QToolButton *browseButton = new QToolButton();
|
||||
browseButton->setText("...");
|
||||
browseButton->setSizePolicy(buttonSizePolicy);
|
||||
layout->addWidget(browseButton);
|
||||
|
||||
container->connect(browseButton, &QToolButton::clicked, browseCallback);
|
||||
|
||||
// The "clear" button is not shown in output cells
|
||||
// or the insertion point's input cell.
|
||||
if (!isOutput && state != RemuxEntryState::Empty) {
|
||||
QToolButton *clearButton = new QToolButton();
|
||||
clearButton->setText("X");
|
||||
clearButton->setSizePolicy(buttonSizePolicy);
|
||||
layout->addWidget(clearButton);
|
||||
|
||||
container->connect(clearButton, &QToolButton::clicked, clearCallback);
|
||||
}
|
||||
|
||||
container->setLayout(layout);
|
||||
container->setFocusProxy(text);
|
||||
return container;
|
||||
}
|
||||
}
|
||||
|
||||
void RemuxEntryPathItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
|
||||
{
|
||||
QLineEdit *text = editor->findChild<QLineEdit *>();
|
||||
text->setText(index.data().toString());
|
||||
editor->setProperty(PATH_LIST_PROP, QVariant());
|
||||
}
|
||||
|
||||
void RemuxEntryPathItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
|
||||
const QModelIndex &index) const
|
||||
{
|
||||
// We use the PATH_LIST_PROP property to pass a list of
|
||||
// path strings from the editor widget into the model's
|
||||
// NewPathsToProcessRole. This is only used when paths
|
||||
// are selected through the "browse" or "delete" buttons
|
||||
// in the editor. If the user enters new text in the
|
||||
// text box, we simply pass that text on to the model
|
||||
// as normal text data in the default role.
|
||||
QVariant pathListProp = editor->property(PATH_LIST_PROP);
|
||||
if (pathListProp.isValid()) {
|
||||
QStringList list = editor->property(PATH_LIST_PROP).toStringList();
|
||||
if (isOutput) {
|
||||
if (list.size() > 0)
|
||||
model->setData(index, list);
|
||||
} else
|
||||
model->setData(index, list, RemuxEntryRole::NewPathsToProcessRole);
|
||||
} else {
|
||||
QLineEdit *lineEdit = editor->findChild<QLineEdit *>();
|
||||
model->setData(index, lineEdit->text());
|
||||
}
|
||||
}
|
||||
|
||||
void RemuxEntryPathItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index) const
|
||||
{
|
||||
RemuxEntryState state = index.model()
|
||||
->index(index.row(), RemuxEntryColumn::State)
|
||||
.data(RemuxEntryRole::EntryStateRole)
|
||||
.value<RemuxEntryState>();
|
||||
|
||||
QStyleOptionViewItem localOption = option;
|
||||
initStyleOption(&localOption, index);
|
||||
|
||||
if (isOutput) {
|
||||
if (state != Ready) {
|
||||
QColor background =
|
||||
localOption.palette.color(QPalette::ColorGroup::Disabled, QPalette::ColorRole::Window);
|
||||
|
||||
localOption.backgroundBrush = QBrush(background);
|
||||
}
|
||||
}
|
||||
|
||||
QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &localOption, painter);
|
||||
}
|
||||
|
||||
void RemuxEntryPathItemDelegate::handleBrowse(QWidget *container)
|
||||
{
|
||||
QString ExtensionPattern = "(*.mp4 *.flv *.mov *.mkv *.ts *.m3u8)";
|
||||
|
||||
QLineEdit *text = container->findChild<QLineEdit *>();
|
||||
|
||||
QString currentPath = text->text();
|
||||
if (currentPath.isEmpty())
|
||||
currentPath = defaultPath;
|
||||
|
||||
bool isSet = false;
|
||||
if (isOutput) {
|
||||
QString newPath = SaveFile(container, QTStr("Remux.SelectTarget"), currentPath, ExtensionPattern);
|
||||
|
||||
if (!newPath.isEmpty()) {
|
||||
container->setProperty(PATH_LIST_PROP, QStringList() << newPath);
|
||||
isSet = true;
|
||||
}
|
||||
} else {
|
||||
QStringList paths = OpenFiles(container, QTStr("Remux.SelectRecording"), currentPath,
|
||||
QTStr("Remux.OBSRecording") + QString(" ") + ExtensionPattern);
|
||||
|
||||
if (!paths.empty()) {
|
||||
container->setProperty(PATH_LIST_PROP, paths);
|
||||
isSet = true;
|
||||
}
|
||||
#ifdef __APPLE__
|
||||
// TODO: Revisit when QTBUG-42661 is fixed
|
||||
container->window()->raise();
|
||||
#endif
|
||||
}
|
||||
|
||||
if (isSet)
|
||||
emit commitData(container);
|
||||
}
|
||||
|
||||
void RemuxEntryPathItemDelegate::handleClear(QWidget *container)
|
||||
{
|
||||
// An empty string list will indicate that the entry is being
|
||||
// blanked and should be deleted.
|
||||
container->setProperty(PATH_LIST_PROP, QStringList());
|
||||
|
||||
emit commitData(container);
|
||||
}
|
||||
|
||||
void RemuxEntryPathItemDelegate::updateText()
|
||||
{
|
||||
QLineEdit *lineEdit = dynamic_cast<QLineEdit *>(sender());
|
||||
QWidget *editor = lineEdit->parentWidget();
|
||||
emit commitData(editor);
|
||||
}
|
||||
|
||||
/**********************************************************
|
||||
Model - Manages the queue's data
|
||||
**********************************************************/
|
||||
|
||||
int RemuxQueueModel::rowCount(const QModelIndex &) const
|
||||
{
|
||||
return queue.length() + (isProcessing ? 0 : 1);
|
||||
}
|
||||
|
||||
int RemuxQueueModel::columnCount(const QModelIndex &) const
|
||||
{
|
||||
return RemuxEntryColumn::Count;
|
||||
}
|
||||
|
||||
QVariant RemuxQueueModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
QVariant result = QVariant();
|
||||
|
||||
if (index.row() >= queue.length()) {
|
||||
return QVariant();
|
||||
} else if (role == Qt::DisplayRole) {
|
||||
switch (index.column()) {
|
||||
case RemuxEntryColumn::InputPath:
|
||||
result = queue[index.row()].sourcePath;
|
||||
break;
|
||||
case RemuxEntryColumn::OutputPath:
|
||||
result = queue[index.row()].targetPath;
|
||||
break;
|
||||
}
|
||||
} else if (role == Qt::DecorationRole && index.column() == RemuxEntryColumn::State) {
|
||||
result = getIcon(queue[index.row()].state);
|
||||
} else if (role == RemuxEntryRole::EntryStateRole) {
|
||||
result = queue[index.row()].state;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QVariant RemuxQueueModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
QVariant result = QVariant();
|
||||
|
||||
if (role == Qt::DisplayRole && orientation == Qt::Orientation::Horizontal) {
|
||||
switch (section) {
|
||||
case RemuxEntryColumn::State:
|
||||
result = QString();
|
||||
break;
|
||||
case RemuxEntryColumn::InputPath:
|
||||
result = QTStr("Remux.SourceFile");
|
||||
break;
|
||||
case RemuxEntryColumn::OutputPath:
|
||||
result = QTStr("Remux.TargetFile");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Qt::ItemFlags RemuxQueueModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
Qt::ItemFlags flags = QAbstractTableModel::flags(index);
|
||||
|
||||
if (index.column() == RemuxEntryColumn::InputPath) {
|
||||
flags |= Qt::ItemIsEditable;
|
||||
} else if (index.column() == RemuxEntryColumn::OutputPath && index.row() != queue.length()) {
|
||||
flags |= Qt::ItemIsEditable;
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
bool RemuxQueueModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
||||
{
|
||||
bool success = false;
|
||||
|
||||
if (role == RemuxEntryRole::NewPathsToProcessRole) {
|
||||
QStringList pathList = value.toStringList();
|
||||
|
||||
if (pathList.size() == 0) {
|
||||
if (index.row() < queue.size()) {
|
||||
beginRemoveRows(QModelIndex(), index.row(), index.row());
|
||||
queue.removeAt(index.row());
|
||||
endRemoveRows();
|
||||
}
|
||||
} else {
|
||||
if (pathList.size() >= 1 && index.row() < queue.length()) {
|
||||
queue[index.row()].sourcePath = pathList[0];
|
||||
checkInputPath(index.row());
|
||||
|
||||
pathList.removeAt(0);
|
||||
|
||||
success = true;
|
||||
}
|
||||
|
||||
if (pathList.size() > 0) {
|
||||
int row = index.row();
|
||||
int lastRow = row + pathList.size() - 1;
|
||||
beginInsertRows(QModelIndex(), row, lastRow);
|
||||
|
||||
for (QString path : pathList) {
|
||||
RemuxQueueEntry entry;
|
||||
entry.sourcePath = path;
|
||||
entry.state = RemuxEntryState::Empty;
|
||||
|
||||
queue.insert(row, entry);
|
||||
row++;
|
||||
}
|
||||
endInsertRows();
|
||||
|
||||
for (row = index.row(); row <= lastRow; row++) {
|
||||
checkInputPath(row);
|
||||
}
|
||||
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
} else if (index.row() == queue.length()) {
|
||||
QString path = value.toString();
|
||||
|
||||
if (!path.isEmpty()) {
|
||||
RemuxQueueEntry entry;
|
||||
entry.sourcePath = path;
|
||||
entry.state = RemuxEntryState::Empty;
|
||||
|
||||
beginInsertRows(QModelIndex(), queue.length() + 1, queue.length() + 1);
|
||||
queue.append(entry);
|
||||
endInsertRows();
|
||||
|
||||
checkInputPath(index.row());
|
||||
success = true;
|
||||
}
|
||||
} else {
|
||||
QString path = value.toString();
|
||||
|
||||
if (path.isEmpty()) {
|
||||
if (index.column() == RemuxEntryColumn::InputPath) {
|
||||
beginRemoveRows(QModelIndex(), index.row(), index.row());
|
||||
queue.removeAt(index.row());
|
||||
endRemoveRows();
|
||||
}
|
||||
} else {
|
||||
switch (index.column()) {
|
||||
case RemuxEntryColumn::InputPath:
|
||||
queue[index.row()].sourcePath = value.toString();
|
||||
checkInputPath(index.row());
|
||||
success = true;
|
||||
break;
|
||||
case RemuxEntryColumn::OutputPath:
|
||||
queue[index.row()].targetPath = value.toString();
|
||||
emit dataChanged(index, index);
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
QVariant RemuxQueueModel::getIcon(RemuxEntryState state)
|
||||
{
|
||||
QVariant icon;
|
||||
QStyle *style = QApplication::style();
|
||||
|
||||
switch (state) {
|
||||
case RemuxEntryState::Complete:
|
||||
icon = style->standardIcon(QStyle::SP_DialogApplyButton);
|
||||
break;
|
||||
|
||||
case RemuxEntryState::InProgress:
|
||||
icon = style->standardIcon(QStyle::SP_ArrowRight);
|
||||
break;
|
||||
|
||||
case RemuxEntryState::Error:
|
||||
icon = style->standardIcon(QStyle::SP_DialogCancelButton);
|
||||
break;
|
||||
|
||||
case RemuxEntryState::InvalidPath:
|
||||
icon = style->standardIcon(QStyle::SP_MessageBoxWarning);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return icon;
|
||||
}
|
||||
|
||||
void RemuxQueueModel::checkInputPath(int row)
|
||||
{
|
||||
RemuxQueueEntry &entry = queue[row];
|
||||
|
||||
if (entry.sourcePath.isEmpty()) {
|
||||
entry.state = RemuxEntryState::Empty;
|
||||
} else {
|
||||
entry.sourcePath = QDir::toNativeSeparators(entry.sourcePath);
|
||||
QFileInfo fileInfo(entry.sourcePath);
|
||||
if (fileInfo.exists())
|
||||
entry.state = RemuxEntryState::Ready;
|
||||
else
|
||||
entry.state = RemuxEntryState::InvalidPath;
|
||||
|
||||
QString newExt = ".mp4";
|
||||
QString suffix = fileInfo.suffix();
|
||||
|
||||
if (suffix.contains("mov", Qt::CaseInsensitive) || suffix.contains("mp4", Qt::CaseInsensitive)) {
|
||||
newExt = ".remuxed." + suffix;
|
||||
}
|
||||
|
||||
if (entry.state == RemuxEntryState::Ready)
|
||||
entry.targetPath = QDir::toNativeSeparators(fileInfo.path() + QDir::separator() +
|
||||
fileInfo.completeBaseName() + newExt);
|
||||
}
|
||||
|
||||
if (entry.state == RemuxEntryState::Ready && isProcessing)
|
||||
entry.state = RemuxEntryState::Pending;
|
||||
|
||||
emit dataChanged(index(row, 0), index(row, RemuxEntryColumn::Count));
|
||||
}
|
||||
|
||||
QFileInfoList RemuxQueueModel::checkForOverwrites() const
|
||||
{
|
||||
QFileInfoList list;
|
||||
|
||||
for (const RemuxQueueEntry &entry : queue) {
|
||||
if (entry.state == RemuxEntryState::Ready) {
|
||||
QFileInfo fileInfo(entry.targetPath);
|
||||
if (fileInfo.exists()) {
|
||||
list.append(fileInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
bool RemuxQueueModel::checkForErrors() const
|
||||
{
|
||||
bool hasErrors = false;
|
||||
|
||||
for (const RemuxQueueEntry &entry : queue) {
|
||||
if (entry.state == RemuxEntryState::Error) {
|
||||
hasErrors = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return hasErrors;
|
||||
}
|
||||
|
||||
void RemuxQueueModel::clearAll()
|
||||
{
|
||||
beginRemoveRows(QModelIndex(), 0, queue.size() - 1);
|
||||
queue.clear();
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
void RemuxQueueModel::clearFinished()
|
||||
{
|
||||
int index = 0;
|
||||
|
||||
for (index = 0; index < queue.size(); index++) {
|
||||
const RemuxQueueEntry &entry = queue[index];
|
||||
if (entry.state == RemuxEntryState::Complete) {
|
||||
beginRemoveRows(QModelIndex(), index, index);
|
||||
queue.removeAt(index);
|
||||
endRemoveRows();
|
||||
index--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool RemuxQueueModel::canClearFinished() const
|
||||
{
|
||||
bool canClearFinished = false;
|
||||
for (const RemuxQueueEntry &entry : queue)
|
||||
if (entry.state == RemuxEntryState::Complete) {
|
||||
canClearFinished = true;
|
||||
break;
|
||||
}
|
||||
|
||||
return canClearFinished;
|
||||
}
|
||||
|
||||
void RemuxQueueModel::beginProcessing()
|
||||
{
|
||||
for (RemuxQueueEntry &entry : queue)
|
||||
if (entry.state == RemuxEntryState::Ready)
|
||||
entry.state = RemuxEntryState::Pending;
|
||||
|
||||
// Signal that the insertion point no longer exists.
|
||||
beginRemoveRows(QModelIndex(), queue.length(), queue.length());
|
||||
endRemoveRows();
|
||||
|
||||
isProcessing = true;
|
||||
|
||||
emit dataChanged(index(0, RemuxEntryColumn::State), index(queue.length(), RemuxEntryColumn::State));
|
||||
}
|
||||
|
||||
void RemuxQueueModel::endProcessing()
|
||||
{
|
||||
for (RemuxQueueEntry &entry : queue) {
|
||||
if (entry.state == RemuxEntryState::Pending) {
|
||||
entry.state = RemuxEntryState::Ready;
|
||||
}
|
||||
}
|
||||
|
||||
// Signal that the insertion point exists again.
|
||||
isProcessing = false;
|
||||
if (!autoRemux) {
|
||||
beginInsertRows(QModelIndex(), queue.length(), queue.length());
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
emit dataChanged(index(0, RemuxEntryColumn::State), index(queue.length(), RemuxEntryColumn::State));
|
||||
}
|
||||
|
||||
bool RemuxQueueModel::beginNextEntry(QString &inputPath, QString &outputPath)
|
||||
{
|
||||
bool anyStarted = false;
|
||||
|
||||
for (int row = 0; row < queue.length(); row++) {
|
||||
RemuxQueueEntry &entry = queue[row];
|
||||
if (entry.state == RemuxEntryState::Pending) {
|
||||
entry.state = RemuxEntryState::InProgress;
|
||||
|
||||
inputPath = entry.sourcePath;
|
||||
outputPath = entry.targetPath;
|
||||
|
||||
QModelIndex index = this->index(row, RemuxEntryColumn::State);
|
||||
emit dataChanged(index, index);
|
||||
|
||||
anyStarted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return anyStarted;
|
||||
}
|
||||
|
||||
void RemuxQueueModel::finishEntry(bool success)
|
||||
{
|
||||
for (int row = 0; row < queue.length(); row++) {
|
||||
RemuxQueueEntry &entry = queue[row];
|
||||
if (entry.state == RemuxEntryState::InProgress) {
|
||||
if (success)
|
||||
entry.state = RemuxEntryState::Complete;
|
||||
else
|
||||
entry.state = RemuxEntryState::Error;
|
||||
|
||||
QModelIndex index = this->index(row, RemuxEntryColumn::State);
|
||||
emit dataChanged(index, index);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**********************************************************
|
||||
The actual remux window implementation
|
||||
**********************************************************/
|
||||
|
||||
OBSRemux::OBSRemux(const char *path, QWidget *parent, bool autoRemux_)
|
||||
: QDialog(parent),
|
||||
queueModel(new RemuxQueueModel),
|
||||
worker(new RemuxWorker()),
|
||||
ui(new Ui::OBSRemux),
|
||||
recPath(path),
|
||||
autoRemux(autoRemux_)
|
||||
{
|
||||
setAcceptDrops(true);
|
||||
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
ui->setupUi(this);
|
||||
|
||||
ui->progressBar->setVisible(false);
|
||||
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
|
||||
ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setEnabled(false);
|
||||
|
||||
if (autoRemux) {
|
||||
resize(280, 40);
|
||||
ui->tableView->hide();
|
||||
ui->buttonBox->hide();
|
||||
ui->label->hide();
|
||||
}
|
||||
|
||||
ui->progressBar->setMinimum(0);
|
||||
ui->progressBar->setMaximum(1000);
|
||||
ui->progressBar->setValue(0);
|
||||
|
||||
ui->tableView->setModel(queueModel);
|
||||
ui->tableView->setItemDelegateForColumn(RemuxEntryColumn::InputPath,
|
||||
new RemuxEntryPathItemDelegate(false, recPath));
|
||||
ui->tableView->setItemDelegateForColumn(RemuxEntryColumn::OutputPath,
|
||||
new RemuxEntryPathItemDelegate(true, recPath));
|
||||
ui->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeMode::Stretch);
|
||||
ui->tableView->horizontalHeader()->setSectionResizeMode(RemuxEntryColumn::State,
|
||||
QHeaderView::ResizeMode::Fixed);
|
||||
ui->tableView->setEditTriggers(QAbstractItemView::EditTrigger::CurrentChanged);
|
||||
ui->tableView->setTextElideMode(Qt::ElideMiddle);
|
||||
ui->tableView->setWordWrap(false);
|
||||
|
||||
installEventFilter(CreateShortcutFilter());
|
||||
|
||||
ui->buttonBox->button(QDialogButtonBox::Ok)->setText(QTStr("Remux.Remux"));
|
||||
ui->buttonBox->button(QDialogButtonBox::Reset)->setText(QTStr("Remux.ClearFinished"));
|
||||
ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setText(QTStr("Remux.ClearAll"));
|
||||
ui->buttonBox->button(QDialogButtonBox::Reset)->setDisabled(true);
|
||||
|
||||
connect(ui->buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, this, &OBSRemux::beginRemux);
|
||||
connect(ui->buttonBox->button(QDialogButtonBox::Reset), &QPushButton::clicked, this, &OBSRemux::clearFinished);
|
||||
connect(ui->buttonBox->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, this,
|
||||
&OBSRemux::clearAll);
|
||||
connect(ui->buttonBox->button(QDialogButtonBox::Close), &QPushButton::clicked, this, &OBSRemux::close);
|
||||
|
||||
worker->moveToThread(&remuxer);
|
||||
remuxer.start();
|
||||
|
||||
connect(worker.data(), &RemuxWorker::updateProgress, this, &OBSRemux::updateProgress);
|
||||
connect(&remuxer, &QThread::finished, worker.data(), &QObject::deleteLater);
|
||||
connect(worker.data(), &RemuxWorker::remuxFinished, this, &OBSRemux::remuxFinished);
|
||||
connect(this, &OBSRemux::remux, worker.data(), &RemuxWorker::remux);
|
||||
|
||||
connect(queueModel.data(), &RemuxQueueModel::rowsInserted, this, &OBSRemux::rowCountChanged);
|
||||
connect(queueModel.data(), &RemuxQueueModel::rowsRemoved, this, &OBSRemux::rowCountChanged);
|
||||
|
||||
QModelIndex index = queueModel->createIndex(0, 1);
|
||||
QMetaObject::invokeMethod(ui->tableView, "setCurrentIndex", Qt::QueuedConnection,
|
||||
Q_ARG(const QModelIndex &, index));
|
||||
}
|
||||
|
||||
bool OBSRemux::stopRemux()
|
||||
{
|
||||
if (!worker->isWorking)
|
||||
return true;
|
||||
|
||||
// By locking the worker thread's mutex, we ensure that its
|
||||
// update poll will be blocked as long as we're in here with
|
||||
// the popup open.
|
||||
QMutexLocker lock(&worker->updateMutex);
|
||||
|
||||
bool exit = false;
|
||||
|
||||
if (QMessageBox::critical(nullptr, QTStr("Remux.ExitUnfinishedTitle"), QTStr("Remux.ExitUnfinished"),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes) {
|
||||
exit = true;
|
||||
}
|
||||
|
||||
if (exit) {
|
||||
// Inform the worker it should no longer be
|
||||
// working. It will interrupt accordingly in
|
||||
// its next update callback.
|
||||
worker->isWorking = false;
|
||||
}
|
||||
|
||||
return exit;
|
||||
}
|
||||
|
||||
OBSRemux::~OBSRemux()
|
||||
{
|
||||
stopRemux();
|
||||
remuxer.quit();
|
||||
remuxer.wait();
|
||||
}
|
||||
|
||||
void OBSRemux::rowCountChanged(const QModelIndex &, int, int)
|
||||
{
|
||||
// See if there are still any rows ready to remux. Change
|
||||
// the state of the "go" button accordingly.
|
||||
// There must be more than one row, since there will always be
|
||||
// at least one row for the empty insertion point.
|
||||
if (queueModel->rowCount() > 1) {
|
||||
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
|
||||
ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setEnabled(true);
|
||||
ui->buttonBox->button(QDialogButtonBox::Reset)->setEnabled(queueModel->canClearFinished());
|
||||
} else {
|
||||
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
|
||||
ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setEnabled(false);
|
||||
ui->buttonBox->button(QDialogButtonBox::Reset)->setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
void OBSRemux::dropEvent(QDropEvent *ev)
|
||||
{
|
||||
QStringList urlList;
|
||||
|
||||
for (QUrl url : ev->mimeData()->urls()) {
|
||||
QFileInfo fileInfo(url.toLocalFile());
|
||||
|
||||
if (fileInfo.isDir()) {
|
||||
QStringList directoryFilter;
|
||||
directoryFilter << "*.flv"
|
||||
<< "*.mp4"
|
||||
<< "*.mov"
|
||||
<< "*.mkv"
|
||||
<< "*.ts"
|
||||
<< "*.m3u8";
|
||||
|
||||
QDirIterator dirIter(fileInfo.absoluteFilePath(), directoryFilter, QDir::Files,
|
||||
QDirIterator::Subdirectories);
|
||||
|
||||
while (dirIter.hasNext()) {
|
||||
urlList.append(dirIter.next());
|
||||
}
|
||||
} else {
|
||||
urlList.append(fileInfo.canonicalFilePath());
|
||||
}
|
||||
}
|
||||
|
||||
if (urlList.empty()) {
|
||||
QMessageBox::information(nullptr, QTStr("Remux.NoFilesAddedTitle"), QTStr("Remux.NoFilesAdded"),
|
||||
QMessageBox::Ok);
|
||||
} else if (!autoRemux) {
|
||||
QModelIndex insertIndex = queueModel->index(queueModel->rowCount() - 1, RemuxEntryColumn::InputPath);
|
||||
queueModel->setData(insertIndex, urlList, RemuxEntryRole::NewPathsToProcessRole);
|
||||
}
|
||||
}
|
||||
|
||||
void OBSRemux::dragEnterEvent(QDragEnterEvent *ev)
|
||||
{
|
||||
if (ev->mimeData()->hasUrls() && !worker->isWorking)
|
||||
ev->accept();
|
||||
}
|
||||
|
||||
void OBSRemux::beginRemux()
|
||||
{
|
||||
if (worker->isWorking) {
|
||||
stopRemux();
|
||||
return;
|
||||
}
|
||||
|
||||
bool proceedWithRemux = true;
|
||||
QFileInfoList overwriteFiles = queueModel->checkForOverwrites();
|
||||
|
||||
if (!overwriteFiles.empty()) {
|
||||
QString message = QTStr("Remux.FileExists");
|
||||
message += "\n\n";
|
||||
|
||||
for (QFileInfo fileInfo : overwriteFiles)
|
||||
message += fileInfo.canonicalFilePath() + "\n";
|
||||
|
||||
if (OBSMessageBox::question(this, QTStr("Remux.FileExistsTitle"), message) != QMessageBox::Yes)
|
||||
proceedWithRemux = false;
|
||||
}
|
||||
|
||||
if (!proceedWithRemux)
|
||||
return;
|
||||
|
||||
// Set all jobs to "pending" first.
|
||||
queueModel->beginProcessing();
|
||||
|
||||
ui->progressBar->setVisible(true);
|
||||
ui->buttonBox->button(QDialogButtonBox::Ok)->setText(QTStr("Remux.Stop"));
|
||||
setAcceptDrops(false);
|
||||
|
||||
remuxNextEntry();
|
||||
}
|
||||
|
||||
void OBSRemux::AutoRemux(QString inFile, QString outFile)
|
||||
{
|
||||
if (inFile != "" && outFile != "" && autoRemux) {
|
||||
ui->progressBar->setVisible(true);
|
||||
emit remux(inFile, outFile);
|
||||
autoRemuxFile = outFile;
|
||||
}
|
||||
}
|
||||
|
||||
void OBSRemux::remuxNextEntry()
|
||||
{
|
||||
worker->lastProgress = 0.f;
|
||||
|
||||
QString inputPath, outputPath;
|
||||
if (queueModel->beginNextEntry(inputPath, outputPath)) {
|
||||
emit remux(inputPath, outputPath);
|
||||
} else {
|
||||
queueModel->autoRemux = autoRemux;
|
||||
queueModel->endProcessing();
|
||||
|
||||
if (!autoRemux) {
|
||||
OBSMessageBox::information(this, QTStr("Remux.FinishedTitle"),
|
||||
queueModel->checkForErrors() ? QTStr("Remux.FinishedError")
|
||||
: QTStr("Remux.Finished"));
|
||||
}
|
||||
|
||||
ui->progressBar->setVisible(autoRemux);
|
||||
ui->buttonBox->button(QDialogButtonBox::Ok)->setText(QTStr("Remux.Remux"));
|
||||
ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setEnabled(true);
|
||||
ui->buttonBox->button(QDialogButtonBox::Reset)->setEnabled(queueModel->canClearFinished());
|
||||
setAcceptDrops(true);
|
||||
}
|
||||
}
|
||||
|
||||
void OBSRemux::closeEvent(QCloseEvent *event)
|
||||
{
|
||||
if (!stopRemux())
|
||||
event->ignore();
|
||||
else
|
||||
QDialog::closeEvent(event);
|
||||
}
|
||||
|
||||
void OBSRemux::reject()
|
||||
{
|
||||
if (!stopRemux())
|
||||
return;
|
||||
|
||||
QDialog::reject();
|
||||
}
|
||||
|
||||
void OBSRemux::updateProgress(float percent)
|
||||
{
|
||||
ui->progressBar->setValue(percent * 10);
|
||||
}
|
||||
|
||||
void OBSRemux::remuxFinished(bool success)
|
||||
{
|
||||
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
|
||||
|
||||
queueModel->finishEntry(success);
|
||||
|
||||
if (autoRemux && autoRemuxFile != "") {
|
||||
QTimer::singleShot(3000, this, &OBSRemux::close);
|
||||
|
||||
OBSBasic *main = OBSBasic::Get();
|
||||
main->ShowStatusBarMessage(QTStr("Basic.StatusBar.AutoRemuxedTo").arg(autoRemuxFile));
|
||||
}
|
||||
|
||||
remuxNextEntry();
|
||||
}
|
||||
|
||||
void OBSRemux::clearFinished()
|
||||
{
|
||||
queueModel->clearFinished();
|
||||
}
|
||||
|
||||
void OBSRemux::clearAll()
|
||||
{
|
||||
queueModel->clearAll();
|
||||
}
|
||||
|
||||
/**********************************************************
|
||||
Worker thread - Executes the libobs remux operation as a
|
||||
background process.
|
||||
**********************************************************/
|
||||
|
||||
void RemuxWorker::UpdateProgress(float percent)
|
||||
{
|
||||
if (abs(lastProgress - percent) < 0.1f)
|
||||
return;
|
||||
|
||||
emit updateProgress(percent);
|
||||
lastProgress = percent;
|
||||
}
|
||||
|
||||
void RemuxWorker::remux(const QString &source, const QString &target)
|
||||
{
|
||||
isWorking = true;
|
||||
|
||||
auto callback = [](void *data, float percent) {
|
||||
RemuxWorker *rw = static_cast<RemuxWorker *>(data);
|
||||
|
||||
QMutexLocker lock(&rw->updateMutex);
|
||||
|
||||
rw->UpdateProgress(percent);
|
||||
|
||||
return rw->isWorking;
|
||||
};
|
||||
|
||||
bool stopped = false;
|
||||
bool success = false;
|
||||
|
||||
media_remux_job_t mr_job = nullptr;
|
||||
if (media_remux_job_create(&mr_job, QT_TO_UTF8(source), QT_TO_UTF8(target))) {
|
||||
|
||||
success = media_remux_job_process(mr_job, callback, this);
|
||||
|
||||
media_remux_job_destroy(mr_job);
|
||||
|
||||
stopped = !isWorking;
|
||||
}
|
||||
|
||||
isWorking = false;
|
||||
|
||||
emit remuxFinished(!stopped && success);
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
/******************************************************************************
|
||||
Copyright (C) 2014 by Ruwen Hahn <palana@stunned.de>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QMutex>
|
||||
#include <QPointer>
|
||||
#include <QThread>
|
||||
#include <QStyledItemDelegate>
|
||||
#include <memory>
|
||||
#include "ui_OBSRemux.h"
|
||||
|
||||
#include <media-io/media-remux.h>
|
||||
#include <util/threading.h>
|
||||
|
||||
class RemuxQueueModel;
|
||||
class RemuxWorker;
|
||||
|
||||
enum RemuxEntryState { Empty, Ready, Pending, InProgress, Complete, InvalidPath, Error };
|
||||
Q_DECLARE_METATYPE(RemuxEntryState);
|
||||
|
||||
class OBSRemux : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
QPointer<RemuxQueueModel> queueModel;
|
||||
QThread remuxer;
|
||||
QPointer<RemuxWorker> worker;
|
||||
|
||||
std::unique_ptr<Ui::OBSRemux> ui;
|
||||
|
||||
const char *recPath;
|
||||
|
||||
virtual void closeEvent(QCloseEvent *event) override;
|
||||
virtual void reject() override;
|
||||
|
||||
bool autoRemux;
|
||||
QString autoRemuxFile;
|
||||
|
||||
public:
|
||||
explicit OBSRemux(const char *recPath, QWidget *parent = nullptr, bool autoRemux = false);
|
||||
virtual ~OBSRemux() override;
|
||||
|
||||
using job_t = std::shared_ptr<struct media_remux_job>;
|
||||
|
||||
void AutoRemux(QString inFile, QString outFile);
|
||||
|
||||
protected:
|
||||
virtual void dropEvent(QDropEvent *ev) override;
|
||||
virtual void dragEnterEvent(QDragEnterEvent *ev) override;
|
||||
|
||||
void remuxNextEntry();
|
||||
|
||||
private slots:
|
||||
void rowCountChanged(const QModelIndex &parent, int first, int last);
|
||||
|
||||
public slots:
|
||||
void updateProgress(float percent);
|
||||
void remuxFinished(bool success);
|
||||
void beginRemux();
|
||||
bool stopRemux();
|
||||
void clearFinished();
|
||||
void clearAll();
|
||||
|
||||
signals:
|
||||
void remux(const QString &source, const QString &target);
|
||||
};
|
||||
|
||||
class RemuxQueueModel : public QAbstractTableModel {
|
||||
Q_OBJECT
|
||||
|
||||
friend class OBSRemux;
|
||||
|
||||
public:
|
||||
RemuxQueueModel(QObject *parent = 0) : QAbstractTableModel(parent), isProcessing(false) {}
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const;
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const;
|
||||
QVariant data(const QModelIndex &index, int role) const;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const;
|
||||
bool setData(const QModelIndex &index, const QVariant &value, int role);
|
||||
|
||||
QFileInfoList checkForOverwrites() const;
|
||||
bool checkForErrors() const;
|
||||
void beginProcessing();
|
||||
void endProcessing();
|
||||
bool beginNextEntry(QString &inputPath, QString &outputPath);
|
||||
void finishEntry(bool success);
|
||||
bool canClearFinished() const;
|
||||
void clearFinished();
|
||||
void clearAll();
|
||||
|
||||
bool autoRemux = false;
|
||||
|
||||
private:
|
||||
struct RemuxQueueEntry {
|
||||
RemuxEntryState state;
|
||||
|
||||
QString sourcePath;
|
||||
QString targetPath;
|
||||
};
|
||||
|
||||
QList<RemuxQueueEntry> queue;
|
||||
bool isProcessing;
|
||||
|
||||
static QVariant getIcon(RemuxEntryState state);
|
||||
|
||||
void checkInputPath(int row);
|
||||
};
|
||||
|
||||
class RemuxWorker : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
QMutex updateMutex;
|
||||
|
||||
bool isWorking;
|
||||
|
||||
float lastProgress;
|
||||
void UpdateProgress(float percent);
|
||||
|
||||
explicit RemuxWorker() : isWorking(false) {}
|
||||
virtual ~RemuxWorker(){};
|
||||
|
||||
private slots:
|
||||
void remux(const QString &source, const QString &target);
|
||||
|
||||
signals:
|
||||
void updateProgress(float percent);
|
||||
void remuxFinished(bool success);
|
||||
|
||||
friend class OBSRemux;
|
||||
};
|
||||
|
||||
class RemuxEntryPathItemDelegate : public QStyledItemDelegate {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
RemuxEntryPathItemDelegate(bool isOutput, const QString &defaultPath);
|
||||
|
||||
virtual QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem & /* option */,
|
||||
const QModelIndex &index) const override;
|
||||
|
||||
virtual void setEditorData(QWidget *editor, const QModelIndex &index) const override;
|
||||
virtual void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
|
||||
virtual void paint(QPainter *painter, const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index) const override;
|
||||
|
||||
private:
|
||||
bool isOutput;
|
||||
QString defaultPath;
|
||||
const char *PATH_LIST_PROP = "pathList";
|
||||
|
||||
void handleBrowse(QWidget *container);
|
||||
void handleClear(QWidget *container);
|
||||
|
||||
private slots:
|
||||
void updateText();
|
||||
};
|
||||
@@ -54,7 +54,7 @@ invoke_formatter() {
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if (( ! #source_files )) source_files=((libobs|libobs-*|UI|plugins|deps|shared)/**/*.(c|cpp|h|hpp|m|mm)(.N))
|
||||
if (( ! #source_files )) source_files=((libobs|libobs-*|frontend|plugins|deps|shared)/**/*.(c|cpp|h|hpp|m|mm)(.N))
|
||||
|
||||
source_files=(${source_files:#*/(obs-websocket/deps|decklink/*/decklink-sdk|mac-syphon/syphon-framework|libdshowcapture)/*})
|
||||
|
||||
@@ -102,7 +102,7 @@ invoke_formatter() {
|
||||
fi
|
||||
}
|
||||
|
||||
if (( ! #source_files )) source_files=(CMakeLists.txt (libobs|libobs-*|UI|plugins|deps|shared|cmake|test)/**/(CMakeLists.txt|*.cmake)(.N))
|
||||
if (( ! #source_files )) source_files=(CMakeLists.txt (libobs|libobs-*|frontend|plugins|deps|shared|cmake|test)/**/(CMakeLists.txt|*.cmake)(.N))
|
||||
|
||||
source_files=(${source_files:#*/(jansson|decklink/*/decklink-sdk|obs-websocket|obs-browser|libdshowcapture)/*})
|
||||
source_files=(${source_files:#(cmake/Modules/*|*/legacy.cmake)})
|
||||
@@ -150,7 +150,7 @@ invoke_formatter() {
|
||||
exit 2
|
||||
}
|
||||
|
||||
if (( ! #source_files )) source_files=((libobs|libobs-*|UI|plugins)/**/*.swift(.N))
|
||||
if (( ! #source_files )) source_files=((libobs|libobs-*|frontend|plugins)/**/*.swift(.N))
|
||||
|
||||
check_files() {
|
||||
local -i num_failures=0
|
||||
|
||||
@@ -5,7 +5,7 @@ include_guard(GLOBAL)
|
||||
include(cpackconfig_common)
|
||||
|
||||
# Add GPLv2 license file to CPack
|
||||
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/UI/data/license/gplv2.txt")
|
||||
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/frontend/data/license/gplv2.txt")
|
||||
set(CPACK_PACKAGE_EXECUTABLES "obs")
|
||||
|
||||
if(ENABLE_RELEASE_BUILD)
|
||||
|
||||
@@ -5,7 +5,7 @@ include_guard(GLOBAL)
|
||||
include(cpackconfig_common)
|
||||
|
||||
# Add GPLv2 license file to CPack
|
||||
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/UI/data/license/gplv2.txt")
|
||||
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/frontend/data/license/gplv2.txt")
|
||||
set(CPACK_PACKAGE_VERSION "${OBS_VERSION_CANONICAL}")
|
||||
set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-windows-${CMAKE_VS_PLATFORM_NAME}")
|
||||
set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY FALSE)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
cmake_minimum_required(VERSION 3.28...3.30)
|
||||
|
||||
add_subdirectory(obs-frontend-api)
|
||||
add_subdirectory(api)
|
||||
|
||||
option(ENABLE_UI "Enable building with UI (requires Qt)" ON)
|
||||
option(ENABLE_FRONTEND "Enable building with UI frontend (requires Qt6)" ON)
|
||||
|
||||
if(NOT ENABLE_UI)
|
||||
if(NOT ENABLE_FRONTEND)
|
||||
target_disable_feature(obs "User Interface")
|
||||
return()
|
||||
else()
|
||||
@@ -15,11 +15,15 @@ find_package(FFmpeg REQUIRED COMPONENTS avcodec avutil avformat)
|
||||
find_package(CURL REQUIRED)
|
||||
|
||||
if(NOT TARGET OBS::json11)
|
||||
add_subdirectory("${CMAKE_SOURCE_DIR}/deps/json11" "${CMAKE_BINARY_DIR}/deps/json11")
|
||||
add_subdirectory("${CMAKE_SOURCE_DIR}/deps/json11" json11)
|
||||
endif()
|
||||
|
||||
if(NOT TARGET OBS::libobs)
|
||||
add_subdirectory("${CMAKE_SOURCE_DIR}/libobs" libobs)
|
||||
endif()
|
||||
|
||||
if(NOT TARGET OBS::bpm)
|
||||
add_subdirectory("${CMAKE_SOURCE_DIR}/shared/bpm" "${CMAKE_BINARY_DIR}/shared/bpm")
|
||||
add_subdirectory("${CMAKE_SOURCE_DIR}/shared/bpm" bpm)
|
||||
endif()
|
||||
|
||||
add_executable(obs-studio)
|
||||
@@ -32,17 +36,23 @@ target_link_libraries(
|
||||
FFmpeg::avcodec
|
||||
FFmpeg::avutil
|
||||
FFmpeg::avformat
|
||||
OBS::bpm
|
||||
OBS::libobs
|
||||
OBS::frontend-api
|
||||
OBS::json11
|
||||
OBS::bpm
|
||||
)
|
||||
|
||||
include(cmake/ui-qt.cmake)
|
||||
include(cmake/ui-elements.cmake)
|
||||
include(cmake/ui-windows.cmake)
|
||||
include(cmake/ui-components.cmake)
|
||||
include(cmake/ui-dialogs.cmake)
|
||||
include(cmake/ui-docks.cmake)
|
||||
include(cmake/feature-importers.cmake)
|
||||
include(cmake/ui-oauth.cmake)
|
||||
include(cmake/feature-browserpanels.cmake)
|
||||
include(cmake/ui-qt.cmake)
|
||||
include(cmake/ui-settings.cmake)
|
||||
include(cmake/ui-utility.cmake)
|
||||
include(cmake/ui-widgets.cmake)
|
||||
include(cmake/ui-wizards.cmake)
|
||||
|
||||
if(NOT OAUTH_BASE_URL)
|
||||
set(OAUTH_BASE_URL "https://auth.obsproject.com/" CACHE STRING "Default OAuth base URL")
|
||||
@@ -53,56 +63,13 @@ include(cmake/feature-restream.cmake)
|
||||
include(cmake/feature-youtube.cmake)
|
||||
include(cmake/feature-whatsnew.cmake)
|
||||
|
||||
add_subdirectory(frontend-plugins)
|
||||
add_subdirectory(plugins)
|
||||
|
||||
configure_file(ui-config.h.in ui-config.h)
|
||||
configure_file(cmake/templates/ui-config.h.in ui-config.h)
|
||||
|
||||
target_sources(
|
||||
obs-studio
|
||||
PRIVATE
|
||||
api-interface.cpp
|
||||
auth-base.cpp
|
||||
auth-base.hpp
|
||||
auth-listener.cpp
|
||||
auth-listener.hpp
|
||||
auth-oauth.cpp
|
||||
auth-oauth.hpp
|
||||
display-helpers.hpp
|
||||
ffmpeg-utils.cpp
|
||||
ffmpeg-utils.hpp
|
||||
multiview.cpp
|
||||
multiview.hpp
|
||||
obf.c
|
||||
obf.h
|
||||
obs-app-theming.cpp
|
||||
obs-app-theming.hpp
|
||||
obs-app.cpp
|
||||
obs-app.hpp
|
||||
obs-proxy-style.cpp
|
||||
obs-proxy-style.hpp
|
||||
platform.hpp
|
||||
qt-display.cpp
|
||||
qt-display.hpp
|
||||
ui-config.h
|
||||
ui-validation.cpp
|
||||
ui-validation.hpp
|
||||
)
|
||||
|
||||
target_sources(
|
||||
obs-studio
|
||||
PRIVATE
|
||||
goliveapi-censoredjson.cpp
|
||||
goliveapi-censoredjson.hpp
|
||||
goliveapi-network.cpp
|
||||
goliveapi-network.hpp
|
||||
goliveapi-postdata.cpp
|
||||
goliveapi-postdata.hpp
|
||||
models/multitrack-video.hpp
|
||||
multitrack-video-error.cpp
|
||||
multitrack-video-error.hpp
|
||||
multitrack-video-output.cpp
|
||||
multitrack-video-output.hpp
|
||||
system-info.hpp
|
||||
PRIVATE obs-main.cpp OBSStudioAPI.cpp OBSStudioAPI.hpp OBSApp.cpp OBSApp.hpp OBSApp_Themes.cpp ui-config.h
|
||||
)
|
||||
|
||||
if(OS_WINDOWS)
|
||||
@@ -132,4 +99,18 @@ get_property(obs_module_list GLOBAL PROPERTY OBS_MODULES_ENABLED)
|
||||
list(JOIN obs_module_list "|" SAFE_MODULES)
|
||||
target_compile_definitions(obs-studio PRIVATE "SAFE_MODULES=\"${SAFE_MODULES}\"")
|
||||
|
||||
get_target_property(target_sources obs-studio SOURCES)
|
||||
set(target_cpp_sources ${target_sources})
|
||||
set(target_hpp_sources ${target_sources})
|
||||
set(target_qt_sources ${target_sources})
|
||||
list(FILTER target_cpp_sources INCLUDE REGEX ".+\\.(cpp|mm|c|m)")
|
||||
list(SORT target_cpp_sources COMPARE NATURAL CASE SENSITIVE ORDER ASCENDING)
|
||||
list(FILTER target_hpp_sources INCLUDE REGEX ".+\\.(hpp|h)")
|
||||
list(SORT target_hpp_sources COMPARE NATURAL CASE SENSITIVE ORDER ASCENDING)
|
||||
list(FILTER target_qt_sources INCLUDE REGEX ".+\\.(ui|qrc)")
|
||||
list(SORT target_qt_sources COMPARE NATURAL CASE SENSITIVE ORDER ASCENDING)
|
||||
source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" PREFIX "Source Files" FILES ${target_cpp_sources})
|
||||
source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" PREFIX "Header Files" FILES ${target_hpp_sources})
|
||||
source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" PREFIX "Qt Files" FILES ${target_qt_sources})
|
||||
|
||||
set_target_properties_obs(obs-studio PROPERTIES FOLDER frontend OUTPUT_NAME "$<IF:$<PLATFORM_ID:Windows>,obs64,obs>")
|
||||
File diff suppressed because it is too large.
Load diff
@@ -17,63 +17,30 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QApplication>
|
||||
#include <QTranslator>
|
||||
#include <QPointer>
|
||||
#include <QFileSystemWatcher>
|
||||
#include <utility/OBSTheme.hpp>
|
||||
#include <widgets/OBSMainWindow.hpp>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <QSocketNotifier>
|
||||
#else
|
||||
#include <QSessionManager>
|
||||
#endif
|
||||
#include <obs.hpp>
|
||||
#include <util/lexer.h>
|
||||
#include <util/profiler.h>
|
||||
#include <util/util.hpp>
|
||||
#include <util/platform.h>
|
||||
#include <obs-frontend-api.h>
|
||||
#include <util/platform.h>
|
||||
#include <util/profiler.hpp>
|
||||
#include <util/util.hpp>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QPalette>
|
||||
#include <QPointer>
|
||||
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include <filesystem>
|
||||
|
||||
#include "window-main.hpp"
|
||||
#include "obs-app-theming.hpp"
|
||||
|
||||
std::string CurrentTimeString();
|
||||
std::string CurrentDateTimeString();
|
||||
std::string GenerateTimeDateFilename(const char *extension, bool noSpace = false);
|
||||
std::string GenerateSpecifiedFilename(const char *extension, bool noSpace, const char *format);
|
||||
std::string GetFormatString(const char *format, const char *prefix, const char *suffix);
|
||||
std::string GetFormatExt(const char *container);
|
||||
std::string GetOutputFilename(const char *path, const char *container, bool noSpace, bool overwrite,
|
||||
const char *format);
|
||||
QObject *CreateShortcutFilter();
|
||||
|
||||
struct BaseLexer {
|
||||
lexer lex;
|
||||
|
||||
public:
|
||||
inline BaseLexer() { lexer_init(&lex); }
|
||||
inline ~BaseLexer() { lexer_free(&lex); }
|
||||
operator lexer *() { return &lex; }
|
||||
};
|
||||
|
||||
class OBSTranslator : public QTranslator {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
virtual bool isEmpty() const override { return false; }
|
||||
|
||||
virtual QString translate(const char *context, const char *sourceText, const char *disambiguation,
|
||||
int n) const override;
|
||||
};
|
||||
|
||||
typedef std::function<void()> VoidFunc;
|
||||
|
||||
Q_DECLARE_METATYPE(VoidFunc)
|
||||
|
||||
class QFileSystemWatcher;
|
||||
class QSocketNotifier;
|
||||
|
||||
struct UpdateBranch {
|
||||
QString name;
|
||||
QString display_name;
|
||||
@@ -233,9 +200,6 @@ signals:
|
||||
int GetAppConfigPath(char *path, size_t size, const char *name);
|
||||
char *GetAppConfigPathPtr(const char *name);
|
||||
|
||||
int GetProgramDataPath(char *path, size_t size, const char *name);
|
||||
char *GetProgramDataPathPtr(const char *name);
|
||||
|
||||
inline OBSApp *App()
|
||||
{
|
||||
return static_cast<OBSApp *>(qApp);
|
||||
@@ -251,30 +215,23 @@ inline QString QTStr(const char *lookupVal)
|
||||
return QString::fromUtf8(Str(lookupVal));
|
||||
}
|
||||
|
||||
int GetProgramDataPath(char *path, size_t size, const char *name);
|
||||
char *GetProgramDataPathPtr(const char *name);
|
||||
|
||||
bool GetFileSafeName(const char *name, std::string &file);
|
||||
bool GetClosestUnusedFileName(std::string &path, const char *extension);
|
||||
bool GetUnusedSceneCollectionFile(std::string &name, std::string &file);
|
||||
|
||||
bool WindowPositionValid(QRect rect);
|
||||
|
||||
extern bool portable_mode;
|
||||
extern bool steam;
|
||||
extern bool safe_mode;
|
||||
extern bool disable_3p_plugins;
|
||||
|
||||
extern bool opt_start_streaming;
|
||||
extern bool opt_start_recording;
|
||||
extern bool opt_start_replaybuffer;
|
||||
extern bool opt_start_virtualcam;
|
||||
extern bool opt_minimize_tray;
|
||||
extern bool opt_studio_mode;
|
||||
extern bool opt_allow_opengl;
|
||||
extern bool opt_always_on_top;
|
||||
extern std::string opt_starting_scene;
|
||||
extern bool restart;
|
||||
extern bool restart_safe;
|
||||
|
||||
#ifdef _WIN32
|
||||
extern "C" void install_dll_blocklist_hook(void);
|
||||
extern "C" void log_blocked_dlls(void);
|
||||
#endif
|
||||
|
||||
std::string CurrentDateTimeString();
|
||||
std::string GetFormatString(const char *format, const char *prefix, const char *suffix);
|
||||
std::string GenerateTimeDateFilename(const char *extension, bool noSpace = false);
|
||||
std::string GetFormatExt(const char *container);
|
||||
std::string GetOutputFilename(const char *path, const char *container, bool noSpace, bool overwrite,
|
||||
const char *format);
|
||||
QObject *CreateShortcutFilter();
|
||||
@@ -15,25 +15,23 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
#include <cinttypes>
|
||||
#include "OBSApp.hpp"
|
||||
|
||||
#include <utility/OBSProxyStyle.hpp>
|
||||
#include <utility/OBSThemeVariable.hpp>
|
||||
#include <utility/platform.hpp>
|
||||
|
||||
#include <qt-wrappers.hpp>
|
||||
#include <ui-config.h>
|
||||
#include <util/cf-parser.h>
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QTimer>
|
||||
#include <QMetaEnum>
|
||||
#include <QDirIterator>
|
||||
#include <QGuiApplication>
|
||||
#include <QFile>
|
||||
#include <QFileSystemWatcher>
|
||||
#include <QMetaEnum>
|
||||
#include <QRandomGenerator>
|
||||
|
||||
#include "qt-wrappers.hpp"
|
||||
#include "obs-app.hpp"
|
||||
#include "obs-app-theming.hpp"
|
||||
#include "obs-proxy-style.hpp"
|
||||
#include "platform.hpp"
|
||||
|
||||
#include "ui-config.h"
|
||||
#include <QTimer>
|
||||
|
||||
using namespace std;
|
||||
|
||||
@@ -0,0 +1,726 @@
|
||||
#include "OBSStudioAPI.hpp"
|
||||
|
||||
#include <widgets/OBSBasic.hpp>
|
||||
#include <widgets/OBSProjector.hpp>
|
||||
|
||||
#include <qt-wrappers.hpp>
|
||||
|
||||
extern volatile bool streaming_active;
|
||||
extern volatile bool recording_active;
|
||||
extern volatile bool recording_paused;
|
||||
extern volatile bool replaybuf_active;
|
||||
extern volatile bool virtualcam_active;
|
||||
|
||||
template<typename T>
|
||||
inline size_t GetCallbackIdx(vector<OBSStudioCallback<T>> &callbacks, T callback, void *private_data)
|
||||
{
|
||||
for (size_t i = 0; i < callbacks.size(); i++) {
|
||||
OBSStudioCallback<T> curCB = callbacks[i];
|
||||
if (curCB.callback == callback && curCB.private_data == private_data)
|
||||
return i;
|
||||
}
|
||||
|
||||
return (size_t)-1;
|
||||
}
|
||||
|
||||
void *OBSStudioAPI::obs_frontend_get_main_window()
|
||||
{
|
||||
return (void *)main;
|
||||
}
|
||||
|
||||
void *OBSStudioAPI::obs_frontend_get_main_window_handle()
|
||||
{
|
||||
return (void *)main->winId();
|
||||
}
|
||||
|
||||
void *OBSStudioAPI::obs_frontend_get_system_tray()
|
||||
{
|
||||
return (void *)main->trayIcon.data();
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_get_scenes(struct obs_frontend_source_list *sources)
|
||||
{
|
||||
for (int i = 0; i < main->ui->scenes->count(); i++) {
|
||||
QListWidgetItem *item = main->ui->scenes->item(i);
|
||||
OBSScene scene = GetOBSRef<OBSScene>(item);
|
||||
obs_source_t *source = obs_scene_get_source(scene);
|
||||
|
||||
if (obs_source_get_ref(source) != nullptr)
|
||||
da_push_back(sources->sources, &source);
|
||||
}
|
||||
}
|
||||
|
||||
obs_source_t *OBSStudioAPI::obs_frontend_get_current_scene()
|
||||
{
|
||||
if (main->IsPreviewProgramMode()) {
|
||||
return obs_weak_source_get_source(main->programScene);
|
||||
} else {
|
||||
OBSSource source = main->GetCurrentSceneSource();
|
||||
return obs_source_get_ref(source);
|
||||
}
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_set_current_scene(obs_source_t *scene)
|
||||
{
|
||||
if (main->IsPreviewProgramMode()) {
|
||||
QMetaObject::invokeMethod(main, "TransitionToScene", WaitConnection(),
|
||||
Q_ARG(OBSSource, OBSSource(scene)));
|
||||
} else {
|
||||
QMetaObject::invokeMethod(main, "SetCurrentScene", WaitConnection(), Q_ARG(OBSSource, OBSSource(scene)),
|
||||
Q_ARG(bool, false));
|
||||
}
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_get_transitions(struct obs_frontend_source_list *sources)
|
||||
{
|
||||
for (int i = 0; i < main->ui->transitions->count(); i++) {
|
||||
OBSSource tr = main->ui->transitions->itemData(i).value<OBSSource>();
|
||||
|
||||
if (!tr)
|
||||
continue;
|
||||
|
||||
if (obs_source_get_ref(tr) != nullptr)
|
||||
da_push_back(sources->sources, &tr);
|
||||
}
|
||||
}
|
||||
|
||||
obs_source_t *OBSStudioAPI::obs_frontend_get_current_transition()
|
||||
{
|
||||
OBSSource tr = main->GetCurrentTransition();
|
||||
return obs_source_get_ref(tr);
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_set_current_transition(obs_source_t *transition)
|
||||
{
|
||||
QMetaObject::invokeMethod(main, "SetTransition", Q_ARG(OBSSource, OBSSource(transition)));
|
||||
}
|
||||
|
||||
int OBSStudioAPI::obs_frontend_get_transition_duration()
|
||||
{
|
||||
return main->ui->transitionDuration->value();
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_set_transition_duration(int duration)
|
||||
{
|
||||
QMetaObject::invokeMethod(main->ui->transitionDuration, "setValue", Q_ARG(int, duration));
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_release_tbar()
|
||||
{
|
||||
QMetaObject::invokeMethod(main, "TBarReleased");
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_set_tbar_position(int position)
|
||||
{
|
||||
QMetaObject::invokeMethod(main, "TBarChanged", Q_ARG(int, position));
|
||||
}
|
||||
|
||||
int OBSStudioAPI::obs_frontend_get_tbar_position()
|
||||
{
|
||||
return main->tBar->value();
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_get_scene_collections(std::vector<std::string> &strings)
|
||||
{
|
||||
for (auto &[collectionName, collection] : main->GetSceneCollectionCache()) {
|
||||
strings.emplace_back(collectionName);
|
||||
}
|
||||
}
|
||||
|
||||
char *OBSStudioAPI::obs_frontend_get_current_scene_collection()
|
||||
{
|
||||
const OBSSceneCollection ¤tCollection = main->GetCurrentSceneCollection();
|
||||
return bstrdup(currentCollection.name.c_str());
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_set_current_scene_collection(const char *collection)
|
||||
{
|
||||
QList<QAction *> menuActions = main->ui->sceneCollectionMenu->actions();
|
||||
QString qstrCollection = QT_UTF8(collection);
|
||||
|
||||
for (int i = 0; i < menuActions.count(); i++) {
|
||||
QAction *action = menuActions[i];
|
||||
QVariant v = action->property("file_name");
|
||||
|
||||
if (v.typeName() != nullptr) {
|
||||
if (action->text() == qstrCollection) {
|
||||
action->trigger();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool OBSStudioAPI::obs_frontend_add_scene_collection(const char *name)
|
||||
{
|
||||
bool success = false;
|
||||
QMetaObject::invokeMethod(main, "CreateNewSceneCollection", WaitConnection(), Q_RETURN_ARG(bool, success),
|
||||
Q_ARG(QString, QT_UTF8(name)));
|
||||
return success;
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_get_profiles(std::vector<std::string> &strings)
|
||||
{
|
||||
const OBSProfileCache &profiles = main->GetProfileCache();
|
||||
|
||||
for (auto &[profileName, profile] : profiles) {
|
||||
strings.emplace_back(profileName);
|
||||
}
|
||||
}
|
||||
|
||||
char *OBSStudioAPI::obs_frontend_get_current_profile()
|
||||
{
|
||||
const OBSProfile &profile = main->GetCurrentProfile();
|
||||
return bstrdup(profile.name.c_str());
|
||||
}
|
||||
|
||||
char *OBSStudioAPI::obs_frontend_get_current_profile_path()
|
||||
{
|
||||
const OBSProfile &profile = main->GetCurrentProfile();
|
||||
|
||||
return bstrdup(profile.path.u8string().c_str());
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_set_current_profile(const char *profile)
|
||||
{
|
||||
QList<QAction *> menuActions = main->ui->profileMenu->actions();
|
||||
QString qstrProfile = QT_UTF8(profile);
|
||||
|
||||
for (int i = 0; i < menuActions.count(); i++) {
|
||||
QAction *action = menuActions[i];
|
||||
QVariant v = action->property("file_name");
|
||||
|
||||
if (v.typeName() != nullptr) {
|
||||
if (action->text() == qstrProfile) {
|
||||
action->trigger();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_create_profile(const char *name)
|
||||
{
|
||||
QMetaObject::invokeMethod(main, "CreateNewProfile", Q_ARG(QString, name));
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_duplicate_profile(const char *name)
|
||||
{
|
||||
QMetaObject::invokeMethod(main, "CreateDuplicateProfile", Q_ARG(QString, name));
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_delete_profile(const char *profile)
|
||||
{
|
||||
QMetaObject::invokeMethod(main, "DeleteProfile", Q_ARG(QString, profile));
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_streaming_start()
|
||||
{
|
||||
QMetaObject::invokeMethod(main, "StartStreaming");
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_streaming_stop()
|
||||
{
|
||||
QMetaObject::invokeMethod(main, "StopStreaming");
|
||||
}
|
||||
|
||||
bool OBSStudioAPI::obs_frontend_streaming_active()
|
||||
{
|
||||
return os_atomic_load_bool(&streaming_active);
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_recording_start()
|
||||
{
|
||||
QMetaObject::invokeMethod(main, "StartRecording");
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_recording_stop()
|
||||
{
|
||||
QMetaObject::invokeMethod(main, "StopRecording");
|
||||
}
|
||||
|
||||
bool OBSStudioAPI::obs_frontend_recording_active()
|
||||
{
|
||||
return os_atomic_load_bool(&recording_active);
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_recording_pause(bool pause)
|
||||
{
|
||||
QMetaObject::invokeMethod(main, pause ? "PauseRecording" : "UnpauseRecording");
|
||||
}
|
||||
|
||||
bool OBSStudioAPI::obs_frontend_recording_paused()
|
||||
{
|
||||
return os_atomic_load_bool(&recording_paused);
|
||||
}
|
||||
|
||||
bool OBSStudioAPI::obs_frontend_recording_split_file()
|
||||
{
|
||||
if (os_atomic_load_bool(&recording_active) && !os_atomic_load_bool(&recording_paused)) {
|
||||
proc_handler_t *ph = obs_output_get_proc_handler(main->outputHandler->fileOutput);
|
||||
uint8_t stack[128];
|
||||
calldata cd;
|
||||
calldata_init_fixed(&cd, stack, sizeof(stack));
|
||||
proc_handler_call(ph, "split_file", &cd);
|
||||
bool result = calldata_bool(&cd, "split_file_enabled");
|
||||
return result;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool OBSStudioAPI::obs_frontend_recording_add_chapter(const char *name)
|
||||
{
|
||||
if (!os_atomic_load_bool(&recording_active) || os_atomic_load_bool(&recording_paused))
|
||||
return false;
|
||||
|
||||
proc_handler_t *ph = obs_output_get_proc_handler(main->outputHandler->fileOutput);
|
||||
|
||||
calldata cd;
|
||||
calldata_init(&cd);
|
||||
calldata_set_string(&cd, "chapter_name", name);
|
||||
bool result = proc_handler_call(ph, "add_chapter", &cd);
|
||||
calldata_free(&cd);
|
||||
return result;
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_replay_buffer_start()
|
||||
{
|
||||
QMetaObject::invokeMethod(main, "StartReplayBuffer");
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_replay_buffer_save()
|
||||
{
|
||||
QMetaObject::invokeMethod(main, "ReplayBufferSave");
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_replay_buffer_stop()
|
||||
{
|
||||
QMetaObject::invokeMethod(main, "StopReplayBuffer");
|
||||
}
|
||||
|
||||
bool OBSStudioAPI::obs_frontend_replay_buffer_active()
|
||||
{
|
||||
return os_atomic_load_bool(&replaybuf_active);
|
||||
}
|
||||
|
||||
void *OBSStudioAPI::obs_frontend_add_tools_menu_qaction(const char *name)
|
||||
{
|
||||
main->ui->menuTools->setEnabled(true);
|
||||
return (void *)main->ui->menuTools->addAction(QT_UTF8(name));
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_add_tools_menu_item(const char *name, obs_frontend_cb callback, void *private_data)
|
||||
{
|
||||
main->ui->menuTools->setEnabled(true);
|
||||
|
||||
auto func = [private_data, callback]() {
|
||||
callback(private_data);
|
||||
};
|
||||
|
||||
QAction *action = main->ui->menuTools->addAction(QT_UTF8(name));
|
||||
QObject::connect(action, &QAction::triggered, func);
|
||||
}
|
||||
|
||||
void *OBSStudioAPI::obs_frontend_add_dock(void *dock)
|
||||
{
|
||||
QDockWidget *d = reinterpret_cast<QDockWidget *>(dock);
|
||||
|
||||
QString name = d->objectName();
|
||||
if (name.isEmpty() || main->IsDockObjectNameUsed(name)) {
|
||||
blog(LOG_WARNING, "The object name of the added dock is empty or already used,"
|
||||
" a temporary one will be set to avoid conflicts");
|
||||
|
||||
char *uuid = os_generate_uuid();
|
||||
name = QT_UTF8(uuid);
|
||||
bfree(uuid);
|
||||
name.append("_oldExtraDock");
|
||||
|
||||
d->setObjectName(name);
|
||||
}
|
||||
|
||||
return (void *)main->AddDockWidget(d);
|
||||
}
|
||||
|
||||
bool OBSStudioAPI::obs_frontend_add_dock_by_id(const char *id, const char *title, void *widget)
|
||||
{
|
||||
if (main->IsDockObjectNameUsed(QT_UTF8(id))) {
|
||||
blog(LOG_WARNING,
|
||||
"Dock id '%s' already used! "
|
||||
"Duplicate library?",
|
||||
id);
|
||||
return false;
|
||||
}
|
||||
|
||||
OBSDock *dock = new OBSDock(main);
|
||||
dock->setWidget((QWidget *)widget);
|
||||
dock->setWindowTitle(QT_UTF8(title));
|
||||
dock->setObjectName(QT_UTF8(id));
|
||||
|
||||
main->AddDockWidget(dock, Qt::RightDockWidgetArea);
|
||||
|
||||
dock->setVisible(false);
|
||||
dock->setFloating(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_remove_dock(const char *id)
|
||||
{
|
||||
main->RemoveDockWidget(QT_UTF8(id));
|
||||
}
|
||||
|
||||
bool OBSStudioAPI::obs_frontend_add_custom_qdock(const char *id, void *dock)
|
||||
{
|
||||
if (main->IsDockObjectNameUsed(QT_UTF8(id))) {
|
||||
blog(LOG_WARNING,
|
||||
"Dock id '%s' already used! "
|
||||
"Duplicate library?",
|
||||
id);
|
||||
return false;
|
||||
}
|
||||
|
||||
QDockWidget *d = reinterpret_cast<QDockWidget *>(dock);
|
||||
d->setObjectName(QT_UTF8(id));
|
||||
|
||||
main->AddCustomDockWidget(d);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_add_event_callback(obs_frontend_event_cb callback, void *private_data)
|
||||
{
|
||||
size_t idx = GetCallbackIdx(callbacks, callback, private_data);
|
||||
if (idx == (size_t)-1)
|
||||
callbacks.emplace_back(callback, private_data);
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_remove_event_callback(obs_frontend_event_cb callback, void *private_data)
|
||||
{
|
||||
size_t idx = GetCallbackIdx(callbacks, callback, private_data);
|
||||
if (idx == (size_t)-1)
|
||||
return;
|
||||
|
||||
callbacks.erase(callbacks.begin() + idx);
|
||||
}
|
||||
|
||||
obs_output_t *OBSStudioAPI::obs_frontend_get_streaming_output()
|
||||
{
|
||||
auto multitrackVideo = main->outputHandler->multitrackVideo.get();
|
||||
auto mtvOutput = multitrackVideo ? obs_output_get_ref(multitrackVideo->StreamingOutput()) : nullptr;
|
||||
if (mtvOutput)
|
||||
return mtvOutput;
|
||||
|
||||
OBSOutput output = main->outputHandler->streamOutput.Get();
|
||||
return obs_output_get_ref(output);
|
||||
}
|
||||
|
||||
obs_output_t *OBSStudioAPI::obs_frontend_get_recording_output()
|
||||
{
|
||||
OBSOutput out = main->outputHandler->fileOutput.Get();
|
||||
return obs_output_get_ref(out);
|
||||
}
|
||||
|
||||
obs_output_t *OBSStudioAPI::obs_frontend_get_replay_buffer_output()
|
||||
{
|
||||
OBSOutput out = main->outputHandler->replayBuffer.Get();
|
||||
return obs_output_get_ref(out);
|
||||
}
|
||||
|
||||
config_t *OBSStudioAPI::obs_frontend_get_profile_config()
|
||||
{
|
||||
return main->activeConfiguration;
|
||||
}
|
||||
|
||||
config_t *OBSStudioAPI::obs_frontend_get_global_config()
|
||||
{
|
||||
blog(LOG_WARNING,
|
||||
"DEPRECATION: obs_frontend_get_global_config is deprecated. Read from global or user configuration explicitly instead.");
|
||||
return App()->GetAppConfig();
|
||||
}
|
||||
|
||||
config_t *OBSStudioAPI::obs_frontend_get_app_config()
|
||||
{
|
||||
return App()->GetAppConfig();
|
||||
}
|
||||
|
||||
config_t *OBSStudioAPI::obs_frontend_get_user_config()
|
||||
{
|
||||
return App()->GetUserConfig();
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_open_projector(const char *type, int monitor, const char *geometry, const char *name)
|
||||
{
|
||||
SavedProjectorInfo proj = {
|
||||
ProjectorType::Preview,
|
||||
monitor,
|
||||
geometry ? geometry : "",
|
||||
name ? name : "",
|
||||
};
|
||||
if (type) {
|
||||
if (astrcmpi(type, "Source") == 0)
|
||||
proj.type = ProjectorType::Source;
|
||||
else if (astrcmpi(type, "Scene") == 0)
|
||||
proj.type = ProjectorType::Scene;
|
||||
else if (astrcmpi(type, "StudioProgram") == 0)
|
||||
proj.type = ProjectorType::StudioProgram;
|
||||
else if (astrcmpi(type, "Multiview") == 0)
|
||||
proj.type = ProjectorType::Multiview;
|
||||
}
|
||||
QMetaObject::invokeMethod(main, "OpenSavedProjector", WaitConnection(), Q_ARG(SavedProjectorInfo *, &proj));
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_save()
|
||||
{
|
||||
main->SaveProject();
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_defer_save_begin()
|
||||
{
|
||||
QMetaObject::invokeMethod(main, "DeferSaveBegin");
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_defer_save_end()
|
||||
{
|
||||
QMetaObject::invokeMethod(main, "DeferSaveEnd");
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_add_save_callback(obs_frontend_save_cb callback, void *private_data)
|
||||
{
|
||||
size_t idx = GetCallbackIdx(saveCallbacks, callback, private_data);
|
||||
if (idx == (size_t)-1)
|
||||
saveCallbacks.emplace_back(callback, private_data);
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_remove_save_callback(obs_frontend_save_cb callback, void *private_data)
|
||||
{
|
||||
size_t idx = GetCallbackIdx(saveCallbacks, callback, private_data);
|
||||
if (idx == (size_t)-1)
|
||||
return;
|
||||
|
||||
saveCallbacks.erase(saveCallbacks.begin() + idx);
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_add_preload_callback(obs_frontend_save_cb callback, void *private_data)
|
||||
{
|
||||
size_t idx = GetCallbackIdx(preloadCallbacks, callback, private_data);
|
||||
if (idx == (size_t)-1)
|
||||
preloadCallbacks.emplace_back(callback, private_data);
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_remove_preload_callback(obs_frontend_save_cb callback, void *private_data)
|
||||
{
|
||||
size_t idx = GetCallbackIdx(preloadCallbacks, callback, private_data);
|
||||
if (idx == (size_t)-1)
|
||||
return;
|
||||
|
||||
preloadCallbacks.erase(preloadCallbacks.begin() + idx);
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_push_ui_translation(obs_frontend_translate_ui_cb translate)
|
||||
{
|
||||
App()->PushUITranslation(translate);
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_pop_ui_translation()
|
||||
{
|
||||
App()->PopUITranslation();
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_set_streaming_service(obs_service_t *service)
|
||||
{
|
||||
main->SetService(service);
|
||||
}
|
||||
|
||||
obs_service_t *OBSStudioAPI::obs_frontend_get_streaming_service()
|
||||
{
|
||||
return main->GetService();
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_save_streaming_service()
|
||||
{
|
||||
main->SaveService();
|
||||
}
|
||||
|
||||
bool OBSStudioAPI::obs_frontend_preview_program_mode_active()
|
||||
{
|
||||
return main->IsPreviewProgramMode();
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_set_preview_program_mode(bool enable)
|
||||
{
|
||||
main->SetPreviewProgramMode(enable);
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_preview_program_trigger_transition()
|
||||
{
|
||||
QMetaObject::invokeMethod(main, "TransitionClicked");
|
||||
}
|
||||
|
||||
bool OBSStudioAPI::obs_frontend_preview_enabled()
|
||||
{
|
||||
return main->previewEnabled;
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_set_preview_enabled(bool enable)
|
||||
{
|
||||
if (main->previewEnabled != enable)
|
||||
main->EnablePreviewDisplay(enable);
|
||||
}
|
||||
|
||||
obs_source_t *OBSStudioAPI::obs_frontend_get_current_preview_scene()
|
||||
{
|
||||
if (main->IsPreviewProgramMode()) {
|
||||
OBSSource source = main->GetCurrentSceneSource();
|
||||
return obs_source_get_ref(source);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_set_current_preview_scene(obs_source_t *scene)
|
||||
{
|
||||
if (main->IsPreviewProgramMode()) {
|
||||
QMetaObject::invokeMethod(main, "SetCurrentScene", Q_ARG(OBSSource, OBSSource(scene)),
|
||||
Q_ARG(bool, false));
|
||||
}
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_take_screenshot()
|
||||
{
|
||||
QMetaObject::invokeMethod(main, "Screenshot");
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_take_source_screenshot(obs_source_t *source)
|
||||
{
|
||||
QMetaObject::invokeMethod(main, "Screenshot", Q_ARG(OBSSource, OBSSource(source)));
|
||||
}
|
||||
|
||||
obs_output_t *OBSStudioAPI::obs_frontend_get_virtualcam_output()
|
||||
{
|
||||
OBSOutput output = main->outputHandler->virtualCam.Get();
|
||||
return obs_output_get_ref(output);
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_start_virtualcam()
|
||||
{
|
||||
QMetaObject::invokeMethod(main, "StartVirtualCam");
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_stop_virtualcam()
|
||||
{
|
||||
QMetaObject::invokeMethod(main, "StopVirtualCam");
|
||||
}
|
||||
|
||||
bool OBSStudioAPI::obs_frontend_virtualcam_active()
|
||||
{
|
||||
return os_atomic_load_bool(&virtualcam_active);
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_reset_video()
|
||||
{
|
||||
main->ResetVideo();
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_open_source_properties(obs_source_t *source)
|
||||
{
|
||||
QMetaObject::invokeMethod(main, "OpenProperties", Q_ARG(OBSSource, OBSSource(source)));
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_open_source_filters(obs_source_t *source)
|
||||
{
|
||||
QMetaObject::invokeMethod(main, "OpenFilters", Q_ARG(OBSSource, OBSSource(source)));
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_open_source_interaction(obs_source_t *source)
|
||||
{
|
||||
QMetaObject::invokeMethod(main, "OpenInteraction", Q_ARG(OBSSource, OBSSource(source)));
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_open_sceneitem_edit_transform(obs_sceneitem_t *item)
|
||||
{
|
||||
QMetaObject::invokeMethod(main, "OpenEditTransform", Q_ARG(OBSSceneItem, OBSSceneItem(item)));
|
||||
}
|
||||
|
||||
char *OBSStudioAPI::obs_frontend_get_current_record_output_path()
|
||||
{
|
||||
const char *recordOutputPath = main->GetCurrentOutputPath();
|
||||
|
||||
return bstrdup(recordOutputPath);
|
||||
}
|
||||
|
||||
const char *OBSStudioAPI::obs_frontend_get_locale_string(const char *string)
|
||||
{
|
||||
return Str(string);
|
||||
}
|
||||
|
||||
bool OBSStudioAPI::obs_frontend_is_theme_dark()
|
||||
{
|
||||
return App()->IsThemeDark();
|
||||
}
|
||||
|
||||
char *OBSStudioAPI::obs_frontend_get_last_recording()
|
||||
{
|
||||
return bstrdup(main->outputHandler->lastRecordingPath.c_str());
|
||||
}
|
||||
|
||||
char *OBSStudioAPI::obs_frontend_get_last_screenshot()
|
||||
{
|
||||
return bstrdup(main->lastScreenshot.c_str());
|
||||
}
|
||||
|
||||
char *OBSStudioAPI::obs_frontend_get_last_replay()
|
||||
{
|
||||
return bstrdup(main->lastReplay.c_str());
|
||||
}
|
||||
|
||||
void OBSStudioAPI::obs_frontend_add_undo_redo_action(const char *name, const undo_redo_cb undo, const undo_redo_cb redo,
|
||||
const char *undo_data, const char *redo_data, bool repeatable)
|
||||
{
|
||||
main->undo_s.add_action(
|
||||
name, [undo](const std::string &data) { undo(data.c_str()); },
|
||||
[redo](const std::string &data) { redo(data.c_str()); }, undo_data, redo_data, repeatable);
|
||||
}
|
||||
|
||||
void OBSStudioAPI::on_load(obs_data_t *settings)
|
||||
{
|
||||
for (size_t i = saveCallbacks.size(); i > 0; i--) {
|
||||
auto cb = saveCallbacks[i - 1];
|
||||
cb.callback(settings, false, cb.private_data);
|
||||
}
|
||||
}
|
||||
|
||||
void OBSStudioAPI::on_preload(obs_data_t *settings)
|
||||
{
|
||||
for (size_t i = preloadCallbacks.size(); i > 0; i--) {
|
||||
auto cb = preloadCallbacks[i - 1];
|
||||
cb.callback(settings, false, cb.private_data);
|
||||
}
|
||||
}
|
||||
|
||||
void OBSStudioAPI::on_save(obs_data_t *settings)
|
||||
{
|
||||
for (size_t i = saveCallbacks.size(); i > 0; i--) {
|
||||
auto cb = saveCallbacks[i - 1];
|
||||
cb.callback(settings, true, cb.private_data);
|
||||
}
|
||||
}
|
||||
|
||||
void OBSStudioAPI::on_event(enum obs_frontend_event event)
|
||||
{
|
||||
if (main->disableSaving && event != OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP &&
|
||||
event != OBS_FRONTEND_EVENT_EXIT)
|
||||
return;
|
||||
|
||||
for (size_t i = callbacks.size(); i > 0; i--) {
|
||||
auto cb = callbacks[i - 1];
|
||||
cb.callback(event, cb.private_data);
|
||||
}
|
||||
}
|
||||
|
||||
obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main)
|
||||
{
|
||||
obs_frontend_callbacks *api = new OBSStudioAPI(main);
|
||||
obs_frontend_set_callbacks_internal(api);
|
||||
return api;
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
/******************************************************************************
|
||||
Copyright (C) 2024 by Patrick Heyer <opensource@patrickheyer.com>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <obs-frontend-internal.hpp>
|
||||
|
||||
class OBSBasic;
|
||||
|
||||
using namespace std;
|
||||
|
||||
template<typename T> struct OBSStudioCallback {
|
||||
T callback;
|
||||
void *private_data;
|
||||
|
||||
inline OBSStudioCallback(T cb, void *p) : callback(cb), private_data(p) {}
|
||||
};
|
||||
|
||||
struct OBSStudioAPI : obs_frontend_callbacks {
|
||||
OBSBasic *main;
|
||||
vector<OBSStudioCallback<obs_frontend_event_cb>> callbacks;
|
||||
vector<OBSStudioCallback<obs_frontend_save_cb>> saveCallbacks;
|
||||
vector<OBSStudioCallback<obs_frontend_save_cb>> preloadCallbacks;
|
||||
|
||||
inline OBSStudioAPI(OBSBasic *main_) : main(main_) {}
|
||||
|
||||
void *obs_frontend_get_main_window(void) override;
|
||||
|
||||
void *obs_frontend_get_main_window_handle(void) override;
|
||||
|
||||
void *obs_frontend_get_system_tray(void) override;
|
||||
|
||||
void obs_frontend_get_scenes(struct obs_frontend_source_list *sources) override;
|
||||
|
||||
obs_source_t *obs_frontend_get_current_scene(void) override;
|
||||
|
||||
void obs_frontend_set_current_scene(obs_source_t *scene) override;
|
||||
|
||||
void obs_frontend_get_transitions(struct obs_frontend_source_list *sources) override;
|
||||
|
||||
obs_source_t *obs_frontend_get_current_transition(void) override;
|
||||
|
||||
void obs_frontend_set_current_transition(obs_source_t *transition) override;
|
||||
|
||||
int obs_frontend_get_transition_duration(void) override;
|
||||
|
||||
void obs_frontend_set_transition_duration(int duration) override;
|
||||
|
||||
void obs_frontend_release_tbar(void) override;
|
||||
|
||||
void obs_frontend_set_tbar_position(int position) override;
|
||||
|
||||
int obs_frontend_get_tbar_position(void) override;
|
||||
|
||||
void obs_frontend_get_scene_collections(std::vector<std::string> &strings) override;
|
||||
|
||||
char *obs_frontend_get_current_scene_collection(void) override;
|
||||
|
||||
void obs_frontend_set_current_scene_collection(const char *collection) override;
|
||||
|
||||
bool obs_frontend_add_scene_collection(const char *name) override;
|
||||
|
||||
void obs_frontend_get_profiles(std::vector<std::string> &strings) override;
|
||||
|
||||
char *obs_frontend_get_current_profile(void) override;
|
||||
|
||||
char *obs_frontend_get_current_profile_path(void) override;
|
||||
|
||||
void obs_frontend_set_current_profile(const char *profile) override;
|
||||
|
||||
void obs_frontend_create_profile(const char *name) override;
|
||||
|
||||
void obs_frontend_duplicate_profile(const char *name) override;
|
||||
|
||||
void obs_frontend_delete_profile(const char *profile) override;
|
||||
|
||||
void obs_frontend_streaming_start(void) override;
|
||||
|
||||
void obs_frontend_streaming_stop(void) override;
|
||||
|
||||
bool obs_frontend_streaming_active(void) override;
|
||||
|
||||
void obs_frontend_recording_start(void) override;
|
||||
|
||||
void obs_frontend_recording_stop(void) override;
|
||||
|
||||
bool obs_frontend_recording_active(void) override;
|
||||
|
||||
void obs_frontend_recording_pause(bool pause) override;
|
||||
|
||||
bool obs_frontend_recording_paused(void) override;
|
||||
|
||||
bool obs_frontend_recording_split_file(void) override;
|
||||
|
||||
bool obs_frontend_recording_add_chapter(const char *name) override;
|
||||
|
||||
void obs_frontend_replay_buffer_start(void) override;
|
||||
|
||||
void obs_frontend_replay_buffer_save(void) override;
|
||||
|
||||
void obs_frontend_replay_buffer_stop(void) override;
|
||||
|
||||
bool obs_frontend_replay_buffer_active(void) override;
|
||||
|
||||
void *obs_frontend_add_tools_menu_qaction(const char *name) override;
|
||||
|
||||
void obs_frontend_add_tools_menu_item(const char *name, obs_frontend_cb callback, void *private_data) override;
|
||||
|
||||
void *obs_frontend_add_dock(void *dock) override;
|
||||
|
||||
bool obs_frontend_add_dock_by_id(const char *id, const char *title, void *widget) override;
|
||||
|
||||
void obs_frontend_remove_dock(const char *id) override;
|
||||
|
||||
bool obs_frontend_add_custom_qdock(const char *id, void *dock) override;
|
||||
|
||||
void obs_frontend_add_event_callback(obs_frontend_event_cb callback, void *private_data) override;
|
||||
|
||||
void obs_frontend_remove_event_callback(obs_frontend_event_cb callback, void *private_data) override;
|
||||
|
||||
obs_output_t *obs_frontend_get_streaming_output(void) override;
|
||||
|
||||
obs_output_t *obs_frontend_get_recording_output(void) override;
|
||||
|
||||
obs_output_t *obs_frontend_get_replay_buffer_output(void) override;
|
||||
|
||||
config_t *obs_frontend_get_profile_config(void) override;
|
||||
|
||||
config_t *obs_frontend_get_global_config(void) override;
|
||||
|
||||
config_t *obs_frontend_get_app_config(void) override;
|
||||
|
||||
config_t *obs_frontend_get_user_config(void) override;
|
||||
|
||||
void obs_frontend_open_projector(const char *type, int monitor, const char *geometry,
|
||||
const char *name) override;
|
||||
|
||||
void obs_frontend_save(void) override;
|
||||
|
||||
void obs_frontend_defer_save_begin(void) override;
|
||||
|
||||
void obs_frontend_defer_save_end(void) override;
|
||||
|
||||
void obs_frontend_add_save_callback(obs_frontend_save_cb callback, void *private_data) override;
|
||||
|
||||
void obs_frontend_remove_save_callback(obs_frontend_save_cb callback, void *private_data) override;
|
||||
|
||||
void obs_frontend_add_preload_callback(obs_frontend_save_cb callback, void *private_data) override;
|
||||
|
||||
void obs_frontend_remove_preload_callback(obs_frontend_save_cb callback, void *private_data) override;
|
||||
|
||||
void obs_frontend_push_ui_translation(obs_frontend_translate_ui_cb translate) override;
|
||||
|
||||
void obs_frontend_pop_ui_translation(void) override;
|
||||
|
||||
void obs_frontend_set_streaming_service(obs_service_t *service) override;
|
||||
|
||||
obs_service_t *obs_frontend_get_streaming_service(void) override;
|
||||
|
||||
void obs_frontend_save_streaming_service(void) override;
|
||||
|
||||
bool obs_frontend_preview_program_mode_active(void) override;
|
||||
|
||||
void obs_frontend_set_preview_program_mode(bool enable) override;
|
||||
|
||||
void obs_frontend_preview_program_trigger_transition(void) override;
|
||||
|
||||
bool obs_frontend_preview_enabled(void) override;
|
||||
|
||||
void obs_frontend_set_preview_enabled(bool enable) override;
|
||||
|
||||
obs_source_t *obs_frontend_get_current_preview_scene(void) override;
|
||||
|
||||
void obs_frontend_set_current_preview_scene(obs_source_t *scene) override;
|
||||
|
||||
void obs_frontend_take_screenshot(void) override;
|
||||
|
||||
void obs_frontend_take_source_screenshot(obs_source_t *source) override;
|
||||
|
||||
obs_output_t *obs_frontend_get_virtualcam_output(void) override;
|
||||
|
||||
void obs_frontend_start_virtualcam(void) override;
|
||||
|
||||
void obs_frontend_stop_virtualcam(void) override;
|
||||
|
||||
bool obs_frontend_virtualcam_active(void) override;
|
||||
|
||||
void obs_frontend_reset_video(void) override;
|
||||
|
||||
void obs_frontend_open_source_properties(obs_source_t *source) override;
|
||||
|
||||
void obs_frontend_open_source_filters(obs_source_t *source) override;
|
||||
|
||||
void obs_frontend_open_source_interaction(obs_source_t *source) override;
|
||||
|
||||
void obs_frontend_open_sceneitem_edit_transform(obs_sceneitem_t *item) override;
|
||||
|
||||
char *obs_frontend_get_current_record_output_path(void) override;
|
||||
|
||||
const char *obs_frontend_get_locale_string(const char *string) override;
|
||||
|
||||
bool obs_frontend_is_theme_dark(void) override;
|
||||
|
||||
char *obs_frontend_get_last_recording(void) override;
|
||||
|
||||
char *obs_frontend_get_last_screenshot(void) override;
|
||||
|
||||
char *obs_frontend_get_last_replay(void) override;
|
||||
|
||||
void obs_frontend_add_undo_redo_action(const char *name, const undo_redo_cb undo, const undo_redo_cb redo,
|
||||
const char *undo_data, const char *redo_data, bool repeatable) override;
|
||||
|
||||
void on_load(obs_data_t *settings) override;
|
||||
|
||||
void on_preload(obs_data_t *settings) override;
|
||||
|
||||
void on_save(obs_data_t *settings) override;
|
||||
|
||||
void on_event(enum obs_frontend_event event) override;
|
||||
};
|
||||
|
||||
obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main);
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,18 @@
|
||||
if(TARGET OBS::browser-panels)
|
||||
target_enable_feature(obs-studio "Browser panels" BROWSER_AVAILABLE)
|
||||
|
||||
target_link_libraries(obs-studio PRIVATE OBS::browser-panels)
|
||||
|
||||
target_sources(
|
||||
obs-studio
|
||||
PRIVATE
|
||||
dialogs/OBSExtraBrowsers.cpp
|
||||
dialogs/OBSExtraBrowsers.hpp
|
||||
docks/BrowserDock.cpp
|
||||
docks/BrowserDock.hpp
|
||||
utility/ExtraBrowsersDelegate.cpp
|
||||
utility/ExtraBrowsersDelegate.hpp
|
||||
utility/ExtraBrowsersModel.cpp
|
||||
utility/ExtraBrowsersModel.hpp
|
||||
)
|
||||
endif()
|
||||
@@ -0,0 +1,16 @@
|
||||
target_sources(
|
||||
obs-studio
|
||||
PRIVATE
|
||||
importer/ImporterEntryPathItemDelegate.cpp
|
||||
importer/ImporterEntryPathItemDelegate.hpp
|
||||
importer/ImporterModel.cpp
|
||||
importer/ImporterModel.hpp
|
||||
importer/OBSImporter.cpp
|
||||
importer/OBSImporter.hpp
|
||||
importers/classic.cpp
|
||||
importers/importers.cpp
|
||||
importers/importers.hpp
|
||||
importers/sl.cpp
|
||||
importers/studio.cpp
|
||||
importers/xsplit.cpp
|
||||
)
|
||||
@@ -9,14 +9,16 @@ endif()
|
||||
target_sources(
|
||||
obs-studio
|
||||
PRIVATE
|
||||
update/crypto-helpers-mac.mm
|
||||
update/crypto-helpers.hpp
|
||||
update/models/branches.hpp
|
||||
update/models/whatsnew.hpp
|
||||
update/shared-update.cpp
|
||||
update/shared-update.hpp
|
||||
update/update-helpers.cpp
|
||||
update/update-helpers.hpp
|
||||
utility/crypto-helpers-mac.mm
|
||||
utility/crypto-helpers.hpp
|
||||
utility/models/branches.hpp
|
||||
utility/models/whatsnew.hpp
|
||||
utility/update-helpers.cpp
|
||||
utility/update-helpers.hpp
|
||||
utility/WhatsNewBrowserInitThread.cpp
|
||||
utility/WhatsNewBrowserInitThread.hpp
|
||||
utility/WhatsNewInfoThread.cpp
|
||||
utility/WhatsNewInfoThread.hpp
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
@@ -1,5 +1,5 @@
|
||||
if(RESTREAM_CLIENTID AND RESTREAM_HASH MATCHES "^(0|[a-fA-F0-9]+)$" AND TARGET OBS::browser-panels)
|
||||
target_sources(obs-studio PRIVATE auth-restream.cpp auth-restream.hpp)
|
||||
target_sources(obs-studio PRIVATE oauth/RestreamAuth.cpp oauth/RestreamAuth.hpp)
|
||||
target_enable_feature(obs-studio "Restream API connection" RESTREAM_ENABLED)
|
||||
else()
|
||||
target_disable_feature(obs-studio "Restream API connection")
|
||||
@@ -1,8 +1,17 @@
|
||||
if(SPARKLE_APPCAST_URL AND SPARKLE_PUBLIC_KEY)
|
||||
find_library(SPARKLE Sparkle)
|
||||
mark_as_advanced(SPARKLE)
|
||||
target_sources(obs-studio PRIVATE update/mac-update.cpp update/mac-update.hpp update/sparkle-updater.mm)
|
||||
set_source_files_properties(update/sparkle-updater.mm PROPERTIES COMPILE_FLAGS -fobjc-arc)
|
||||
target_sources(
|
||||
obs-studio
|
||||
PRIVATE
|
||||
utility/MacUpdateThread.cpp
|
||||
utility/MacUpdateThread.hpp
|
||||
utility/OBSSparkle.hpp
|
||||
utility/OBSSparkle.mm
|
||||
utility/OBSUpdateDelegate.h
|
||||
utility/OBSUpdateDelegate.mm
|
||||
)
|
||||
set_source_files_properties(utility/OBSSparkle.mm PROPERTIES COMPILE_FLAGS -fobjc-arc)
|
||||
|
||||
target_link_libraries(obs-studio PRIVATE "$<LINK_LIBRARY:FRAMEWORK,${SPARKLE}>")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
if(TWITCH_CLIENTID AND TWITCH_HASH MATCHES "^(0|[a-fA-F0-9]+)$" AND TARGET OBS::browser-panels)
|
||||
target_sources(obs-studio PRIVATE auth-twitch.cpp auth-twitch.hpp)
|
||||
target_sources(obs-studio PRIVATE oauth/TwitchAuth.cpp oauth/TwitchAuth.hpp)
|
||||
target_enable_feature(obs-studio "Twitch API connection" TWITCH_ENABLED)
|
||||
else()
|
||||
target_disable_feature(obs-studio "Twitch API connection")
|
||||
@@ -20,13 +20,15 @@ if(ENABLE_WHATSNEW AND TARGET OBS::browser-panels)
|
||||
target_sources(
|
||||
obs-studio
|
||||
PRIVATE
|
||||
update/crypto-helpers-mbedtls.cpp
|
||||
update/crypto-helpers.hpp
|
||||
update/models/whatsnew.hpp
|
||||
update/shared-update.cpp
|
||||
update/shared-update.hpp
|
||||
update/update-helpers.cpp
|
||||
update/update-helpers.hpp
|
||||
utility/crypto-helpers-mbedtls.cpp
|
||||
utility/crypto-helpers.hpp
|
||||
utility/models/whatsnew.hpp
|
||||
utility/update-helpers.cpp
|
||||
utility/update-helpers.hpp
|
||||
utility/WhatsNewBrowserInitThread.cpp
|
||||
utility/WhatsNewBrowserInitThread.hpp
|
||||
utility/WhatsNewInfoThread.cpp
|
||||
utility/WhatsNewInfoThread.hpp
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -8,14 +8,17 @@ if(
|
||||
target_sources(
|
||||
obs-studio
|
||||
PRIVATE
|
||||
auth-youtube.cpp
|
||||
auth-youtube.hpp
|
||||
window-dock-youtube-app.cpp
|
||||
window-dock-youtube-app.hpp
|
||||
window-youtube-actions.cpp
|
||||
window-youtube-actions.hpp
|
||||
youtube-api-wrappers.cpp
|
||||
youtube-api-wrappers.hpp
|
||||
dialogs/OBSYoutubeActions.cpp
|
||||
dialogs/OBSYoutubeActions.hpp
|
||||
docks/YouTubeAppDock.cpp
|
||||
docks/YouTubeAppDock.hpp
|
||||
docks/YouTubeChatDock.cpp
|
||||
docks/YouTubeChatDock.hpp
|
||||
forms/OBSYoutubeActions.ui
|
||||
oauth/YoutubeAuth.cpp
|
||||
oauth/YoutubeAuth.hpp
|
||||
utility/YoutubeApiWrappers.cpp
|
||||
utility/YoutubeApiWrappers.hpp
|
||||
)
|
||||
|
||||
target_enable_feature(obs-studio "YouTube API connection" YOUTUBE_ENABLED)
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,9 +1,7 @@
|
||||
target_sources(obs-studio PRIVATE platform-x11.cpp)
|
||||
target_sources(obs-studio PRIVATE utility/platform-x11.cpp utility/system-info-posix.cpp)
|
||||
target_compile_definitions(obs-studio PRIVATE OBS_INSTALL_PREFIX="${OBS_INSTALL_PREFIX}")
|
||||
target_link_libraries(obs-studio PRIVATE Qt::GuiPrivate Qt::DBus procstat)
|
||||
|
||||
target_sources(obs-studio PRIVATE system-info-posix.cpp)
|
||||
|
||||
if(TARGET OBS::python)
|
||||
find_package(Python REQUIRED COMPONENTS Interpreter Development)
|
||||
target_link_libraries(obs-studio PRIVATE Python::Python)
|
||||
@@ -1,12 +1,10 @@
|
||||
target_sources(obs-studio PRIVATE platform-x11.cpp)
|
||||
target_sources(obs-studio PRIVATE utility/platform-x11.cpp utility/system-info-posix.cpp)
|
||||
target_compile_definitions(
|
||||
obs-studio
|
||||
PRIVATE OBS_INSTALL_PREFIX="${OBS_INSTALL_PREFIX}" $<$<BOOL:${ENABLE_PORTABLE_CONFIG}>:ENABLE_PORTABLE_CONFIG>
|
||||
)
|
||||
target_link_libraries(obs-studio PRIVATE Qt::GuiPrivate Qt::DBus)
|
||||
|
||||
target_sources(obs-studio PRIVATE system-info-posix.cpp)
|
||||
|
||||
if(TARGET OBS::python)
|
||||
find_package(Python REQUIRED COMPONENTS Interpreter Development)
|
||||
target_link_libraries(obs-studio PRIVATE Python::Python)
|
||||
@@ -1,10 +1,16 @@
|
||||
include(cmake/feature-sparkle.cmake)
|
||||
|
||||
target_sources(obs-studio PRIVATE platform-osx.mm forms/OBSPermissions.ui window-permissions.cpp window-permissions.hpp)
|
||||
target_sources(
|
||||
obs-studio
|
||||
PRIVATE
|
||||
dialogs/OBSPermissions.cpp
|
||||
dialogs/OBSPermissions.hpp
|
||||
forms/OBSPermissions.ui
|
||||
utility/platform-osx.mm
|
||||
utility/system-info-macos.mm
|
||||
)
|
||||
target_compile_options(obs-studio PRIVATE -Wno-quoted-include-in-framework-header -Wno-comma)
|
||||
|
||||
target_sources(obs-studio PRIVATE system-info-macos.mm)
|
||||
|
||||
set_source_files_properties(platform-osx.mm PROPERTIES COMPILE_FLAGS -fobjc-arc)
|
||||
|
||||
if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 14.0.3)
|
||||
@@ -18,42 +18,48 @@ target_sources(
|
||||
obs-studio
|
||||
PRIVATE
|
||||
cmake/windows/obs.manifest
|
||||
dialogs/OBSUpdate.cpp
|
||||
dialogs/OBSUpdate.hpp
|
||||
forms/OBSUpdate.ui
|
||||
obs.rc
|
||||
platform-windows.cpp
|
||||
update/crypto-helpers-mbedtls.cpp
|
||||
update/crypto-helpers.hpp
|
||||
update/models/branches.hpp
|
||||
update/models/whatsnew.hpp
|
||||
update/shared-update.cpp
|
||||
update/shared-update.hpp
|
||||
update/update-helpers.cpp
|
||||
update/update-helpers.hpp
|
||||
update/update-window.cpp
|
||||
update/update-window.hpp
|
||||
update/win-update.cpp
|
||||
update/win-update.hpp
|
||||
win-dll-blocklist.c
|
||||
win-update/updater/manifest.hpp
|
||||
utility/AutoUpdateThread.cpp
|
||||
utility/AutoUpdateThread.hpp
|
||||
utility/crypto-helpers-mbedtls.cpp
|
||||
utility/crypto-helpers.hpp
|
||||
utility/models/branches.hpp
|
||||
utility/models/whatsnew.hpp
|
||||
utility/platform-windows.cpp
|
||||
utility/system-info-windows.cpp
|
||||
utility/update-helpers.cpp
|
||||
utility/update-helpers.hpp
|
||||
utility/WhatsNewBrowserInitThread.cpp
|
||||
utility/WhatsNewBrowserInitThread.hpp
|
||||
utility/WhatsNewInfoThread.cpp
|
||||
utility/WhatsNewInfoThread.hpp
|
||||
utility/win-dll-blocklist.c
|
||||
)
|
||||
|
||||
target_sources(obs-studio PRIVATE system-info-windows.cpp)
|
||||
add_library(obs-updater-manifest INTERFACE)
|
||||
add_library(OBS::updater-manifest ALIAS obs-updater-manifest)
|
||||
|
||||
target_sources(obs-updater-manifest INTERFACE updater/manifest.hpp)
|
||||
|
||||
target_link_libraries(
|
||||
obs-studio
|
||||
PRIVATE crypt32 OBS::blake2 OBS::w32-pthreads MbedTLS::mbedtls nlohmann_json::nlohmann_json Detours::Detours
|
||||
PRIVATE
|
||||
crypt32
|
||||
OBS::blake2
|
||||
OBS::updater-manifest
|
||||
OBS::w32-pthreads
|
||||
MbedTLS::mbedtls
|
||||
nlohmann_json::nlohmann_json
|
||||
Detours::Detours
|
||||
)
|
||||
|
||||
target_compile_definitions(obs-studio PRIVATE PSAPI_VERSION=2)
|
||||
|
||||
target_link_options(obs-studio PRIVATE /IGNORE:4099 $<$<CONFIG:DEBUG>:/NODEFAULTLIB:MSVCRT>)
|
||||
|
||||
add_library(obs-update-helpers INTERFACE)
|
||||
add_library(OBS::update-helpers ALIAS obs-update-helpers)
|
||||
|
||||
target_sources(obs-update-helpers INTERFACE win-update/win-update-helpers.cpp win-update/win-update-helpers.hpp)
|
||||
|
||||
target_include_directories(obs-update-helpers INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/win-update")
|
||||
|
||||
# Set commit for untagged version comparisons in the Windows updater
|
||||
if(OBS_VERSION MATCHES ".+g[a-f0-9]+.*")
|
||||
string(REGEX REPLACE ".+g([a-f0-9]+).*$" "\\1" OBS_COMMIT ${OBS_VERSION})
|
||||
@@ -61,9 +67,9 @@ else()
|
||||
set(OBS_COMMIT "")
|
||||
endif()
|
||||
|
||||
set_source_files_properties(update/win-update.cpp PROPERTIES COMPILE_DEFINITIONS OBS_COMMIT="${OBS_COMMIT}")
|
||||
set_source_files_properties(utility/AutoUpdateThread.cpp PROPERTIES COMPILE_DEFINITIONS OBS_COMMIT="${OBS_COMMIT}")
|
||||
|
||||
add_subdirectory(win-update/updater)
|
||||
add_subdirectory(updater)
|
||||
|
||||
set_property(TARGET obs-studio APPEND PROPERTY AUTORCC_OPTIONS --format-version 1)
|
||||
|
||||
File renamed without changes.
@@ -0,0 +1,85 @@
|
||||
if(NOT TARGET OBS::qt-slider-ignorewheel)
|
||||
add_subdirectory(
|
||||
"${CMAKE_SOURCE_DIR}/shared/qt/slider-ignorewheel"
|
||||
"${CMAKE_BINARY_DIR}/shared/qt/slider-ignorewheel"
|
||||
)
|
||||
endif()
|
||||
|
||||
target_link_libraries(obs-studio PRIVATE OBS::qt-slider-ignorewheel)
|
||||
|
||||
target_sources(
|
||||
obs-studio
|
||||
PRIVATE
|
||||
components/AbsoluteSlider.cpp
|
||||
components/AbsoluteSlider.hpp
|
||||
components/ApplicationAudioCaptureToolbar.cpp
|
||||
components/ApplicationAudioCaptureToolbar.hpp
|
||||
components/AudioCaptureToolbar.cpp
|
||||
components/AudioCaptureToolbar.hpp
|
||||
components/BalanceSlider.hpp
|
||||
components/BrowserToolbar.cpp
|
||||
components/BrowserToolbar.hpp
|
||||
components/ClickableLabel.hpp
|
||||
components/ColorSourceToolbar.cpp
|
||||
components/ColorSourceToolbar.hpp
|
||||
components/ComboSelectToolbar.cpp
|
||||
components/ComboSelectToolbar.hpp
|
||||
components/DelButton.hpp
|
||||
components/DeviceCaptureToolbar.cpp
|
||||
components/DeviceCaptureToolbar.hpp
|
||||
components/DisplayCaptureToolbar.cpp
|
||||
components/DisplayCaptureToolbar.hpp
|
||||
components/EditWidget.hpp
|
||||
components/FocusList.cpp
|
||||
components/FocusList.hpp
|
||||
components/GameCaptureToolbar.cpp
|
||||
components/GameCaptureToolbar.hpp
|
||||
components/HScrollArea.cpp
|
||||
components/HScrollArea.hpp
|
||||
components/ImageSourceToolbar.cpp
|
||||
components/ImageSourceToolbar.hpp
|
||||
components/MediaControls.cpp
|
||||
components/MediaControls.hpp
|
||||
components/MenuButton.cpp
|
||||
components/MenuButton.hpp
|
||||
components/Multiview.cpp
|
||||
components/Multiview.hpp
|
||||
components/MuteCheckBox.hpp
|
||||
components/NonCheckableButton.hpp
|
||||
components/OBSAdvAudioCtrl.cpp
|
||||
components/OBSAdvAudioCtrl.hpp
|
||||
components/OBSPreviewScalingComboBox.cpp
|
||||
components/OBSPreviewScalingComboBox.hpp
|
||||
components/OBSPreviewScalingLabel.cpp
|
||||
components/OBSPreviewScalingLabel.hpp
|
||||
components/OBSSourceLabel.cpp
|
||||
components/OBSSourceLabel.hpp
|
||||
components/SceneTree.cpp
|
||||
components/SceneTree.hpp
|
||||
components/SilentUpdateCheckBox.hpp
|
||||
components/SilentUpdateSpinBox.hpp
|
||||
components/SourceToolbar.cpp
|
||||
components/SourceToolbar.hpp
|
||||
components/SourceTree.cpp
|
||||
components/SourceTree.hpp
|
||||
components/SourceTreeDelegate.cpp
|
||||
components/SourceTreeDelegate.hpp
|
||||
components/SourceTreeItem.cpp
|
||||
components/SourceTreeItem.hpp
|
||||
components/SourceTreeModel.cpp
|
||||
components/SourceTreeModel.hpp
|
||||
components/TextSourceToolbar.cpp
|
||||
components/TextSourceToolbar.hpp
|
||||
components/UIValidation.cpp
|
||||
components/UIValidation.hpp
|
||||
components/UrlPushButton.cpp
|
||||
components/UrlPushButton.hpp
|
||||
components/VisibilityItemDelegate.cpp
|
||||
components/VisibilityItemDelegate.hpp
|
||||
components/VisibilityItemWidget.cpp
|
||||
components/VisibilityItemWidget.hpp
|
||||
components/VolumeSlider.cpp
|
||||
components/VolumeSlider.hpp
|
||||
components/WindowCaptureToolbar.cpp
|
||||
components/WindowCaptureToolbar.hpp
|
||||
)
|
||||
@@ -0,0 +1,40 @@
|
||||
if(NOT TARGET OBS::properties-view)
|
||||
add_subdirectory("${CMAKE_SOURCE_DIR}/shared/properties-view" "${CMAKE_BINARY_DIR}/shared/properties-view")
|
||||
endif()
|
||||
|
||||
target_link_libraries(obs-studio PRIVATE OBS::properties-view)
|
||||
|
||||
target_sources(
|
||||
obs-studio
|
||||
PRIVATE
|
||||
dialogs/NameDialog.cpp
|
||||
dialogs/NameDialog.hpp
|
||||
dialogs/OAuthLogin.cpp
|
||||
dialogs/OAuthLogin.hpp
|
||||
dialogs/OBSAbout.cpp
|
||||
dialogs/OBSAbout.hpp
|
||||
dialogs/OBSBasicAdvAudio.cpp
|
||||
dialogs/OBSBasicAdvAudio.hpp
|
||||
dialogs/OBSBasicFilters.cpp
|
||||
dialogs/OBSBasicFilters.hpp
|
||||
dialogs/OBSBasicInteraction.cpp
|
||||
dialogs/OBSBasicInteraction.hpp
|
||||
dialogs/OBSBasicProperties.cpp
|
||||
dialogs/OBSBasicProperties.hpp
|
||||
dialogs/OBSBasicSourceSelect.cpp
|
||||
dialogs/OBSBasicSourceSelect.hpp
|
||||
dialogs/OBSBasicTransform.cpp
|
||||
dialogs/OBSBasicTransform.hpp
|
||||
dialogs/OBSBasicVCamConfig.cpp
|
||||
dialogs/OBSBasicVCamConfig.hpp
|
||||
dialogs/OBSLogReply.cpp
|
||||
dialogs/OBSLogReply.hpp
|
||||
dialogs/OBSLogViewer.cpp
|
||||
dialogs/OBSLogViewer.hpp
|
||||
dialogs/OBSMissingFiles.cpp
|
||||
dialogs/OBSMissingFiles.hpp
|
||||
dialogs/OBSRemux.cpp
|
||||
dialogs/OBSRemux.hpp
|
||||
dialogs/OBSWhatsNew.cpp
|
||||
dialogs/OBSWhatsNew.hpp
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
target_sources(obs-studio PRIVATE docks/OBSDock.cpp docks/OBSDock.hpp)
|
||||
@@ -0,0 +1,4 @@
|
||||
target_sources(
|
||||
obs-studio
|
||||
PRIVATE oauth/Auth.cpp oauth/Auth.hpp oauth/AuthListener.cpp oauth/AuthListener.hpp oauth/OAuth.cpp oauth/OAuth.hpp
|
||||
)
|
||||
@@ -0,0 +1,58 @@
|
||||
find_package(Qt6 REQUIRED Widgets Network Svg Xml)
|
||||
|
||||
if(OS_LINUX OR OS_FREEBSD OR OS_OPENBSD)
|
||||
find_package(Qt6 REQUIRED Gui DBus)
|
||||
endif()
|
||||
|
||||
if(NOT TARGET OBS::qt-wrappers)
|
||||
add_subdirectory("${CMAKE_SOURCE_DIR}/shared/qt/wrappers" "${CMAKE_BINARY_DIR}/shared/qt/wrappers")
|
||||
endif()
|
||||
|
||||
target_link_libraries(
|
||||
obs-studio
|
||||
PRIVATE Qt::Widgets Qt::Svg Qt::Xml Qt::Network OBS::qt-wrappers
|
||||
)
|
||||
|
||||
set_target_properties(
|
||||
obs-studio
|
||||
PROPERTIES AUTOMOC TRUE AUTOUIC TRUE AUTORCC TRUE AUTOGEN_PARALLEL AUTO
|
||||
)
|
||||
|
||||
set_property(TARGET obs-studio APPEND PROPERTY AUTOUIC_SEARCH_PATHS forms forms/source-toolbar)
|
||||
|
||||
target_sources(
|
||||
obs-studio
|
||||
PRIVATE
|
||||
forms/AutoConfigFinishPage.ui
|
||||
forms/AutoConfigStartPage.ui
|
||||
forms/AutoConfigStartPage.ui
|
||||
forms/AutoConfigStreamPage.ui
|
||||
forms/AutoConfigTestPage.ui
|
||||
forms/AutoConfigVideoPage.ui
|
||||
forms/ColorSelect.ui
|
||||
forms/obs.qrc
|
||||
forms/OBSAbout.ui
|
||||
forms/OBSAdvAudio.ui
|
||||
forms/OBSBasic.ui
|
||||
forms/OBSBasicControls.ui
|
||||
forms/OBSBasicFilters.ui
|
||||
forms/OBSBasicInteraction.ui
|
||||
forms/OBSBasicProperties.ui
|
||||
forms/OBSBasicSettings.ui
|
||||
forms/OBSBasicSourceSelect.ui
|
||||
forms/OBSBasicVCamConfig.ui
|
||||
forms/OBSExtraBrowsers.ui
|
||||
forms/OBSImporter.ui
|
||||
forms/OBSLogReply.ui
|
||||
forms/OBSLogReply.ui
|
||||
forms/OBSMissingFiles.ui
|
||||
forms/OBSRemux.ui
|
||||
forms/source-toolbar/browser-source-toolbar.ui
|
||||
forms/source-toolbar/color-source-toolbar.ui
|
||||
forms/source-toolbar/device-select-toolbar.ui
|
||||
forms/source-toolbar/game-capture-toolbar.ui
|
||||
forms/source-toolbar/image-source-toolbar.ui
|
||||
forms/source-toolbar/media-controls.ui
|
||||
forms/source-toolbar/text-source-toolbar.ui
|
||||
forms/StatusBarWidget.ui
|
||||
)
|
||||
@@ -0,0 +1,15 @@
|
||||
target_sources(
|
||||
obs-studio
|
||||
PRIVATE
|
||||
settings/OBSBasicSettings_A11y.cpp
|
||||
settings/OBSBasicSettings_Appearance.cpp
|
||||
settings/OBSBasicSettings_Stream.cpp
|
||||
settings/OBSBasicSettings.cpp
|
||||
settings/OBSBasicSettings.hpp
|
||||
settings/OBSHotkeyEdit.cpp
|
||||
settings/OBSHotkeyEdit.hpp
|
||||
settings/OBSHotkeyLabel.cpp
|
||||
settings/OBSHotkeyLabel.hpp
|
||||
settings/OBSHotkeyWidget.cpp
|
||||
settings/OBSHotkeyWidget.hpp
|
||||
)
|
||||
@@ -0,0 +1,69 @@
|
||||
target_sources(
|
||||
obs-studio
|
||||
PRIVATE
|
||||
utility/AdvancedOutput.cpp
|
||||
utility/AdvancedOutput.hpp
|
||||
utility/audio-encoders.cpp
|
||||
utility/audio-encoders.hpp
|
||||
utility/BaseLexer.hpp
|
||||
utility/BasicOutputHandler.cpp
|
||||
utility/BasicOutputHandler.hpp
|
||||
utility/display-helpers.hpp
|
||||
utility/FFmpegCodec.cpp
|
||||
utility/FFmpegCodec.hpp
|
||||
utility/FFmpegFormat.cpp
|
||||
utility/FFmpegFormat.hpp
|
||||
utility/FFmpegShared.hpp
|
||||
utility/GoLiveAPI_CensoredJson.cpp
|
||||
utility/GoLiveAPI_CensoredJson.hpp
|
||||
utility/GoLiveAPI_Network.cpp
|
||||
utility/GoLiveAPI_Network.hpp
|
||||
utility/GoLiveAPI_PostData.cpp
|
||||
utility/GoLiveAPI_PostData.hpp
|
||||
utility/item-widget-helpers.cpp
|
||||
utility/item-widget-helpers.hpp
|
||||
utility/MissingFilesModel.cpp
|
||||
utility/MissingFilesModel.hpp
|
||||
utility/MissingFilesPathItemDelegate.cpp
|
||||
utility/MissingFilesPathItemDelegate.hpp
|
||||
utility/models/multitrack-video.hpp
|
||||
utility/MultitrackVideoError.cpp
|
||||
utility/MultitrackVideoError.hpp
|
||||
utility/MultitrackVideoOutput.cpp
|
||||
utility/MultitrackVideoOutput.hpp
|
||||
utility/obf.c
|
||||
utility/obf.h
|
||||
utility/OBSEventFilter.hpp
|
||||
utility/OBSProxyStyle.cpp
|
||||
utility/OBSProxyStyle.hpp
|
||||
utility/OBSTheme.hpp
|
||||
utility/OBSThemeVariable.hpp
|
||||
utility/OBSTranslator.cpp
|
||||
utility/OBSTranslator.hpp
|
||||
utility/platform.hpp
|
||||
utility/QuickTransition.cpp
|
||||
utility/QuickTransition.hpp
|
||||
utility/RemoteTextThread.cpp
|
||||
utility/RemoteTextThread.hpp
|
||||
utility/RemuxEntryPathItemDelegate.cpp
|
||||
utility/RemuxEntryPathItemDelegate.hpp
|
||||
utility/RemuxQueueModel.cpp
|
||||
utility/RemuxQueueModel.hpp
|
||||
utility/RemuxWorker.cpp
|
||||
utility/RemuxWorker.hpp
|
||||
utility/SceneRenameDelegate.cpp
|
||||
utility/SceneRenameDelegate.hpp
|
||||
utility/ScreenshotObj.cpp
|
||||
utility/ScreenshotObj.hpp
|
||||
utility/SettingsEventFilter.hpp
|
||||
utility/SimpleOutput.cpp
|
||||
utility/SimpleOutput.hpp
|
||||
utility/StartMultiTrackVideoStreamingGuard.hpp
|
||||
utility/SurfaceEventFilter.hpp
|
||||
utility/system-info.hpp
|
||||
utility/undo_stack.cpp
|
||||
utility/undo_stack.hpp
|
||||
utility/VCamConfig.hpp
|
||||
utility/VolumeMeterTimer.cpp
|
||||
utility/VolumeMeterTimer.hpp
|
||||
)
|
||||
@@ -0,0 +1,66 @@
|
||||
if(NOT TARGET OBS::qt-vertical-scroll-area)
|
||||
add_subdirectory(
|
||||
"${CMAKE_SOURCE_DIR}/shared/qt/vertical-scroll-area"
|
||||
"${CMAKE_BINARY_DIR}/shared/qt/vertical-scroll-area"
|
||||
)
|
||||
endif()
|
||||
|
||||
target_link_libraries(obs-studio PRIVATE OBS::qt-vertical-scroll-area)
|
||||
|
||||
target_sources(
|
||||
obs-studio
|
||||
PRIVATE
|
||||
widgets/ColorSelect.cpp
|
||||
widgets/ColorSelect.hpp
|
||||
widgets/OBSBasic.cpp
|
||||
widgets/OBSBasic.hpp
|
||||
widgets/OBSBasic_Browser.cpp
|
||||
widgets/OBSBasic_Clipboard.cpp
|
||||
widgets/OBSBasic_ContextToolbar.cpp
|
||||
widgets/OBSBasic_Docks.cpp
|
||||
widgets/OBSBasic_Dropfiles.cpp
|
||||
widgets/OBSBasic_Hotkeys.cpp
|
||||
widgets/OBSBasic_Icons.cpp
|
||||
widgets/OBSBasic_MainControls.cpp
|
||||
widgets/OBSBasic_OutputHandler.cpp
|
||||
widgets/OBSBasic_Preview.cpp
|
||||
widgets/OBSBasic_Profiles.cpp
|
||||
widgets/OBSBasic_Projectors.cpp
|
||||
widgets/OBSBasic_Recording.cpp
|
||||
widgets/OBSBasic_ReplayBuffer.cpp
|
||||
widgets/OBSBasic_SceneCollections.cpp
|
||||
widgets/OBSBasic_SceneItems.cpp
|
||||
widgets/OBSBasic_Scenes.cpp
|
||||
widgets/OBSBasic_Screenshots.cpp
|
||||
widgets/OBSBasic_Service.cpp
|
||||
widgets/OBSBasic_StatusBar.cpp
|
||||
widgets/OBSBasic_Streaming.cpp
|
||||
widgets/OBSBasic_StudioMode.cpp
|
||||
widgets/OBSBasic_SysTray.cpp
|
||||
widgets/OBSBasic_Transitions.cpp
|
||||
widgets/OBSBasic_Updater.cpp
|
||||
widgets/OBSBasic_VirtualCam.cpp
|
||||
widgets/OBSBasic_VolControl.cpp
|
||||
widgets/OBSBasic_YouTube.cpp
|
||||
widgets/OBSBasicControls.cpp
|
||||
widgets/OBSBasicControls.hpp
|
||||
widgets/OBSBasicPreview.cpp
|
||||
widgets/OBSBasicPreview.hpp
|
||||
widgets/OBSBasicStats.cpp
|
||||
widgets/OBSBasicStats.hpp
|
||||
widgets/OBSBasicStatusBar.cpp
|
||||
widgets/OBSBasicStatusBar.hpp
|
||||
widgets/OBSMainWindow.hpp
|
||||
widgets/OBSProjector.cpp
|
||||
widgets/OBSProjector.hpp
|
||||
widgets/OBSQTDisplay.cpp
|
||||
widgets/OBSQTDisplay.hpp
|
||||
widgets/StatusBarWidget.cpp
|
||||
widgets/StatusBarWidget.hpp
|
||||
widgets/VolControl.cpp
|
||||
widgets/VolControl.hpp
|
||||
widgets/VolumeAccessibleInterface.cpp
|
||||
widgets/VolumeAccessibleInterface.hpp
|
||||
widgets/VolumeMeter.cpp
|
||||
widgets/VolumeMeter.hpp
|
||||
)
|
||||
@@ -0,0 +1,15 @@
|
||||
target_sources(
|
||||
obs-studio
|
||||
PRIVATE
|
||||
wizards/AutoConfig.cpp
|
||||
wizards/AutoConfig.hpp
|
||||
wizards/AutoConfigStartPage.cpp
|
||||
wizards/AutoConfigStartPage.hpp
|
||||
wizards/AutoConfigStreamPage.cpp
|
||||
wizards/AutoConfigStreamPage.hpp
|
||||
wizards/AutoConfigTestPage.cpp
|
||||
wizards/AutoConfigTestPage.hpp
|
||||
wizards/AutoConfigVideoPage.cpp
|
||||
wizards/AutoConfigVideoPage.hpp
|
||||
wizards/TestMode.hpp
|
||||
)
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,4 +1,5 @@
|
||||
#include "moc_absolute-slider.cpp"
|
||||
#include "AbsoluteSlider.hpp"
|
||||
#include "moc_AbsoluteSlider.cpp"
|
||||
|
||||
AbsoluteSlider::AbsoluteSlider(QWidget *parent) : SliderIgnoreScroll(parent)
|
||||
{
|
||||
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include <slider-ignorewheel.hpp>
|
||||
|
||||
class AbsoluteSlider : public SliderIgnoreScroll {
|
||||
@@ -0,0 +1,22 @@
|
||||
#include "ApplicationAudioCaptureToolbar.hpp"
|
||||
#include "ui_device-select-toolbar.h"
|
||||
#include "moc_ApplicationAudioCaptureToolbar.cpp"
|
||||
|
||||
ApplicationAudioCaptureToolbar::ApplicationAudioCaptureToolbar(QWidget *parent, OBSSource source)
|
||||
: ComboSelectToolbar(parent, source)
|
||||
{
|
||||
}
|
||||
|
||||
void ApplicationAudioCaptureToolbar::Init()
|
||||
{
|
||||
delete ui->activateButton;
|
||||
ui->activateButton = nullptr;
|
||||
|
||||
obs_module_t *mod = obs_get_module("win-wasapi");
|
||||
const char *device_str = obs_module_get_locale_text(mod, "Window");
|
||||
ui->deviceLabel->setText(device_str);
|
||||
|
||||
prop_name = "window";
|
||||
|
||||
ComboSelectToolbar::Init();
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "ComboSelectToolbar.hpp"
|
||||
|
||||
class ApplicationAudioCaptureToolbar : public ComboSelectToolbar {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ApplicationAudioCaptureToolbar(QWidget *parent, OBSSource source);
|
||||
void Init() override;
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
#include "AudioCaptureToolbar.hpp"
|
||||
#include "ui_device-select-toolbar.h"
|
||||
#include "moc_AudioCaptureToolbar.cpp"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define get_os_module(win, mac, linux) obs_get_module(win)
|
||||
#define get_os_text(mod, win, mac, linux) obs_module_get_locale_text(mod, win)
|
||||
#elif __APPLE__
|
||||
#define get_os_module(win, mac, linux) obs_get_module(mac)
|
||||
#define get_os_text(mod, win, mac, linux) obs_module_get_locale_text(mod, mac)
|
||||
#else
|
||||
#define get_os_module(win, mac, linux) obs_get_module(linux)
|
||||
#define get_os_text(mod, win, mac, linux) obs_module_get_locale_text(mod, linux)
|
||||
#endif
|
||||
|
||||
AudioCaptureToolbar::AudioCaptureToolbar(QWidget *parent, OBSSource source) : ComboSelectToolbar(parent, source) {}
|
||||
|
||||
void AudioCaptureToolbar::Init()
|
||||
{
|
||||
delete ui->activateButton;
|
||||
ui->activateButton = nullptr;
|
||||
|
||||
obs_module_t *mod = get_os_module("win-wasapi", "mac-capture", "linux-pulseaudio");
|
||||
if (!mod)
|
||||
return;
|
||||
|
||||
const char *device_str = get_os_text(mod, "Device", "CoreAudio.Device", "Device");
|
||||
ui->deviceLabel->setText(device_str);
|
||||
|
||||
prop_name = "device_id";
|
||||
|
||||
ComboSelectToolbar::Init();
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "ComboSelectToolbar.hpp"
|
||||
|
||||
class AudioCaptureToolbar : public ComboSelectToolbar {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AudioCaptureToolbar(QWidget *parent, OBSSource source);
|
||||
void Init() override;
|
||||
};
|
||||
Loaded 100 of 980 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user