/* This file is part of darktable, Copyright (C) 2009-2024 darktable developers. darktable is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. darktable is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with darktable. If not, see . */ #include "common/darktable.h" #include "common/image.h" #include "develop/develop.h" #include "common/gimp.h" #include "common/image_cache.h" #include "gui/gtk.h" #include #ifdef __APPLE__ #include "osx/osx.h" #endif #ifdef _WIN32 #include "win/main_wrapper.h" #include "common/datetime.h" typedef struct dt_alloc_console_options_t { int mode; BOOL use_show_window; WORD show_window; } dt_alloc_console_options_t; typedef HRESULT(WINAPI *dt_alloc_console_with_options_t)( dt_alloc_console_options_t *options, int *result); static gboolean _allocate_console(gboolean *visible) { *visible = FALSE; const HMODULE kernel = GetModuleHandleW(L"kernel32.dll"); const FARPROC allocation_proc = GetProcAddress(kernel, "AllocConsoleWithOptions"); dt_alloc_console_with_options_t alloc_with_options = NULL; memcpy(&alloc_with_options, &allocation_proc, sizeof(alloc_with_options)); if(alloc_with_options) { // mode 2 requests ALLOC_CONSOLE_MODE_NO_WINDOW, available since Windows 11 24H2 dt_alloc_console_options_t options = { 2, FALSE, SW_HIDE }; if(SUCCEEDED(alloc_with_options(&options, NULL)) && GetConsoleCP() != 0) return TRUE; } *visible = AllocConsole(); return *visible; } #endif #ifdef __APPLE__ int apple_main(int argc, char *argv[]) #else int main(int argc, char *argv[]) #endif { #ifdef __APPLE__ dt_osx_prepare_environment(); #endif #ifdef _WIN32 gboolean show_console_notice = FALSE; // On Windows we have a hard time showing stuff printed to stdout/stderr to the user. // Because of that we write it to a log file. char datetime[DT_DATETIME_EXIF_LENGTH]; dt_datetime_now_to_exif(datetime); // Users are more accustomed to the ISO 8601 format than to separating // the year, month, and day with a colon (which is the Exif convention) datetime[4] = datetime[7] = '-'; // Make sure to not redirect output when the output is already // being redirected, either to a file or a pipe. if(GetConsoleCP() == 0) { const HANDLE initial_input_handle = GetStdHandle(STD_INPUT_HANDLE); const HANDLE initial_output_handle = GetStdHandle(STD_OUTPUT_HANDLE); const HANDLE initial_error_handle = GetStdHandle(STD_ERROR_HANDLE); const DWORD initial_in_type = GetFileType(initial_input_handle); const DWORD initial_out_type = GetFileType(initial_output_handle); const DWORD initial_err_type = GetFileType(initial_error_handle); const gboolean input_redirected = initial_in_type == FILE_TYPE_DISK || initial_in_type == FILE_TYPE_PIPE; const gboolean output_redirected = initial_out_type == FILE_TYPE_DISK || initial_out_type == FILE_TYPE_PIPE; const gboolean error_redirected = initial_err_type == FILE_TYPE_DISK || initial_err_type == FILE_TYPE_PIPE; if(_allocate_console(&show_console_notice)) { if(input_redirected) SetStdHandle(STD_INPUT_HANDLE, initial_input_handle); else g_freopen("CONIN$", "r", stdin); if(output_redirected) SetStdHandle(STD_OUTPUT_HANDLE, initial_output_handle); else g_freopen("CONOUT$", "w", stdout); if(error_redirected) SetStdHandle(STD_ERROR_HANDLE, initial_error_handle); else g_freopen("CONOUT$", "w", stderr); } } int out_type = GetFileType(GetStdHandle(STD_OUTPUT_HANDLE)); int err_type = GetFileType(GetStdHandle(STD_ERROR_HANDLE)); gboolean redirect_output = ((out_type != FILE_TYPE_DISK && out_type != FILE_TYPE_PIPE) && (err_type != FILE_TYPE_DISK && err_type != FILE_TYPE_PIPE)); for(int k = 1; k < argc; k++) { // For simple arguments do not redirect stdout if(!strcmp(argv[k], "--help") || !strcmp(argv[k], "-h") || !strcmp(argv[k], "/?") || !strcmp(argv[k], "--version") || !strcmp(argv[k], "--gimp")) { redirect_output = FALSE; break; } } if(redirect_output) { // NOTE! If the log file path is changed, reflect the changes at least // in the console message in 'usage()' of 'common\darktable.c' char *logdir = g_build_filename(g_get_home_dir(), "Documents", "Darktable", NULL); char *logfile = g_build_filename(logdir, "darktable-log.txt", NULL); g_mkdir_with_parents(logdir, 0700); g_freopen(logfile, "a", stdout); dup2(fileno(stdout), STDOUT_FILENO); dup2(fileno(stdout), STDERR_FILENO); dup2(fileno(stdout), fileno(stderr)); const HANDLE output_handle = (HANDLE)_get_osfhandle(fileno(stdout)); SetStdHandle(STD_OUTPUT_HANDLE, output_handle); SetStdHandle(STD_ERROR_HANDLE, output_handle); g_free(logdir); g_free(logfile); // Don't buffer stdout/stderr. We have basically two options: // unbuffered or line buffered. // Unbuffered keeps the order in which things are printed but concurrent // threads printing can lead to intermangled output. Ugly. // Line buffered should keep lines together but the order of things // no longer matches. Ugly and potentially confusing. // Thus we are doing the thing that is just ugly (in rare cases) // but at least not confusing. setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); printf("========================================\n"); printf("version: %s\n", darktable_package_string); printf("start: %s\n", datetime); printf("\n"); } // detached in the manifest // is Windows 11 24H2+. On older Windows the loader gives this console // subsystem binary a console before main() runs, so _allocate_console() above // is never reached and the user is left with a bare console window. Claim any // visible console that is ours alone; one inherited from a shell has more // than one process attached and must be left alone. if(!show_console_notice) { const HWND console_window = GetConsoleWindow(); DWORD console_pids[2]; if(console_window && IsWindowVisible(console_window) && GetConsoleProcessList(console_pids, 2) == 1) show_console_notice = TRUE; } if(show_console_notice) dt_request_console_notice(); // Make sure GTK client side decoration is disabled, // otherwise windows resizing issues can be observed. g_setenv("GTK_CSD", "0", TRUE); #endif if(dt_init(argc, argv, TRUE, TRUE, NULL)) { if(dt_gimpmode()) printf("\n<<>>\n"); exit(1); } if(dt_check_gimpmode_ok("version")) { printf("\n<<>>\n", DT_GIMP_VERSION); exit(0); } if(dt_check_gimpmode("version") || (dt_check_gimpmode("file") && !dt_check_gimpmode_ok("file")) || (dt_check_gimpmode("thumb") && !dt_check_gimpmode_ok("thumb")) || darktable.gimp.error) { printf("\n<<>>\n"); exit(1); } if(dt_check_gimpmode_ok("file")) { const dt_imgid_t id = dt_gimp_load_darkroom(darktable.gimp.path); if(!dt_is_valid_imgid(id)) darktable.gimp.error = TRUE; } if(dt_check_gimpmode_ok("thumb")) { const dt_imgid_t id = dt_gimp_load_image(darktable.gimp.path); if(dt_is_valid_imgid(id)) darktable.gimp.error = !dt_export_gimp_file(id); else darktable.gimp.error = TRUE; return darktable.gimp.error ? 1 : 0; } if(!dt_gimpmode() || dt_check_gimpmode_ok("file")) dt_gui_gtk_run(darktable.gui); dt_cleanup(); if(darktable.restart && darktable.restart_argv) { GError *error = NULL; const gboolean abs_exe = darktable.restart_argv[0] && g_path_is_absolute(darktable.restart_argv[0]); const gboolean ok = g_spawn_async(NULL, darktable.restart_argv, NULL, abs_exe ? G_SPAWN_DEFAULT : G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &error); if(!ok) { fprintf(stderr, "darktable: failed to restart: %s\n", error ? error->message : "unknown error"); if(error) g_error_free(error); } g_strfreev(darktable.restart_argv); darktable.restart_argv = NULL; } if(dt_gimpmode() && darktable.gimp.error) printf("\n<<>>\n"); #ifdef _WIN32 if(redirect_output) { printf("\n"); printf("end: %s\n", datetime); printf("========================================\n"); printf("\n"); } #endif const int exitcode = dt_gimpmode() ? (darktable.gimp.error ? 1 : 0) : 0; exit(exitcode); } // clang-format off // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py // vim: shiftwidth=2 expandtab tabstop=2 cindent // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified; // clang-format on