RFC/PATCH: Twain + Gphoto

Aric Stewart aric at codeweavers.com
Mon Apr 17 08:20:31 CDT 2006


This is really cool!  I have poked a bit at it to see and like how it is 
going so far. It does not appear to actually get any photos from the 
cameras yet or am i missing that code.

The ImageInfoGet and ImageMemXferGet are sort of the heart of that. At 
least for the program I am working on support for.

Very cool. I look forward to seeing more on this!
-aric

Marcus Meissner wrote:

>Hi,
>
>I have started adding gphoto2 support to twain.dll.
>
>My current work (before a otherwise busy week begins again), 
>is attached.
>
>I started to break up the currently very sane centric view.
>
>Alexandre/Aric, can I do it this way? Some other general comments?
>
>(The code btw works as-is for detection at least.)
>
>Ciao, Marcus
>
>Index: configure.ac
>===================================================================
>RCS file: /home/wine/wine/configure.ac,v
>retrieving revision 1.468
>diff -u -r1.468 configure.ac
>--- configure.ac	6 Apr 2006 10:53:39 -0000	1.468
>+++ configure.ac	9 Apr 2006 19:13:29 -0000
>@@ -521,6 +521,37 @@
>     CPPFLAGS="$ac_save_CPPFLAGS"
> fi
> 
>+dnl **** Check for libgphoto2 ****
>+AC_CHECK_PROG(gphoto2_devel,gphoto2-config,gphoto2-config,no)
>+AC_CHECK_PROG(gphoto2port_devel,gphoto2-port-config,gphoto2-port-config,no)
>+AC_SUBST(GPHOTO2LIBS,"")
>+AC_SUBST(GPHOTO2INCL,"")
>+if test "$gphoto2_devel" != "no" -a "$gphoto2port_devel" != "no"
>+then
>+    GPHOTO2INCL="`$gphoto2_devel --cflags` `$gphoto2port_devel --cflags`"
>+    GPHOTO2LIBS=""
>+    for i in `$gphoto2_devel --libs` `$gphoto2port_devel --libs`
>+    do
>+      case "$i" in
>+        -L/usr/lib|-L/usr/lib64) ;;
>+        -L*|-l*) GPHOTO2LIBS="$GPHOTO2LIBS $i";;
>+      esac
>+    done
>+    ac_save_CPPFLAGS="$CPPFLAGS"
>+    ac_save_LIBS="$LIBS"
>+    CPPFLAGS="$CPPFLAGS $GPHOTO2INCL"
>+    LIBS="$LIBS $GPHOTO2LIBS"
>+    AC_CHECK_HEADER(gphoto2-camera.h,
>+                    [AC_CHECK_LIB(gphoto2,gp_camera_new,
>+                                  [AC_DEFINE(HAVE_GPHOTO2, 1, [Define if we have libgphoto2 development environment])],
>+                                  [GPHOTO2LIBS=""
>+                                  GPHOTO2INCL=""])],
>+                    [GPHOTO2LIBS=""
>+                    GPHOTO2INCL=""])
>+    LIBS="$ac_save_LIBS"
>+    CPPFLAGS="$ac_save_CPPFLAGS"
>+fi
>+
> dnl **** Check for the ICU library ****
> if test "$ac_cv_header_unicode_ubidi_h" = "yes"
> then
>Index: dlls/twain/Makefile.in
>===================================================================
>RCS file: /home/wine/wine/dlls/twain/Makefile.in,v
>retrieving revision 1.10
>diff -u -r1.10 Makefile.in
>--- dlls/twain/Makefile.in	28 Mar 2006 18:13:00 -0000	1.10
>+++ dlls/twain/Makefile.in	9 Apr 2006 19:13:29 -0000
>@@ -4,8 +4,8 @@
> VPATH     = @srcdir@
> MODULE    = twain_32.dll
> IMPORTS   = comctl32 user32 gdi32 kernel32 ntdll
>-EXTRALIBS = @SANELIBS@
>-EXTRAINCL = @SANEINCL@
>+EXTRALIBS = @SANELIBS@ @GPHOTO2LIBS@
>+EXTRAINCL = @SANEINCL@ @GPHOTO2INCL@
> 
> C_SRCS = \
> 	capability.c \
>Index: dlls/twain/ds_ctrl.c
>===================================================================
>RCS file: /home/wine/wine/dlls/twain/ds_ctrl.c,v
>retrieving revision 1.6
>diff -u -r1.6 ds_ctrl.c
>--- dlls/twain/ds_ctrl.c	28 Mar 2006 18:13:00 -0000	1.6
>+++ dlls/twain/ds_ctrl.c	9 Apr 2006 19:13:29 -0000
>@@ -648,27 +650,37 @@
>         if (pUserInterface->ShowUI)
>         {
>             BOOL rc;
>+	    FIXME("Showing UI now.\n");
>             pSource->currentState = 5; /* Transitions to state 5 */
>-            rc = DoScannerUI(pSource);
>-            if (!rc)
>-            {
>-                pSource->pendingEvent.TWMessage = MSG_CLOSEDSREQ;
>-            }
>+	    switch (devices[pSource->deviceIndex].type) {
> #ifdef HAVE_SANE
>-            else
>-            {
>-                sane_get_parameters (pSource->deviceHandle, &pSource->sane_param);
>-                pSource->sane_param_valid = TRUE;
>-            }
>+	    case DEVTYPE_SANE:
>+		rc = DoScannerUI(pSource);
>+		if (!rc) {
>+			pSource->pendingEvent.TWMessage = MSG_CLOSEDSREQ;
>+            	} else {
>+                	sane_get_parameters (pSource->deviceHandle, &pSource->sane_param);
>+                	pSource->sane_param_valid = TRUE;
>+            	}
>+		break;
> #endif
>-        }
>-        else
>-        {
>+#ifdef HAVE_GPHOTO2
>+	    case DEVTYPE_GPHOTO:
>+		FIXME("No GPHOTO UI yet.\n");
>+            	pSource->pendingEvent.TWMessage = MSG_XFERREADY;
>+		pSource->currentState = 6; /* Transitions to state 6 directly */
>+		break;
>+#endif
>+	    default:
>+		FIXME("Device type %d is unknown\n", devices[pSource->deviceIndex].type);
>+		break;
>+	    }
>+        } else {
>+	    FIXME("UI not shown, preparing to transfer data.\n");
>             /* no UI will be displayed, so source is ready to transfer data */
>             pSource->pendingEvent.TWMessage = MSG_XFERREADY;
>             pSource->currentState = 6; /* Transitions to state 6 directly */
>         }
>-
>         pSource->hwndOwner = pUserInterface->hParent;
>         twRC = TWRC_SUCCESS;
>         pSource->twCC = TWCC_SUCCESS;
>Index: dlls/twain/ds_image.c
>===================================================================
>RCS file: /home/wine/wine/dlls/twain/ds_image.c,v
>retrieving revision 1.8
>diff -u -r1.8 ds_image.c
>--- dlls/twain/ds_image.c	28 Mar 2006 18:13:00 -0000	1.8
>+++ dlls/twain/ds_image.c	9 Apr 2006 19:13:29 -0000
>@@ -80,76 +80,81 @@
> TW_UINT16 TWAIN_ImageInfoGet (pTW_IDENTITY pOrigin, pTW_IDENTITY pDest,
>                               TW_MEMREF pData)
> {
>-#ifndef HAVE_SANE
>-    return TWRC_FAILURE;
>-#else
>-    TW_UINT16 twRC = TWRC_SUCCESS;
>-    pTW_IMAGEINFO pImageInfo = (pTW_IMAGEINFO) pData;
>-    activeDS *pSource = TWAIN_LookupSource (pDest);
>-    SANE_Status status;
>-
>-    TRACE("DG_IMAGE/DAT_IMAGEINFO/MSG_GET\n");
>-
>-    if (!pSource)
>-    {
>-        twRC = TWRC_FAILURE;
>-        DSM_twCC = TWCC_BADDEST;
>-    }
>-    else if (pSource->currentState != 6 && pSource->currentState != 7)
>-    {
>-        twRC = TWRC_FAILURE;
>-        pSource->twCC = TWCC_SEQERROR;
>-    }
>-    else
>-    {
>-        if (pSource->currentState == 6)
>-        {
>-            /* return general image description information about the image about to be transferred */
>-            status = sane_get_parameters (pSource->deviceHandle, &pSource->sane_param);
>-            pSource->sane_param_valid = TRUE;
>-            TRACE("Getting parameters\n");
>-        }
>+	pTW_IMAGEINFO pImageInfo = (pTW_IMAGEINFO) pData;
>+	activeDS *pSource = TWAIN_LookupSource (pDest);
> 
>-        pImageInfo->XResolution.Whole = -1;
>-        pImageInfo->XResolution.Frac = 0;
>-        pImageInfo->YResolution.Whole = -1;
>-        pImageInfo->YResolution.Frac = 0;
>-        pImageInfo->ImageWidth = pSource->sane_param.pixels_per_line;
>-        pImageInfo->ImageLength = pSource->sane_param.lines;
>+	TRACE("DG_IMAGE/DAT_IMAGEINFO/MSG_GET\n");
> 
>-        TRACE("Bits per Sample %i\n",pSource->sane_param.depth);
>-        TRACE("Frame Format %i\n",pSource->sane_param.format);
>-
>-        if (pSource->sane_param.format == SANE_FRAME_RGB )
>-        {
>-            pImageInfo->BitsPerPixel = pSource->sane_param.depth * 3;
>-            pImageInfo->Compression = TWCP_NONE;
>-            pImageInfo->Planar = TRUE;
>-            pImageInfo->SamplesPerPixel = 3;
>-            pImageInfo->BitsPerSample[0] = pSource->sane_param.depth;
>-            pImageInfo->BitsPerSample[1] = pSource->sane_param.depth;
>-            pImageInfo->BitsPerSample[2] = pSource->sane_param.depth;
>-            pImageInfo->PixelType = TWPT_RGB;
>-        }
>-        else if (pSource->sane_param.format == SANE_FRAME_GRAY)
>-        {
>-            pImageInfo->BitsPerPixel = pSource->sane_param.depth;
>-            pImageInfo->Compression = TWCP_NONE;
>-            pImageInfo->Planar = TRUE;
>-            pImageInfo->SamplesPerPixel = 1;
>-            pImageInfo->BitsPerSample[0] = pSource->sane_param.depth;
>-            pImageInfo->PixelType = TWPT_GRAY;
>-        }
>-        else
>-        {
>-            ERR("Unhandled source frame type %i\n",pSource->sane_param.format);
>-            twRC = TWRC_FAILURE;
>-            pSource->twCC = TWCC_SEQERROR;
>-        }
>-    }
>-
>-    return twRC;
>+	if (!pSource) {
>+		FIXME("no source\n");
>+		DSM_twCC = TWCC_BADDEST;
>+		return TWRC_FAILURE;
>+	}
>+	if (pSource->currentState != 6 && pSource->currentState != 7) {
>+		FIXME("bad state %d\n", pSource->currentState);
>+		pSource->twCC = TWCC_SEQERROR;
>+		return TWRC_FAILURE;
>+	}
>+
>+	switch (devices[pSource->deviceIndex].type) {
>+#ifdef HAVE_SANE
>+	case DEVTYPE_SANE: {
>+    		SANE_Status status;
>+		if (pSource->currentState == 6)
>+		{
>+		    /* return general image description information about the image about to be transferred */
>+		    status = sane_get_parameters (pSource->deviceHandle, &pSource->sane_param);
>+		    pSource->sane_param_valid = TRUE;
>+		    TRACE("Getting parameters\n");
>+		}
>+
>+		pImageInfo->XResolution.Whole = -1;
>+		pImageInfo->XResolution.Frac = 0;
>+		pImageInfo->YResolution.Whole = -1;
>+		pImageInfo->YResolution.Frac = 0;
>+		pImageInfo->ImageWidth = pSource->sane_param.pixels_per_line;
>+		pImageInfo->ImageLength = pSource->sane_param.lines;
>+
>+		TRACE("Bits per Sample %i\n",pSource->sane_param.depth);
>+		TRACE("Frame Format %i\n",pSource->sane_param.format);
>+
>+		if (pSource->sane_param.format == SANE_FRAME_RGB )
>+		{
>+		    pImageInfo->BitsPerPixel = pSource->sane_param.depth * 3;
>+		    pImageInfo->Compression = TWCP_NONE;
>+		    pImageInfo->Planar = TRUE;
>+		    pImageInfo->SamplesPerPixel = 3;
>+		    pImageInfo->BitsPerSample[0] = pSource->sane_param.depth;
>+		    pImageInfo->BitsPerSample[1] = pSource->sane_param.depth;
>+		    pImageInfo->BitsPerSample[2] = pSource->sane_param.depth;
>+		    pImageInfo->PixelType = TWPT_RGB;
>+		}
>+		else if (pSource->sane_param.format == SANE_FRAME_GRAY)
>+		{
>+		    pImageInfo->BitsPerPixel = pSource->sane_param.depth;
>+		    pImageInfo->Compression = TWCP_NONE;
>+		    pImageInfo->Planar = TRUE;
>+		    pImageInfo->SamplesPerPixel = 1;
>+		    pImageInfo->BitsPerSample[0] = pSource->sane_param.depth;
>+		    pImageInfo->PixelType = TWPT_GRAY;
>+		}
>+		else
>+		{
>+		    ERR("Unhandled source frame type %i\n",pSource->sane_param.format);
>+		    pSource->twCC = TWCC_SEQERROR;
>+		    return TWRC_FAILURE;
>+		}
>+		break;
>+	}
> #endif
>+	case DEVTYPE_GPHOTO:
>+		FIXME("gphoto case\n");
>+		break;
>+	default:
>+		FIXME("Unknown devtype %d\n", devices[pSource->deviceIndex].type);
>+		break;
>+	}
>+	return TWRC_SUCCESS;
> }
> 
> /* DG_IMAGE/DAT_IMAGELAYOUT/MSG_GET */
>Index: dlls/twain/dsm_ctrl.c
>===================================================================
>RCS file: /home/wine/wine/dlls/twain/dsm_ctrl.c,v
>retrieving revision 1.13
>diff -u -r1.13 dsm_ctrl.c
>--- dlls/twain/dsm_ctrl.c	28 Mar 2006 18:13:00 -0000	1.13
>+++ dlls/twain/dsm_ctrl.c	9 Apr 2006 19:13:29 -0000
>@@ -2,6 +2,7 @@
>  * TWAIN32 Source Manager
>  *
>  * Copyright 2000 Corel Corporation
>+ * Copyright 2006 Marcus Meissner
>  *
>  * This library is free software; you can redistribute it and/or
>  * modify it under the terms of the GNU Lesser General Public
>@@ -34,54 +35,149 @@
> 
> WINE_DEFAULT_DEBUG_CHANNEL(twain);
> 
>-/* DG_CONTROL/DAT_IDENTITY/MSG_CLOSEDS */
>-TW_UINT16 TWAIN_CloseDS (pTW_IDENTITY pOrigin, TW_MEMREF pData)
>-{
>-#ifndef HAVE_SANE
>-    DSM_twCC = TWCC_NODS;
>-    return TWRC_FAILURE;
>-#else
>-    TW_UINT16 twRC = TWRC_SUCCESS;
>-    pTW_IDENTITY pIdentity = (pTW_IDENTITY) pData;
>-    activeDS *currentDS = NULL, *prevDS = NULL;
>+int nrdevices = 0;
>+struct all_devices *devices = NULL;
> 
>-    TRACE ("DG_CONTROL/DAT_IDENTITY/MSG_CLOSEDS\n");
>+static int detectionrun = 0;
> 
>-    for (currentDS = activeSources; currentDS; currentDS = currentDS->next)
>-    {
>-        if (currentDS->identity.Id == pIdentity->Id)
>-            break;
>-        prevDS = currentDS;
>-    }
>-    if (currentDS)
>-    {
>-        /* Only valid to close a data source if it is in state 4 */
>-        if (currentDS->currentState == 4)
>-        {
>-            sane_close (currentDS->deviceHandle);
>-            /* remove the data source from active data source list */
>-            if (prevDS)
>-                prevDS->next = currentDS->next;
>-            else
>-                activeSources = currentDS->next;
>-            HeapFree (GetProcessHeap(), 0, currentDS);
>-            twRC = TWRC_SUCCESS;
>-            DSM_twCC = TWCC_SUCCESS;
>+static void
>+detect_sane_devices() {
>+#ifdef HAVE_SANE
>+	const SANE_Device **sane_devlist;
>+	int i;
>+
>+	FIXME("detecting sane...\n");
>+	if (sane_get_devices (&sane_devlist, SANE_FALSE) != SANE_STATUS_GOOD)
>+		return;
>+	for (	i=0;
>+		sane_devlist[i]		&&
>+		sane_devlist[i]->model	&&
>+		sane_devlist[i]->vendor	&&
>+		sane_devlist[i]->name;
>+		i++
>+	) {
>+		if (nrdevices)
>+			devices = realloc(devices, sizeof(devices[0])*(nrdevices+1));
>+		else
>+			devices = malloc(sizeof(devices[0]));
>+		devices[nrdevices].type = DEVTYPE_SANE;
>+		devices[nrdevices].u.sane.dev = sane_devlist[i];
>+		nrdevices++;
>+	}
>+	return;
>+#endif
>+}
>+
>+static void
>+detect_gphoto_devices() {
>+#ifdef HAVE_GPHOTO2
>+        int x, count;
>+        CameraList *list;
>+        CameraAbilitiesList *al = NULL;
>+        int  result;
>+
>+        GPPortInfoList *plist = NULL;
>+
>+	TRACE("detecting gphoto...\n");
>+        if (gp_port_info_list_new (&plist) < GP_OK)
>+                return;
>+        result = gp_port_info_list_load (plist);
>+        if (result < 0) {
>+                gp_port_info_list_free (plist);
>+                return;
>         }
>-        else
>-        {
>-            twRC = TWRC_FAILURE;
>-            DSM_twCC = TWCC_SEQERROR;
>+        count = gp_port_info_list_count (plist);
>+	if (count <= 0)
>+		return;
>+        if (gp_list_new (&list) < GP_OK)
>+		return;
>+        gp_abilities_list_new (&al);
>+        gp_abilities_list_load (al, NULL);
>+        gp_abilities_list_detect (al, plist, list, NULL);
>+        gp_abilities_list_free (al);
>+
>+        count = gp_list_count (list);
>+	if (count < GP_OK) {
>+		gp_list_free (list);
>+		return;
>+	}
>+        for (x = 0; x < count; x++) {
>+		const char *s;
>+		if (nrdevices)
>+			devices = realloc(devices, sizeof(devices[0])*(nrdevices+1));
>+		else
>+			devices = malloc(sizeof(devices[0]));
>+		devices[nrdevices].type = DEVTYPE_GPHOTO;
>+                gp_list_get_name  (list, x, &s);
>+                devices[nrdevices].u.gphoto.name = strdup(s);
>+                gp_list_get_value (list, x, &s);
>+                devices[nrdevices].u.gphoto.port = strdup(s);
>+		TRACE("Adding gphoto device %s:%s...\n", devices[nrdevices].u.gphoto.name, devices[nrdevices].u.gphoto.port);
>+		nrdevices++;
>         }
>-    }
>-    else
>-    {
>-        twRC = TWRC_FAILURE;
>-        DSM_twCC = TWCC_NODS;
>-    }
>+        gp_list_free (list);
>+#endif
>+}
> 
>-    return twRC;
>+static void
>+twain_autodetect() {
>+	if (detectionrun) return;
>+	detectionrun = 1;
>+	
>+	detect_sane_devices();
>+	detect_gphoto_devices();
>+}
>+
>+/* DG_CONTROL/DAT_IDENTITY/MSG_CLOSEDS */
>+TW_UINT16 TWAIN_CloseDS (pTW_IDENTITY pOrigin, TW_MEMREF pData)
>+{
>+	TW_UINT16 twRC = TWRC_SUCCESS;
>+	pTW_IDENTITY pIdentity = (pTW_IDENTITY) pData;
>+	activeDS *currentDS = NULL, *prevDS = NULL;
>+
>+	TRACE ("DG_CONTROL/DAT_IDENTITY/MSG_CLOSEDS\n");
>+
>+	for (currentDS = activeSources; currentDS; currentDS = currentDS->next) {
>+		if (currentDS->identity.Id == pIdentity->Id)
>+			break;
>+		prevDS = currentDS;
>+	}
>+	if (currentDS) {
>+		/* Only valid to close a data source if it is in state 4 */
>+		if (currentDS->currentState == 4) {
>+			switch (devices[currentDS->deviceIndex].type) {
>+#ifdef HAVE_SANE
>+			case DEVTYPE_SANE:
>+				sane_close (currentDS->deviceHandle);
>+				break;
>+#endif
>+#ifdef HAVE_GPHOTO2
>+			case DEVTYPE_GPHOTO:
>+				gp_camera_exit (currentDS->camera, NULL);
>+				currentDS->camera = NULL;
>+				break;
> #endif
>+			default:
>+				FIXME("Unknown devtype %d\n", devices[currentDS->deviceIndex].type);
>+				break;
>+			}
>+			/* remove the data source from active data source list */
>+			if (prevDS)
>+				prevDS->next = currentDS->next;
>+			else
>+				activeSources = currentDS->next;
>+			HeapFree (GetProcessHeap(), 0, currentDS);
>+			twRC = TWRC_SUCCESS;
>+			DSM_twCC = TWCC_SUCCESS;
>+		} else {
>+			twRC = TWRC_FAILURE;
>+			DSM_twCC = TWCC_SEQERROR;
>+		}
>+	} else {
>+		twRC = TWRC_FAILURE;
>+		DSM_twCC = TWCC_NODS;
>+	}
>+	return twRC;
> }
> 
> /* Sane returns device names that are longer than the 32 bytes allowed
>@@ -118,229 +214,221 @@
> }
> #endif
> 
>+static int
>+_get_id(pTW_IDENTITY pSourceIdentity, int i) {
>+	pSourceIdentity->Id = DSM_sourceId++;
>+	pSourceIdentity->ProtocolMajor = TWON_PROTOCOLMAJOR;
>+	pSourceIdentity->ProtocolMinor = TWON_PROTOCOLMINOR;
>+
>+	/* FIXME: the default device is not necessarily the first device.  *
>+	 * Users should be able to choose the default device               */
>+	switch (devices[i].type) {
>+#ifdef HAVE_SANE
>+	case DEVTYPE_SANE: {
>+		copy_sane_short_name(devices[i].u.sane.dev->name, pSourceIdentity->ProductName, sizeof(pSourceIdentity->ProductName) - 1);
>+		TRACE("got: %s (short [%s]), %s, %s\n",
>+			devices[i].u.sane.dev->name,
>+			pSourceIdentity->ProductName,
>+			devices[i].u.sane.dev->vendor,
>+			devices[i].u.sane.dev->model
>+		);
>+		lstrcpynA (pSourceIdentity->Manufacturer,  devices[i].u.sane.dev->vendor, sizeof(pSourceIdentity->Manufacturer) - 1);
>+		lstrcpynA (pSourceIdentity->ProductFamily, devices[i].u.sane.dev->model, sizeof(pSourceIdentity->ProductFamily) - 1);
>+		DSM_twCC = TWCC_SUCCESS;
>+		return TWRC_SUCCESS;
>+	}
>+#endif
>+	case DEVTYPE_GPHOTO: {
>+		TRACE ("return gphoto entry %s.%s\n", devices[i].u.gphoto.name, devices[i].u.gphoto.port);
>+		lstrcpynA (pSourceIdentity->Manufacturer, devices[i].u.gphoto.name, sizeof(pSourceIdentity->Manufacturer) - 1);
>+		lstrcpynA (pSourceIdentity->ProductFamily, "GPhoto Camera", sizeof(pSourceIdentity->ProductFamily) - 1);
>+		lstrcpynA (pSourceIdentity->ProductName, devices[i].u.gphoto.port, sizeof(pSourceIdentity->ProductName) - 1);
>+		DSM_twCC = TWCC_SUCCESS;
>+		return TWRC_SUCCESS;
>+	}
>+	default:
>+		TRACE("No TWAIN device available.\n");
>+		DSM_twCC = TWCC_NODS;
>+		return TWRC_FAILURE;
>+	}
>+}
>+
> /* DG_CONTROL/DAT_IDENTITY/MSG_GETDEFAULT */
> TW_UINT16 TWAIN_IdentityGetDefault (pTW_IDENTITY pOrigin, TW_MEMREF pData)
> {
>-#ifndef HAVE_SANE
>-    DSM_twCC = TWCC_NODS;
>-    return TWRC_FAILURE;
>-#else
>-    TW_UINT16 twRC = TWRC_SUCCESS;
>-    pTW_IDENTITY pSourceIdentity = (pTW_IDENTITY) pData;
>-
>-    TRACE("DG_CONTROL/DAT_IDENTITY/MSG_GETDEFAULT\n");
>-
>-    if (!device_list)
>-    {
>-        if ((sane_get_devices (&device_list, SANE_FALSE) != SANE_STATUS_GOOD))
>-        {
>-            DSM_twCC = TWCC_NODS;
>-            return TWRC_FAILURE;
>-        }
>-    }
>-
>-    /* FIXME: the default device is not necessarily the first device.  *
>-     * Users should be able to choose the default device               */
>-    if (device_list && device_list[0])
>-    {
>-        pSourceIdentity->Id = DSM_sourceId ++;
>-        copy_sane_short_name(device_list[0]->name, pSourceIdentity->ProductName, sizeof(pSourceIdentity->ProductName) - 1);
>-        TRACE("got: %s (short [%s]), %s, %s\n", device_list[0]->name, pSourceIdentity->ProductName, device_list[0]->vendor, device_list[0]->model);
>-        lstrcpynA (pSourceIdentity->Manufacturer, device_list[0]->vendor, sizeof(pSourceIdentity->Manufacturer) - 1);
>-        lstrcpynA (pSourceIdentity->ProductFamily, device_list[0]->model, sizeof(pSourceIdentity->ProductFamily) - 1);
>-        pSourceIdentity->ProtocolMajor = TWON_PROTOCOLMAJOR;
>-        pSourceIdentity->ProtocolMinor = TWON_PROTOCOLMINOR;
>-
>-        twRC = TWRC_SUCCESS;
>-        DSM_twCC = TWCC_SUCCESS;
>-
>-    }
>-    else
>-    {
>-        twRC = TWRC_FAILURE;
>-        DSM_twCC = TWCC_NODS;
>-    }
>+	pTW_IDENTITY pSourceIdentity = (pTW_IDENTITY) pData;
> 
>-    return twRC;
>-#endif
>+	TRACE("DG_CONTROL/DAT_IDENTITY/MSG_GETDEFAULT\n");
>+	DSM_twCC = TWCC_NODS;
>+	twain_autodetect();
>+	if (!nrdevices)
>+		return TWRC_FAILURE;
>+	return _get_id(pSourceIdentity,0);
> }
> 
> /* DG_CONTROL/DAT_IDENTITY/MSG_GETFIRST */
> TW_UINT16 TWAIN_IdentityGetFirst (pTW_IDENTITY pOrigin, TW_MEMREF pData)
> {
>-#ifndef HAVE_SANE
>-    DSM_twCC = TWCC_NODS;
>-    return TWRC_FAILURE;
>-#else
>-    TW_UINT16 twRC = TWRC_SUCCESS;
>-    pTW_IDENTITY pSourceIdentity = (pTW_IDENTITY) pData;
>-    SANE_Status status;
>-
>-    TRACE ("DG_CONTROL/DAT_IDENTITY/MSG_GETFIRST\n");
>-
>-    device_list = NULL;
>-    status = sane_get_devices (&device_list, SANE_FALSE);
>-    if (status == SANE_STATUS_GOOD)
>-    {
>-        if (device_list[0])
>-        {
>-            pSourceIdentity->Id = DSM_sourceId ++;
>-            copy_sane_short_name(device_list[0]->name, pSourceIdentity->ProductName, sizeof(pSourceIdentity->ProductName) - 1);
>-            TRACE("got: %s (short [%s]), %s, %s\n", device_list[0]->name, pSourceIdentity->ProductName, device_list[0]->vendor, device_list[0]->model);
>-            lstrcpynA (pSourceIdentity->Manufacturer, device_list[0]->vendor, sizeof(pSourceIdentity->Manufacturer) - 1);
>-            lstrcpynA (pSourceIdentity->ProductFamily, device_list[0]->model, sizeof(pSourceIdentity->ProductFamily) - 1);
>-            pSourceIdentity->ProtocolMajor = TWON_PROTOCOLMAJOR;
>-            pSourceIdentity->ProtocolMinor = TWON_PROTOCOLMINOR;
>-
>-            DSM_currentDevice = 1;
>-            twRC = TWRC_SUCCESS;
>-            DSM_twCC = TWCC_SUCCESS;
>-        }
>-        else
>-        {
>-            TRACE("got empty device list\n");
>-            twRC = TWRC_FAILURE;
>-            DSM_twCC = TWCC_NODS;
>-        }
>-    }
>-    else if (status == SANE_STATUS_NO_MEM)
>-    {
>-        twRC = TWRC_FAILURE;
>-        DSM_twCC = TWCC_LOWMEMORY;
>-    }
>-    else
>-    {
>-        WARN("sane_get_devices() failed: %s\n", sane_strstatus (status));
>-        twRC = TWRC_FAILURE;
>-        DSM_twCC = TWCC_NODS;
>-    }
>+	pTW_IDENTITY pSourceIdentity = (pTW_IDENTITY) pData;
> 
>-    return twRC;
>-#endif
>+	TRACE ("DG_CONTROL/DAT_IDENTITY/MSG_GETFIRST\n");
>+	twain_autodetect();
>+	if (!nrdevices) {
>+		TRACE ("no entries found.\n");
>+		DSM_twCC = TWCC_SUCCESS;
>+		return TWRC_ENDOFLIST;
>+	}
>+	DSM_currentDevice = 0;
>+	return _get_id(pSourceIdentity,DSM_currentDevice++);
> }
> 
> /* DG_CONTROL/DAT_IDENTITY/MSG_GETNEXT */
> TW_UINT16 TWAIN_IdentityGetNext (pTW_IDENTITY pOrigin, TW_MEMREF pData)
> {
>-#ifndef HAVE_SANE
>-    DSM_twCC = TWCC_SUCCESS;
>-    return TWRC_ENDOFLIST;
>-#else
>-    TW_UINT16 twRC = TWRC_SUCCESS;
>-    pTW_IDENTITY pSourceIdentity = (pTW_IDENTITY) pData;
>-
>-    TRACE("DG_CONTROL/DAT_IDENTITY/MSG_GETNEXT\n");
>-
>-    if (device_list && device_list[DSM_currentDevice] &&
>-        device_list[DSM_currentDevice]->name &&
>-        device_list[DSM_currentDevice]->vendor &&
>-        device_list[DSM_currentDevice]->model)
>-    {
>-        pSourceIdentity->Id = DSM_sourceId ++;
>-        copy_sane_short_name(device_list[DSM_currentDevice]->name, pSourceIdentity->ProductName, sizeof(pSourceIdentity->ProductName) - 1);
>-        TRACE("got: %s (short [%s]), %s, %s\n", device_list[DSM_currentDevice]->name, pSourceIdentity->ProductName, device_list[DSM_currentDevice]->vendor, device_list[DSM_currentDevice]->model);
>-        lstrcpynA (pSourceIdentity->Manufacturer, device_list[DSM_currentDevice]->vendor, sizeof(pSourceIdentity->Manufacturer) - 1);
>-        lstrcpynA (pSourceIdentity->ProductFamily, device_list[DSM_currentDevice]->model, sizeof(pSourceIdentity->ProductFamily) - 1);
>-        pSourceIdentity->ProtocolMajor = TWON_PROTOCOLMAJOR;
>-        pSourceIdentity->ProtocolMinor = TWON_PROTOCOLMINOR;
>-        DSM_currentDevice ++;
>-
>-        twRC = TWRC_SUCCESS;
>-        DSM_twCC = TWCC_SUCCESS;
>-    }
>-    else
>-    {
>-        DSM_twCC = TWCC_SUCCESS;
>-        twRC = TWRC_ENDOFLIST;
>-    }
>+	pTW_IDENTITY pSourceIdentity = (pTW_IDENTITY) pData;
> 
>-    return twRC;
>-#endif
>+	TRACE("DG_CONTROL/DAT_IDENTITY/MSG_GETNEXT\n");
>+	if (!nrdevices || (DSM_currentDevice == nrdevices)) {
>+		DSM_twCC = TWCC_SUCCESS;
>+		return TWRC_ENDOFLIST;
>+	}
>+	return _get_id(pSourceIdentity,DSM_currentDevice++);
> }
> 
> /* DG_CONTROL/DAT_IDENTITY/MSG_OPENDS */
> TW_UINT16 TWAIN_OpenDS (pTW_IDENTITY pOrigin, TW_MEMREF pData)
> {
>-#ifndef HAVE_SANE
>-    DSM_twCC = TWCC_NODS;
>-    return TWRC_FAILURE;
>+	TW_UINT16 i = 0;
>+	pTW_IDENTITY pIdentity = (pTW_IDENTITY) pData;
>+	TW_STR32 shortname;
>+	activeDS *newSource;
>+
>+	TRACE("DG_CONTROL/DAT_IDENTITY/MSG_OPENDS\n");
>+
>+	if (DSM_currentState != 3) {
>+		DSM_twCC = TWCC_SEQERROR;
>+		FIXME("sequence error\n");
>+		return TWRC_FAILURE;
>+	}
>+	twain_autodetect();
>+	if (!nrdevices) {
>+		DSM_twCC = TWCC_NODS;
>+		FIXME("No devices.\n");
>+		return TWRC_FAILURE;
>+	}
>+
>+	if (pIdentity->ProductName[0] != '\0') {
>+		/* Make sure the source to be opened exists in the device list */
>+		for (i = 0; i<nrdevices; i++) {
>+			int found = 0;
>+			switch (devices[i].type) {
>+#ifdef HAVE_SANE
>+			case DEVTYPE_SANE:
>+				copy_sane_short_name(devices[i].u.sane.dev->name, shortname, sizeof(shortname) - 1);
>+				if (strcmp (shortname, pIdentity->ProductName) == 0)
>+					found = 1;
>+				break;
>+#endif
>+			case DEVTYPE_GPHOTO:
>+				if (strcmp (devices[i].u.gphoto.port, pIdentity->ProductName) == 0)
>+					found = 1;
>+				break;
>+			default:break;
>+			}
>+			if (found)
>+				break;
>+		}
>+		if (i == nrdevices)
>+			i=0;
>+	} /* else use the first device */
>+
>+	/* the source is found in the device list */
>+	newSource = HeapAlloc (GetProcessHeap(), 0, sizeof (activeDS));
>+	if (!newSource) {
>+		DSM_twCC = TWCC_LOWMEMORY;
>+		FIXME("Out of memory.\n");
>+		return TWRC_FAILURE;
>+	}
>+	newSource->deviceIndex = i;
>+	switch (devices[i].type) {
>+#ifdef HAVE_SANE
>+	case DEVTYPE_SANE: {
>+		SANE_Status status;
>+		status = sane_open(devices[i].u.sane.dev->name,&newSource->deviceHandle);
>+		if (status == SANE_STATUS_GOOD) {
>+			/* Assign name and id for the opened data source */
>+			lstrcpynA (pIdentity->ProductName, shortname, sizeof(pIdentity->ProductName) - 1);
>+			pIdentity->Id = DSM_sourceId ++;
>+			/* add the data source to an internal active source list */
>+			newSource->next = activeSources;
>+			newSource->identity.Id = pIdentity->Id;
>+			strcpy (newSource->identity.ProductName, pIdentity->ProductName);
>+			newSource->currentState = 4; /*transition into state 4*/
>+			newSource->twCC = TWCC_SUCCESS;
>+			activeSources = newSource;
>+			DSM_twCC = TWCC_SUCCESS;
>+			return TWRC_SUCCESS;
>+		} else {
>+			DSM_twCC = TWCC_OPERATIONERROR;
>+			return TWRC_FAILURE;
>+		}
>+		break;
>+	}
>+#endif
>+	case DEVTYPE_GPHOTO: {
>+#ifdef HAVE_GPHOTO2
>+		int ret, m;
>+		CameraAbilities a;
>+        	CameraAbilitiesList *al = NULL;
>+
>+
>+		DSM_twCC = TWCC_OPERATIONERROR;
>+		ret = gp_camera_new (&newSource->camera);
>+		if (ret < GP_OK) {
>+			FIXME("failed to gp_camera_new\n");
>+			return TWRC_FAILURE;
>+		}
>+
>+        	gp_abilities_list_new (&al);
>+        	gp_abilities_list_load (al, NULL);
>+		m = gp_abilities_list_lookup_model (al, devices[i].u.gphoto.name);
>+		if (m < GP_OK) {
>+			FIXME("failed to gp_camera_list_lookup_model\n");
>+			return TWRC_FAILURE;
>+		}
>+		ret = gp_abilities_list_get_abilities (al, m, &a);
>+		if (ret < GP_OK) {
>+			FIXME("failed to gp_camera_list_get_abilities\n");
>+			return TWRC_FAILURE;
>+		}
>+		ret = gp_camera_set_abilities (newSource->camera, a);
>+		if (ret < GP_OK) {
>+			FIXME("failed to gp_camera_set_abilities\n");
>+			return TWRC_FAILURE;
>+		}
>+        	gp_abilities_list_free (al);
>+		/* Assign name and id for the opened data source */
>+		lstrcpynA (pIdentity->ProductName, shortname, sizeof(pIdentity->ProductName) - 1);
>+		pIdentity->Id = DSM_sourceId ++;
>+		/* add the data source to an internal active source list */
>+		newSource->next = activeSources;
>+		newSource->identity.Id = pIdentity->Id;
>+		strcpy (newSource->identity.ProductName, pIdentity->ProductName);
>+		newSource->currentState = 4; /*transition into state 4*/
>+		newSource->twCC = TWCC_SUCCESS;
>+		activeSources = newSource;
>+		DSM_twCC = TWCC_SUCCESS;
>+		TRACE("succeeded opening gphoto camera.\n");
>+		return TWRC_SUCCESS;
> #else
>-    TW_UINT16 twRC = TWRC_SUCCESS, i = 0;
>-    pTW_IDENTITY pIdentity = (pTW_IDENTITY) pData;
>-    TW_STR32 shortname;
>-    activeDS *newSource;
>-    SANE_Status status;
>-
>-    TRACE("DG_CONTROL/DAT_IDENTITY/MSG_OPENDS\n");
>-
>-    if (DSM_currentState != 3)
>-    {
>-        DSM_twCC = TWCC_SEQERROR;
>-        return TWRC_FAILURE;
>-    }
>-
>-    if (!device_list &&
>-       (sane_get_devices (&device_list, SANE_FALSE) != SANE_STATUS_GOOD))
>-    {
>-        DSM_twCC = TWCC_NODS;
>-        return TWRC_FAILURE;
>-    }
>-
>-    if (pIdentity->ProductName[0] != '\0')
>-    {
>-        /* Make sure the source to be opened exists in the device list */
>-        for (i = 0; device_list[i]; i ++)
>-        {
>-            copy_sane_short_name(device_list[i]->name, shortname, sizeof(shortname) - 1);
>-            if (strcmp (shortname, pIdentity->ProductName) == 0)
>-                break;
>-        }
>-
>-    }
>-
>-    if (device_list[i])
>-    {
>-        /* the source is found in the device list */
>-        newSource = HeapAlloc (GetProcessHeap(), 0, sizeof (activeDS));
>-        if (newSource)
>-        {
>-            newSource->deviceIndex = i;
>-            status = sane_open(device_list[i]->name,&newSource->deviceHandle);
>-            if (status == SANE_STATUS_GOOD)
>-            {
>-                /* Assign name and id for the opened data source */
>-                lstrcpynA (pIdentity->ProductName, shortname, sizeof(pIdentity->ProductName) - 1);
>-                pIdentity->Id = DSM_sourceId ++;
>-                /* add the data source to an internal active source list */
>-                newSource->next = activeSources;
>-                newSource->identity.Id = pIdentity->Id;
>-                strcpy (newSource->identity.ProductName, pIdentity->ProductName);
>-                newSource->currentState = 4; /*transition into state 4*/
>-                newSource->twCC = TWCC_SUCCESS;
>-                activeSources = newSource;
>-                twRC = TWRC_SUCCESS;
>-                DSM_twCC = TWCC_SUCCESS;
>-            }
>-            else
>-            {
>-                twRC = TWRC_FAILURE;
>-                DSM_twCC = TWCC_OPERATIONERROR;
>-            }
>-        }
>-        else
>-        {
>-            twRC = TWRC_FAILURE;
>-            DSM_twCC = TWCC_LOWMEMORY;
>-        }
>-    }
>-    else
>-    {
>-        twRC = TWRC_FAILURE;
>-        DSM_twCC = TWCC_NODS;
>-    }
>-
>-    return twRC;
>+		ERR("No gphoto support?\n");
> #endif
>+	}
>+	default:
>+		FIXME("default case, device type %d?\n", devices[i].type);
>+		DSM_twCC = TWCC_OPERATIONERROR;
>+		return TWRC_FAILURE;
>+	}
> }
> 
> /* DG_CONTROL/DAT_IDENTITY/MSG_USERSELECT */
>@@ -351,7 +439,7 @@
> #else
>     TW_UINT16 twRC = TWRC_SUCCESS;
> 
>-    TRACE("DG_CONTROL/DAT_IDENTITY/MSG_USERSELECT\n");
>+    FIXME("DG_CONTROL/DAT_IDENTITY/MSG_USERSELECT\n");
> 
>     /* FIXME: we should replace xscanimage with our own  User Select UI */
>     system("xscanimage");
>@@ -404,50 +492,40 @@
> /* DG_CONTROL/DAT_PARENT/MSG_OPENDSM */
> TW_UINT16 TWAIN_OpenDSM (pTW_IDENTITY pOrigin, TW_MEMREF pData)
> {
>-#ifndef HAVE_SANE
>-    return TWRC_FAILURE;
>-#else
>-    TW_UINT16 twRC = TWRC_SUCCESS;
>-    SANE_Status status;
>-    SANE_Int version_code;
>+	TW_UINT16 twRC = TWRC_SUCCESS;
> 
>-    TRACE("DG_CONTROL/DAT_PARENT/MSG_OPENDSM\n");
>-
>-    if (DSM_currentState == 2)
>-    {
>-        if (!DSM_initialized)
>-        {
>-            DSM_initialized = TRUE;
>-            status = sane_init (&version_code, NULL);
>-            device_list = NULL;
>-            DSM_currentDevice = 0;
>-            DSM_sourceId = 0;
>-        }
>-        DSM_parentHWND = *(TW_HANDLE*)pData;
>-        DSM_currentState = 3; /* transition to state 3 */
>-        DSM_twCC = TWCC_SUCCESS;
>-        twRC = TWRC_SUCCESS;
>-    }
>-    else
>-    {
>-        /* operation invoked in invalid state */
>-        DSM_twCC = TWCC_SEQERROR;
>-        twRC = TWRC_FAILURE;
>-    }
>+	TRACE("DG_CONTROL/DAT_PARENT/MSG_OPENDSM\n");
>+	if (DSM_currentState == 2) {
>+		if (!DSM_initialized) {
>+#ifdef HAVE_SANE
>+			SANE_Status status;
>+			SANE_Int version_code;
> 
>-    return twRC;
>+			status = sane_init (&version_code, NULL);
> #endif
>+			DSM_currentDevice = 0;
>+			DSM_initialized = TRUE;
>+		}
>+		DSM_parentHWND = *(TW_HANDLE*)pData;
>+		DSM_currentState = 3; /* transition to state 3 */
>+		DSM_twCC = TWCC_SUCCESS;
>+		twRC = TWRC_SUCCESS;
>+	} else {
>+		/* operation invoked in invalid state */
>+		DSM_twCC = TWCC_SEQERROR;
>+		twRC = TWRC_FAILURE;
>+	}
>+	return twRC;
> }
> 
> /* DG_CONTROL/DAT_STATUS/MSG_GET */
> TW_UINT16 TWAIN_GetDSMStatus (pTW_IDENTITY pOrigin, TW_MEMREF pData)
> {
>-    pTW_STATUS pSourceStatus = (pTW_STATUS) pData;
>+	pTW_STATUS pSourceStatus = (pTW_STATUS) pData;
> 
>-    TRACE ("DG_CONTROL/DAT_STATUS/MSG_GET\n");
>+	TRACE ("DG_CONTROL/DAT_STATUS/MSG_GET\n");
> 
>-    pSourceStatus->ConditionCode = DSM_twCC;
>-    DSM_twCC = TWCC_SUCCESS;  /* clear the condition code */
>-
>-    return TWRC_SUCCESS;
>+	pSourceStatus->ConditionCode = DSM_twCC;
>+	DSM_twCC = TWCC_SUCCESS;  /* clear the condition code */
>+	return TWRC_SUCCESS;
> }
>Index: dlls/twain/twain_i.h
>===================================================================
>RCS file: /home/wine/wine/dlls/twain/twain_i.h,v
>retrieving revision 1.8
>diff -u -r1.8 twain_i.h
>--- dlls/twain/twain_i.h	28 Mar 2006 18:13:00 -0000	1.8
>+++ dlls/twain/twain_i.h	9 Apr 2006 19:13:29 -0000
>@@ -28,6 +28,13 @@
> #endif
> #include <stdarg.h>
> 
>+#ifdef HAVE_GPHOTO2
>+/* Hack ... otherwise gphoto2 thinks its on Windows */
>+# undef WIN32
>+# include <gphoto2/gphoto2-camera.h>
>+# define WIN32
>+#endif
>+
> #include "windef.h"
> #include "winbase.h"
> #include "twain.h"
>@@ -43,12 +50,15 @@
>     TW_UINT16		twCC;			/* condition code */
>     HWND		hwndOwner;		/* window handle of the app */
>     HWND		progressWnd;		/* window handle of the scanning window */
>+    INT                 deviceIndex;		/* index of the current device */
> #ifdef HAVE_SANE
>     SANE_Handle		deviceHandle;		/* device handle */
>     SANE_Parameters     sane_param;             /* parameters about the image
>                                                    transferred */
>-    BOOL                sane_param_valid;  /* true if valid sane_param*/
>-    INT                 deviceIndex;    /* index of the current device */
>+    BOOL                sane_param_valid;	/* true if valid sane_param*/
>+#endif
>+#ifdef HAVE_GPHOTO2
>+    Camera		*camera;
> #endif
>     /* Capabiblities */
>     TW_UINT16		capXferMech;		/* ICAP_XFERMECH */
>@@ -62,9 +72,33 @@
> TW_UINT16 DSM_currentDevice;    /* keep track of device during enumeration */
> HINSTANCE DSM_instance;
> 
>+enum twain_devtype {
>+	DEVTYPE_SANE,
>+	DEVTYPE_GPHOTO
>+};
>+
>+struct sane_device {
> #ifdef HAVE_SANE
>-const SANE_Device **device_list;/* a list of all sane devices */
>+	const SANE_Device	*dev;
> #endif
>+};
>+
>+struct gphoto_device {
>+	const char *name;
>+	const char *port;
>+};
>+
>+struct all_devices {
>+	enum	twain_devtype	type;
>+	union {
>+		struct sane_device	sane;
>+		struct gphoto_device	gphoto;
>+	} u;
>+};
>+
>+extern int nrdevices;
>+extern struct all_devices *devices;
>+
> activeDS *activeSources;	/* list of active data sources */
> 
> /* Helper functions */
>Index: dlls/twain/ui.c
>===================================================================
>RCS file: /home/wine/wine/dlls/twain/ui.c,v
>retrieving revision 1.1
>diff -u -r1.1 ui.c
>--- dlls/twain/ui.c	28 Mar 2006 18:13:00 -0000	1.1
>+++ dlls/twain/ui.c	9 Apr 2006 19:13:30 -0000
>@@ -574,15 +574,15 @@
>         index ++;
>     }
>  
>-    len = lstrlenA(device_list[pSource->deviceIndex]->vendor)
>-         + lstrlenA(device_list[pSource->deviceIndex]->model) + 2;
>+    len = lstrlenA(devices[pSource->deviceIndex].u.sane.dev->vendor)
>+         + lstrlenA(devices[pSource->deviceIndex].u.sane.dev->model) + 2;
> 
>     szCaption = HeapAlloc(GetProcessHeap(),0,len *sizeof(WCHAR));
>-    MultiByteToWideChar(CP_ACP,0,device_list[pSource->deviceIndex]->vendor,-1,
>+    MultiByteToWideChar(CP_ACP,0,devices[pSource->deviceIndex].u.sane.dev->vendor,-1,
>             szCaption,len);
>-    szCaption[lstrlenA(device_list[pSource->deviceIndex]->vendor)] = ' ';
>-    MultiByteToWideChar(CP_ACP,0,device_list[pSource->deviceIndex]->model,-1,
>-            &szCaption[lstrlenA(device_list[pSource->deviceIndex]->vendor)+1],len);
>+    szCaption[lstrlenA(devices[pSource->deviceIndex].u.sane.dev->vendor)] = ' ';
>+    MultiByteToWideChar(CP_ACP,0,devices[pSource->deviceIndex].u.sane.dev->model,-1,
>+            &szCaption[lstrlenA(devices[pSource->deviceIndex].u.sane.dev->vendor)+1],len);
>     
>     psh.dwSize = sizeof(PROPSHEETHEADERW);
>     psh.dwFlags = PSH_PROPSHEETPAGE|PSH_PROPTITLE|PSH_USECALLBACK;
>
>
>  
>




More information about the wine-devel mailing list