#include "main.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gl_utils.h" #include "camera_config.h" #include "quickpreview.h" #include "io_pipeline.h" #include "process_pipeline.h" #define RENDERDOC #ifdef RENDERDOC #include #include RENDERDOC_API_1_1_2 *rdoc_api = NULL; #endif enum user_control { USER_CONTROL_ISO, USER_CONTROL_SHUTTER }; static bool camera_is_initialized = false; static const struct mp_camera_config *camera = NULL; static MPCameraMode mode; static int preview_width = -1; static int preview_height = -1; static bool gain_is_manual = false; static int gain; static int gain_max; static bool exposure_is_manual = false; static int exposure; static bool has_auto_focus_continuous; static bool has_auto_focus_start; static MPProcessPipelineBuffer *current_preview_buffer = NULL; static cairo_surface_t *status_surface = NULL; static char last_path[260] = ""; static MPZBarScanResult *zbar_result = NULL; static int burst_length = 3; static enum user_control current_control; // Widgets GtkWidget *preview; GtkWidget *error_box; GtkWidget *error_message; GtkWidget *main_stack; GtkWidget *open_last_stack; GtkWidget *thumb_last; GtkWidget *process_spinner; GtkWidget *control_box; GtkWidget *control_name; GtkAdjustment *control_slider; GtkWidget *control_auto; 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 update_io_pipeline() { struct mp_io_pipeline_state io_state = { .camera = camera, .burst_length = burst_length, .preview_width = preview_width, .preview_height = preview_height, .gain_is_manual = gain_is_manual, .gain = gain, .exposure_is_manual = exposure_is_manual, .exposure = exposure, }; mp_io_pipeline_update_state(&io_state); } static bool update_state(const struct mp_main_state *state) { if (!camera_is_initialized) { camera_is_initialized = true; } if (camera == state->camera) { mode = state->mode; if (!gain_is_manual) { gain = state->gain; } gain_max = state->gain_max; if (!exposure_is_manual) { exposure = state->exposure; } has_auto_focus_continuous = state->has_auto_focus_continuous; has_auto_focus_start = state->has_auto_focus_start; } return false; } void mp_main_update_state(const struct mp_main_state *state) { struct mp_main_state *state_copy = malloc(sizeof(struct mp_main_state)); *state_copy = *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); } static void transform_centered(cairo_t *cr, uint32_t dst_width, uint32_t dst_height, int src_width, int src_height) { cairo_translate(cr, dst_width / 2, dst_height / 2); double scale = MIN(dst_width / (double)src_width, dst_height / (double)src_height); cairo_scale(cr, scale, scale); cairo_translate(cr, -src_width / 2, -src_height / 2); } void draw_surface_scaled_centered(cairo_t *cr, uint32_t dst_width, uint32_t dst_height, cairo_surface_t *surface) { cairo_save(cr); int width = cairo_image_surface_get_width(surface); int height = cairo_image_surface_get_height(surface); transform_centered(cr, dst_width, dst_height, width, height); cairo_set_source_surface(cr, surface, 0, 0); cairo_paint(cr); cairo_restore(cr); } struct capture_completed_args { cairo_surface_t *thumb; char *fname; }; static bool capture_completed(struct capture_completed_args *args) { strncpy(last_path, args->fname, 259); // gtk_image_set_from_surface(GTK_IMAGE(thumb_last), args->thumb); gtk_spinner_stop(GTK_SPINNER(process_spinner)); gtk_stack_set_visible_child(GTK_STACK(open_last_stack), thumb_last); cairo_surface_destroy(args->thumb); g_free(args->fname); return false; } void mp_main_capture_completed(cairo_surface_t *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 void draw_controls() { // cairo_t *cr; char iso[6]; int temp; char shutterangle[6]; if (exposure_is_manual) { temp = (int)((float)exposure / (float)camera->capture_mode.height * 360); sprintf(shutterangle, "%d\u00b0", temp); } else { sprintf(shutterangle, "auto"); } if (gain_is_manual) { temp = remap(gain - 1, 0, gain_max, camera->iso_min, camera->iso_max); sprintf(iso, "%d", temp); } else { sprintf(iso, "auto"); } if (status_surface) cairo_surface_destroy(status_surface); // Make a service to show status of controls, 32px high // if (gtk_widget_get_window(preview) == NULL) { // return; // } // status_surface = // gdk_window_create_similar_surface(gtk_widget_get_window(preview), // CAIRO_CONTENT_COLOR_ALPHA, // preview_width, 32); // cr = cairo_create(status_surface); // cairo_set_source_rgba(cr, 0, 0, 0, 0.0); // cairo_paint(cr); // // Draw the outlines for the headings // cairo_select_font_face(cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL, // CAIRO_FONT_WEIGHT_BOLD); // cairo_set_font_size(cr, 9); // cairo_set_source_rgba(cr, 0, 0, 0, 1); // cairo_move_to(cr, 16, 16); // cairo_text_path(cr, "ISO"); // cairo_stroke(cr); // cairo_move_to(cr, 60, 16); // cairo_text_path(cr, "Shutter"); // cairo_stroke(cr); // // Draw the fill for the headings // cairo_set_source_rgba(cr, 1, 1, 1, 1); // cairo_move_to(cr, 16, 16); // cairo_show_text(cr, "ISO"); // cairo_move_to(cr, 60, 16); // cairo_show_text(cr, "Shutter"); // // Draw the outlines for the values // cairo_select_font_face(cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL, // CAIRO_FONT_WEIGHT_NORMAL); // cairo_set_font_size(cr, 11); // cairo_set_source_rgba(cr, 0, 0, 0, 1); // cairo_move_to(cr, 16, 26); // cairo_text_path(cr, iso); // cairo_stroke(cr); // cairo_move_to(cr, 60, 26); // cairo_text_path(cr, shutterangle); // cairo_stroke(cr); // // Draw the fill for the values // cairo_set_source_rgba(cr, 1, 1, 1, 1); // cairo_move_to(cr, 16, 26); // cairo_show_text(cr, iso); // cairo_move_to(cr, 60, 26); // cairo_show_text(cr, shutterangle); // cairo_destroy(cr); // gtk_widget_queue_draw_area(preview, 0, 0, preview_width, 32); } static GLuint blit_program; static GLuint blit_uniform_texture; static void preview_realize(GtkGLArea *area) { gtk_gl_area_make_current(area); if (gtk_gl_area_get_error(area) != NULL) { return; } GLuint blit_shaders[] = { gl_load_shader("data/blit.vert", GL_VERTEX_SHADER), gl_load_shader("data/blit.frag", GL_FRAGMENT_SHADER), }; blit_program = gl_link_program(blit_shaders, 2); glBindAttribLocation(blit_program, 0, "vert"); glBindAttribLocation(blit_program, 1, "tex_coord"); check_gl(); blit_uniform_texture = glGetUniformLocation(blit_program, "texture"); } static gboolean preview_draw(GtkGLArea *area, GdkGLContext *ctx, gpointer data) { if (gtk_gl_area_get_error(area) != NULL) { return FALSE; } if (!camera_is_initialized) { return FALSE; } // #ifdef RENDERDOC // if (rdoc_api) rdoc_api->StartFrameCapture(NULL, NULL); // #endif glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT); if (current_preview_buffer) { glUseProgram(blit_program); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, mp_process_pipeline_buffer_get_texture_id(current_preview_buffer)); glUniform1i(blit_uniform_texture, 0); glVertexAttribPointer(0, 2, GL_FLOAT, 0, 0, gl_quad_vertices); glEnableVertexAttribArray(0); glVertexAttribPointer(1, 2, GL_FLOAT, 0, 0, gl_quad_texcoords); glEnableVertexAttribArray(1); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); check_gl(); } /* // Clear preview area with black cairo_paint(cr); if (surface) { // Draw camera preview cairo_save(cr); int width = cairo_image_surface_get_width(surface); int height = cairo_image_surface_get_height(surface); transform_centered(cr, preview_width, preview_height, width, height); cairo_set_source_surface(cr, surface, 0, 0); cairo_paint(cr); // Draw zbar image if (zbar_result) { for (uint8_t i = 0; i < zbar_result->size; ++i) { MPZBarCode *code = &zbar_result->codes[i]; cairo_set_line_width(cr, 3.0); cairo_set_source_rgba(cr, 0, 0.5, 1, 0.75); cairo_new_path(cr); cairo_move_to(cr, code->bounds_x[0], code->bounds_y[0]); for (uint8_t i = 0; i < 4; ++i) { cairo_line_to(cr, code->bounds_x[i], code->bounds_y[i]); } cairo_close_path(cr); cairo_stroke(cr); cairo_save(cr); cairo_translate(cr, code->bounds_x[0], code->bounds_y[0]); cairo_show_text(cr, code->data); cairo_restore(cr); } } cairo_restore(cr); } // Draw control overlay cairo_set_source_surface(cr, status_surface, 0, 0); cairo_paint(cr); */ 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 (preview_width != width || preview_height != height) { preview_width = width; preview_height = height; update_io_pipeline(); } draw_controls(); return TRUE; } void on_open_last_clicked(GtkWidget *widget, gpointer user_data) { char uri[275]; 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 on_open_directory_clicked(GtkWidget *widget, gpointer user_data) { char uri[270]; 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 on_shutter_clicked(GtkWidget *widget, gpointer user_data) { gtk_spinner_start(GTK_SPINNER(process_spinner)); gtk_stack_set_visible_child(GTK_STACK(open_last_stack), process_spinner); mp_io_pipeline_capture(); } // void // on_capture_shortcut(void) // { // on_shutter_clicked(NULL, NULL); // } // static bool // check_point_inside_bounds(int x, int y, int *bounds_x, 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_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_toplevel(widget)), // flags, // GTK_MESSAGE_QUESTION, // GTK_BUTTONS_NONE, // "Found a URL '%s' encoded in a %s code.", // 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_toplevel(widget)), // flags, // GTK_MESSAGE_QUESTION, // GTK_BUTTONS_NONE, // "Found '%s' encoded in a %s code.", // code->data, // code->type); // } // gtk_dialog_add_buttons( // GTK_DIALOG(dialog), // "_Copy", // GTK_RESPONSE_ACCEPT, // "_Cancel", // GTK_RESPONSE_CANCEL, // NULL); // int result = gtk_dialog_run(GTK_DIALOG(dialog)); // GError *error = NULL; // switch (result) { // 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: // gtk_clipboard_set_text( // gtk_clipboard_get(GDK_SELECTION_PRIMARY), // data, -1); // case GTK_RESPONSE_CANCEL: // break; // default: // g_printerr("Wrong dialog result: %d\n", result); // } // gtk_widget_destroy(dialog); // } // void // on_preview_tap(GtkWidget *widget, GdkEventButton *event, gpointer user_data) // { // if (event->type != GDK_BUTTON_PRESS) // return; // // Handle taps on the controls // if (event->y < 32) { // if (gtk_widget_is_visible(control_box)) { // gtk_widget_hide(control_box); // return; // } else { // gtk_widget_show(control_box); // } // if (event->x < 60) { // // ISO // current_control = USER_CONTROL_ISO; // gtk_label_set_text(GTK_LABEL(control_name), "ISO"); // gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(control_auto), // !gain_is_manual); // gtk_adjustment_set_lower(control_slider, 0.0); // gtk_adjustment_set_upper(control_slider, (float)gain_max); // gtk_adjustment_set_value(control_slider, (double)gain); // } else if (event->x > 60 && event->x < 120) { // // Shutter angle // current_control = USER_CONTROL_SHUTTER; // gtk_label_set_text(GTK_LABEL(control_name), "Shutter"); // gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(control_auto), // !exposure_is_manual); // gtk_adjustment_set_lower(control_slider, 1.0); // gtk_adjustment_set_upper(control_slider, 360.0); // gtk_adjustment_set_value(control_slider, (double)exposure); // } // return; // } // // Tapped zbar result // if (zbar_result) { // // Transform the event coordinates to the image // int width = cairo_image_surface_get_width(surface); // int height = cairo_image_surface_get_height(surface); // double scale = MIN(preview_width / (double)width, preview_height / (double)height); // int x = (event->x - preview_width / 2) / scale + width / 2; // int y = (event->y - preview_height / 2) / scale + height / 2; // for (uint8_t i = 0; i < zbar_result->size; ++i) { // MPZBarCode *code = &zbar_result->codes[i]; // if (check_point_inside_bounds(x, y, code->bounds_x, code->bounds_y)) { // on_zbar_code_tapped(widget, code); // return; // } // } // } // // Tapped preview image itself, try focussing // if (has_auto_focus_start) { // mp_io_pipeline_focus(); // } // } void on_error_close_clicked(GtkWidget *widget, gpointer user_data) { gtk_widget_hide(error_box); } void on_camera_switch_clicked(GtkWidget *widget, gpointer user_data) { size_t next_index = camera->index + 1; const struct mp_camera_config *next_camera = mp_get_camera_config(next_index); if (!next_camera) { next_index = 0; next_camera = mp_get_camera_config(next_index); } camera = next_camera; update_io_pipeline(); } void on_settings_btn_clicked(GtkWidget *widget, gpointer user_data) { gtk_stack_set_visible_child_name(GTK_STACK(main_stack), "settings"); } void on_back_clicked(GtkWidget *widget, gpointer user_data) { gtk_stack_set_visible_child_name(GTK_STACK(main_stack), "main"); } void on_control_auto_toggled(GtkToggleButton *widget, gpointer user_data) { bool is_manual = gtk_toggle_button_get_active(widget) ? false : true; bool has_changed; switch (current_control) { case USER_CONTROL_ISO: if (gain_is_manual != is_manual) { gain_is_manual = is_manual; has_changed = true; } break; case USER_CONTROL_SHUTTER: if (exposure_is_manual != is_manual) { exposure_is_manual = is_manual; has_changed = true; } break; } if (has_changed) { // The slider might have been moved while Auto mode is active. When entering // Manual mode, first read the slider value to sync with those changes. double value = gtk_adjustment_get_value(control_slider); switch (current_control) { case USER_CONTROL_ISO: if (value != gain) { gain = (int)value; } break; case USER_CONTROL_SHUTTER: { // So far all sensors use exposure time in number of sensor rows int new_exposure = (int)(value / 360.0 * camera->capture_mode.height); if (new_exposure != exposure) { exposure = new_exposure; } break; } } update_io_pipeline(); draw_controls(); } } void on_control_slider_changed(GtkAdjustment *widget, gpointer user_data) { double value = gtk_adjustment_get_value(widget); bool has_changed = false; switch (current_control) { case USER_CONTROL_ISO: if (value != gain) { gain = (int)value; has_changed = true; } break; case USER_CONTROL_SHUTTER: { // So far all sensors use exposure time in number of sensor rows int new_exposure = (int)(value / 360.0 * camera->capture_mode.height); if (new_exposure != exposure) { exposure = new_exposure; has_changed = true; } break; } } if (has_changed) { update_io_pipeline(); draw_controls(); } } static void on_realize(GtkWidget *window, gpointer *data) { GtkNative *native = gtk_widget_get_native(window); mp_process_pipeline_init_gl(gtk_native_get_surface(native)); } typedef struct { GtkApplication parent_instance; } MegapixelsApp; typedef GtkApplicationClass MegapixelsAppClass; GType megapixels_app_get_type (void); G_DEFINE_TYPE(MegapixelsApp, megapixels_app, GTK_TYPE_APPLICATION) static void startup(GApplication *app) { G_APPLICATION_CLASS(megapixels_app_parent_class)->startup(app); g_object_set(gtk_settings_get_default(), "gtk-application-prefer-dark-theme", TRUE, NULL); GtkBuilder *builder; if (access("camera.ui", F_OK) != -1) { builder = gtk_builder_new_from_file("camera.ui"); } else { builder = gtk_builder_new_from_file( "/org/postmarketos/Megapixels/camera.ui"); } GtkWidget *window = GTK_WIDGET(gtk_builder_get_object(builder, "window")); GtkWidget *shutter = GTK_WIDGET(gtk_builder_get_object(builder, "shutter")); GtkWidget *switch_btn = GTK_WIDGET(gtk_builder_get_object(builder, "switch_camera")); GtkWidget *settings_btn = GTK_WIDGET(gtk_builder_get_object(builder, "settings")); GtkWidget *settings_back = GTK_WIDGET(gtk_builder_get_object(builder, "settings_back")); GtkWidget *error_close = GTK_WIDGET(gtk_builder_get_object(builder, "error_close")); GtkWidget *open_last = GTK_WIDGET(gtk_builder_get_object(builder, "open_last")); GtkWidget *open_directory = GTK_WIDGET(gtk_builder_get_object(builder, "open_directory")); preview = GTK_WIDGET(gtk_builder_get_object(builder, "preview")); error_box = GTK_WIDGET(gtk_builder_get_object(builder, "error_box")); error_message = GTK_WIDGET(gtk_builder_get_object(builder, "error_message")); 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")); control_box = GTK_WIDGET(gtk_builder_get_object(builder, "control_box")); control_name = GTK_WIDGET(gtk_builder_get_object(builder, "control_name")); control_slider = GTK_ADJUSTMENT(gtk_builder_get_object(builder, "control_adj")); control_auto = GTK_WIDGET(gtk_builder_get_object(builder, "control_auto")); g_signal_connect(window, "realize", G_CALLBACK(on_realize), NULL); // g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL); g_signal_connect(shutter, "clicked", G_CALLBACK(on_shutter_clicked), NULL); g_signal_connect(error_close, "clicked", G_CALLBACK(on_error_close_clicked), NULL); g_signal_connect(switch_btn, "clicked", G_CALLBACK(on_camera_switch_clicked), NULL); g_signal_connect(settings_btn, "clicked", G_CALLBACK(on_settings_btn_clicked), NULL); g_signal_connect(settings_back, "clicked", G_CALLBACK(on_back_clicked), NULL); g_signal_connect(open_last, "clicked", G_CALLBACK(on_open_last_clicked), NULL); g_signal_connect(open_directory, "clicked", G_CALLBACK(on_open_directory_clicked), 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); // gtk_widget_set_events(preview, gtk_widget_get_events(preview) | // GDK_BUTTON_PRESS_MASK | // GDK_POINTER_MOTION_MASK); // g_signal_connect(preview, "button-press-event", G_CALLBACK(on_preview_tap), // NULL); g_signal_connect(control_auto, "toggled", G_CALLBACK(on_control_auto_toggled), NULL); g_signal_connect(control_slider, "value-changed", G_CALLBACK(on_control_slider_changed), NULL); GtkCssProvider *provider = gtk_css_provider_new(); if (access("camera.css", F_OK) != -1) { gtk_css_provider_load_from_path(provider, "camera.css"); } else { gtk_css_provider_load_from_resource( provider, "/org/postmarketos/Megapixels/camera.css"); } GtkStyleContext *context = gtk_widget_get_style_context(error_box); gtk_style_context_add_provider(context, GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_USER); context = gtk_widget_get_style_context(control_box); gtk_style_context_add_provider(context, GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_USER); // GClosure* capture_shortcut = g_cclosure_new(on_capture_shortcut, 0, 0); // GtkAccelGroup* accel_group = gtk_accel_group_new(); // gtk_accel_group_connect(accel_group, // GDK_KEY_space, // 0, // 0, // capture_shortcut); // gtk_window_add_accel_group(GTK_WINDOW(window), accel_group); mp_io_pipeline_start(); camera = mp_get_camera_config(0); update_io_pipeline(); gtk_application_add_window(GTK_APPLICATION(app), GTK_WINDOW(window)); gtk_widget_show(window); } static void shutdown(GApplication *app) { // Only do cleanup in development, let the OS clean up otherwise #ifdef DEBUG mp_io_pipeline_stop(); #endif G_APPLICATION_CLASS(megapixels_app_parent_class)->shutdown(app); } static void megapixels_app_init(MegapixelsApp *app) { } static void megapixels_app_class_init(MegapixelsAppClass *class) { GApplicationClass *application_class = G_APPLICATION_CLASS(class); application_class->startup = startup; application_class->shutdown = shutdown; } 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 if (!mp_load_config()) return 1; setenv("LC_NUMERIC", "C", 1); MegapixelsApp *app = g_object_new( megapixels_app_get_type(), "application-id", "org.postmarketos.Megapixels", NULL); g_application_run(G_APPLICATION(app), argc, argv); return 0; }