You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

387 lines
11 KiB

2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
  1. #include "settings-window.hpp"
  2. #include "settings-manager.hpp"
  3. #include <linux/videodev2.h>
  4. #include <alsa/asoundlib.h>
  5. #include <pulse/pulseaudio.h>
  6. #include <string>
  7. #include <list>
  8. #include <iostream>
  9. #include <filesystem>
  10. #include <stdexcept>
  11. #include <fcntl.h>
  12. #include <libv4l2.h>
  13. #include <sys/dir.h>
  14. using namespace std;
  15. SettingsWindow::SettingsWindow(OBSManager* obs)
  16. : mBox(Gtk::Orientation::ORIENTATION_VERTICAL, 2),
  17. mBoxSettings(Gtk::Orientation::ORIENTATION_VERTICAL, 2),
  18. mBoxPluginDir(Gtk::Orientation::ORIENTATION_HORIZONTAL, 2),
  19. mLabelPluginDir("Plugin Dir: "),
  20. mBoxOutputDir(Gtk::Orientation::ORIENTATION_HORIZONTAL, 2),
  21. mLabelOutputDir("Saved Files Dir: "),
  22. mCheckButtonDesktop("Enable desktop recording"),
  23. mBoxWebcam(Gtk::Orientation::ORIENTATION_VERTICAL, 2),
  24. mCheckButtonWebcam("Enable webcam recording"),
  25. mBoxAudio(Gtk::Orientation::ORIENTATION_VERTICAL),
  26. mCheckButtonAudio("Enable audio recording"),
  27. mButtonClose("Close"),
  28. mButtonSave("Save")
  29. {
  30. set_title("Settings");
  31. set_default_size(640, 480);
  32. set_border_width(10);
  33. set_modal(true);
  34. set_type_hint(Gdk::WindowTypeHint::WINDOW_TYPE_HINT_DIALOG);
  35. settings = new SettingsManager();
  36. signal_key_press_event().connect(
  37. sigc::mem_fun(*this, &SettingsWindow::onKeyPressed));
  38. mButtonClose.signal_clicked().connect(
  39. sigc::mem_fun(*this, &SettingsWindow::onClosePressed));
  40. mButtonSave.signal_clicked().connect(
  41. sigc::mem_fun(*this, &SettingsWindow::onSavePressed));
  42. mBoxSettings.set_border_width(10);
  43. mEntryPluginDir.set_hexpand(true);
  44. mEntryPluginDir.set_text(settings->GetWithDefault(SETTINGS_KEY_PLUGIN_DIR, SETTINGS_DEFAULT_PLUGIN_DIR));
  45. mBoxPluginDir.set_border_width(10);
  46. mBoxPluginDir.add(mLabelPluginDir);
  47. mBoxPluginDir.add(mEntryPluginDir);
  48. mEntryOutputDir.set_hexpand(true);
  49. mEntryOutputDir.set_text(settings->GetWithDefault(SETTINGS_KEY_OUTPUT_DIR, std::filesystem::current_path()));
  50. mBoxOutputDir.set_border_width(10);
  51. mBoxOutputDir.add(mLabelOutputDir);
  52. mBoxOutputDir.add(mEntryOutputDir);
  53. mFrameDesktop.set_label("Desktop");
  54. mFrameDesktop.set_border_width(10);
  55. mCheckButtonDesktop.set_border_width(10);
  56. mCheckButtonDesktop.set_active(settings->GetBoolWithDefault(SETTINGS_KEY_SCREEN_ENABLED, true));
  57. mFrameDesktop.add(mCheckButtonDesktop);
  58. mComboBoxVideoDevice.set_border_width(10);
  59. mFrameWebcam.set_label("Webcam");
  60. mFrameWebcam.set_border_width(10);
  61. mBoxWebcam.set_border_width(10);
  62. mCheckButtonWebcam.set_active(settings->GetBoolWithDefault(SETTINGS_KEY_WEBCAM_ENABLED, true));
  63. mBoxWebcam.add(mCheckButtonWebcam);
  64. mBoxWebcam.add(mComboBoxVideoDevice);
  65. mFrameWebcam.add(mBoxWebcam);
  66. mComboBoxAudioDevice.set_border_width(10);
  67. mFrameAudio.set_label("Microphone");
  68. mFrameAudio.set_border_width(10);
  69. mBoxAudio.set_border_width(10);
  70. mCheckButtonAudio.set_border_width(10);
  71. mCheckButtonAudio.set_active(settings->GetBoolWithDefault(SETTINGS_KEY_AUDIO_ENABLED, true));
  72. mBoxAudio.add(mCheckButtonAudio);
  73. mBoxAudio.add(mComboBoxAudioDevice);
  74. mFrameAudio.add(mBoxAudio);
  75. mBoxSettings.add(mBoxPluginDir);
  76. mBoxSettings.add(mBoxOutputDir);
  77. mBoxSettings.add(mFrameDesktop);
  78. mBoxSettings.add(mFrameWebcam);
  79. mBoxSettings.add(mFrameAudio);
  80. mFrameSettings.set_label("Settings");
  81. mFrameSettings.set_border_width(10);
  82. mFrameSettings.add(mBoxSettings);
  83. mActionBar.pack_end(mButtonSave);
  84. mActionBar.pack_end(mButtonClose);
  85. mBox.add(mFrameSettings);
  86. mBox.add(mActionBar);
  87. mBox.show_all();
  88. add(mBox);
  89. }
  90. SettingsWindow::~SettingsWindow()
  91. {
  92. }
  93. void SettingsWindow::on_show()
  94. {
  95. Gtk::Window::on_show();
  96. populateVideoDevices();
  97. populatePulseAudioDevices();
  98. }
  99. bool SettingsWindow::onKeyPressed(GdkEventKey *event)
  100. {
  101. if (event->keyval == GDK_KEY_Escape)
  102. {
  103. close();
  104. return true;
  105. }
  106. return false;
  107. }
  108. void SettingsWindow::onClosePressed()
  109. {
  110. close();
  111. }
  112. void SettingsWindow::onSavePressed()
  113. {
  114. settings->Update(SETTINGS_KEY_PLUGIN_DIR, mEntryPluginDir.get_text());
  115. settings->Update(SETTINGS_KEY_OUTPUT_DIR, mEntryOutputDir.get_text());
  116. settings->UpdateBool(SETTINGS_KEY_SCREEN_ENABLED, mCheckButtonDesktop.get_active());
  117. settings->UpdateBool(SETTINGS_KEY_WEBCAM_ENABLED, mCheckButtonWebcam.get_active());
  118. settings->Update(SETTINGS_KEY_VIDEO_DEVICE_ID, mComboBoxVideoDevice.get_active_id());
  119. settings->UpdateBool(SETTINGS_KEY_AUDIO_ENABLED, mCheckButtonAudio.get_active());
  120. settings->Update(SETTINGS_KEY_AUDIO_DEVICE_ID, mComboBoxAudioDevice.get_active_id());
  121. settings->SaveAll();
  122. close();
  123. }
  124. void SettingsWindow::populateVideoDevices()
  125. {
  126. cout << "populateVideoDevices" << endl;
  127. mComboBoxVideoDevice.remove_all();
  128. v4l2_capability cap;
  129. dirent *dp;
  130. auto dirp = opendir("/sys/class/video4linux");
  131. if (!dirp)
  132. throw runtime_error("Failed to get video devices");
  133. while ((dp = readdir(dirp)) != nullptr)
  134. {
  135. int fd;
  136. if (dp->d_type == DT_DIR)
  137. continue;
  138. auto name = string(dp->d_name);
  139. if (name.find("video") == string::npos)
  140. continue;
  141. auto device = "/dev/" + name;
  142. if ((fd = v4l2_open(device.c_str(), O_RDWR | O_NONBLOCK)) == -1)
  143. {
  144. cout << "Failed to open device: " << device << endl;
  145. continue;
  146. }
  147. if (v4l2_ioctl(fd, VIDIOC_QUERYCAP, &cap) == -1)
  148. {
  149. cout << "Failed to query device: " << device << endl;
  150. v4l2_close(fd);
  151. continue;
  152. }
  153. auto n = string(reinterpret_cast<char*>(cap.card));
  154. n += " (" + device + ")";
  155. mComboBoxVideoDevice.append(device, n);
  156. if (settings->Get(SETTINGS_KEY_VIDEO_DEVICE_ID) == device)
  157. mComboBoxVideoDevice.set_active_id(device);
  158. v4l2_close(fd);
  159. }
  160. }
  161. void SettingsWindow::populateALSAAudioDevices()
  162. {
  163. cout << "populateALSAAudioDevices" << endl;
  164. mComboBoxAudioDevice.remove_all();
  165. void **hints;
  166. void **hint;
  167. char *name = nullptr;
  168. char *desc = nullptr;
  169. char *ioid = nullptr;
  170. if (snd_device_name_hint(-1, "pcm", &hints) < 0)
  171. throw runtime_error("Failed reading audio devices");
  172. hint = hints;
  173. while (*hint != NULL)
  174. {
  175. name = snd_device_name_get_hint(*hint, "NAME");
  176. desc = snd_device_name_get_hint(*hint, "DESC");
  177. ioid = snd_device_name_get_hint(*hint, "IOID");
  178. if (name == NULL || strstr(name, "front:") == NULL)
  179. goto next;
  180. if (ioid != NULL && strcmp(ioid, "Input") != 0)
  181. goto next;
  182. mComboBoxAudioDevice.append(name, desc);
  183. if (settings->Get(SETTINGS_KEY_AUDIO_DEVICE_ID) == name)
  184. mComboBoxAudioDevice.set_active_id(name);
  185. next:
  186. if (name != NULL)
  187. {
  188. free(name);
  189. name = NULL;
  190. }
  191. if (desc != NULL)
  192. {
  193. free(desc);
  194. desc = NULL;
  195. }
  196. if (ioid != NULL)
  197. {
  198. free(ioid);
  199. ioid = NULL;
  200. }
  201. hint++;
  202. }
  203. snd_device_name_free_hint((void**)hints);
  204. }
  205. class PADevice
  206. {
  207. public:
  208. string name;
  209. string desc;
  210. uint32_t index;
  211. };
  212. // Pulse Audio
  213. void pa_state_cb(pa_context *c, void *userdata)
  214. {
  215. int *pa_ready = (int*)userdata;
  216. pa_context_state_t state = pa_context_get_state(c);
  217. switch (state)
  218. {
  219. case PA_CONTEXT_UNCONNECTED:
  220. case PA_CONTEXT_CONNECTING:
  221. case PA_CONTEXT_AUTHORIZING:
  222. case PA_CONTEXT_SETTING_NAME:
  223. default:
  224. break;
  225. case PA_CONTEXT_FAILED:
  226. case PA_CONTEXT_TERMINATED:
  227. *pa_ready = 2;
  228. break;
  229. case PA_CONTEXT_READY:
  230. *pa_ready = 1;
  231. break;
  232. }
  233. }
  234. void pa_sinklist_cb(pa_context *c, const pa_sink_info *l, int eol, void *userdata)
  235. {
  236. list<PADevice> *dlist = (list<PADevice>*)userdata;
  237. if (eol != 0 || l->monitor_source == PA_INVALID_INDEX)
  238. return;
  239. dlist->push_back({
  240. .name = l->name,
  241. .desc = l->description,
  242. .index = l->index
  243. });
  244. }
  245. void pa_sourcelist_cb(pa_context *c, const pa_source_info *l, int eol, void *userdata)
  246. {
  247. list<PADevice> *dlist = (list<PADevice>*)userdata;
  248. if (eol != 0 || l->monitor_of_sink != PA_INVALID_INDEX)
  249. return;
  250. dlist->push_back({
  251. .name = l->name,
  252. .desc = l->description,
  253. .index = l->index
  254. });
  255. }
  256. void SettingsWindow::populatePulseAudioDevices()
  257. {
  258. cout << "populatePulseAudioDevices" << endl;
  259. mComboBoxAudioDevice.remove_all();
  260. int state = 0;
  261. int pa_ready = 0;
  262. auto input = new list<PADevice>();
  263. auto output = new list<PADevice>();
  264. pa_operation *pa_op;
  265. pa_mainloop *pa_ml = pa_mainloop_new();
  266. pa_mainloop_api *pa_mlapi = pa_mainloop_get_api(pa_ml);
  267. pa_context *pa_ctx = pa_context_new(pa_mlapi, "screenrecorder");
  268. pa_context_connect(pa_ctx, NULL, (pa_context_flags_t)0, NULL);
  269. pa_context_set_state_callback(pa_ctx, pa_state_cb, &pa_ready);
  270. for (;;)
  271. {
  272. if (pa_ready == 0)
  273. {
  274. pa_mainloop_iterate(pa_ml, 1, NULL);
  275. continue;
  276. }
  277. if (pa_ready == 2)
  278. {
  279. pa_context_disconnect(pa_ctx);
  280. pa_context_unref(pa_ctx);
  281. pa_mainloop_free(pa_ml);
  282. throw runtime_error("Failed to get pulse audio devices");
  283. }
  284. switch (state)
  285. {
  286. case 0:
  287. pa_op = pa_context_get_sink_info_list(pa_ctx, pa_sinklist_cb, output);
  288. state++;
  289. break;
  290. case 1:
  291. if (pa_operation_get_state(pa_op) == PA_OPERATION_DONE)
  292. pa_operation_unref(pa_op);
  293. pa_op = pa_context_get_source_info_list(pa_ctx, pa_sourcelist_cb, input);
  294. state++;
  295. break;
  296. case 2:
  297. if (pa_operation_get_state(pa_op) == PA_OPERATION_DONE)
  298. {
  299. pa_operation_unref(pa_op);
  300. pa_context_disconnect(pa_ctx);
  301. pa_context_unref(pa_ctx);
  302. pa_mainloop_free(pa_ml);
  303. for (auto device : *input)
  304. {
  305. mComboBoxAudioDevice.append(device.name, device.desc);
  306. if (settings->Get(SETTINGS_KEY_AUDIO_DEVICE_ID) == device.name)
  307. mComboBoxAudioDevice.set_active_id(device.name);
  308. }
  309. return;
  310. }
  311. break;
  312. default:
  313. return;
  314. }
  315. pa_mainloop_iterate(pa_ml, 1, NULL);
  316. }
  317. }