diff --git a/meson.build b/meson.build index 3e6a324e16..6b19eb89de 100644 --- a/meson.build +++ b/meson.build @@ -1123,6 +1123,9 @@ conf.set('HAVE_LIBGUDEV', gudev.found()) directx = platform_windows ? cc.find_library('dxguid') : no_dep conf.set('HAVE_DX_DINPUT', directx.found()) +# Windows Image Acquistion (WIA) +wia = platform_windows ? cc.find_library('wiaguid') : no_dep + cfitsio_dep = dependency('cfitsio', required: get_option('fits')) diff --git a/plug-ins/common/meson.build b/plug-ins/common/meson.build index 420e4d1cdd..d76525d112 100644 --- a/plug-ins/common/meson.build +++ b/plug-ins/common/meson.build @@ -168,6 +168,11 @@ endif if platform_windows common_plugins_list += { 'name': 'file-lnk', } + if wia.found() + common_plugins_list += { 'name': 'wia', + 'deps': [ wia ], + } + endif endif if platform_linux diff --git a/plug-ins/common/wia.c b/plug-ins/common/wia.c new file mode 100644 index 0000000000..401095e56f --- /dev/null +++ b/plug-ins/common/wia.c @@ -0,0 +1,335 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * WIA Scanner plug-in + * + * Copyright (C) 2026 Alex S. + * + * This program 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. + * + * This program 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 this program. If not, see . + */ + +#include "config.h" + +#include /* Needed when compiling with gcc */ +#include +#include + + +#include +#include +#include + +#include "libgimp/gimp.h" +#include "libgimp/stdplugins-intl.h" + + +#define PLUG_IN_NAME "wia-acquire" +#define PLUG_IN_DESCRIPTION _("Capture an image from a WIA datasource") +#define PLUG_IN_HELP N_("This plug-in will capture an image from a WIA datasource") + +#define MAX_FOLDER_PATH 1024 + +typedef struct _Wia Wia; +typedef struct _WiaClass WiaClass; + +struct _Wia +{ + GimpPlugIn parent_instance; +}; + +struct _WiaClass +{ + GimpPlugInClass parent_class; +}; + + +#define WIA_TYPE (wia_get_type ()) +#define WIA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), WIA_TYPE, Wia)) + +GType wia_get_type (void) G_GNUC_CONST; + +static GList * wia_query_procedures (GimpPlugIn *plug_in); +static GimpProcedure * wia_create_procedure (GimpPlugIn *plug_in, + const gchar *name); + +static GimpValueArray * wia_run (GimpProcedure *procedure, + GimpRunMode run_mode, + GimpImage *image, + GimpDrawable **drawables, + GimpProcedureConfig *config, + gpointer run_data); + +static GimpValueArray * wia_main (GimpProcedure *procedure, + GimpProcedureConfig *config); + +static HRESULT CreateWiaDeviceManager (IWiaDevMgr2 **ppWiaDevMgr); + +G_DEFINE_TYPE (Wia, wia, GIMP_TYPE_PLUG_IN) + +GIMP_MAIN (WIA_TYPE) +DEFINE_STD_SET_I18N + + +static void +wia_class_init (WiaClass *klass) +{ + GimpPlugInClass *plug_in_class = GIMP_PLUG_IN_CLASS (klass); + + plug_in_class->query_procedures = wia_query_procedures; + plug_in_class->create_procedure = wia_create_procedure; + plug_in_class->set_i18n = STD_SET_I18N; +} + +static void +wia_init (Wia *wia) +{ +} + +static GList * +wia_query_procedures (GimpPlugIn *plug_in) +{ + return g_list_append (NULL, g_strdup (PLUG_IN_NAME)); +} + +static GimpProcedure * +wia_create_procedure (GimpPlugIn *plug_in, + const gchar *name) +{ + GimpProcedure *procedure = NULL; + + if (! strcmp (name, PLUG_IN_NAME)) + { + procedure = gimp_image_procedure_new (plug_in, name, + GIMP_PDB_PROC_TYPE_PLUGIN, + wia_run, NULL, NULL); + + gimp_procedure_set_image_types (procedure, "*"); + gimp_procedure_set_sensitivity_mask (procedure, + GIMP_PROCEDURE_SENSITIVE_DRAWABLE | + GIMP_PROCEDURE_SENSITIVE_DRAWABLES | + GIMP_PROCEDURE_SENSITIVE_NO_DRAWABLES | + GIMP_PROCEDURE_SENSITIVE_NO_IMAGE); + + gimp_procedure_set_menu_label (procedure, _("_Scanner/Camera...")); + gimp_procedure_add_menu_path (procedure, "/File/Create"); + + gimp_procedure_set_documentation (procedure, + PLUG_IN_DESCRIPTION, + PLUG_IN_HELP, + name); + gimp_procedure_set_attribution (procedure, + "Alx Sa", + "Alx Sa", + "2026"); + + gimp_procedure_add_string_aux_argument (procedure, "scan-directory", + NULL, NULL, + NULL, + GIMP_PARAM_READWRITE); + + gimp_procedure_add_core_object_array_return_value (procedure, "images", + "Array of acquired images", + "Array of acquired images", + GIMP_TYPE_IMAGE, + G_PARAM_READWRITE); + } + + return procedure; +} + +static GimpValueArray * +wia_run (GimpProcedure *procedure, + GimpRunMode run_mode, + GimpImage *image, + GimpDrawable **drawables, + GimpProcedureConfig *config, + gpointer run_data) +{ + gegl_init (NULL, NULL); + + if (run_mode == GIMP_RUN_NONINTERACTIVE) + /* Currently, we don't do non-interactive calls. + * Bail if someone tries to call us non-interactively + */ + return gimp_procedure_new_return_values (procedure, GIMP_PDB_CALLING_ERROR, + NULL); + + return wia_main (procedure, config); +} + +/* Windows methods */ +static HRESULT +CreateWiaDeviceManager (IWiaDevMgr2 **ppWiaDevMgr) +{ + HRESULT hr; + + if (NULL == ppWiaDevMgr) + return E_INVALIDARG; + + *ppWiaDevMgr = NULL; + + hr = CoCreateInstance (&CLSID_WiaDevMgr2, NULL, CLSCTX_LOCAL_SERVER, + &IID_IWiaDevMgr2, (void**) ppWiaDevMgr); + + return hr; +} + +static GimpValueArray * +wia_main (GimpProcedure *procedure, + GimpProcedureConfig *config) +{ + GError *error = NULL; + HRESULT hr = CoInitialize (NULL); + + if (SUCCEEDED (hr)) + { + IWiaDevMgr2 *pWiaDevMgr2 = NULL; + + hr = CreateWiaDeviceManager (&pWiaDevMgr2); + + if (SUCCEEDED (hr)) + { + GimpImage **images = NULL; + GimpValueArray *return_vals = NULL; + HWND window_handle; + LONG nFiles = 0; + LPWSTR *rFilePaths = NULL; + IWiaItem2 *pItem = NULL; + BSTR szFolder = NULL; + BSTR szFileName; + TCHAR temp_path[MAX_FOLDER_PATH]; + gchar *scan_directory = NULL; + + szFileName = SysAllocString (L"GIMP-SCANNED-IMAGE"); + + /* Check if we have a saved directory, and if so, if it's still + * valid. */ + g_object_get (config, "scan-directory", &scan_directory, NULL); + if (scan_directory != NULL) + { + GDir *directory = g_dir_open (scan_directory, 0, NULL); + + if (directory) + { + szFolder = + SysAllocString (g_utf8_to_utf16 (scan_directory, -1, NULL, + NULL, NULL)); + g_dir_close (directory); + } + g_free (scan_directory); + } + + /* Otherwise, use Window's temp location */ + if (szFolder == NULL) + { + GetTempPath (MAX_FOLDER_PATH, temp_path); + szFolder = SysAllocString (g_utf8_to_utf16 (temp_path, -1, NULL, + NULL, NULL)); + } + + /* Values taken from app/gui/gui-unique.h */ + window_handle = FindWindowW (L"GimpWin32UniqueHandler", L"GimpProxy"); + + hr = pWiaDevMgr2->lpVtbl->GetImageDlg (pWiaDevMgr2, 0, NULL, window_handle, + szFolder, szFileName, &nFiles, + &rFilePaths, &pItem); + SysFreeString (szFolder); + SysFreeString (szFileName); + + if (FAILED (hr)) + { + pWiaDevMgr2->lpVtbl->Release (pWiaDevMgr2); + pWiaDevMgr2 = NULL; + + CoUninitialize (); + + g_set_error (&error, GIMP_PLUG_IN_ERROR, 0, + _("Unable to read scanned images.")); + + return gimp_procedure_new_return_values (procedure, + GIMP_PDB_EXECUTION_ERROR, + NULL); + } + + if (nFiles > 0) + { + gboolean has_directory = FALSE; + images = g_new0 (GimpImage *, nFiles + 1); + + for (gint i = 0; i < nFiles; i++) + { + gchar *filename = g_utf16_to_utf8 (rFilePaths[i], -1, NULL, + NULL, NULL); + + if (filename) + { + GFile *file = g_file_new_for_path (filename); + + images[i] = gimp_file_load (GIMP_RUN_NONINTERACTIVE, + file); + gimp_display_new (images[i]); + if (! gimp_image_undo_is_enabled (images[i])) + gimp_image_undo_enable (images[i]); + + /* Store first valid directory for later use */ + if (! has_directory) + { + GFile *parent = g_file_get_parent (file); + + g_object_set (config, + "scan-directory", + g_file_get_parse_name (parent), + NULL); + g_object_unref (parent); + + has_directory = TRUE; + } + + g_object_unref (file); + g_free (filename); + } + SysFreeString (rFilePaths[i]); + } + + CoTaskMemFree (rFilePaths); + } + + pWiaDevMgr2->lpVtbl->Release (pWiaDevMgr2); + pWiaDevMgr2 = NULL; + + CoUninitialize (); + + return_vals = gimp_procedure_new_return_values (procedure, + GIMP_PDB_SUCCESS, + NULL); + GIMP_VALUES_TAKE_CORE_OBJECT_ARRAY (return_vals, 1, images); + + return return_vals; + } + else + { + g_set_error (&error, GIMP_PLUG_IN_ERROR, 0, + _("WIA driver not found, scanning not available")); + + return gimp_procedure_new_return_values (procedure, + GIMP_PDB_EXECUTION_ERROR, + NULL); + } + + CoUninitialize (); + } + + return gimp_procedure_new_return_values (procedure, GIMP_PDB_EXECUTION_ERROR, + NULL); +} diff --git a/po-plug-ins/POTFILES.in b/po-plug-ins/POTFILES.in index b1743b2070..0b521be1fb 100644 --- a/po-plug-ins/POTFILES.in +++ b/po-plug-ins/POTFILES.in @@ -93,6 +93,7 @@ plug-ins/common/warp.c plug-ins/common/wavelet-decompose.c plug-ins/common/web-browser.c plug-ins/common/web-page.c +plug-ins/common/wia.c plug-ins/file-bmp/bmp-export.c plug-ins/file-bmp/bmp-load.c plug-ins/file-bmp/bmp.c