#include "settings-window.hpp" #include "settings-manager.hpp" #include #include #include #include #include #include #include #include #include #include #include using namespace std; SettingsWindow::SettingsWindow(OBSManager* obs) : mBox(Gtk::Orientation::ORIENTATION_VERTICAL, 2), mBoxSettings(Gtk::Orientation::ORIENTATION_VERTICAL, 2), mBoxPluginDir(Gtk::Orientation::ORIENTATION_HORIZONTAL, 2), mLabelPluginDir("Plugin Dir: "), mBoxOutputDir(Gtk::Orientation::ORIENTATION_HORIZONTAL, 2), mLabelOutputDir("Saved Files Dir: "), mCheckButtonDesktop("Enable desktop recording"), mBoxWebcam(Gtk::Orientation::ORIENTATION_VERTICAL, 2), mCheckButtonWebcam("Enable webcam recording"), mBoxAudio(Gtk::Orientation::ORIENTATION_VERTICAL), mCheckButtonAudio("Enable audio recording"), mButtonClose("Close"), mButtonSave("Save") { set_title("Settings"); set_default_size(640, 480); set_border_width(10); set_modal(true); set_type_hint(Gdk::WindowTypeHint::WINDOW_TYPE_HINT_DIALOG); settings = new SettingsManager(); signal_key_press_event().connect( sigc::mem_fun(*this, &SettingsWindow::onKeyPressed)); mButtonClose.signal_clicked().connect( sigc::mem_fun(*this, &SettingsWindow::onClosePressed)); mButtonSave.signal_clicked().connect( sigc::mem_fun(*this, &SettingsWindow::onSavePressed)); mBoxSettings.set_border_width(10); mEntryPluginDir.set_hexpand(true); mEntryPluginDir.set_text(settings->GetWithDefault(SETTINGS_KEY_PLUGIN_DIR, SETTINGS_DEFAULT_PLUGIN_DIR)); mBoxPluginDir.set_border_width(10); mBoxPluginDir.add(mLabelPluginDir); mBoxPluginDir.add(mEntryPluginDir); mEntryOutputDir.set_hexpand(true); mEntryOutputDir.set_text(settings->GetWithDefault(SETTINGS_KEY_OUTPUT_DIR, std::filesystem::current_path())); mBoxOutputDir.set_border_width(10); mBoxOutputDir.add(mLabelOutputDir); mBoxOutputDir.add(mEntryOutputDir); mFrameDesktop.set_label("Desktop"); mFrameDesktop.set_border_width(10); mCheckButtonDesktop.set_border_width(10); mCheckButtonDesktop.set_active(settings->GetBoolWithDefault(SETTINGS_KEY_SCREEN_ENABLED, true)); mFrameDesktop.add(mCheckButtonDesktop); mComboBoxVideoDevice.set_border_width(10); mFrameWebcam.set_label("Webcam"); mFrameWebcam.set_border_width(10); mBoxWebcam.set_border_width(10); mCheckButtonWebcam.set_active(settings->GetBoolWithDefault(SETTINGS_KEY_WEBCAM_ENABLED, true)); mBoxWebcam.add(mCheckButtonWebcam); mBoxWebcam.add(mComboBoxVideoDevice); mFrameWebcam.add(mBoxWebcam); mComboBoxAudioDevice.set_border_width(10); mFrameAudio.set_label("Microphone"); mFrameAudio.set_border_width(10); mBoxAudio.set_border_width(10); mCheckButtonAudio.set_border_width(10); mCheckButtonAudio.set_active(settings->GetBoolWithDefault(SETTINGS_KEY_AUDIO_ENABLED, true)); mBoxAudio.add(mCheckButtonAudio); mBoxAudio.add(mComboBoxAudioDevice); mFrameAudio.add(mBoxAudio); mBoxSettings.add(mBoxPluginDir); mBoxSettings.add(mBoxOutputDir); mBoxSettings.add(mFrameDesktop); mBoxSettings.add(mFrameWebcam); mBoxSettings.add(mFrameAudio); mFrameSettings.set_label("Settings"); mFrameSettings.set_border_width(10); mFrameSettings.add(mBoxSettings); mActionBar.pack_end(mButtonSave); mActionBar.pack_end(mButtonClose); mBox.add(mFrameSettings); mBox.add(mActionBar); mBox.show_all(); add(mBox); } SettingsWindow::~SettingsWindow() { } void SettingsWindow::on_show() { Gtk::Window::on_show(); populateVideoDevices(); populatePulseAudioDevices(); } bool SettingsWindow::onKeyPressed(GdkEventKey *event) { if (event->keyval == GDK_KEY_Escape) { close(); return true; } return false; } void SettingsWindow::onClosePressed() { close(); } void SettingsWindow::onSavePressed() { settings->Update(SETTINGS_KEY_PLUGIN_DIR, mEntryPluginDir.get_text()); settings->Update(SETTINGS_KEY_OUTPUT_DIR, mEntryOutputDir.get_text()); settings->UpdateBool(SETTINGS_KEY_SCREEN_ENABLED, mCheckButtonDesktop.get_active()); settings->UpdateBool(SETTINGS_KEY_WEBCAM_ENABLED, mCheckButtonWebcam.get_active()); settings->Update(SETTINGS_KEY_VIDEO_DEVICE_ID, mComboBoxVideoDevice.get_active_id()); settings->UpdateBool(SETTINGS_KEY_AUDIO_ENABLED, mCheckButtonAudio.get_active()); settings->Update(SETTINGS_KEY_AUDIO_DEVICE_ID, mComboBoxAudioDevice.get_active_id()); settings->SaveAll(); close(); } void SettingsWindow::populateVideoDevices() { cout << "populateVideoDevices" << endl; mComboBoxVideoDevice.remove_all(); v4l2_capability cap; dirent *dp; auto dirp = opendir("/sys/class/video4linux"); if (!dirp) throw runtime_error("Failed to get video devices"); while ((dp = readdir(dirp)) != nullptr) { int fd; if (dp->d_type == DT_DIR) continue; auto name = string(dp->d_name); if (name.find("video") == string::npos) continue; auto device = "/dev/" + name; if ((fd = v4l2_open(device.c_str(), O_RDWR | O_NONBLOCK)) == -1) { cout << "Failed to open device: " << device << endl; continue; } if (v4l2_ioctl(fd, VIDIOC_QUERYCAP, &cap) == -1) { cout << "Failed to query device: " << device << endl; v4l2_close(fd); continue; } auto n = string(reinterpret_cast(cap.card)); n += " (" + device + ")"; mComboBoxVideoDevice.append(device, n); if (settings->Get(SETTINGS_KEY_VIDEO_DEVICE_ID) == device) mComboBoxVideoDevice.set_active_id(device); v4l2_close(fd); } } void SettingsWindow::populateALSAAudioDevices() { cout << "populateALSAAudioDevices" << endl; mComboBoxAudioDevice.remove_all(); void **hints; void **hint; char *name = nullptr; char *desc = nullptr; char *ioid = nullptr; if (snd_device_name_hint(-1, "pcm", &hints) < 0) throw runtime_error("Failed reading audio devices"); hint = hints; while (*hint != NULL) { name = snd_device_name_get_hint(*hint, "NAME"); desc = snd_device_name_get_hint(*hint, "DESC"); ioid = snd_device_name_get_hint(*hint, "IOID"); if (name == NULL || strstr(name, "front:") == NULL) goto next; if (ioid != NULL && strcmp(ioid, "Input") != 0) goto next; mComboBoxAudioDevice.append(name, desc); if (settings->Get(SETTINGS_KEY_AUDIO_DEVICE_ID) == name) mComboBoxAudioDevice.set_active_id(name); next: if (name != NULL) { free(name); name = NULL; } if (desc != NULL) { free(desc); desc = NULL; } if (ioid != NULL) { free(ioid); ioid = NULL; } hint++; } snd_device_name_free_hint((void**)hints); } class PADevice { public: string name; string desc; uint32_t index; }; // Pulse Audio void pa_state_cb(pa_context *c, void *userdata) { int *pa_ready = (int*)userdata; pa_context_state_t state = pa_context_get_state(c); switch (state) { case PA_CONTEXT_UNCONNECTED: case PA_CONTEXT_CONNECTING: case PA_CONTEXT_AUTHORIZING: case PA_CONTEXT_SETTING_NAME: default: break; case PA_CONTEXT_FAILED: case PA_CONTEXT_TERMINATED: *pa_ready = 2; break; case PA_CONTEXT_READY: *pa_ready = 1; break; } } void pa_sinklist_cb(pa_context *c, const pa_sink_info *l, int eol, void *userdata) { list *dlist = (list*)userdata; if (eol != 0 || l->monitor_source == PA_INVALID_INDEX) return; dlist->push_back({ .name = l->name, .desc = l->description, .index = l->index }); } void pa_sourcelist_cb(pa_context *c, const pa_source_info *l, int eol, void *userdata) { list *dlist = (list*)userdata; if (eol != 0 || l->monitor_of_sink != PA_INVALID_INDEX) return; dlist->push_back({ .name = l->name, .desc = l->description, .index = l->index }); } void SettingsWindow::populatePulseAudioDevices() { cout << "populatePulseAudioDevices" << endl; mComboBoxAudioDevice.remove_all(); int state = 0; int pa_ready = 0; auto input = new list(); auto output = new list(); pa_operation *pa_op; pa_mainloop *pa_ml = pa_mainloop_new(); pa_mainloop_api *pa_mlapi = pa_mainloop_get_api(pa_ml); pa_context *pa_ctx = pa_context_new(pa_mlapi, "screenrecorder"); pa_context_connect(pa_ctx, NULL, (pa_context_flags_t)0, NULL); pa_context_set_state_callback(pa_ctx, pa_state_cb, &pa_ready); for (;;) { if (pa_ready == 0) { pa_mainloop_iterate(pa_ml, 1, NULL); continue; } if (pa_ready == 2) { pa_context_disconnect(pa_ctx); pa_context_unref(pa_ctx); pa_mainloop_free(pa_ml); throw runtime_error("Failed to get pulse audio devices"); } switch (state) { case 0: pa_op = pa_context_get_sink_info_list(pa_ctx, pa_sinklist_cb, output); state++; break; case 1: if (pa_operation_get_state(pa_op) == PA_OPERATION_DONE) pa_operation_unref(pa_op); pa_op = pa_context_get_source_info_list(pa_ctx, pa_sourcelist_cb, input); state++; break; case 2: if (pa_operation_get_state(pa_op) == PA_OPERATION_DONE) { pa_operation_unref(pa_op); pa_context_disconnect(pa_ctx); pa_context_unref(pa_ctx); pa_mainloop_free(pa_ml); for (auto device : *input) { mComboBoxAudioDevice.append(device.name, device.desc); if (settings->Get(SETTINGS_KEY_AUDIO_DEVICE_ID) == device.name) mComboBoxAudioDevice.set_active_id(device.name); } return; } break; default: return; } pa_mainloop_iterate(pa_ml, 1, NULL); } }