main.c 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005
  1. #include "main.h"
  2. #include <errno.h>
  3. #include <fcntl.h>
  4. #include <linux/videodev2.h>
  5. #include <linux/media.h>
  6. #include <linux/v4l2-subdev.h>
  7. #include <sys/ioctl.h>
  8. #include <sys/mman.h>
  9. #include <sys/stat.h>
  10. #include <time.h>
  11. #include <assert.h>
  12. #include <limits.h>
  13. #include <linux/kdev_t.h>
  14. #include <sys/sysmacros.h>
  15. #include <asm/errno.h>
  16. #include <wordexp.h>
  17. #include <gtk/gtk.h>
  18. #include <locale.h>
  19. #include <zbar.h>
  20. #include "gl_util.h"
  21. #include "camera_config.h"
  22. #include "io_pipeline.h"
  23. #include "process_pipeline.h"
  24. #define RENDERDOC
  25. #ifdef RENDERDOC
  26. #include <dlfcn.h>
  27. #include <renderdoc/app.h>
  28. RENDERDOC_API_1_1_2 *rdoc_api = NULL;
  29. #endif
  30. enum user_control { USER_CONTROL_ISO, USER_CONTROL_SHUTTER };
  31. static bool camera_is_initialized = false;
  32. static const struct mp_camera_config *camera = NULL;
  33. static MPCameraMode mode;
  34. static int preview_width = -1;
  35. static int preview_height = -1;
  36. static int device_rotation = 0;
  37. static bool gain_is_manual = false;
  38. static int gain;
  39. static int gain_max;
  40. static bool exposure_is_manual = false;
  41. static int exposure;
  42. static bool has_auto_focus_continuous;
  43. static bool has_auto_focus_start;
  44. static bool setting_save_dng;
  45. static MPProcessPipelineBuffer *current_preview_buffer = NULL;
  46. static int preview_buffer_width = -1;
  47. static int preview_buffer_height = -1;
  48. static char last_path[260] = "";
  49. static MPZBarScanResult *zbar_result = NULL;
  50. static int burst_length = 3;
  51. // Widgets
  52. GtkWidget *preview;
  53. GtkWidget *main_stack;
  54. GtkWidget *open_last_stack;
  55. GtkWidget *thumb_last;
  56. GtkWidget *process_spinner;
  57. GtkWidget *scanned_codes;
  58. GtkWidget *preview_top_box;
  59. GtkWidget *preview_bottom_box;
  60. GSettings *settings;
  61. int
  62. remap(int value, int input_min, int input_max, int output_min, int output_max)
  63. {
  64. const long long factor = 1000000000;
  65. long long output_spread = output_max - output_min;
  66. long long input_spread = input_max - input_min;
  67. long long zero_value = value - input_min;
  68. zero_value *= factor;
  69. long long percentage = zero_value / input_spread;
  70. long long zero_output = percentage * output_spread / factor;
  71. long long result = output_min + zero_output;
  72. return (int)result;
  73. }
  74. static void
  75. update_io_pipeline()
  76. {
  77. struct mp_io_pipeline_state io_state = {
  78. .camera = camera,
  79. .burst_length = burst_length,
  80. .preview_width = preview_width,
  81. .preview_height = preview_height,
  82. .device_rotation = device_rotation,
  83. .gain_is_manual = gain_is_manual,
  84. .gain = gain,
  85. .exposure_is_manual = exposure_is_manual,
  86. .exposure = exposure,
  87. .save_dng = setting_save_dng,
  88. };
  89. mp_io_pipeline_update_state(&io_state);
  90. }
  91. static bool
  92. update_state(const struct mp_main_state *state)
  93. {
  94. if (!camera_is_initialized) {
  95. camera_is_initialized = true;
  96. }
  97. if (camera == state->camera) {
  98. mode = state->mode;
  99. if (!gain_is_manual) {
  100. gain = state->gain;
  101. }
  102. gain_max = state->gain_max;
  103. if (!exposure_is_manual) {
  104. exposure = state->exposure;
  105. }
  106. has_auto_focus_continuous = state->has_auto_focus_continuous;
  107. has_auto_focus_start = state->has_auto_focus_start;
  108. }
  109. preview_buffer_width = state->image_width;
  110. preview_buffer_height = state->image_height;
  111. return false;
  112. }
  113. void
  114. mp_main_update_state(const struct mp_main_state *state)
  115. {
  116. struct mp_main_state *state_copy = malloc(sizeof(struct mp_main_state));
  117. *state_copy = *state;
  118. g_main_context_invoke_full(g_main_context_default(), G_PRIORITY_DEFAULT_IDLE,
  119. (GSourceFunc)update_state, state_copy, free);
  120. }
  121. static bool set_zbar_result(MPZBarScanResult *result)
  122. {
  123. if (zbar_result) {
  124. for (uint8_t i = 0; i < zbar_result->size; ++i) {
  125. free(zbar_result->codes[i].data);
  126. }
  127. free(zbar_result);
  128. }
  129. zbar_result = result;
  130. gtk_widget_queue_draw(preview);
  131. return false;
  132. }
  133. void mp_main_set_zbar_result(MPZBarScanResult *result)
  134. {
  135. g_main_context_invoke_full(g_main_context_default(), G_PRIORITY_DEFAULT_IDLE,
  136. (GSourceFunc)set_zbar_result, result, NULL);
  137. }
  138. static bool
  139. set_preview(MPProcessPipelineBuffer *buffer)
  140. {
  141. if (current_preview_buffer) {
  142. mp_process_pipeline_buffer_unref(current_preview_buffer);
  143. }
  144. current_preview_buffer = buffer;
  145. gtk_widget_queue_draw(preview);
  146. return false;
  147. }
  148. void
  149. mp_main_set_preview(MPProcessPipelineBuffer *buffer)
  150. {
  151. g_main_context_invoke_full(g_main_context_default(), G_PRIORITY_DEFAULT_IDLE,
  152. (GSourceFunc)set_preview, buffer, NULL);
  153. }
  154. struct capture_completed_args {
  155. GdkTexture *thumb;
  156. char *fname;
  157. };
  158. static bool
  159. capture_completed(struct capture_completed_args *args)
  160. {
  161. strncpy(last_path, args->fname, 259);
  162. gtk_image_set_from_paintable(GTK_IMAGE(thumb_last),
  163. GDK_PAINTABLE(args->thumb));
  164. gtk_spinner_stop(GTK_SPINNER(process_spinner));
  165. gtk_stack_set_visible_child(GTK_STACK(open_last_stack), thumb_last);
  166. g_object_unref(args->thumb);
  167. g_free(args->fname);
  168. return false;
  169. }
  170. void
  171. mp_main_capture_completed(GdkTexture *thumb, const char *fname)
  172. {
  173. struct capture_completed_args *args = malloc(sizeof(struct capture_completed_args));
  174. args->thumb = thumb;
  175. args->fname = g_strdup(fname);
  176. g_main_context_invoke_full(g_main_context_default(), G_PRIORITY_DEFAULT_IDLE,
  177. (GSourceFunc)capture_completed, args, free);
  178. }
  179. static GLuint blit_program;
  180. static GLuint blit_uniform_transform;
  181. static GLuint blit_uniform_texture;
  182. static GLuint solid_program;
  183. static GLuint solid_uniform_color;
  184. static GLuint quad;
  185. static void
  186. preview_realize(GtkGLArea *area)
  187. {
  188. gtk_gl_area_make_current(area);
  189. if (gtk_gl_area_get_error(area) != NULL) {
  190. return;
  191. }
  192. // Make a VAO for OpenGL
  193. if (!gtk_gl_area_get_use_es(area)) {
  194. GLuint vao;
  195. glGenVertexArrays(1, &vao);
  196. glBindVertexArray(vao);
  197. check_gl();
  198. }
  199. GLuint blit_shaders[] = {
  200. gl_util_load_shader("/org/postmarketos/Megapixels/blit.vert", GL_VERTEX_SHADER, NULL, 0),
  201. gl_util_load_shader("/org/postmarketos/Megapixels/blit.frag", GL_FRAGMENT_SHADER, NULL, 0),
  202. };
  203. blit_program = gl_util_link_program(blit_shaders, 2);
  204. glBindAttribLocation(blit_program, GL_UTIL_VERTEX_ATTRIBUTE, "vert");
  205. glBindAttribLocation(blit_program, GL_UTIL_TEX_COORD_ATTRIBUTE, "tex_coord");
  206. check_gl();
  207. blit_uniform_transform = glGetUniformLocation(blit_program, "transform");
  208. blit_uniform_texture = glGetUniformLocation(blit_program, "texture");
  209. GLuint solid_shaders[] = {
  210. gl_util_load_shader("/org/postmarketos/Megapixels/solid.vert", GL_VERTEX_SHADER, NULL, 0),
  211. gl_util_load_shader("/org/postmarketos/Megapixels/solid.frag", GL_FRAGMENT_SHADER, NULL, 0),
  212. };
  213. solid_program = gl_util_link_program(solid_shaders, 2);
  214. glBindAttribLocation(solid_program, GL_UTIL_VERTEX_ATTRIBUTE, "vert");
  215. check_gl();
  216. solid_uniform_color = glGetUniformLocation(solid_program, "color");
  217. quad = gl_util_new_quad();
  218. }
  219. static void
  220. position_preview(float *offset_x, float *offset_y, float *size_x, float *size_y)
  221. {
  222. int buffer_width, buffer_height;
  223. if (device_rotation == 0 || device_rotation == 180) {
  224. buffer_width = preview_buffer_width;
  225. buffer_height = preview_buffer_height;
  226. } else {
  227. buffer_width = preview_buffer_height;
  228. buffer_height = preview_buffer_width;
  229. }
  230. int scale_factor = gtk_widget_get_scale_factor(preview);
  231. int top_height = gtk_widget_get_allocated_height(preview_top_box) * scale_factor;
  232. int bottom_height = gtk_widget_get_allocated_height(preview_bottom_box) * scale_factor;
  233. int inner_height = preview_height - top_height - bottom_height;
  234. double scale = MIN(preview_width / (float) buffer_width, preview_height / (float) buffer_height);
  235. *size_x = scale * buffer_width;
  236. *size_y = scale * buffer_height;
  237. *offset_x = (preview_width - *size_x) / 2.0;
  238. if (*size_y > inner_height) {
  239. *offset_y = (preview_height - *size_y) / 2.0;
  240. } else {
  241. *offset_y = top_height + (inner_height - *size_y) / 2.0;
  242. }
  243. }
  244. static gboolean
  245. preview_draw(GtkGLArea *area, GdkGLContext *ctx, gpointer data)
  246. {
  247. if (gtk_gl_area_get_error(area) != NULL) {
  248. return FALSE;
  249. }
  250. if (!camera_is_initialized) {
  251. return FALSE;
  252. }
  253. #ifdef RENDERDOC
  254. if (rdoc_api) rdoc_api->StartFrameCapture(NULL, NULL);
  255. #endif
  256. glClearColor(0, 0, 0, 1);
  257. glClear(GL_COLOR_BUFFER_BIT);
  258. float offset_x, offset_y, size_x, size_y;
  259. position_preview(&offset_x, &offset_y, &size_x, &size_y);
  260. glViewport(offset_x,
  261. preview_height - size_y - offset_y,
  262. size_x,
  263. size_y);
  264. if (current_preview_buffer) {
  265. glUseProgram(blit_program);
  266. GLfloat rotation_list[4] = { 0, -1, 0, 1 };
  267. int rotation_index = device_rotation / 90;
  268. GLfloat sin_rot = rotation_list[rotation_index];
  269. GLfloat cos_rot = rotation_list[(4 + rotation_index - 1) % 4];
  270. GLfloat matrix[9] = {
  271. cos_rot, sin_rot, 0,
  272. -sin_rot, cos_rot, 0,
  273. 0, 0, 1,
  274. };
  275. glUniformMatrix3fv(blit_uniform_transform, 1, GL_FALSE, matrix);
  276. check_gl();
  277. glActiveTexture(GL_TEXTURE0);
  278. glBindTexture(GL_TEXTURE_2D, mp_process_pipeline_buffer_get_texture_id(current_preview_buffer));
  279. glUniform1i(blit_uniform_texture, 0);
  280. check_gl();
  281. gl_util_bind_quad(quad);
  282. gl_util_draw_quad(quad);
  283. }
  284. if (zbar_result) {
  285. GLuint buffer;
  286. if (!gtk_gl_area_get_use_es(area)) {
  287. glGenBuffers(1, &buffer);
  288. glBindBuffer(GL_ARRAY_BUFFER, buffer);
  289. check_gl();
  290. }
  291. glUseProgram(solid_program);
  292. glEnable(GL_BLEND);
  293. glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  294. glUniform4f(solid_uniform_color, 1, 0, 0, 0.5);
  295. for (uint8_t i = 0; i < zbar_result->size; ++i) {
  296. MPZBarCode *code = &zbar_result->codes[i];
  297. GLfloat vertices[] = {
  298. code->bounds_x[0], code->bounds_y[0],
  299. code->bounds_x[1], code->bounds_y[1],
  300. code->bounds_x[3], code->bounds_y[3],
  301. code->bounds_x[2], code->bounds_y[2],
  302. };
  303. for (int i = 0; i < 4; ++i) {
  304. vertices[i * 2] = 2 * vertices[i * 2] / preview_buffer_width - 1.0;
  305. vertices[i * 2 + 1] = 1.0 - 2 * vertices[i * 2 + 1] / preview_buffer_height;
  306. }
  307. if (gtk_gl_area_get_use_es(area)) {
  308. glVertexAttribPointer(GL_UTIL_VERTEX_ATTRIBUTE, 2, GL_FLOAT, 0, 0, vertices);
  309. check_gl();
  310. glEnableVertexAttribArray(GL_UTIL_VERTEX_ATTRIBUTE);
  311. check_gl();
  312. } else {
  313. glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STREAM_DRAW);
  314. check_gl();
  315. glVertexAttribPointer(GL_UTIL_VERTEX_ATTRIBUTE, 2, GL_FLOAT, GL_FALSE, 0, 0);
  316. glEnableVertexAttribArray(GL_UTIL_VERTEX_ATTRIBUTE);
  317. check_gl();
  318. }
  319. glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
  320. check_gl();
  321. }
  322. glDisable(GL_BLEND);
  323. glBindBuffer(GL_ARRAY_BUFFER, 0);
  324. }
  325. glFlush();
  326. #ifdef RENDERDOC
  327. if(rdoc_api) rdoc_api->EndFrameCapture(NULL, NULL);
  328. #endif
  329. return FALSE;
  330. }
  331. static gboolean
  332. preview_resize(GtkWidget *widget, int width, int height, gpointer data)
  333. {
  334. if (preview_width != width || preview_height != height) {
  335. preview_width = width;
  336. preview_height = height;
  337. update_io_pipeline();
  338. }
  339. return TRUE;
  340. }
  341. void
  342. run_open_last_action(GSimpleAction *action, GVariant *param, gpointer user_data)
  343. {
  344. char uri[275];
  345. GError *error = NULL;
  346. if (strlen(last_path) == 0) {
  347. return;
  348. }
  349. sprintf(uri, "file://%s", last_path);
  350. if (!g_app_info_launch_default_for_uri(uri, NULL, &error)) {
  351. g_printerr("Could not launch image viewer for '%s': %s\n", uri, error->message);
  352. }
  353. }
  354. void
  355. run_open_photos_action(GSimpleAction *action, GVariant *param, gpointer user_data)
  356. {
  357. char uri[270];
  358. GError *error = NULL;
  359. sprintf(uri, "file://%s", g_get_user_special_dir(G_USER_DIRECTORY_PICTURES));
  360. if (!g_app_info_launch_default_for_uri(uri, NULL, &error)) {
  361. g_printerr("Could not launch image viewer: %s\n", error->message);
  362. }
  363. }
  364. void
  365. run_capture_action(GSimpleAction *action, GVariant *param, gpointer user_data)
  366. {
  367. gtk_spinner_start(GTK_SPINNER(process_spinner));
  368. gtk_stack_set_visible_child(GTK_STACK(open_last_stack), process_spinner);
  369. mp_io_pipeline_capture();
  370. }
  371. void
  372. run_about_action(GSimpleAction *action, GVariant *param, GApplication *app)
  373. {
  374. gtk_show_about_dialog(NULL, "program-name", "Megapixels",
  375. "title", "Megapixels",
  376. "logo-icon-name", "org.postmarketos.Megapixels",
  377. "comments", "The postmarketOS camera application",
  378. "website", "https://sr.ht/~martijnbraam/megapixels",
  379. "version", VERSION,
  380. "license-type", GTK_LICENSE_GPL_3_0_ONLY,
  381. NULL);
  382. }
  383. void
  384. run_quit_action(GSimpleAction *action, GVariant *param, GApplication *app)
  385. {
  386. g_application_quit(app);
  387. }
  388. static bool
  389. check_point_inside_bounds(int x, int y, int *bounds_x, int *bounds_y)
  390. {
  391. bool right = false, left = false, top = false, bottom = false;
  392. for (int i = 0; i < 4; ++i) {
  393. if (x <= bounds_x[i])
  394. left = true;
  395. if (x >= bounds_x[i])
  396. right = true;
  397. if (y <= bounds_y[i])
  398. top = true;
  399. if (y >= bounds_y[i])
  400. bottom = true;
  401. }
  402. return right && left && top && bottom;
  403. }
  404. static void
  405. on_zbar_dialog_response(GtkDialog *dialog, int response, char *data)
  406. {
  407. GError *error = NULL;
  408. switch (response) {
  409. case GTK_RESPONSE_YES:
  410. if (!g_app_info_launch_default_for_uri(data,
  411. NULL, &error)) {
  412. g_printerr("Could not launch application: %s\n",
  413. error->message);
  414. }
  415. case GTK_RESPONSE_ACCEPT:
  416. {
  417. GdkDisplay *display = gtk_widget_get_display(GTK_WIDGET(dialog));
  418. gdk_clipboard_set_text(
  419. gdk_display_get_clipboard(display),
  420. data);
  421. }
  422. case GTK_RESPONSE_CANCEL:
  423. break;
  424. default:
  425. g_printerr("Wrong dialog response: %d\n", response);
  426. }
  427. g_free(data);
  428. gtk_window_destroy(GTK_WINDOW(dialog));
  429. }
  430. static void
  431. on_zbar_code_tapped(GtkWidget *widget, const MPZBarCode *code)
  432. {
  433. GtkWidget *dialog;
  434. GtkDialogFlags flags = GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT;
  435. bool data_is_url = g_uri_is_valid(
  436. code->data, G_URI_FLAGS_PARSE_RELAXED, NULL);
  437. char* data = strdup(code->data);
  438. if (data_is_url) {
  439. dialog = gtk_message_dialog_new(
  440. GTK_WINDOW(gtk_widget_get_root(widget)),
  441. flags,
  442. GTK_MESSAGE_QUESTION,
  443. GTK_BUTTONS_NONE,
  444. "Found a URL '%s' encoded in a %s.",
  445. code->data,
  446. code->type);
  447. gtk_dialog_add_buttons(
  448. GTK_DIALOG(dialog),
  449. "_Open URL",
  450. GTK_RESPONSE_YES,
  451. NULL);
  452. } else {
  453. dialog = gtk_message_dialog_new(
  454. GTK_WINDOW(gtk_widget_get_root(widget)),
  455. flags,
  456. GTK_MESSAGE_QUESTION,
  457. GTK_BUTTONS_NONE,
  458. "Found data encoded in a %s.",
  459. code->type);
  460. gtk_message_dialog_format_secondary_markup (
  461. GTK_MESSAGE_DIALOG(dialog),
  462. "<small>%s</small>",
  463. code->data
  464. );
  465. }
  466. gtk_dialog_add_buttons(
  467. GTK_DIALOG(dialog),
  468. "_Copy",
  469. GTK_RESPONSE_ACCEPT,
  470. "_Cancel",
  471. GTK_RESPONSE_CANCEL,
  472. NULL);
  473. g_signal_connect(dialog, "response", G_CALLBACK(on_zbar_dialog_response), data);
  474. gtk_widget_show(GTK_WIDGET(dialog));
  475. }
  476. static void
  477. preview_pressed(GtkGestureClick *gesture, int n_press, double x, double y)
  478. {
  479. GtkWidget *widget = gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(gesture));
  480. int scale_factor = gtk_widget_get_scale_factor(widget);
  481. // Tapped zbar result
  482. if (zbar_result) {
  483. // Transform the event coordinates to the image
  484. float offset_x, offset_y, size_x, size_y;
  485. position_preview(&offset_x, &offset_y, &size_x, &size_y);
  486. int zbar_x = (x - offset_x) * scale_factor / size_x * preview_buffer_width;
  487. int zbar_y = (y - offset_y) * scale_factor / size_y * preview_buffer_height;
  488. for (uint8_t i = 0; i < zbar_result->size; ++i) {
  489. MPZBarCode *code = &zbar_result->codes[i];
  490. if (check_point_inside_bounds(zbar_x, zbar_y, code->bounds_x, code->bounds_y)) {
  491. on_zbar_code_tapped(widget, code);
  492. return;
  493. }
  494. }
  495. }
  496. // Tapped preview image itself, try focussing
  497. if (has_auto_focus_start) {
  498. mp_io_pipeline_focus();
  499. }
  500. }
  501. static void
  502. run_camera_switch_action(GSimpleAction *action, GVariant *param, gpointer user_data)
  503. {
  504. size_t next_index = camera->index + 1;
  505. const struct mp_camera_config *next_camera =
  506. mp_get_camera_config(next_index);
  507. if (!next_camera) {
  508. next_index = 0;
  509. next_camera = mp_get_camera_config(next_index);
  510. }
  511. camera = next_camera;
  512. update_io_pipeline();
  513. }
  514. static void
  515. run_open_settings_action(GSimpleAction *action, GVariant *param, gpointer user_data)
  516. {
  517. gtk_stack_set_visible_child_name(GTK_STACK(main_stack), "settings");
  518. }
  519. static void
  520. run_close_settings_action(GSimpleAction *action, GVariant *param, gpointer user_data)
  521. {
  522. gtk_stack_set_visible_child_name(GTK_STACK(main_stack), "main");
  523. // Update settings
  524. bool save_dng = g_settings_get_boolean(settings, "save-raw");
  525. if (save_dng != setting_save_dng)
  526. {
  527. setting_save_dng = save_dng;
  528. update_io_pipeline();
  529. }
  530. }
  531. static void
  532. on_controls_scale_changed(GtkAdjustment *adjustment, void (*set_fn)(double))
  533. {
  534. set_fn(gtk_adjustment_get_value(adjustment));
  535. }
  536. static void
  537. update_value(GtkAdjustment *adjustment, GtkLabel *label)
  538. {
  539. char buf[12];
  540. snprintf(buf, 12, "%.0f", gtk_adjustment_get_value(adjustment));
  541. gtk_label_set_label(label, buf);
  542. }
  543. static void
  544. on_auto_controls_toggled(GtkToggleButton *button, void (*set_auto_fn)(bool))
  545. {
  546. set_auto_fn(gtk_toggle_button_get_active(button));
  547. }
  548. static void
  549. update_scale(GtkToggleButton *button, GtkScale *scale)
  550. {
  551. gtk_widget_set_sensitive(GTK_WIDGET(scale), !gtk_toggle_button_get_active(button));
  552. }
  553. static void
  554. open_controls(GtkWidget *parent, const char *title_name,
  555. double min_value, double max_value, double current,
  556. bool auto_enabled,
  557. void (*set_fn)(double),
  558. void (*set_auto_fn)(bool))
  559. {
  560. GtkBuilder *builder = gtk_builder_new_from_resource(
  561. "/org/postmarketos/Megapixels/controls-popover.ui");
  562. GtkPopover *popover = GTK_POPOVER(gtk_builder_get_object(builder, "controls"));
  563. GtkScale *scale = GTK_SCALE(gtk_builder_get_object(builder, "scale"));
  564. GtkLabel *title = GTK_LABEL(gtk_builder_get_object(builder, "title"));
  565. GtkLabel *value_label = GTK_LABEL(gtk_builder_get_object(builder, "value-label"));
  566. GtkToggleButton *auto_button = GTK_TOGGLE_BUTTON(gtk_builder_get_object(builder, "auto-button"));
  567. gtk_label_set_label(title, title_name);
  568. GtkAdjustment *adjustment = gtk_range_get_adjustment(GTK_RANGE(scale));
  569. gtk_adjustment_set_lower(adjustment, min_value);
  570. gtk_adjustment_set_upper(adjustment, max_value);
  571. gtk_adjustment_set_value(adjustment, current);
  572. update_value(adjustment, value_label);
  573. gtk_toggle_button_set_active(auto_button, auto_enabled);
  574. update_scale(auto_button, scale);
  575. g_signal_connect(adjustment, "value-changed", G_CALLBACK(on_controls_scale_changed), set_fn);
  576. g_signal_connect(adjustment, "value-changed", G_CALLBACK(update_value), value_label);
  577. g_signal_connect(auto_button, "toggled", G_CALLBACK(on_auto_controls_toggled), set_auto_fn);
  578. g_signal_connect(auto_button, "toggled", G_CALLBACK(update_scale), scale);
  579. gtk_widget_set_parent(GTK_WIDGET(popover), parent);
  580. gtk_popover_popup(popover);
  581. // g_object_unref(popover);
  582. }
  583. static void
  584. set_gain(double value)
  585. {
  586. if (gain != (int)value) {
  587. gain = value;
  588. update_io_pipeline();
  589. }
  590. }
  591. static void
  592. set_gain_auto(bool is_auto)
  593. {
  594. if (gain_is_manual != !is_auto) {
  595. gain_is_manual = !is_auto;
  596. update_io_pipeline();
  597. }
  598. }
  599. static void
  600. open_iso_controls(GtkWidget *button, gpointer user_data)
  601. {
  602. open_controls(button, "ISO", 0, gain_max, gain, !gain_is_manual, set_gain, set_gain_auto);
  603. }
  604. static void
  605. set_shutter(double value)
  606. {
  607. int new_exposure =
  608. (int)(value / 360.0 * camera->capture_mode.height);
  609. if (new_exposure != exposure) {
  610. exposure = new_exposure;
  611. update_io_pipeline();
  612. }
  613. }
  614. static void
  615. set_shutter_auto(bool is_auto)
  616. {
  617. if (exposure_is_manual != !is_auto) {
  618. exposure_is_manual = !is_auto;
  619. update_io_pipeline();
  620. }
  621. }
  622. static void
  623. open_shutter_controls(GtkWidget *button, gpointer user_data)
  624. {
  625. open_controls(button, "Shutter", 1.0, 360.0, exposure, !exposure_is_manual, set_shutter, set_shutter_auto);
  626. }
  627. static void
  628. on_realize(GtkWidget *window, gpointer *data)
  629. {
  630. GtkNative *native = gtk_widget_get_native(window);
  631. mp_process_pipeline_init_gl(gtk_native_get_surface(native));
  632. camera = mp_get_camera_config(0);
  633. update_io_pipeline();
  634. }
  635. static GSimpleAction *
  636. create_simple_action(GtkApplication *app, const char *name, GCallback callback)
  637. {
  638. GSimpleAction *action = g_simple_action_new(name, NULL);
  639. g_signal_connect(action, "activate", callback, app);
  640. g_action_map_add_action(G_ACTION_MAP(app), G_ACTION(action));
  641. return action;
  642. }
  643. static void update_ui_rotation()
  644. {
  645. if (device_rotation == 0 || device_rotation == 180) {
  646. // Portrait
  647. gtk_widget_set_halign(preview_top_box, GTK_ALIGN_FILL);
  648. gtk_orientable_set_orientation(GTK_ORIENTABLE(preview_top_box), GTK_ORIENTATION_VERTICAL);
  649. gtk_widget_set_halign(preview_bottom_box, GTK_ALIGN_FILL);
  650. gtk_orientable_set_orientation(GTK_ORIENTABLE(preview_bottom_box), GTK_ORIENTATION_HORIZONTAL);
  651. if (device_rotation == 0) {
  652. gtk_widget_set_valign(preview_top_box, GTK_ALIGN_START);
  653. gtk_widget_set_valign(preview_bottom_box, GTK_ALIGN_END);
  654. } else {
  655. gtk_widget_set_valign(preview_top_box, GTK_ALIGN_END);
  656. gtk_widget_set_valign(preview_bottom_box, GTK_ALIGN_START);
  657. }
  658. } else {
  659. // Landscape
  660. gtk_widget_set_valign(preview_top_box, GTK_ALIGN_FILL);
  661. gtk_orientable_set_orientation(GTK_ORIENTABLE(preview_top_box), GTK_ORIENTATION_HORIZONTAL);
  662. gtk_widget_set_valign(preview_bottom_box, GTK_ALIGN_FILL);
  663. gtk_orientable_set_orientation(GTK_ORIENTABLE(preview_bottom_box), GTK_ORIENTATION_VERTICAL);
  664. if (device_rotation == 90) {
  665. gtk_widget_set_halign(preview_top_box, GTK_ALIGN_END);
  666. gtk_widget_set_halign(preview_bottom_box, GTK_ALIGN_START);
  667. } else {
  668. gtk_widget_set_halign(preview_top_box, GTK_ALIGN_START);
  669. gtk_widget_set_halign(preview_bottom_box, GTK_ALIGN_END);
  670. }
  671. }
  672. }
  673. static void display_config_received(GDBusConnection *conn, GAsyncResult *res, gpointer user_data)
  674. {
  675. GError *error = NULL;
  676. GVariant *result = g_dbus_connection_call_finish(conn, res, &error);
  677. if (!result) {
  678. printf("Failed to get display configuration: %s\n", error->message);
  679. g_object_unref(error);
  680. return;
  681. }
  682. GVariant *configs = g_variant_get_child_value(result, 1);
  683. if (g_variant_n_children(configs) == 0) {
  684. return;
  685. }
  686. GVariant *config = g_variant_get_child_value(configs, 0);
  687. GVariant *rot_config = g_variant_get_child_value(config, 7);
  688. uint32_t rotation_index = g_variant_get_uint32(rot_config);
  689. assert(rotation_index < 4);
  690. int new_rotation = rotation_index * 90;
  691. if (new_rotation != device_rotation) {
  692. device_rotation = new_rotation;
  693. update_io_pipeline();
  694. update_ui_rotation();
  695. }
  696. g_variant_unref(result);
  697. }
  698. static void update_screen_rotation(GDBusConnection *conn)
  699. {
  700. g_dbus_connection_call(conn,
  701. "org.gnome.Mutter.DisplayConfig",
  702. "/org/gnome/Mutter/DisplayConfig",
  703. "org.gnome.Mutter.DisplayConfig",
  704. "GetResources",
  705. NULL,
  706. NULL,
  707. G_DBUS_CALL_FLAGS_NO_AUTO_START,
  708. -1,
  709. NULL,
  710. (GAsyncReadyCallback)display_config_received,
  711. NULL);
  712. }
  713. static void on_screen_rotate(
  714. GDBusConnection *conn,
  715. const gchar *sender_name,
  716. const gchar *object_path,
  717. const gchar *interface_name,
  718. const gchar *signal_name,
  719. GVariant *parameters,
  720. gpointer user_data)
  721. {
  722. update_screen_rotation(conn);
  723. }
  724. static void
  725. activate(GtkApplication *app, gpointer data)
  726. {
  727. g_object_set(gtk_settings_get_default(), "gtk-application-prefer-dark-theme",
  728. TRUE, NULL);
  729. GdkDisplay *display = gdk_display_get_default();
  730. GtkIconTheme *icon_theme = gtk_icon_theme_get_for_display(display);
  731. gtk_icon_theme_add_resource_path(icon_theme, "/org/postmarketos/Megapixels");
  732. GtkCssProvider *provider = gtk_css_provider_new();
  733. gtk_css_provider_load_from_resource(
  734. provider, "/org/postmarketos/Megapixels/camera.css");
  735. gtk_style_context_add_provider_for_display(
  736. display, GTK_STYLE_PROVIDER(provider),
  737. GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
  738. GtkBuilder *builder = gtk_builder_new_from_resource(
  739. "/org/postmarketos/Megapixels/camera.ui");
  740. GtkWidget *window = GTK_WIDGET(gtk_builder_get_object(builder, "window"));
  741. GtkWidget *iso_button = GTK_WIDGET(gtk_builder_get_object(builder, "iso-controls-button"));
  742. GtkWidget *shutter_button = GTK_WIDGET(gtk_builder_get_object(builder, "shutter-controls-button"));
  743. GtkWidget *setting_dng_button = GTK_WIDGET(gtk_builder_get_object(builder, "setting-raw"));
  744. preview = GTK_WIDGET(gtk_builder_get_object(builder, "preview"));
  745. main_stack = GTK_WIDGET(gtk_builder_get_object(builder, "main_stack"));
  746. open_last_stack = GTK_WIDGET(gtk_builder_get_object(builder, "open_last_stack"));
  747. thumb_last = GTK_WIDGET(gtk_builder_get_object(builder, "thumb_last"));
  748. process_spinner = GTK_WIDGET(gtk_builder_get_object(builder, "process_spinner"));
  749. scanned_codes = GTK_WIDGET(gtk_builder_get_object(builder, "scanned-codes"));
  750. preview_top_box = GTK_WIDGET(gtk_builder_get_object(builder, "top-box"));
  751. preview_bottom_box = GTK_WIDGET(gtk_builder_get_object(builder, "bottom-box"));
  752. g_signal_connect(window, "realize", G_CALLBACK(on_realize), NULL);
  753. g_signal_connect(preview, "realize", G_CALLBACK(preview_realize), NULL);
  754. g_signal_connect(preview, "render", G_CALLBACK(preview_draw), NULL);
  755. g_signal_connect(preview, "resize", G_CALLBACK(preview_resize), NULL);
  756. GtkGesture *click = gtk_gesture_click_new();
  757. g_signal_connect(click, "pressed", G_CALLBACK(preview_pressed), NULL);
  758. gtk_widget_add_controller(preview, GTK_EVENT_CONTROLLER(click));
  759. g_signal_connect(iso_button, "clicked", G_CALLBACK(open_iso_controls), NULL);
  760. g_signal_connect(shutter_button, "clicked", G_CALLBACK(open_shutter_controls), NULL);
  761. // Setup actions
  762. create_simple_action(app, "capture", G_CALLBACK(run_capture_action));
  763. create_simple_action(app, "switch-camera", G_CALLBACK(run_camera_switch_action));
  764. create_simple_action(app, "open-settings", G_CALLBACK(run_open_settings_action));
  765. create_simple_action(app, "close-settings", G_CALLBACK(run_close_settings_action));
  766. create_simple_action(app, "open-last", G_CALLBACK(run_open_last_action));
  767. create_simple_action(app, "open-photos", G_CALLBACK(run_open_photos_action));
  768. create_simple_action(app, "about", G_CALLBACK(run_about_action));
  769. create_simple_action(app, "quit", G_CALLBACK(run_quit_action));
  770. // Setup shortcuts
  771. const char *capture_accels[] = { "space", NULL };
  772. gtk_application_set_accels_for_action(app, "app.capture", capture_accels);
  773. const char *quit_accels[] = { "<Ctrl>q", "<Ctrl>w", NULL };
  774. gtk_application_set_accels_for_action(app, "app.quit", quit_accels);
  775. // Setup settings
  776. settings = g_settings_new("org.postmarketos.Megapixels");
  777. g_settings_bind (settings, "save-raw", setting_dng_button, "active", G_SETTINGS_BIND_DEFAULT);
  778. setting_save_dng = g_settings_get_boolean(settings, "save-raw");
  779. // Listen for phosh rotation
  780. GDBusConnection *conn = g_application_get_dbus_connection(G_APPLICATION(app));
  781. g_dbus_connection_signal_subscribe(
  782. conn,
  783. NULL,
  784. "org.gnome.Mutter.DisplayConfig",
  785. "MonitorsChanged",
  786. "/org/gnome/Mutter/DisplayConfig",
  787. NULL,
  788. G_DBUS_SIGNAL_FLAGS_NONE,
  789. &on_screen_rotate,
  790. NULL,
  791. NULL);
  792. update_screen_rotation(conn);
  793. mp_io_pipeline_start();
  794. gtk_application_add_window(app, GTK_WINDOW(window));
  795. gtk_widget_show(window);
  796. }
  797. static void
  798. shutdown(GApplication *app, gpointer data)
  799. {
  800. // Only do cleanup in development, let the OS clean up otherwise
  801. #ifdef DEBUG
  802. mp_io_pipeline_stop();
  803. #endif
  804. }
  805. int
  806. main(int argc, char *argv[])
  807. {
  808. #ifdef RENDERDOC
  809. {
  810. void *mod = dlopen("librenderdoc.so", RTLD_NOW | RTLD_NOLOAD);
  811. if (mod)
  812. {
  813. pRENDERDOC_GetAPI RENDERDOC_GetAPI = (pRENDERDOC_GetAPI)dlsym(mod, "RENDERDOC_GetAPI");
  814. int ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_1_2, (void **)&rdoc_api);
  815. assert(ret == 1);
  816. }
  817. else
  818. {
  819. printf("Renderdoc not found\n");
  820. }
  821. }
  822. #endif
  823. if (!mp_load_config())
  824. return 1;
  825. setenv("LC_NUMERIC", "C", 1);
  826. GtkApplication *app = gtk_application_new("org.postmarketos.Megapixels", 0);
  827. g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
  828. g_signal_connect(app, "shutdown", G_CALLBACK(shutdown), NULL);
  829. g_application_run(G_APPLICATION(app), argc, argv);
  830. return 0;
  831. }