1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411 |
- #include "main.h"
- #include "flash.h"
- #include "gl_util.h"
- #include "io_pipeline.h"
- #include "process_pipeline.h"
- #include "state.h"
- #include <asm/errno.h>
- #include <assert.h>
- #include <errno.h>
- #include <fcntl.h>
- #include <gtk/gtk.h>
- #define LIBFEEDBACK_USE_UNSTABLE_API
- #include <libfeedback.h>
- #ifdef GDK_WINDOWING_WAYLAND
- #include <gdk/wayland/gdkwayland.h>
- #include <wayland-client.h>
- #endif
- #ifdef GDK_WINDOWING_X11
- #include <X11/Xlib.h>
- #include <X11/extensions/Xrandr.h>
- #include <gdk/x11/gdkx.h>
- #endif
- #include <libmegapixels.h>
- #include <limits.h>
- #include <linux/kdev_t.h>
- #include <linux/media.h>
- #include <linux/v4l2-subdev.h>
- #include <linux/videodev2.h>
- #include <locale.h>
- #include <sys/ioctl.h>
- #include <sys/mman.h>
- #include <sys/stat.h>
- #include <sys/sysmacros.h>
- #include <time.h>
- #include <wordexp.h>
- #include <zbar.h>
- // #define RENDERDOC
- #ifdef RENDERDOC
- #include <dlfcn.h>
- #include <renderdoc/app.h>
- RENDERDOC_API_1_1_2 *rdoc_api = NULL;
- #endif
- mp_state_main state;
- static MPProcessPipelineBuffer *current_preview_buffer = NULL;
- static char last_path[260] = "";
- static MPZBarScanResult *zbar_result = NULL;
- // Widgets
- GtkWidget *window;
- GtkWidget *preview;
- GtkWidget *main_stack;
- GtkWidget *open_last_stack;
- GtkWidget *thumb_last;
- GtkWidget *process_spinner;
- GtkWidget *scanned_codes;
- GtkWidget *preview_top_box;
- GtkWidget *preview_bottom_box;
- GtkWidget *message_box;
- GtkWidget *message_label;
- GtkWidget *flash_button;
- GtkWidget *iso_button;
- GtkWidget *shutter_button;
- LfbEvent *capture_event;
- GSettings *settings;
- GSettings *fb_settings;
- int
- remap(int value, int input_min, int input_max, int output_min, int output_max)
- {
- const long long factor = 1000000000;
- long long output_spread = output_max - output_min;
- long long input_spread = input_max - input_min;
- long long zero_value = value - input_min;
- zero_value *= factor;
- long long percentage = zero_value / input_spread;
- long long zero_output = percentage * output_spread / factor;
- long long result = output_min + zero_output;
- return (int)result;
- }
- static void
- display_error(const char *message)
- {
- gtk_label_set_label(GTK_LABEL(message_label), message);
- gtk_widget_set_visible(message_box, true);
- }
- bool
- check_window_active()
- {
- return gtk_window_is_active(GTK_WINDOW(window));
- }
- static void
- update_io_pipeline()
- {
- mp_state_io new_state = {
- .camera = state.camera,
- .configuration = state.configuration,
- .burst_length = state.burst_length,
- .preview_width = state.preview_width,
- .preview_height = state.preview_height,
- .device_rotation = state.device_rotation,
- .gain.control = state.gain.control,
- .gain.auto_control = state.gain.auto_control,
- .gain.value = state.gain.value,
- .gain.value_req = state.gain.value_req,
- .gain.max = state.gain.max,
- .gain.manual = state.gain.manual,
- .gain.manual_req = state.gain.manual_req,
- .exposure.control = state.exposure.control,
- .exposure.auto_control = state.exposure.auto_control,
- .exposure.value = state.exposure.value,
- .exposure.value_req = state.exposure.value_req,
- .exposure.max = state.exposure.max,
- .exposure.manual = state.exposure.manual,
- .exposure.manual_req = state.exposure.manual_req,
- .focus.control = state.focus.control,
- .focus.auto_control = state.focus.auto_control,
- .focus.value = state.focus.value,
- .focus.value_req = state.focus.value_req,
- .focus.max = state.focus.max,
- .focus.manual = state.focus.manual,
- .focus.manual_req = state.focus.manual_req,
- .stats.exposure = state.stats.exposure,
- .stats.temp = state.stats.temp,
- .stats.tint = state.stats.tint,
- .stats.focus = state.stats.focus,
- };
- mp_io_pipeline_update_state(&new_state);
- }
- /*
- * State transfer from Process -> Main
- */
- static bool
- update_state(const mp_state_main *new_state)
- {
- state.gain.control = new_state->gain.control;
- state.gain.auto_control = new_state->gain.auto_control;
- state.gain.value = new_state->gain.value;
- state.gain.max = new_state->gain.max;
- state.gain.manual = new_state->gain.manual;
- state.exposure.control = new_state->exposure.control;
- state.exposure.auto_control = new_state->exposure.auto_control;
- state.exposure.value = new_state->exposure.value;
- state.exposure.max = new_state->exposure.max;
- state.exposure.manual = new_state->exposure.manual;
- state.focus.control = new_state->focus.control;
- state.focus.auto_control = new_state->focus.auto_control;
- state.focus.value = new_state->focus.value;
- state.focus.max = new_state->focus.max;
- state.focus.manual = new_state->focus.manual;
- state.has_auto_focus_continuous = new_state->has_auto_focus_continuous;
- state.has_auto_focus_start = new_state->has_auto_focus_start;
- state.preview_buffer_width = new_state->preview_buffer_width;
- state.preview_buffer_height = new_state->preview_buffer_height;
- state.stats.exposure = new_state->stats.exposure;
- state.stats.temp = new_state->stats.temp;
- state.stats.tint = new_state->stats.tint;
- state.stats.focus = new_state->stats.focus;
- // Make the right settings available for the camera
- gtk_widget_set_visible(flash_button, state.control_flash);
- gtk_widget_set_visible(iso_button, state.gain.control != 0);
- gtk_widget_set_visible(shutter_button, state.exposure.control != 0);
- update_io_pipeline();
- return false;
- }
- void
- mp_main_update_state(const mp_state_main *new_state)
- {
- mp_state_main *state_copy = malloc(sizeof(mp_state_main));
- *state_copy = *new_state;
- g_main_context_invoke_full(g_main_context_default(),
- G_PRIORITY_DEFAULT_IDLE,
- (GSourceFunc)update_state,
- state_copy,
- free);
- }
- static bool
- set_zbar_result(MPZBarScanResult *result)
- {
- if (zbar_result) {
- for (uint8_t i = 0; i < zbar_result->size; ++i) {
- free(zbar_result->codes[i].data);
- }
- free(zbar_result);
- }
- zbar_result = result;
- gtk_widget_queue_draw(preview);
- return false;
- }
- void
- mp_main_set_zbar_result(MPZBarScanResult *result)
- {
- g_main_context_invoke_full(g_main_context_default(),
- G_PRIORITY_DEFAULT_IDLE,
- (GSourceFunc)set_zbar_result,
- result,
- NULL);
- }
- static bool
- set_preview(MPProcessPipelineBuffer *buffer)
- {
- if (current_preview_buffer) {
- mp_process_pipeline_buffer_unref(current_preview_buffer);
- }
- current_preview_buffer = buffer;
- gtk_widget_queue_draw(preview);
- return false;
- }
- void
- mp_main_set_preview(MPProcessPipelineBuffer *buffer)
- {
- g_main_context_invoke_full(g_main_context_default(),
- G_PRIORITY_DEFAULT_IDLE,
- (GSourceFunc)set_preview,
- buffer,
- NULL);
- }
- struct capture_completed_args {
- GdkTexture *thumb;
- char *fname;
- };
- static bool
- capture_completed(struct capture_completed_args *args)
- {
- strncpy(last_path, args->fname, 259);
- gtk_image_set_from_paintable(GTK_IMAGE(thumb_last),
- GDK_PAINTABLE(args->thumb));
- gtk_spinner_stop(GTK_SPINNER(process_spinner));
- gtk_stack_set_visible_child(GTK_STACK(open_last_stack), thumb_last);
- g_object_unref(args->thumb);
- g_free(args->fname);
- return false;
- }
- void
- mp_main_capture_completed(GdkTexture *thumb, const char *fname)
- {
- struct capture_completed_args *args =
- malloc(sizeof(struct capture_completed_args));
- args->thumb = thumb;
- args->fname = g_strdup(fname);
- g_main_context_invoke_full(g_main_context_default(),
- G_PRIORITY_DEFAULT_IDLE,
- (GSourceFunc)capture_completed,
- args,
- free);
- }
- static GLuint blit_program;
- static GLuint blit_uniform_transform;
- static GLuint blit_uniform_texture;
- static GLuint solid_program;
- static GLuint solid_uniform_color;
- static GLuint quad;
- static void
- preview_realize(GtkGLArea *area)
- {
- gtk_gl_area_make_current(area);
- if (gtk_gl_area_get_error(area) != NULL) {
- return;
- }
- // Make a VAO for OpenGL
- if (!gtk_gl_area_get_use_es(area)) {
- GLuint vao;
- glGenVertexArrays(1, &vao);
- glBindVertexArray(vao);
- check_gl();
- }
- GLuint blit_shaders[] = {
- gl_util_load_shader("/org/postmarketos/Megapixels/blit.vert",
- GL_VERTEX_SHADER,
- NULL,
- 0),
- gl_util_load_shader("/org/postmarketos/Megapixels/blit.frag",
- GL_FRAGMENT_SHADER,
- NULL,
- 0),
- };
- blit_program = gl_util_link_program(blit_shaders, 2);
- glBindAttribLocation(blit_program, GL_UTIL_VERTEX_ATTRIBUTE, "vert");
- glBindAttribLocation(blit_program, GL_UTIL_TEX_COORD_ATTRIBUTE, "tex_coord");
- check_gl();
- blit_uniform_transform = glGetUniformLocation(blit_program, "transform");
- blit_uniform_texture = glGetUniformLocation(blit_program, "texture");
- GLuint solid_shaders[] = {
- gl_util_load_shader("/org/postmarketos/Megapixels/solid.vert",
- GL_VERTEX_SHADER,
- NULL,
- 0),
- gl_util_load_shader("/org/postmarketos/Megapixels/solid.frag",
- GL_FRAGMENT_SHADER,
- NULL,
- 0),
- };
- solid_program = gl_util_link_program(solid_shaders, 2);
- glBindAttribLocation(solid_program, GL_UTIL_VERTEX_ATTRIBUTE, "vert");
- check_gl();
- solid_uniform_color = glGetUniformLocation(solid_program, "color");
- quad = gl_util_new_quad();
- }
- static void
- position_preview(float *offset_x, float *offset_y, float *size_x, float *size_y)
- {
- int buffer_width, buffer_height;
- if (state.device_rotation == 0 || state.device_rotation == 180) {
- buffer_width = state.preview_buffer_width;
- buffer_height = state.preview_buffer_height;
- } else {
- buffer_width = state.preview_buffer_height;
- buffer_height = state.preview_buffer_width;
- }
- int scale_factor = gtk_widget_get_scale_factor(preview);
- int top_height = gtk_widget_get_height(preview_top_box) * scale_factor;
- int bottom_height = gtk_widget_get_height(preview_bottom_box) * scale_factor;
- int inner_height = state.preview_height - top_height - bottom_height;
- float scale = (float)MIN(state.preview_width / (float)buffer_width,
- state.preview_height / (float)buffer_height);
- *size_x = scale * (float)buffer_width;
- *size_y = scale * (float)buffer_height;
- *offset_x = ((float)state.preview_width - *size_x) / 2.0f;
- if (*size_y > (float)inner_height) {
- *offset_y = ((float)state.preview_height - *size_y) / 2.0f;
- } else {
- *offset_y =
- (float)top_height + ((float)inner_height - *size_y) / 2.0f;
- }
- }
- static gboolean
- preview_draw(GtkGLArea *area, GdkGLContext *ctx, gpointer data)
- {
- if (gtk_gl_area_get_error(area) != NULL) {
- return FALSE;
- }
- if (current_preview_buffer == NULL) {
- return FALSE;
- }
- #ifdef RENDERDOC
- if (rdoc_api) {
- rdoc_api->StartFrameCapture(NULL, NULL);
- }
- #endif
- glClearColor(0, 0, 0, 1);
- glClear(GL_COLOR_BUFFER_BIT);
- float offset_x, offset_y, size_x, size_y;
- position_preview(&offset_x, &offset_y, &size_x, &size_y);
- glViewport(
- offset_x, state.preview_height - size_y - offset_y, size_x, size_y);
- if (current_preview_buffer) {
- glUseProgram(blit_program);
- GLfloat rotation_list[4] = { 0, -1, 0, 1 };
- int rotation_index = state.device_rotation / 90;
- GLfloat sin_rot = rotation_list[rotation_index];
- GLfloat cos_rot = rotation_list[(4 + rotation_index - 1) % 4];
- GLfloat matrix[9] = {
- // clang-format off
- cos_rot, sin_rot, 0,
- -sin_rot, cos_rot, 0,
- 0, 0, 1,
- // clang-format on
- };
- glUniformMatrix3fv(blit_uniform_transform, 1, GL_FALSE, matrix);
- check_gl();
- glActiveTexture(GL_TEXTURE0);
- glBindTexture(GL_TEXTURE_2D,
- mp_process_pipeline_buffer_get_texture_id(
- current_preview_buffer));
- glUniform1i(blit_uniform_texture, 0);
- check_gl();
- gl_util_bind_quad(quad);
- gl_util_draw_quad(quad);
- }
- if (zbar_result) {
- GLuint buffer;
- if (!gtk_gl_area_get_use_es(area)) {
- glGenBuffers(1, &buffer);
- glBindBuffer(GL_ARRAY_BUFFER, buffer);
- check_gl();
- }
- glUseProgram(solid_program);
- glEnable(GL_BLEND);
- glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
- glUniform4f(solid_uniform_color, 1, 0, 0, 0.5);
- for (uint8_t i = 0; i < zbar_result->size; ++i) {
- MPZBarCode *code = &zbar_result->codes[i];
- GLfloat vertices[] = {
- code->bounds_x[0], code->bounds_y[0],
- code->bounds_x[1], code->bounds_y[1],
- code->bounds_x[3], code->bounds_y[3],
- code->bounds_x[2], code->bounds_y[2],
- };
- for (int i = 0; i < 4; ++i) {
- vertices[i * 2] =
- 2 * vertices[i * 2] /
- state.preview_buffer_width -
- 1.0;
- vertices[i * 2 + 1] =
- 1.0 - 2 * vertices[i * 2 + 1] /
- state.preview_buffer_height;
- }
- if (gtk_gl_area_get_use_es(area)) {
- glVertexAttribPointer(GL_UTIL_VERTEX_ATTRIBUTE,
- 2,
- GL_FLOAT,
- 0,
- 0,
- vertices);
- check_gl();
- glEnableVertexAttribArray(GL_UTIL_VERTEX_ATTRIBUTE);
- check_gl();
- } else {
- glBufferData(GL_ARRAY_BUFFER,
- sizeof(vertices),
- vertices,
- GL_STREAM_DRAW);
- check_gl();
- glVertexAttribPointer(GL_UTIL_VERTEX_ATTRIBUTE,
- 2,
- GL_FLOAT,
- GL_FALSE,
- 0,
- 0);
- glEnableVertexAttribArray(GL_UTIL_VERTEX_ATTRIBUTE);
- check_gl();
- }
- glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
- check_gl();
- }
- glDisable(GL_BLEND);
- glBindBuffer(GL_ARRAY_BUFFER, 0);
- }
- glFlush();
- #ifdef RENDERDOC
- if (rdoc_api) {
- rdoc_api->EndFrameCapture(NULL, NULL);
- }
- #endif
- return FALSE;
- }
- static gboolean
- preview_resize(GtkWidget *widget, int width, int height, gpointer data)
- {
- if (state.preview_width != width || state.preview_height != height) {
- state.preview_width = width;
- state.preview_height = height;
- if (state.configuration->count == 0)
- return TRUE;
- update_io_pipeline();
- }
- return TRUE;
- }
- void
- run_open_last_action(GSimpleAction *action, GVariant *param, gpointer user_data)
- {
- char uri[275];
- g_autoptr(GError) error = NULL;
- if (strlen(last_path) == 0) {
- return;
- }
- sprintf(uri, "file://%s", last_path);
- if (!g_app_info_launch_default_for_uri(uri, NULL, &error)) {
- g_printerr("Could not launch image viewer for '%s': %s\n",
- uri,
- error->message);
- }
- }
- void
- run_open_photos_action(GSimpleAction *action, GVariant *param, gpointer user_data)
- {
- char uri[270];
- g_autoptr(GError) error = NULL;
- sprintf(uri, "file://%s", g_get_user_special_dir(G_USER_DIRECTORY_PICTURES));
- if (!g_app_info_launch_default_for_uri(uri, NULL, &error)) {
- g_printerr("Could not launch image viewer: %s\n", error->message);
- }
- }
- void
- run_capture_action(GSimpleAction *action, GVariant *param, gpointer user_data)
- {
- gtk_spinner_start(GTK_SPINNER(process_spinner));
- gtk_stack_set_visible_child(GTK_STACK(open_last_stack), process_spinner);
- if (capture_event)
- lfb_event_trigger_feedback_async(capture_event, NULL, NULL, NULL);
- mp_io_pipeline_capture();
- }
- void
- run_about_action(GSimpleAction *action, GVariant *param, GApplication *app)
- {
- GtkWindow *parent = gtk_application_get_active_window(GTK_APPLICATION(app));
- gtk_show_about_dialog(parent,
- "program-name",
- "Megapixels",
- "title",
- "Megapixels",
- "logo-icon-name",
- "me.gapixels.Megapixels",
- "comments",
- "The postmarketOS camera application",
- "website",
- "https://gitlab.com/postmarketOS/megapixels",
- "version",
- VERSION,
- "license-type",
- GTK_LICENSE_GPL_3_0_ONLY,
- NULL);
- }
- void
- run_quit_action(GSimpleAction *action, GVariant *param, GApplication *app)
- {
- g_application_quit(app);
- }
- static bool
- check_point_inside_bounds(int x, int y, const int *bounds_x, const int *bounds_y)
- {
- bool right = false, left = false, top = false, bottom = false;
- for (int i = 0; i < 4; ++i) {
- if (x <= bounds_x[i])
- left = true;
- if (x >= bounds_x[i])
- right = true;
- if (y <= bounds_y[i])
- top = true;
- if (y >= bounds_y[i])
- bottom = true;
- }
- return right && left && top && bottom;
- }
- static void
- on_zbar_dialog_response(GtkDialog *dialog, int response, char *data)
- {
- g_autoptr(GError) error = NULL;
- switch (response) {
- case GTK_RESPONSE_YES:
- if (!g_app_info_launch_default_for_uri(data, NULL, &error)) {
- g_printerr("Could not launch application: %s\n",
- error->message);
- }
- case GTK_RESPONSE_ACCEPT: {
- GdkDisplay *display = gtk_widget_get_display(GTK_WIDGET(dialog));
- gdk_clipboard_set_text(gdk_display_get_clipboard(display), data);
- }
- case GTK_RESPONSE_CANCEL:
- break;
- default:
- g_printerr("Wrong dialog response: %d\n", response);
- }
- g_free(data);
- gtk_window_destroy(GTK_WINDOW(dialog));
- }
- static void
- on_zbar_code_tapped(GtkWidget *widget, const MPZBarCode *code)
- {
- GtkWidget *dialog;
- GtkDialogFlags flags = GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT;
- bool data_is_url =
- g_uri_is_valid(code->data, G_URI_FLAGS_PARSE_RELAXED, NULL);
- char *data = strdup(code->data);
- if (data_is_url) {
- dialog = gtk_message_dialog_new(
- GTK_WINDOW(gtk_widget_get_root(widget)),
- flags,
- GTK_MESSAGE_QUESTION,
- GTK_BUTTONS_NONE,
- "Found a URL '%s' encoded in a %s.",
- code->data,
- code->type);
- gtk_dialog_add_buttons(
- GTK_DIALOG(dialog), "_Open URL", GTK_RESPONSE_YES, NULL);
- } else {
- dialog = gtk_message_dialog_new(
- GTK_WINDOW(gtk_widget_get_root(widget)),
- flags,
- GTK_MESSAGE_QUESTION,
- GTK_BUTTONS_NONE,
- "Found data encoded in a %s.",
- code->type);
- gtk_message_dialog_format_secondary_markup(
- GTK_MESSAGE_DIALOG(dialog), "<small>%s</small>", code->data);
- }
- gtk_dialog_add_buttons(GTK_DIALOG(dialog),
- "_Copy",
- GTK_RESPONSE_ACCEPT,
- "_Cancel",
- GTK_RESPONSE_CANCEL,
- NULL);
- g_signal_connect(
- dialog, "response", G_CALLBACK(on_zbar_dialog_response), data);
- gtk_widget_set_visible(GTK_WIDGET(dialog), true);
- }
- static void
- preview_pressed(GtkGestureClick *gesture, int n_press, double x, double y)
- {
- GtkWidget *widget =
- gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(gesture));
- int scale_factor = gtk_widget_get_scale_factor(widget);
- // Tapped zbar result
- if (zbar_result) {
- // Transform the event coordinates to the image
- float offset_x, offset_y, size_x, size_y;
- position_preview(&offset_x, &offset_y, &size_x, &size_y);
- int zbar_x = (x - offset_x) * scale_factor / size_x *
- state.preview_buffer_width;
- int zbar_y = (y - offset_y) * scale_factor / size_y *
- state.preview_buffer_height;
- for (uint8_t i = 0; i < zbar_result->size; ++i) {
- MPZBarCode *code = &zbar_result->codes[i];
- if (check_point_inside_bounds(zbar_x,
- zbar_y,
- code->bounds_x,
- code->bounds_y)) {
- on_zbar_code_tapped(widget, code);
- return;
- }
- }
- }
- // Tapped preview image itself, try focussing
- if (state.has_auto_focus_start) {
- mp_io_pipeline_focus();
- }
- }
- static void
- run_camera_switch_action(GSimpleAction *action, GVariant *param, gpointer user_data)
- {
- int new_index = state.camera->index + 1;
- if (new_index > state.configuration->count - 1 || new_index < 0) {
- new_index = 0;
- }
- state.camera = state.configuration->cameras[new_index];
- // TODO: allow setting burst length in the config
- state.burst_length = 5;
- update_io_pipeline();
- }
- static void
- run_open_settings_action(GSimpleAction *action, GVariant *param, gpointer user_data)
- {
- gtk_stack_set_visible_child_name(GTK_STACK(main_stack), "settings");
- }
- static void
- run_close_settings_action(GSimpleAction *action, GVariant *param, gpointer user_data)
- {
- gtk_stack_set_visible_child_name(GTK_STACK(main_stack), "main");
- }
- static void
- on_controls_scale_changed(GtkAdjustment *adjustment, void (*set_fn)(double))
- {
- set_fn(gtk_adjustment_get_value(adjustment));
- }
- static void
- update_value(GtkAdjustment *adjustment, GtkLabel *label)
- {
- char buf[12];
- snprintf(buf, 12, "%.0f", gtk_adjustment_get_value(adjustment));
- gtk_label_set_label(label, buf);
- }
- static void
- on_auto_controls_toggled(GtkToggleButton *button, void (*set_auto_fn)(bool))
- {
- set_auto_fn(gtk_toggle_button_get_active(button));
- }
- static void
- update_scale(GtkToggleButton *button, GtkScale *scale)
- {
- gtk_widget_set_sensitive(GTK_WIDGET(scale),
- !gtk_toggle_button_get_active(button));
- }
- static void
- open_controls(GtkWidget *parent,
- const char *title_name,
- double min_value,
- double max_value,
- double current,
- bool auto_enabled,
- void (*set_fn)(double),
- void (*set_auto_fn)(bool))
- {
- GtkBuilder *builder = gtk_builder_new_from_resource(
- "/org/postmarketos/Megapixels/controls-popover.ui");
- GtkPopover *popover =
- GTK_POPOVER(gtk_builder_get_object(builder, "controls"));
- GtkScale *scale = GTK_SCALE(gtk_builder_get_object(builder, "scale"));
- GtkLabel *title = GTK_LABEL(gtk_builder_get_object(builder, "title"));
- GtkLabel *value_label =
- GTK_LABEL(gtk_builder_get_object(builder, "value-label"));
- GtkToggleButton *auto_button =
- GTK_TOGGLE_BUTTON(gtk_builder_get_object(builder, "auto-button"));
- gtk_label_set_label(title, title_name);
- GtkAdjustment *adjustment = gtk_range_get_adjustment(GTK_RANGE(scale));
- gtk_adjustment_set_lower(adjustment, min_value);
- gtk_adjustment_set_upper(adjustment, max_value);
- gtk_adjustment_set_value(adjustment, current);
- update_value(adjustment, value_label);
- gtk_toggle_button_set_active(auto_button, auto_enabled);
- update_scale(auto_button, scale);
- g_signal_connect(adjustment,
- "value-changed",
- G_CALLBACK(on_controls_scale_changed),
- set_fn);
- g_signal_connect(
- adjustment, "value-changed", G_CALLBACK(update_value), value_label);
- g_signal_connect(auto_button,
- "toggled",
- G_CALLBACK(on_auto_controls_toggled),
- set_auto_fn);
- g_signal_connect(auto_button, "toggled", G_CALLBACK(update_scale), scale);
- gtk_widget_set_parent(GTK_WIDGET(popover), parent);
- gtk_popover_popup(popover);
- // g_object_unref(popover);
- }
- static void
- set_gain(double value)
- {
- if (state.gain.value != (int)value) {
- state.gain.value_req = (int)value;
- update_io_pipeline();
- }
- }
- static void
- set_gain_auto(bool is_auto)
- {
- if (state.gain.manual != !is_auto) {
- state.gain.manual_req = !is_auto;
- update_io_pipeline();
- }
- }
- static void
- open_iso_controls(GtkWidget *button, gpointer user_data)
- {
- open_controls(button,
- "ISO",
- 0,
- state.gain.max,
- state.gain.value,
- !state.gain.manual,
- set_gain,
- set_gain_auto);
- }
- static void
- set_shutter(double value)
- {
- int new_exposure = (int)(value / 360.0 * state.camera->current_mode->height);
- if (new_exposure != state.exposure.value) {
- state.exposure.value_req = new_exposure;
- update_io_pipeline();
- }
- }
- static void
- set_shutter_auto(bool is_auto)
- {
- if (state.exposure.manual != !is_auto) {
- state.exposure.manual_req = !is_auto;
- update_io_pipeline();
- }
- }
- static void
- open_shutter_controls(GtkWidget *button, gpointer user_data)
- {
- float value =
- ((float)state.exposure.value / (float)state.exposure.max) * 360.0f;
- open_controls(button,
- "Shutter",
- 1.0,
- 360.0,
- value,
- !state.exposure.manual,
- set_shutter,
- set_shutter_auto);
- }
- static void
- flash_button_clicked(GtkWidget *button, gpointer user_data)
- {
- state.flash_enabled = !state.flash_enabled;
- update_io_pipeline();
- const char *icon_name = state.flash_enabled ? "flash-enabled-symbolic" :
- "flash-disabled-symbolic";
- gtk_button_set_icon_name(GTK_BUTTON(button), icon_name);
- }
- static void
- on_realize(GtkWidget *window, gpointer *data)
- {
- if (state.configuration->count == 0) {
- return;
- }
- GtkNative *native = gtk_widget_get_native(window);
- mp_process_pipeline_init_gl(gtk_native_get_surface(native));
- state.camera = state.configuration->cameras[0];
- update_io_pipeline();
- }
- static GSimpleAction *
- create_simple_action(GtkApplication *app, const char *name, GCallback callback)
- {
- GSimpleAction *action = g_simple_action_new(name, NULL);
- g_signal_connect(action, "activate", callback, app);
- g_action_map_add_action(G_ACTION_MAP(app), G_ACTION(action));
- return action;
- }
- static void
- update_ui_rotation()
- {
- if (state.device_rotation == 0 || state.device_rotation == 180) {
- // Portrait
- gtk_widget_set_halign(preview_top_box, GTK_ALIGN_FILL);
- gtk_orientable_set_orientation(GTK_ORIENTABLE(preview_top_box),
- GTK_ORIENTATION_VERTICAL);
- gtk_widget_set_halign(preview_bottom_box, GTK_ALIGN_FILL);
- gtk_orientable_set_orientation(GTK_ORIENTABLE(preview_bottom_box),
- GTK_ORIENTATION_HORIZONTAL);
- if (state.device_rotation == 0) {
- gtk_widget_set_valign(preview_top_box, GTK_ALIGN_START);
- gtk_widget_set_valign(preview_bottom_box, GTK_ALIGN_END);
- } else {
- gtk_widget_set_valign(preview_top_box, GTK_ALIGN_END);
- gtk_widget_set_valign(preview_bottom_box, GTK_ALIGN_START);
- }
- } else {
- // Landscape
- gtk_widget_set_valign(preview_top_box, GTK_ALIGN_FILL);
- gtk_orientable_set_orientation(GTK_ORIENTABLE(preview_top_box),
- GTK_ORIENTATION_HORIZONTAL);
- gtk_widget_set_valign(preview_bottom_box, GTK_ALIGN_FILL);
- gtk_orientable_set_orientation(GTK_ORIENTABLE(preview_bottom_box),
- GTK_ORIENTATION_VERTICAL);
- if (state.device_rotation == 90) {
- gtk_widget_set_halign(preview_top_box, GTK_ALIGN_END);
- gtk_widget_set_halign(preview_bottom_box, GTK_ALIGN_START);
- } else {
- gtk_widget_set_halign(preview_top_box, GTK_ALIGN_START);
- gtk_widget_set_halign(preview_bottom_box, GTK_ALIGN_END);
- }
- }
- }
- char *
- munge_app_id(const char *app_id)
- {
- char *id = g_strdup(app_id);
- int i;
- if (g_str_has_suffix(id, ".desktop")) {
- char *c = g_strrstr(id, ".desktop");
- if (c)
- *c = '\0';
- }
- g_strcanon(id,
- "0123456789"
- "abcdefghijklmnopqrstuvwxyz"
- "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
- "-",
- '-');
- for (i = 0; id[i] != '\0'; i++)
- id[i] = g_ascii_tolower(id[i]);
- return id;
- }
- /* Verbatim from feedbackd */
- #define FEEDBACKD_SCHEMA_ID "org.sigxcpu.feedbackd"
- #define FEEDBACKD_KEY_PROFILE "profile"
- #define FEEDBACKD_APP_SCHEMA FEEDBACKD_SCHEMA_ID ".application"
- #define FEEDBACKD_APP_PREFIX "/org/sigxcpu/feedbackd/application/"
- static gboolean
- fb_profile_to_state(GValue *value, GVariant *variant, gpointer user_data)
- {
- const gchar *name;
- gboolean fb_state = FALSE;
- name = g_variant_get_string(variant, NULL);
- if (g_strcmp0(name, "full") == 0)
- fb_state = TRUE;
- g_value_set_boolean(value, fb_state);
- return TRUE;
- }
- static GVariant *
- state_to_fb_profile(const GValue *value,
- const GVariantType *expected_type,
- gpointer user_data)
- {
- gboolean fb_state = g_value_get_boolean(value);
- return g_variant_new_string(fb_state ? "full" : "silent");
- }
- static void
- setup_fb_switch(GtkBuilder *builder)
- {
- g_autofree char *path = NULL;
- g_autofree char *munged_id = NULL;
- g_autoptr(GSettingsSchema) schema = NULL;
- GSettingsSchemaSource *schema_source =
- g_settings_schema_source_get_default();
- GtkWidget *shutter_sound_switch =
- GTK_WIDGET(gtk_builder_get_object(builder, "shutter-sound-switch"));
- GtkWidget *feedback_box =
- GTK_WIDGET(gtk_builder_get_object(builder, "feedback-box"));
- schema = g_settings_schema_source_lookup(
- schema_source, FEEDBACKD_APP_SCHEMA, TRUE);
- if (schema == NULL) {
- gtk_widget_set_sensitive(feedback_box, FALSE);
- return;
- }
- munged_id = munge_app_id(APP_ID);
- path = g_strconcat(FEEDBACKD_APP_PREFIX, munged_id, "/", NULL);
- fb_settings = g_settings_new_with_path(FEEDBACKD_APP_SCHEMA, path);
- g_settings_bind_with_mapping(fb_settings,
- FEEDBACKD_KEY_PROFILE,
- shutter_sound_switch,
- "active",
- G_SETTINGS_BIND_DEFAULT,
- fb_profile_to_state,
- state_to_fb_profile,
- NULL,
- NULL);
- }
- #ifdef GDK_WINDOWING_WAYLAND
- static void
- wl_handle_geometry(void *data,
- struct wl_output *wl_output,
- int32_t x,
- int32_t y,
- int32_t physical_width,
- int32_t physical_height,
- int32_t subpixel,
- const char *make,
- const char *model,
- int32_t transform)
- {
- assert(transform < 4);
- int new_rotation = transform * 90;
- if (new_rotation != state.device_rotation) {
- state.device_rotation = new_rotation;
- update_io_pipeline();
- update_ui_rotation();
- }
- }
- static void
- wl_handle_mode(void *data,
- struct wl_output *wl_output,
- uint32_t flags,
- int32_t width,
- int32_t height,
- int32_t refresh)
- {
- // Do nothing
- }
- static const struct wl_output_listener output_listener = {
- .geometry = wl_handle_geometry,
- .mode = wl_handle_mode
- };
- static void
- wl_handle_global(void *data,
- struct wl_registry *wl_registry,
- uint32_t name,
- const char *interface,
- uint32_t version)
- {
- if (strcmp(interface, wl_output_interface.name) == 0) {
- struct wl_output *output =
- wl_registry_bind(wl_registry, name, &wl_output_interface, 1);
- wl_output_add_listener(output, &output_listener, NULL);
- }
- }
- static void
- wl_handle_global_remove(void *data, struct wl_registry *wl_registry, uint32_t name)
- {
- // Do nothing
- }
- static const struct wl_registry_listener registry_listener = {
- .global = wl_handle_global,
- .global_remove = wl_handle_global_remove
- };
- #endif // GDK_WINDOWING_WAYLAND
- #ifdef GDK_WINDOWING_X11
- static gboolean
- xevent_handler(GdkDisplay *display, XEvent *xevent, gpointer data)
- {
- Display *xdisplay = gdk_x11_display_get_xdisplay(display);
- int event_base, error_base;
- XRRQueryExtension(xdisplay, &event_base, &error_base);
- if (xevent->type - event_base == RRScreenChangeNotify) {
- Rotation xrotation =
- ((XRRScreenChangeNotifyEvent *)xevent)->rotation;
- int new_rotation = 0;
- switch (xrotation) {
- case RR_Rotate_0:
- new_rotation = 0;
- break;
- case RR_Rotate_90:
- new_rotation = 90;
- break;
- case RR_Rotate_180:
- new_rotation = 180;
- break;
- case RR_Rotate_270:
- new_rotation = 270;
- break;
- }
- if (new_rotation != state.device_rotation) {
- state.device_rotation = new_rotation;
- update_io_pipeline();
- update_ui_rotation();
- }
- }
- // The return value of this function should always be FALSE; if it's
- // TRUE, we prevent GTK/GDK from handling the event.
- return FALSE;
- }
- #endif // GDK_WINDOWING_X11
- static void
- activate(GtkApplication *app, gpointer data)
- {
- g_object_set(gtk_settings_get_default(),
- "gtk-application-prefer-dark-theme",
- TRUE,
- NULL);
- GdkDisplay *display = gdk_display_get_default();
- GtkIconTheme *icon_theme = gtk_icon_theme_get_for_display(display);
- gtk_icon_theme_add_resource_path(icon_theme, "/org/postmarketos/Megapixels");
- GtkCssProvider *provider = gtk_css_provider_new();
- gtk_css_provider_load_from_resource(
- provider, "/org/postmarketos/Megapixels/camera.css");
- gtk_style_context_add_provider_for_display(
- display,
- GTK_STYLE_PROVIDER(provider),
- GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
- GtkBuilder *builder = gtk_builder_new_from_resource(
- "/org/postmarketos/Megapixels/camera.ui");
- window = GTK_WIDGET(gtk_builder_get_object(builder, "window"));
- iso_button =
- GTK_WIDGET(gtk_builder_get_object(builder, "iso-controls-button"));
- shutter_button = GTK_WIDGET(
- gtk_builder_get_object(builder, "shutter-controls-button"));
- flash_button =
- GTK_WIDGET(gtk_builder_get_object(builder, "flash-controls-button"));
- GtkWidget *setting_dng_button =
- GTK_WIDGET(gtk_builder_get_object(builder, "setting-raw"));
- GtkWidget *setting_postprocessor_combo =
- GTK_WIDGET(gtk_builder_get_object(builder, "setting-processor"));
- GtkListStore *setting_postprocessor_list = GTK_LIST_STORE(
- gtk_builder_get_object(builder, "list-postprocessors"));
- preview = GTK_WIDGET(gtk_builder_get_object(builder, "preview"));
- main_stack = GTK_WIDGET(gtk_builder_get_object(builder, "main_stack"));
- open_last_stack =
- GTK_WIDGET(gtk_builder_get_object(builder, "open_last_stack"));
- thumb_last = GTK_WIDGET(gtk_builder_get_object(builder, "thumb_last"));
- process_spinner =
- GTK_WIDGET(gtk_builder_get_object(builder, "process_spinner"));
- scanned_codes = GTK_WIDGET(gtk_builder_get_object(builder, "scanned-codes"));
- preview_top_box = GTK_WIDGET(gtk_builder_get_object(builder, "top-box"));
- preview_bottom_box =
- GTK_WIDGET(gtk_builder_get_object(builder, "bottom-box"));
- message_box = GTK_WIDGET(gtk_builder_get_object(builder, "message-box"));
- message_label = GTK_WIDGET(gtk_builder_get_object(builder, "message-label"));
- g_signal_connect(window, "realize", G_CALLBACK(on_realize), NULL);
- g_signal_connect(preview, "realize", G_CALLBACK(preview_realize), NULL);
- g_signal_connect(preview, "render", G_CALLBACK(preview_draw), NULL);
- g_signal_connect(preview, "resize", G_CALLBACK(preview_resize), NULL);
- GtkGesture *click = gtk_gesture_click_new();
- g_signal_connect(click, "pressed", G_CALLBACK(preview_pressed), NULL);
- gtk_widget_add_controller(preview, GTK_EVENT_CONTROLLER(click));
- g_signal_connect(iso_button, "clicked", G_CALLBACK(open_iso_controls), NULL);
- g_signal_connect(
- shutter_button, "clicked", G_CALLBACK(open_shutter_controls), NULL);
- g_signal_connect(
- flash_button, "clicked", G_CALLBACK(flash_button_clicked), NULL);
- setup_fb_switch(builder);
- // Setup actions
- create_simple_action(app, "capture", G_CALLBACK(run_capture_action));
- create_simple_action(
- app, "switch-camera", G_CALLBACK(run_camera_switch_action));
- create_simple_action(
- app, "open-settings", G_CALLBACK(run_open_settings_action));
- create_simple_action(
- app, "close-settings", G_CALLBACK(run_close_settings_action));
- create_simple_action(app, "open-last", G_CALLBACK(run_open_last_action));
- create_simple_action(app, "open-photos", G_CALLBACK(run_open_photos_action));
- create_simple_action(app, "about", G_CALLBACK(run_about_action));
- create_simple_action(app, "quit", G_CALLBACK(run_quit_action));
- // Setup shortcuts
- const char *capture_accels[] = { "space", NULL };
- gtk_application_set_accels_for_action(app, "app.capture", capture_accels);
- const char *quit_accels[] = { "<Ctrl>q", "<Ctrl>w", NULL };
- gtk_application_set_accels_for_action(app, "app.quit", quit_accels);
- // Setup settings
- settings = g_settings_new(APP_ID);
- char *setting_postproc = g_settings_get_string(settings, "postprocessor");
- // Initialize the postprocessing gsetting to the old processor if
- // it was not set yet
- if (setting_postproc == NULL || setting_postproc[0] == '\0') {
- printf("Initializing postprocessor gsetting\n");
- setting_postproc = malloc(512);
- if (!mp_process_find_processor(setting_postproc)) {
- printf("No processor found\n");
- exit(1);
- }
- g_settings_set_string(settings, "postprocessor", setting_postproc);
- printf("Initialized postprocessor to %s\n", setting_postproc);
- }
- // Find all postprocessors for the settings list
- mp_process_find_all_processors(setting_postprocessor_list);
- // Bind settings widgets to the actual settings
- g_settings_bind(settings,
- "save-raw",
- setting_dng_button,
- "active",
- G_SETTINGS_BIND_DEFAULT);
- g_settings_bind(settings,
- "postprocessor",
- setting_postprocessor_combo,
- "active-id",
- G_SETTINGS_BIND_DEFAULT);
- #ifdef GDK_WINDOWING_WAYLAND
- // Listen for Wayland rotation
- if (GDK_IS_WAYLAND_DISPLAY(display)) {
- struct wl_display *wl_display =
- gdk_wayland_display_get_wl_display(display);
- struct wl_registry *wl_registry =
- wl_display_get_registry(wl_display);
- // The registry listener will bind to our wl_output and add our
- // listeners
- wl_registry_add_listener(wl_registry, ®istry_listener, NULL);
- // GTK will take care of dispatching wayland events for us.
- // Wayland sends us a geometry event as soon as we bind to the
- // wl_output, so we don't need to manually check the initial
- // rotation here.
- }
- #endif
- #ifdef GDK_WINDOWING_X11
- // Listen for X rotation
- if (GDK_IS_X11_DISPLAY(display)) {
- g_signal_connect(
- display, "xevent", G_CALLBACK(xevent_handler), NULL);
- // Set initial rotation
- Display *xdisplay = gdk_x11_display_get_xdisplay(display);
- int screen =
- XScreenNumberOfScreen(gdk_x11_display_get_xscreen(display));
- Rotation xrotation;
- XRRRotations(xdisplay, screen, &xrotation);
- int new_rotation = 0;
- switch (xrotation) {
- case RR_Rotate_0:
- new_rotation = 0;
- break;
- case RR_Rotate_90:
- new_rotation = 90;
- break;
- case RR_Rotate_180:
- new_rotation = 180;
- break;
- case RR_Rotate_270:
- new_rotation = 270;
- break;
- }
- if (new_rotation != state.device_rotation) {
- state.device_rotation = new_rotation;
- update_ui_rotation();
- }
- }
- #endif
- // Initialize display flash
- GDBusConnection *conn =
- g_application_get_dbus_connection(G_APPLICATION(app));
- mp_flash_gtk_init(conn);
- if (state.configuration->count > 0) {
- mp_io_pipeline_start();
- } else {
- display_error("No camera found");
- }
- gtk_application_add_window(app, GTK_WINDOW(window));
- gtk_widget_set_visible(window, true);
- }
- static void
- startup(GApplication *app, gpointer data)
- {
- g_autoptr(GError) err = NULL;
- if (lfb_init(APP_ID, &err))
- capture_event = lfb_event_new("camera-shutter");
- else
- g_warning("Failed to init libfeedback: %s", err->message);
- }
- static void
- shutdown(GApplication *app, gpointer data)
- {
- // Only do cleanup in development, let the OS clean up otherwise
- #ifdef DEBUG
- mp_io_pipeline_stop();
- mp_flash_gtk_clean();
- g_clear_object(&fb_settings);
- g_clear_object(&capture_event);
- lfb_uninit();
- #endif
- }
- int
- main(int argc, char *argv[])
- {
- #ifdef RENDERDOC
- {
- void *mod = dlopen("librenderdoc.so", RTLD_NOW | RTLD_NOLOAD);
- if (mod) {
- pRENDERDOC_GetAPI RENDERDOC_GetAPI =
- (pRENDERDOC_GetAPI)dlsym(mod, "RENDERDOC_GetAPI");
- int ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_1_2,
- (void **)&rdoc_api);
- assert(ret == 1);
- } else {
- printf("Renderdoc not found\n");
- }
- }
- #endif
- char configfile[PATH_MAX];
- libmegapixels_init(&state.configuration);
- if (libmegapixels_find_config(configfile)) {
- if (!libmegapixels_load_file(state.configuration, configfile)) {
- fprintf(stderr, "Could not load config\n");
- return 1;
- }
- } else {
- if (!libmegapixels_load_uvc(state.configuration)) {
- fprintf(stderr, "No config found\n");
- return 1;
- }
- }
- setenv("LC_NUMERIC", "C", 1);
- GtkApplication *app = gtk_application_new(APP_ID, 0);
- g_signal_connect(app, "startup", G_CALLBACK(startup), NULL);
- g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
- g_signal_connect(app, "shutdown", G_CALLBACK(shutdown), NULL);
- g_application_run(G_APPLICATION(app), argc, argv);
- return 0;
- }
|