gtk2/gdk/win32/gdkselection-win32.c
Руслан Ижбулатов 6c29e81051 W32: Massive W32 DnD fix
Massive changes to OLE2 DnD protocol, which was completely broken before:
* Keep GdkDragContext and OLE2 objects separate (don't ref/unref them
  together, don't necessarily create them together).
* Keep IDataObject formats in the object itself, not in a global variable.
* Fix getdata() to look up the request target in its format list, not in the
  global hash table
* Create target GdkDragContext on each drag_enter, destroy it on drag_leave,
  whereas IDropTarget is created when a window becomes a drag destination
  and is re-used indefinitely.
* Query the source IDataObject for its supported types, cache them in the
  target (!) context. This is how GTK+ works, honestly.
* Remember current_src_object when we initiate a drag, to be able
  to detect later on that the data object is ours and use a
  shortcut when querying targets
* Make sure GDK_DRAG_MOTION is only sent when something changes
* Support GTK drag cursors
* Ensure that exotic GTK clipboard formats are registered
  (but try to avoid registering formats that can't be used between applications).
* Don't enumerate internal formats
* Ensure that DnD indicator window can't accept drags or receive any kind of input
  (use WS_EX_TRANSPARENT).
* Remove unneeded indentation in _gdk_win32_dnd_do_dragdrop()
* Fix indentation in gdk_win32_drag_context_drop_finish()
* Remove obsolete comments in _gdk_win32_window_register_dnd()
* Check for DnD in progress when processing WM_KILLFOCUS, don't emit a grab
  break event in such cases (this allows alt-tabbing while DnD is in progress,
  though there may be lingering issues with focus after dropping...)
* Support Shell ID List -> text/uri-list conversion, now it's possible
  to drop files (dragged from Explorer) on GTK+ applications
* Explicitly use RegisterClipboardFormatA() when we know that the string
  is not in unicode. Otherwise explicitly use RegisterClipboardFormatW()
  with a UTF8->UTF16 converted string
* Fix _gdk_win32_display_get_selection_owner() to correctly bail
  when selection owner HWND is NULL (looking up GdkWindow for NULL
  HWND always succeeds and returns the root window - not the intended
  effect)
* More logging
* Send DROP_FINISHED event after DnD loop ends
* Send STATUS event on feedback
* Move GetKeyboardState() and related code into _gdk_win32_window_drag_begin(),
  so that it's closer to the point where last_pt and start_pt are set
* Use & 0x80 to check for the key being pressed. Windows will set low-order bit
  to 1 for all mouse buttons to indicate that they are toggled, so simply
  checking for the value not being 0 is not enough anymore.
  This is probably a new thing in modern W32 that didn't exist before
  (OLE2 DnD code is old).
* Fixed (hopefully) and simplified HiDPI parts of the code.

Also adds managed DnD implementation for W32 GDK backend (for both
OLE2 and LOCAL protocols). Mostly a copy of the X11 backend code, but
there are some minor differences:
* doesn't use drag_window field in GdkDragContext,
  uses the one in GdkWin32DragContext exclusively
* subtracts hotspot offset from the window coordinates when showing
  the dragback animation
* tries to consistently support scaling and caches the scale
  in the context
* Some keynav code is removed (places where grabbing/ungrabbing should
  happen is marked with TODOs), and the rest is probably inert.

Also significantly changes the way selection (and clipboard) is handled
(as MSDN rightly notes, the handling for DnD and Clipboard
 formats is virtually the same, so it makes sense to handle
 both with the same code):
* Don't spam GDK_OWNER_CHANGE, send them only when owner
  actually changes
* Open clipboard when our process becomes the clipboard owner
  (we are doing it anyway, to empty the clipboard and *become* the owner),
  and then don't close it until a scheduled selection request event
  (with TARGETS target) is received. Process that event by announcing
  all of our supported formats (by that time add_targets() should have
  been called up the stack, thus the formats are known; just in case,
  add_targets() will also schedule a selection request, if one isn't
  scheduled already, so that late-coming formats can still be announced).
* Allow clipboard opening for selection_convert() to be delayed if it
  fails initially.
* The last two points above should fix all the bugs about GTK+ rising
  too much ruckus over OpenClipboard() failures, as owner change
  *is allowed* to fail (though not all callers currently handle
  that case), and selection_convert() is asynchronous to begin with.
  Still, this is somewhat risky, as there's a possibility that the
  code will work in unexpected ways and the clipboard will remain open.
  There's now logging to track the clipboard being opened and closed,
  and a number of failsafes that try to ensure that it isn't kept open
  for no reason.
* Added copious notes on the way clipboard works on X11, Windows and GDK-W32,
  also removed old comments in DnD implementation, replaced some of them
  with the new ones
* A lot of crufty module-global variables are stuffed into a singleton
  object, GdkWin32Selection. It's technically possible to make it a
  sub-object of the Display object (the way Wayland backend does),
  but since Display object on W32 is a singleton anyway... why bother?
* Fixed the send_change_events() a bit (was slightly broken in one of the
  previous iterations)
* Ensure that there's no confusion between selection conversion (an artifact
  term from X11) and selection transmutation (changing the data to be W32-compatible)
* Put all the transmutation code and format-target-matching code into gdkselection-win32.c,
  now this code isn't spread across multiple files.
* Consequently, moved some code away from gdkproperty-win32.c and gdkdnd-win32.c
* Extensive format transmutation checks for OLE2 DnD and clipboard.
  We now keep track of which format mappings are for transmutations,
  and which aren't (for example, when formats are passed as-is, or when
  a registered name is just an alias)
* Put transmutation code into separate functions

* Ensure that drop target keeps a format->target map for supported formats,
  this is useful when selection_convert() is called, as it only receives a
  single target and no hints on the format from which the data should
  be transmuted into this target.
* Add clear_targets() on W32, to de called by GTK
* Use g_set_object() instead of g_ref_object() where it is allowed.
* Fix indentation (and convert tabs to spaces), remove unused variables

(This commit is cherry-picked from the gtk-3-22 branch)

https://bugzilla.gnome.org/show_bug.cgi?id=786509
2017-12-02 10:38:34 +00:00

2865 lines
92 KiB
C

/* GDK - The GIMP Drawing Kit
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
* Copyright (C) 1998-2002 Tor Lillqvist
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Modified by the GTK+ Team and others 1997-2000. See the AUTHORS
* file for a list of people on the GTK+ Team. See the ChangeLog
* files for a list of changes. These files are distributed with
* GTK+ at ftp://ftp.gtk.org/pub/gtk/.
*/
/*
GTK+ selection works like this:
There are three selections that matter - GDK_SELECTION_CLIPBOARD,
GDK_SELECTION_PRIMARY and DND. Primary selection is only handled
internally by GTK+ (it's not portable to Windows). DND is actually
represented by two selections - LOCAL and OLE2, one for each DnD protocol,
but they work the same way.
"Target" is a GdkAtom describing a clipboard format.
For Clipboard:
GTK+ calls gtk_clipboard_set_contents(), which first ensures the
clipboard is owned by the clipboard widget (which also indirectly
causes an SelectionRequest xevent to be sent to it), then clears the old
supported targets from the clipboard, then adds all the
targets it's given to the clipboard. No data is sent anywhere.
gtk_clipboard_set_contents() is also given a callback to invoke when
the actual data is needed. This callback is implemented by the widget
from which the data can be put into clipboard.
GTK+ might also call gtk_clipboard_set_can_store(), which sets the
targets for which the data can be put into system clipboard, so that
it remains usable even if the application is no longer around. Usually
all data formats are storable, except for the shortcut formats, which
refer to actual widgets directly, and are thus only working while
the application is alive.
("C:" means clipboard client (requestor), "S:" means clipboard server (provider))
When something needs to be obtained from clipboard, GTK+ calls
C: gtk_selection_convert().
That function has a shortcut where it directly gets the selection contents by calling
S: gtk_selection_invoke_handler(),
asking the widget to provide data, and then calling
C: gtk_selection_retrieval_report()
to report the data back to the caller.
If that shortcut isn't possible (selection is owned by another process),
gtk_selection_convert() calls
C:gdk_selection_convert() (_gdk_x11_display_convert_selection())
On X11 gdk_selection_convert() just calls
C:XConvertSelection(),
which sends SelectionRequest xevent to the window that owns the selection.
The client gives its clipboard window as the requestor for that event,
and gives the property as GDK_SELECTION.
Server-side GTK+ catches SelectionRequest in a
S:_gtk_selection_request()
event handler, which calls
S:gtk_selection_invoke_handler()
to get the data, and then calls
S:gdk_property_change() (_gdk_x11_window_change_property())
to submit the data, by setting the property given by the message sender
(GDK_SELECTION) on the requestor window (our client clipboard window).
On X11 data submission takes from of
S:XChangeProperty()
call, which causes SelectionNotify (and PropertyNotify for INCR)
xevent to be sent, which client-side GTK+ catches and handles in
C:_gtk_selection_notify()
(and
C:_gtk_selection_property_notify(),
for INCR)
event handler, which calls
C:gtk_selection_retrieval_report()
to report back to the caller. The caller gets the property
data from the window, and returns it up the stack.
On X11 the "TARGETS" target might be given in a SelectionRequest xmessage to request
all supported targets for a selection.
If data must be stored on the clipboard, because the application is quitting,
GTK+ will call
S:gdk_clipboard_store() -> gdk_display_store_clipboard() (gdk_x11_display_store_clipboard())
on all the clipboards it owns.
X11 gdk_display_store_clipboard() puts a list of storeable targets into GDK_SELECTION
property of the clipboard window, then calls
S:XConvertSelection()
on the clipboard manager window (retrieved from the CLIPBOARD_MANAGER atom),
and the clipboard manager responds by requesting all these formats and storing the data,
then responds with SelectionNotify xevent to allow the application to quit.
When clipboard owner changes, the old owner receives SelectionClear xevent,
GTK+ handles it by clearing the clipboard object on its own level, GDK
is not involved.
On Windows:
Clipboard is opened by OpenClipboard(), emptied by EmptyClipboard() (which also
makes the window the clipboard owner), data is put into it by SetClipboardData().
Clipboard is closed with CloseClipboard().
If SetClipboardData() is given a NULL data value, the owner will later
receive WM_RENDERFORMAT message, in response to which it must call
SetClipboardData() with the provided handle and the actual data this time.
This way applications can avoid storing everything in the clipboard
all the time, only putting the data there as it is requested by other applications.
At some undefined points of time an application might get WM_RENDERALLFORMATS
message, it should respond by opening the clipboard and rendering
into it all the data that it offers, as if responding to multiple WM_RENDERFORMAT
messages.
On GDK-Win32:
GTK+ calls gtk_clipboard_set_contents(), which first ensures the
clipboard is owned by the clipboard widget (calls OpenClipboard(),
then EmptyClipboard() to become the owner, then
sends a TARGETS GDK_SELECTION_REQUEST to itself, without closing the clipboard),
then clears the old supported targets from the clipboard, then adds all the
targets it's given to the clipboard. No data is sent anywhere.
gtk_clipboard_set_contents() is also given a callback to invoke when
the actual data is needed. This callback is implemented by the widget
from which the data can be put into clipboard.
GTK+ might also call gtk_clipboard_set_can_store(), which sets the
targets for which the data can be put into system clipboard, so that
it remains usable even if the application is no longer around. Usually
all data formats are storable, except for the shortcut formats, which
refer to actual widgets directly, and are thus only working while
the application is alive.
("C:" means clipboard client (requestor), "S:" means clipboard server (provider))
("transmute" here means "change the format of some data"; this term is used here
instead of "convert" to avoid clashing with g(t|d)k_selection_convert(), which
is completely unrelated)
When something needs to be obtained from clipboard, GTK+ calls
C: gtk_selection_convert().
That function has a shortcut where it directly gets the selection contents by calling
S: gtk_selection_invoke_handler(),
asking the widget to provide data, and then calling
C: gtk_selection_retrieval_report()
to report the data back to the caller.
If that shortcut isn't possible (selection is owned by another process),
gtk_selection_convert() calls
C:gdk_selection_convert() (_gdk_win32_display_convert_selection())
On GDK-Win32 gdk_selection_convert() just calls
C:OpenClipboard()
to open clipboard (if that fails, it shedules a timeout to regularly
try to open clipboard for the next 30 seconds, and do the actions
outlined below once the clipboard is opened, or notify about
conversion failure after 30 seconds),
C:EnumClipboardFormats() (2000+)
to get the list of supported formats, figures out the format it should
use to request the data (first it looks for supported formats with names
that match the target name, then looks through compatibility
formats for the target and checks whether these are supported).
Note that it has no list of supported targets at hand,
just the single requested target, and thus it might have
to do some transmutation between formats; the caller up the stack
either only supports just one format that it asks for,
or supports multiple formats and asks for them in sequence (from
the most preferred to the least preferred), until one call succeeds,
or supports multiple formats and asks for the TARGETS format first,
and then figures out what to ask for - GDK can't know that.
Either way, GDK has to call
C:GetClipboardData()
to get the data (this causes WM_RENDERFORMAT to be sent to the owner,
if the owner uses delayed rendering for the requested format, otherwise
it just picks the data right from the OS)
Server-side GDK catches WM_RENDERFORMAT, figures out a target
to request (this one is easier, as it has the list of supported
targets saved up), and posts a GDK_SELECTION_REQUEST event, then runs the main loop,
while GTK+ catches the event in a
S:_gtk_selection_request()
event handler, which calls
S:gtk_selection_invoke_handler()
to get the data, and then calls
S:gdk_property_change() (_gdk_win32_window_change_property())
to submit the data, by first transmuting it to the format actually requested
by the sender of WM_RENDERFORMAT, and then by returning thedata back up the stack,
to the WM_RENDERFORMAT handler, which then calls
S:SetClipboardData()
with the handle provided by the sender.
Meanwhile, the client code, still in
C:_gdk_win32_display_convert_selection(),
gets the data in response to GetClipboardData(),
transmutes it (if needed) to the target format, sets the requested
window property to that data (unlike change_property!),
calls
C:CloseClipboard() (if there are no more clipboard opeartions
scheduled)
and posts a GDK_SELECTION_NOTIFY event, which GTK+ catches in
C:_gtk_selection_notify()
event handler, which calls
C:gtk_selection_retrieval_report()
to report back to the caller. The caller gets the property
data from the window, and returns it up the stack.
On GDK-Win32 the "TARGETS" target might be given in a GDK_SELECTION_REQUEST to request
all supported targets for a selection.
Note that this server side -
client side should call gdk_selection_convert() -> gdk_selection_convert() with "TARGETS" target
to get the list of targets offered by the clipboard holder. It never causes GDK_SELECTION_REQUEST
to be generated, just queries the system clipboard.
On server side GDK_SELECTION_REQUEST is only generated internally:
in response to WM_RENDERFORMAT (it renders a target),
in response to idataobject_getdata() (it renders a target),
after DnD ends (with a DELETE target, this is caught by GTK to make it delete the selection),
and in response to owner change, with TARGETS target, which makes it register its formats by calling
S:SetClipboardData(..., NULL)
If data must be stored on the clipboard, because the application is quitting,
GTK+ will call
S:gdk_clipboard_store() -> gdk_display_store_clipboard() (gdk_win32_display_store_clipboard())
on all the clipboards it owns.
GDK-Win32 gdk_display_store_clipboard() sends WM_RENDERALLFORMATS to the window,
then posts a GDK_SELECTION_NOTIFY event allow the application to quit.
When clipboard owner changes, the old owner receives WM_DESTROYCLIPBOARD message,
GDK handles it by posting a GDK_SELECTION_CLEAR event, which
GTK+ handles by clearing the clipboard object on its own level.
Any operations that require OpenClipboard()/CloseClipboard() combo (i.e.
everything, except for WM_RENDERFORMAT handling) must be put into a queue,
and then a once-per-second-for-up-to-30-seconds timeout must be added.
The timeout function must call OpenClipboard(),
and then proceed to perform the queued actions on the clipboard, once it opened,
or return and try again a second later, as long as there are still items in the queue,
and remove the queue items that are older than 30 seconds.
Once the queue is empty, the clipboard is closed.
DND:
GDK-Win32:
S:idataobject_getdata()
sends a GDK_SELECTION_REQUEST event, which results in a call to
S:_gdk_win32_window_change_property()
which passes clipboard data back via the selection singleton.
GDK-Win32 uses delayed rendering for all formats, even text.
GTK+ will call
C:gtk_selection_convert() -> gdk_selection_convert() (_gdk_win32_display_convert_selection())
to get the data associated with the drag, when GTK+ apps want to inspect the data,
but with a OLE2_DND selection instead of CLIPBOARD selection.
_gdk_win32_display_convert_selection() queries the droptarget global variable,
which should already contain a matched list of supported formats and targets,
picks a format there, then queries it from the IDataObject that the droptarget kept around.
Then optionally transmutes the data, and sets the property. Then posts GDK_SELECTION_NOTIFY.
GTK+ catches that event and processes it, causeing "selection-received" signal to
be emitted on the selection widget, and its handler is
C:gtk_drag_selection_received(),
which emits the "drag-data-received" signal for the app.
*/
#include "config.h"
#include <string.h>
#include <stdlib.h>
/* For C-style COM wrapper macros */
#define COBJMACROS
/* for CIDA */
#include <shlobj.h>
#include "gdkproperty.h"
#include "gdkselection.h"
#include "gdkdisplay.h"
#include "gdkprivate-win32.h"
#include "gdkselection-win32.h"
#include "gdk/gdkdndprivate.h"
#include "gdkwin32dnd-private.h"
#include "gdkwin32.h"
typedef struct _GdkWin32ClipboardQueueInfo GdkWin32ClipboardQueueInfo;
typedef enum _GdkWin32ClipboardQueueAction GdkWin32ClipboardQueueAction;
enum _GdkWin32ClipboardQueueAction
{
GDK_WIN32_CLIPBOARD_QUEUE_ACTION_CONVERT = 0,
GDK_WIN32_CLIPBOARD_QUEUE_ACTION_TARGETS
};
struct _GdkWin32ClipboardQueueInfo
{
GdkDisplay *display;
GdkWindow *requestor;
GdkAtom selection;
GdkAtom target;
guint32 time;
/* Number of seconds since we started our
* attempts to open clipboard.
*/
guint32 idle_time;
/* What to do once clipboard is opened */
GdkWin32ClipboardQueueAction action;
};
/* List of GdkWin32ClipboardQueueInfo slices */
static GList *clipboard_queue = NULL;
#define HIDA_GetPIDLFolder(pida) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[0])
#define HIDA_GetPIDLItem(pida, i) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[i+1])
G_DEFINE_TYPE (GdkWin32Selection, gdk_win32_selection, G_TYPE_OBJECT)
static void
gdk_win32_selection_class_init (GdkWin32SelectionClass *klass)
{
}
void
_gdk_win32_selection_init (void)
{
_win32_selection = GDK_WIN32_SELECTION (g_object_new (GDK_TYPE_WIN32_SELECTION, NULL));
}
static void
gdk_win32_selection_init (GdkWin32Selection *win32_selection)
{
GArray *atoms;
GArray *cfs;
GSList *pixbuf_formats;
GSList *rover;
int i;
GArray *comp;
GdkSelTargetFormat fmt;
win32_selection->ignore_destroy_clipboard = FALSE;
win32_selection->clipboard_opened_for = INVALID_HANDLE_VALUE;
win32_selection->dnd_target_state = GDK_WIN32_DND_NONE;
win32_selection->dnd_source_state = GDK_WIN32_DND_NONE;
win32_selection->dnd_data_object_target = NULL;
win32_selection->property_change_format = 0;
win32_selection->property_change_data = NULL;
atoms = g_array_sized_new (FALSE, TRUE, sizeof (GdkAtom), GDK_WIN32_ATOM_INDEX_LAST);
g_array_set_size (atoms, GDK_WIN32_ATOM_INDEX_LAST);
cfs = g_array_sized_new (FALSE, TRUE, sizeof (UINT), GDK_WIN32_CF_INDEX_LAST);
g_array_set_size (cfs, GDK_WIN32_CF_INDEX_LAST);
win32_selection->known_atoms = atoms;
win32_selection->known_clipboard_formats = cfs;
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_GDK_SELECTION) = gdk_atom_intern_static_string ("GDK_SELECTION");
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_CLIPBOARD_MANAGER) = gdk_atom_intern_static_string ("CLIPBOARD_MANAGER");
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_WM_TRANSIENT_FOR) = gdk_atom_intern_static_string ("WM_TRANSIENT_FOR");
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_TARGETS) = gdk_atom_intern_static_string ("TARGETS");
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_DELETE) = gdk_atom_intern_static_string ("DELETE");
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_SAVE_TARGETS) = gdk_atom_intern_static_string ("SAVE_TARGETS");
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_UTF8_STRING) = gdk_atom_intern_static_string ("UTF8_STRING");
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_TEXT) = gdk_atom_intern_static_string ("TEXT");
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_COMPOUND_TEXT) = gdk_atom_intern_static_string ("COMPOUND_TEXT");
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_TEXT_URI_LIST) = gdk_atom_intern_static_string ("text/uri-list");
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_TEXT_HTML) = gdk_atom_intern_static_string ("text/html");
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_PNG) = gdk_atom_intern_static_string ("image/png");
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_JPEG) = gdk_atom_intern_static_string ("image/jpeg");
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_BMP) = gdk_atom_intern_static_string ("image/bmp");
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_GIF) = gdk_atom_intern_static_string ("image/gif");
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_LOCAL_DND_SELECTION) = gdk_atom_intern_static_string ("LocalDndSelection");
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_DROPFILES_DND) = gdk_atom_intern_static_string ("DROPFILES_DND");
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_OLE2_DND) = gdk_atom_intern_static_string ("OLE2_DND");
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_PNG)= gdk_atom_intern_static_string ("PNG");
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_JFIF) = gdk_atom_intern_static_string ("JFIF");
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_GIF) = gdk_atom_intern_static_string ("GIF");
/* These are a bit unusual. It's here to allow GTK+ applications
* to actually support CF_DIB and Shell ID List clipboard formats on their own,
* instead of allowing GDK to use them internally for interoperability.
*/
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_CF_DIB) = gdk_atom_intern_static_string ("CF_DIB");
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_CFSTR_SHELLIDLIST) = gdk_atom_intern_static_string (CFSTR_SHELLIDLIST);
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_CF_UNICODETEXT) = gdk_atom_intern_static_string ("CF_UNICODETEXT");
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_CF_TEXT) = gdk_atom_intern_static_string ("CF_TEXT");
/* MS Office 2007, at least, offers images in common file formats
* using clipboard format names like "PNG" and "JFIF". So we follow
* the lead and map the GDK target name "image/png" to the clipboard
* format name "PNG" etc.
*/
_gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_PNG) = RegisterClipboardFormatA ("PNG");
_gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_JFIF) = RegisterClipboardFormatA ("JFIF");
_gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_GIF) = RegisterClipboardFormatA ("GIF");
_gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_UNIFORMRESOURCELOCATORW) = RegisterClipboardFormatA ("UniformResourceLocatorW");
_gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_CFSTR_SHELLIDLIST) = RegisterClipboardFormatA (CFSTR_SHELLIDLIST);
_gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_HTML_FORMAT) = RegisterClipboardFormatA ("HTML Format");
_gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_TEXT_HTML) = RegisterClipboardFormatA ("text/html");
_gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_IMAGE_PNG) = RegisterClipboardFormatA ("image/png");
_gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_IMAGE_JPEG) = RegisterClipboardFormatA ("image/jpeg");
_gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_IMAGE_BMP) = RegisterClipboardFormatA ("image/bmp");
_gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_IMAGE_GIF) = RegisterClipboardFormatA ("image/gif");
_gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_TEXT_URI_LIST) = RegisterClipboardFormatA ("text/uri-list");
_gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_UTF8_STRING) = RegisterClipboardFormatA ("UTF8_STRING");
win32_selection->sel_prop_table = g_hash_table_new (NULL, NULL);
win32_selection->sel_owner_table = g_hash_table_new (NULL, NULL);
pixbuf_formats = gdk_pixbuf_get_formats ();
win32_selection->n_known_pixbuf_formats = 0;
for (rover = pixbuf_formats; rover != NULL; rover = rover->next)
{
gchar **mime_types =
gdk_pixbuf_format_get_mime_types ((GdkPixbufFormat *) rover->data);
gchar **mime_type;
for (mime_type = mime_types; *mime_type != NULL; mime_type++)
win32_selection->n_known_pixbuf_formats++;
}
win32_selection->known_pixbuf_formats = g_new (GdkAtom, win32_selection->n_known_pixbuf_formats);
i = 0;
for (rover = pixbuf_formats; rover != NULL; rover = rover->next)
{
gchar **mime_types =
gdk_pixbuf_format_get_mime_types ((GdkPixbufFormat *) rover->data);
gchar **mime_type;
for (mime_type = mime_types; *mime_type != NULL; mime_type++)
win32_selection->known_pixbuf_formats[i++] = gdk_atom_intern (*mime_type, FALSE);
}
g_slist_free (pixbuf_formats);
win32_selection->dnd_selection_targets = g_array_new (FALSE, FALSE, sizeof (GdkSelTargetFormat));
win32_selection->clipboard_selection_targets = g_array_new (FALSE, FALSE, sizeof (GdkSelTargetFormat));
win32_selection->compatibility_formats = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) g_array_unref);
/* GTK+ actually has more text formats, but it's unlikely that we'd
* get anything other than UTF8_STRING these days.
* GTKTEXTBUFFERCONTENTS format can potentially be converted to
* W32-compatible rich text format, but that's too complex to address right now.
*/
comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkSelTargetFormat), 3);
fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_UTF8_STRING);
fmt.format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_UTF8_STRING);
fmt.transmute = FALSE;
g_array_append_val (comp, fmt);
fmt.format = CF_UNICODETEXT;
fmt.transmute = TRUE;
g_array_append_val (comp, fmt);
fmt.format = CF_TEXT;
g_array_append_val (comp, fmt);
g_hash_table_replace (win32_selection->compatibility_formats, fmt.target, comp);
comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkSelTargetFormat), 3);
fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_PNG);
fmt.format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_IMAGE_PNG);
fmt.transmute = FALSE;
g_array_append_val (comp, fmt);
fmt.format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_PNG);
g_array_append_val (comp, fmt);
fmt.format = CF_DIB;
fmt.transmute = TRUE;
g_array_append_val (comp, fmt);
g_hash_table_replace (win32_selection->compatibility_formats, fmt.target, comp);
comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkSelTargetFormat), 4);
fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_JPEG);
fmt.format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_IMAGE_JPEG);
fmt.transmute = FALSE;
g_array_append_val (comp, fmt);
fmt.format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_JFIF);
g_array_append_val (comp, fmt);
fmt.format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_PNG);
fmt.transmute = TRUE;
g_array_append_val (comp, fmt);
fmt.format = CF_DIB;
g_array_append_val (comp, fmt);
g_hash_table_replace (win32_selection->compatibility_formats, fmt.target, comp);
comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkSelTargetFormat), 4);
fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_GIF);
fmt.format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_IMAGE_GIF);
fmt.transmute = FALSE;
g_array_append_val (comp, fmt);
fmt.format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_GIF);
g_array_append_val (comp, fmt);
fmt.format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_PNG);
fmt.transmute = TRUE;
g_array_append_val (comp, fmt);
fmt.format = CF_DIB;
g_array_append_val (comp, fmt);
g_hash_table_replace (win32_selection->compatibility_formats, fmt.target, comp);
comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkSelTargetFormat), 2);
fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_BMP);
fmt.format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_IMAGE_BMP);
fmt.transmute = FALSE;
g_array_append_val (comp, fmt);
fmt.format = CF_DIB;
fmt.transmute = TRUE;
g_array_append_val (comp, fmt);
g_hash_table_replace (win32_selection->compatibility_formats, fmt.target, comp);
/* Not implemented, but definitely possible
comp = g_array_sized_new (FALSE, FALSE, 2);
fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_TEXT_URI_LIST);
fmt.format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_TEXT_URI_LIST);
fmt.transmute = FALSE;
g_array_append_val (comp, fmt);
fmt.format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_CFSTR_SHELLIDLIST);
fmt.transmute = TRUE;
g_array_append_val (comp, fmt);
g_hash_table_replace (win32_selection->compatibility_formats, fmt.target, comp);
*/
win32_selection->compatibility_targets = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) g_array_unref);
comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkSelTargetFormat), 2);
fmt.format = CF_TEXT;
fmt.transmute = FALSE;
fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_CF_TEXT);
g_array_append_val (comp, fmt);
fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_UTF8_STRING);
fmt.transmute = TRUE;
g_array_append_val (comp, fmt);
g_hash_table_replace (win32_selection->compatibility_targets, GINT_TO_POINTER (CF_TEXT), comp);
comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkSelTargetFormat), 2);
fmt.format = CF_UNICODETEXT;
fmt.transmute = FALSE;
fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_CF_UNICODETEXT);
g_array_append_val (comp, fmt);
fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_UTF8_STRING);
fmt.transmute = TRUE;
g_array_append_val (comp, fmt);
g_hash_table_replace (win32_selection->compatibility_targets, GINT_TO_POINTER (CF_UNICODETEXT), comp);
comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkSelTargetFormat), 3);
fmt.format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_PNG);
fmt.transmute = FALSE;
fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_PNG);
g_array_append_val (comp, fmt);
fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_PNG);
g_array_append_val (comp, fmt);
fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_BMP);
fmt.transmute = TRUE;
g_array_append_val (comp, fmt);
g_hash_table_replace (win32_selection->compatibility_targets, GINT_TO_POINTER (_gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_PNG)), comp);
comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkSelTargetFormat), 4);
fmt.format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_JFIF);
fmt.transmute = FALSE;
fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_JFIF);
g_array_append_val (comp, fmt);
fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_JPEG);
g_array_append_val (comp, fmt);
fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_PNG);
fmt.transmute = TRUE;
g_array_append_val (comp, fmt);
fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_BMP);
g_array_append_val (comp, fmt);
g_hash_table_replace (win32_selection->compatibility_targets, GINT_TO_POINTER (_gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_JFIF)), comp);
comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkSelTargetFormat), 4);
fmt.format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_GIF);
fmt.transmute = FALSE;
fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_GIF);
g_array_append_val (comp, fmt);
fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_GIF);
g_array_append_val (comp, fmt);
fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_PNG);
fmt.transmute = TRUE;
g_array_append_val (comp, fmt);
fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_BMP);
g_array_append_val (comp, fmt);
g_hash_table_replace (win32_selection->compatibility_targets, GINT_TO_POINTER (_gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_GIF)), comp);
comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkSelTargetFormat), 3);
fmt.format = CF_DIB;
fmt.transmute = FALSE;
fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_CF_DIB);
g_array_append_val (comp, fmt);
fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_BMP);
fmt.transmute = TRUE;
g_array_append_val (comp, fmt);
g_hash_table_replace (win32_selection->compatibility_targets, GINT_TO_POINTER (CF_DIB), comp);
comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkSelTargetFormat), 3);
fmt.format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_CFSTR_SHELLIDLIST);
fmt.transmute = FALSE;
fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_CFSTR_SHELLIDLIST);
g_array_append_val (comp, fmt);
fmt.target = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_TEXT_URI_LIST);
fmt.transmute = TRUE;
g_array_append_val (comp, fmt);
g_hash_table_replace (win32_selection->compatibility_targets, GINT_TO_POINTER (_gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_CFSTR_SHELLIDLIST)), comp);
}
/* The specifications for COMPOUND_TEXT and STRING specify that C0 and
* C1 are not allowed except for \n and \t, however the X conversions
* routines for COMPOUND_TEXT only enforce this in one direction,
* causing cut-and-paste of \r and \r\n separated text to fail.
* This routine strips out all non-allowed C0 and C1 characters
* from the input string and also canonicalizes \r, and \r\n to \n
*/
static gchar *
sanitize_utf8 (const gchar *src,
gint length)
{
GString *result = g_string_sized_new (length + 1);
const gchar *p = src;
const gchar *endp = src + length;
while (p < endp)
{
if (*p == '\r')
{
p++;
if (*p == '\n')
p++;
g_string_append_c (result, '\n');
}
else
{
gunichar ch = g_utf8_get_char (p);
char buf[7];
gint buflen;
if (!((ch < 0x20 && ch != '\t' && ch != '\n') || (ch >= 0x7f && ch < 0xa0)))
{
buflen = g_unichar_to_utf8 (ch, buf);
g_string_append_len (result, buf, buflen);
}
p = g_utf8_next_char (p);
}
}
g_string_append_c (result, '\0');
return g_string_free (result, FALSE);
}
static gchar *
_gdk_utf8_to_string_target_internal (const gchar *str,
gint length)
{
GError *error = NULL;
gchar *tmp_str = sanitize_utf8 (str, length);
gchar *result = g_convert_with_fallback (tmp_str, -1,
"ISO-8859-1", "UTF-8",
NULL, NULL, NULL, &error);
if (!result)
{
g_warning ("Error converting from UTF-8 to STRING: %s",
error->message);
g_error_free (error);
}
g_free (tmp_str);
return result;
}
static void
selection_property_store (GdkWindow *owner,
GdkAtom type,
gint format,
guchar *data,
gint length)
{
GdkSelProp *prop;
GdkWin32Selection *win32_sel = _gdk_win32_selection_get ();
g_return_if_fail (type != GDK_TARGET_STRING);
prop = g_hash_table_lookup (win32_sel->sel_prop_table, GDK_WINDOW_HWND (owner));
if (prop != NULL)
{
g_free (prop->data);
g_free (prop);
g_hash_table_remove (win32_sel->sel_prop_table, GDK_WINDOW_HWND (owner));
}
prop = g_new (GdkSelProp, 1);
prop->data = data;
prop->length = length;
prop->bitness = format;
prop->target = type;
g_hash_table_insert (win32_sel->sel_prop_table, GDK_WINDOW_HWND (owner), prop);
}
void
_gdk_dropfiles_store (gchar *data)
{
GdkWin32Selection *win32_sel = _gdk_win32_selection_get ();
if (data != NULL)
{
g_assert (win32_sel->dropfiles_prop == NULL);
win32_sel->dropfiles_prop = g_new (GdkSelProp, 1);
win32_sel->dropfiles_prop->data = (guchar *) data;
win32_sel->dropfiles_prop->length = strlen (data) + 1;
win32_sel->dropfiles_prop->bitness = 8;
win32_sel->dropfiles_prop->target = _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_TEXT_URI_LIST);
}
else
{
if (win32_sel->dropfiles_prop != NULL)
{
g_free (win32_sel->dropfiles_prop->data);
g_free (win32_sel->dropfiles_prop);
}
win32_sel->dropfiles_prop = NULL;
}
}
static void
generate_selection_notify (GdkWindow *requestor,
GdkAtom selection,
GdkAtom target,
GdkAtom property,
guint32 time)
{
GdkEvent tmp_event;
memset (&tmp_event, 0, sizeof (tmp_event));
tmp_event.selection.type = GDK_SELECTION_NOTIFY;
tmp_event.selection.window = requestor;
tmp_event.selection.send_event = FALSE;
tmp_event.selection.selection = selection;
tmp_event.selection.target = target;
tmp_event.selection.property = property;
tmp_event.selection.requestor = 0;
tmp_event.selection.time = time;
gdk_event_put (&tmp_event);
}
void
_gdk_win32_clear_clipboard_queue ()
{
GList *tmp_list, *next;
GdkWin32ClipboardQueueInfo *info;
GdkWin32Selection *win32_sel = _gdk_win32_selection_get ();
GDK_NOTE (DND, g_print ("Clear clipboard queue\n"));
for (tmp_list = clipboard_queue; tmp_list; tmp_list = next)
{
info = (GdkWin32ClipboardQueueInfo *) tmp_list->data;
next = g_list_next (tmp_list);
clipboard_queue = g_list_remove_link (clipboard_queue, tmp_list);
g_list_free (tmp_list);
switch (info->action)
{
case GDK_WIN32_CLIPBOARD_QUEUE_ACTION_TARGETS:
break;
case GDK_WIN32_CLIPBOARD_QUEUE_ACTION_CONVERT:
generate_selection_notify (info->requestor, info->selection, info->target, NULL, info->time);
break;
}
g_clear_object (&info->requestor);
g_slice_free (GdkWin32ClipboardQueueInfo, info);
}
win32_sel->targets_request_pending = FALSE;
}
/* Send ourselves a selection request message with
* the TARGETS target, we will do multiple SetClipboarData(...,NULL)
* calls in response to announce the formats we support.
*/
static void
send_targets_request (guint time)
{
GdkWindow *owner;
GdkEvent tmp_event;
GdkWin32Selection *win32_sel = _gdk_win32_selection_get ();
if (win32_sel->targets_request_pending)
return;
owner = _gdk_win32_display_get_selection_owner (gdk_display_get_default (),
GDK_SELECTION_CLIPBOARD);
if (owner == NULL)
return;
if (win32_sel->clipboard_opened_for == INVALID_HANDLE_VALUE)
{
if (OpenClipboard (GDK_WINDOW_HWND (owner)))
{
win32_sel->clipboard_opened_for = GDK_WINDOW_HWND (owner);
GDK_NOTE (DND, g_print ("Opened clipboard for 0x%p @ %s:%d\n", win32_sel->clipboard_opened_for, __FILE__, __LINE__));
}
}
GDK_NOTE (DND, g_print ("... sending GDK_SELECTION_REQUEST to ourselves\n"));
memset (&tmp_event, 0, sizeof (tmp_event));
tmp_event.selection.type = GDK_SELECTION_REQUEST;
tmp_event.selection.window = owner;
tmp_event.selection.send_event = FALSE;
tmp_event.selection.selection = GDK_SELECTION_CLIPBOARD;
tmp_event.selection.target = _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_TARGETS);
tmp_event.selection.property = _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_GDK_SELECTION);
tmp_event.selection.requestor = owner;
tmp_event.selection.time = time;
gdk_event_put (&tmp_event);
win32_sel->targets_request_pending = TRUE;
}
#define CLIPBOARD_IDLE_ABORT_TIME 30
static const gchar *
predefined_name (UINT fmt)
{
#define CASE(fmt) case fmt: return #fmt
switch (fmt)
{
CASE (CF_TEXT);
CASE (CF_BITMAP);
CASE (CF_METAFILEPICT);
CASE (CF_SYLK);
CASE (CF_DIF);
CASE (CF_TIFF);
CASE (CF_OEMTEXT);
CASE (CF_DIB);
CASE (CF_PALETTE);
CASE (CF_PENDATA);
CASE (CF_RIFF);
CASE (CF_WAVE);
CASE (CF_UNICODETEXT);
CASE (CF_ENHMETAFILE);
CASE (CF_HDROP);
CASE (CF_LOCALE);
CASE (CF_DIBV5);
CASE (CF_MAX);
CASE (CF_OWNERDISPLAY);
CASE (CF_DSPTEXT);
CASE (CF_DSPBITMAP);
CASE (CF_DSPMETAFILEPICT);
CASE (CF_DSPENHMETAFILE);
#undef CASE
default:
return NULL;
}
}
gchar *
_gdk_win32_get_clipboard_format_name (UINT fmt,
gboolean *is_predefined)
{
gint registered_name_w_len = 1024;
wchar_t *registered_name_w = g_malloc (registered_name_w_len);
gchar *registered_name = NULL;
int gcfn_result;
const gchar *predef = predefined_name (fmt);
/* FIXME: cache the result in a hash table */
do
{
gcfn_result = GetClipboardFormatNameW (fmt, registered_name_w, registered_name_w_len);
if (gcfn_result > 0 && gcfn_result < registered_name_w_len)
{
registered_name = g_utf16_to_utf8 (registered_name_w, -1, NULL, NULL, NULL);
g_clear_pointer (&registered_name_w, g_free);
if (!registered_name)
gcfn_result = 0;
else
*is_predefined = FALSE;
break;
}
/* If GetClipboardFormatNameW() used up all the space, it means that
* we probably need a bigger buffer, but cap this at 1 kilobyte.
*/
if (gcfn_result == 0 || registered_name_w_len > 1024 * 1024)
{
gcfn_result = 0;
g_clear_pointer (&registered_name_w, g_free);
break;
}
registered_name_w_len *= 2;
registered_name_w = g_realloc (registered_name_w, registered_name_w_len);
gcfn_result = registered_name_w_len;
} while (gcfn_result == registered_name_w_len);
if (registered_name == NULL &&
predef != NULL)
{
registered_name = g_strdup (predef);
*is_predefined = TRUE;
}
return registered_name;
}
static GArray *
get_compatibility_formats_for_target (GdkAtom target)
{
GArray *result = NULL;
gint i;
GdkWin32Selection *win32_sel = _gdk_win32_selection_get ();
result = g_hash_table_lookup (win32_sel->compatibility_formats, target);
if (result != NULL)
return result;
for (i = 0; i < win32_sel->n_known_pixbuf_formats; i++)
{
if (target != win32_sel->known_pixbuf_formats[i])
continue;
/* Any format known to gdk-pixbuf can be presented as PNG or BMP */
result = g_hash_table_lookup (win32_sel->compatibility_formats,
_gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_IMAGE_PNG));
break;
}
return result;
}
static GArray *
_gdk_win32_selection_get_compatibility_targets_for_format (UINT format)
{
GArray *result = NULL;
GdkWin32Selection *win32_sel = _gdk_win32_selection_get ();
result = g_hash_table_lookup (win32_sel->compatibility_targets, GINT_TO_POINTER (format));
if (result != NULL)
return result;
/* TODO: reverse gdk-pixbuf conversion? We have to somehow
* match gdk-pixbuf format names to the corresponding clipboard
* format names. The former are known only at runtime,
* the latter are presently unknown...
* Maybe try to get the data and then just feed it to gdk-pixbuf,
* see if it knows what it is?
*/
return result;
}
void
_gdk_win32_add_format_to_targets (UINT format,
GArray *array,
GList **list)
{
gboolean predef;
gchar *format_name = _gdk_win32_get_clipboard_format_name (format, &predef);
GdkAtom target_atom;
GdkSelTargetFormat target_selformat;
GArray *target_selformats;
gint i,j;
if (format_name != NULL)
{
target_atom = gdk_atom_intern (format_name, FALSE);
GDK_NOTE (DND, g_print ("Maybe add as-is format %s (0x%p)\n", format_name, target_atom));
g_free (format_name);
if (array && target_atom != 0)
{
for (j = 0; j < array->len; j++)
if (g_array_index (array, GdkSelTargetFormat, j).target == target_atom)
break;
if (j == array->len)
{
target_selformat.format = format;
target_selformat.target = target_atom;
target_selformat.transmute = FALSE;
g_array_append_val (array, target_selformat);
}
}
if (list && target_atom != 0 && g_list_find (*list, target_atom) == NULL)
*list = g_list_prepend (*list, target_atom);
}
target_selformats = _gdk_win32_selection_get_compatibility_targets_for_format (format);
if (array && target_selformats != NULL)
for (i = 0; i < target_selformats->len; i++)
{
target_selformat = g_array_index (target_selformats, GdkSelTargetFormat, i);
for (j = 0; j < array->len; j++)
if (g_array_index (array, GdkSelTargetFormat, j).target == target_selformat.target &&
g_array_index (array, GdkSelTargetFormat, j).format == target_selformat.format)
break;
if (j == array->len)
g_array_append_val (array, target_selformat);
}
if (list && target_selformats != NULL)
for (i = 0; i < target_selformats->len; i++)
{
target_selformat = g_array_index (target_selformats, GdkSelTargetFormat, i);
if (g_list_find (*list, target_selformat.target) == NULL)
*list = g_list_prepend (*list, target_selformat.target);
}
}
static void
transmute_cf_unicodetext_to_utf8_string (const guchar *data,
gint length,
guchar **set_data,
gint *set_data_length,
GDestroyNotify *set_data_destroy)
{
wchar_t *ptr, *p, *q;
gchar *result;
glong wclen, u8_len;
/* Strip out \r */
ptr = (wchar_t *) data;
p = ptr;
q = ptr;
wclen = 0;
while (p < ptr + length / 2)
{
if (*p != L'\r')
{
*q++ = *p;
wclen++;
}
p++;
}
result = g_utf16_to_utf8 (ptr, wclen, NULL, &u8_len, NULL);
if (result)
{
*set_data = (guchar *) result;
if (set_data_length)
*set_data_length = u8_len + 1;
if (set_data_destroy)
*set_data_destroy = (GDestroyNotify) g_free;
}
}
static void
transmute_utf8_string_to_cf_unicodetext (const guchar *data,
gint length,
guchar **set_data,
gint *set_data_length,
GDestroyNotify *set_data_destroy)
{
glong wclen;
GError *err = NULL;
glong size;
gint i;
wchar_t *wcptr, *p;
wcptr = g_utf8_to_utf16 ((char *) data, length, NULL, &wclen, &err);
if (err != NULL)
{
g_warning ("Failed to convert utf8: %s", err->message);
g_clear_error (&err);
return;
}
wclen++; /* Terminating 0 */
size = wclen * 2;
for (i = 0; i < wclen; i++)
if (wcptr[i] == L'\n' && (i == 0 || wcptr[i - 1] != L'\r'))
size += 2;
*set_data = g_malloc0 (size);
if (set_data_length)
*set_data_length = size;
if (set_data_destroy)
*set_data_destroy = (GDestroyNotify) g_free;
p = (wchar_t *) *set_data;
for (i = 0; i < wclen; i++)
{
if (wcptr[i] == L'\n' && (i == 0 || wcptr[i - 1] != L'\r'))
*p++ = L'\r';
*p++ = wcptr[i];
}
g_free (wcptr);
}
static int
wchar_to_str (const wchar_t *wstr,
char **retstr,
UINT cp)
{
char *str;
int len;
int lenc;
len = WideCharToMultiByte (cp, 0, wstr, -1, NULL, 0, NULL, NULL);
if (len <= 0)
return -1;
str = g_malloc (sizeof (char) * len);
lenc = WideCharToMultiByte (cp, 0, wstr, -1, str, len, NULL, NULL);
if (lenc != len)
{
g_free (str);
return -3;
}
*retstr = str;
return 0;
}
static void
transmute_utf8_string_to_cf_text (const guchar *data,
gint length,
guchar **set_data,
gint *set_data_length,
GDestroyNotify *set_data_destroy)
{
glong rlen;
GError *err = NULL;
glong size;
gint i;
char *strptr, *p;
wchar_t *wcptr;
wcptr = g_utf8_to_utf16 ((char *) data, length, NULL, NULL, &err);
if (err != NULL)
{
g_warning ("Failed to convert utf8: %s", err->message);
g_clear_error (&err);
return;
}
if (wchar_to_str (wcptr, &strptr, CP_ACP) != 0)
{
g_warning ("Failed to convert utf-16 %S to ACP", wcptr);
g_free (wcptr);
return;
}
rlen = strlen (strptr);
g_free (wcptr);
rlen++; /* Terminating 0 */
size = rlen * sizeof (char);
for (i = 0; i < rlen; i++)
if (strptr[i] == '\n' && (i == 0 || strptr[i - 1] != '\r'))
size += sizeof (char);
*set_data = g_malloc0 (size);
if (set_data_length)
*set_data_length = size;
if (set_data_destroy)
*set_data_destroy = (GDestroyNotify) g_free;
p = (char *) *set_data;
for (i = 0; i < rlen; i++)
{
if (strptr[i] == '\n' && (i == 0 || strptr[i - 1] != '\r'))
*p++ = '\r';
*p++ = strptr[i];
}
g_free (strptr);
}
static int
str_to_wchar (const char *str,
wchar_t **wretstr,
UINT cp)
{
wchar_t *wstr;
int len;
int lenc;
len = MultiByteToWideChar (cp, 0, str, -1, NULL, 0);
if (len <= 0)
return -1;
wstr = g_malloc (sizeof (wchar_t) * len);
lenc = MultiByteToWideChar (cp, 0, str, -1, wstr, len);
if (lenc != len)
{
g_free (wstr);
return -3;
}
*wretstr = wstr;
return 0;
}
static void
transmute_cf_text_to_utf8_string (const guchar *data,
gint length,
guchar **set_data,
gint *set_data_length,
GDestroyNotify *set_data_destroy)
{
char *ptr, *p, *q;
gchar *result;
glong wclen, u8_len;
wchar_t *wstr;
/* Strip out \r */
ptr = (gchar *) data;
p = ptr;
q = ptr;
wclen = 0;
while (p < ptr + length / 2)
{
if (*p != '\r')
{
*q++ = *p;
wclen++;
}
p++;
}
if (str_to_wchar (ptr, &wstr, CP_ACP) < 0)
return;
result = g_utf16_to_utf8 (wstr, -1, NULL, &u8_len, NULL);
if (result)
{
*set_data = (guchar *) result;
if (set_data_length)
*set_data_length = u8_len + 1;
if (set_data_destroy)
*set_data_destroy = (GDestroyNotify) g_free;
}
g_free (wstr);
}
static void
transmute_cf_dib_to_image_bmp (const guchar *data,
gint length,
guchar **set_data,
gint *set_data_length,
GDestroyNotify *set_data_destroy)
{
/* Need to add a BMP file header so gdk-pixbuf can load
* it.
*
* If the data is from Mozilla Firefox or IE7, and
* starts with an "old fashioned" BITMAPINFOHEADER,
* i.e. with biSize==40, and biCompression == BI_RGB and
* biBitCount==32, we assume that the "extra" byte in
* each pixel in fact is alpha.
*
* The gdk-pixbuf bmp loader doesn't trust 32-bit BI_RGB
* bitmaps to in fact have alpha, so we have to convince
* it by changing the bitmap header to a version 5
* BI_BITFIELDS one with explicit alpha mask indicated.
*
* The RGB bytes that are in bitmaps on the clipboard
* originating from Firefox or IE7 seem to be
* premultiplied with alpha. The gdk-pixbuf bmp loader
* of course doesn't expect that, so we have to undo the
* premultiplication before feeding the bitmap to the
* bmp loader.
*
* Note that for some reason the bmp loader used to want
* the alpha bytes in its input to actually be
* 255-alpha, but here we assume that this has been
* fixed before this is committed.
*/
BITMAPINFOHEADER *bi = (BITMAPINFOHEADER *) data;
BITMAPFILEHEADER *bf;
gpointer result;
gint data_length = length;
gint new_length;
gboolean make_dibv5 = FALSE;
BITMAPV5HEADER *bV5;
guchar *p;
guint i;
if (bi->biSize == sizeof (BITMAPINFOHEADER) &&
bi->biPlanes == 1 &&
bi->biBitCount == 32 &&
bi->biCompression == BI_RGB &&
#if 0
/* Maybe check explicitly for Mozilla or IE7?
*
* If the clipboard format
* application/x-moz-nativeimage is present, that is
* a reliable indicator that the data is offered by
* Mozilla one would think. For IE7,
* UniformResourceLocatorW is presumably not that
* uniqie, so probably need to do some
* GetClipboardOwner(), GetWindowThreadProcessId(),
* OpenProcess(), GetModuleFileNameEx() dance to
* check?
*/
(IsClipboardFormatAvailable
(RegisterClipboardFormatA ("application/x-moz-nativeimage")) ||
IsClipboardFormatAvailable
(RegisterClipboardFormatA ("UniformResourceLocatorW"))) &&
#endif
TRUE)
{
/* We turn the BITMAPINFOHEADER into a
* BITMAPV5HEADER before feeding it to gdk-pixbuf.
*/
new_length = (data_length +
sizeof (BITMAPFILEHEADER) +
(sizeof (BITMAPV5HEADER) - sizeof (BITMAPINFOHEADER)));
make_dibv5 = TRUE;
}
else
{
new_length = data_length + sizeof (BITMAPFILEHEADER);
}
result = g_try_malloc (new_length);
if (result == NULL)
return;
bf = (BITMAPFILEHEADER *) result;
bf->bfType = 0x4d42; /* "BM" */
bf->bfSize = new_length;
bf->bfReserved1 = 0;
bf->bfReserved2 = 0;
*set_data = result;
if (set_data_length)
*set_data_length = new_length;
if (set_data_destroy)
*set_data_destroy = g_free;
if (!make_dibv5)
{
bf->bfOffBits = (sizeof (BITMAPFILEHEADER) +
bi->biSize +
bi->biClrUsed * sizeof (RGBQUAD));
if (bi->biCompression == BI_BITFIELDS && bi->biBitCount >= 16)
{
/* Screenshots taken with PrintScreen or
* Alt + PrintScreen are found on the clipboard in
* this format. In this case the BITMAPINFOHEADER is
* followed by three DWORD specifying the masks of the
* red green and blue components, so adjust the offset
* accordingly. */
bf->bfOffBits += (3 * sizeof (DWORD));
}
memcpy ((char *) result + sizeof (BITMAPFILEHEADER),
bi,
data_length);
return;
}
bV5 = (BITMAPV5HEADER *) ((char *) result + sizeof (BITMAPFILEHEADER));
bV5->bV5Size = sizeof (BITMAPV5HEADER);
bV5->bV5Width = bi->biWidth;
bV5->bV5Height = bi->biHeight;
bV5->bV5Planes = 1;
bV5->bV5BitCount = 32;
bV5->bV5Compression = BI_BITFIELDS;
bV5->bV5SizeImage = 4 * bV5->bV5Width * ABS (bV5->bV5Height);
bV5->bV5XPelsPerMeter = bi->biXPelsPerMeter;
bV5->bV5YPelsPerMeter = bi->biYPelsPerMeter;
bV5->bV5ClrUsed = 0;
bV5->bV5ClrImportant = 0;
/* Now the added mask fields */
bV5->bV5RedMask = 0x00ff0000;
bV5->bV5GreenMask = 0x0000ff00;
bV5->bV5BlueMask = 0x000000ff;
bV5->bV5AlphaMask = 0xff000000;
((char *) &bV5->bV5CSType)[3] = 's';
((char *) &bV5->bV5CSType)[2] = 'R';
((char *) &bV5->bV5CSType)[1] = 'G';
((char *) &bV5->bV5CSType)[0] = 'B';
/* Ignore colorspace and profile fields */
bV5->bV5Intent = LCS_GM_GRAPHICS;
bV5->bV5Reserved = 0;
bf->bfOffBits = (sizeof (BITMAPFILEHEADER) +
bV5->bV5Size);
p = ((guchar *) result) + sizeof (BITMAPFILEHEADER) + sizeof (BITMAPV5HEADER);
memcpy (p, ((char *) bi) + bi->biSize,
data_length - sizeof (BITMAPINFOHEADER));
for (i = 0; i < bV5->bV5SizeImage/4; i++)
{
if (p[3] != 0)
{
gdouble inverse_alpha = 255./p[3];
p[0] = p[0] * inverse_alpha + 0.5;
p[1] = p[1] * inverse_alpha + 0.5;
p[2] = p[2] * inverse_alpha + 0.5;
}
p += 4;
}
}
static void
transmute_cf_shell_id_list_to_text_uri_list (const guchar *data,
gint length,
guchar **set_data,
gint *set_data_length,
GDestroyNotify *set_data_destroy)
{
guint i;
CIDA *cida = (CIDA *) data;
guint number_of_ids = cida->cidl;
GString *result = g_string_new (NULL);
PCIDLIST_ABSOLUTE folder_id = HIDA_GetPIDLFolder (cida);
wchar_t path_w[MAX_PATH + 1];
for (i = 0; i < number_of_ids; i++)
{
PCUIDLIST_RELATIVE file_id = HIDA_GetPIDLItem (cida, i);
PIDLIST_ABSOLUTE file_id_full = ILCombine (folder_id, file_id);
if (SHGetPathFromIDListW (file_id_full, path_w))
{
gchar *filename = g_utf16_to_utf8 (path_w, -1, NULL, NULL, NULL);
if (filename)
{
gchar *uri = g_filename_to_uri (filename, NULL, NULL);
if (uri)
{
g_string_append (result, uri);
g_string_append (result, "\r\n");
g_free (uri);
}
g_free (filename);
}
}
ILFree (file_id_full);
}
*set_data = (guchar *) result->str;
if (set_data_length)
*set_data_length = result->len;
if (set_data_destroy)
*set_data_destroy = g_free;
g_string_free (result, FALSE);
}
void
transmute_image_bmp_to_cf_dib (const guchar *data,
gint length,
guchar **set_data,
gint *set_data_length,
GDestroyNotify *set_data_destroy)
{
gint size;
guchar *ptr;
g_return_if_fail (length >= sizeof (BITMAPFILEHEADER));
/* No conversion is needed, just strip the BITMAPFILEHEADER */
size = length - sizeof (BITMAPFILEHEADER);
ptr = g_malloc (size);
memcpy (ptr, data + sizeof (BITMAPFILEHEADER), size);
*set_data = ptr;
if (set_data_length)
*set_data_length = size;
if (set_data_destroy)
*set_data_destroy = g_free;
}
static void
transmute_selection_format (UINT from_format,
GdkAtom to_target,
const guchar *data,
gint length,
guchar **set_data,
gint *set_data_length)
{
if ((to_target == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_IMAGE_PNG) &&
from_format == _gdk_win32_selection_cf (GDK_WIN32_CF_INDEX_PNG)) ||
(to_target == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_IMAGE_JPEG) &&
from_format == _gdk_win32_selection_cf (GDK_WIN32_CF_INDEX_JFIF)) ||
(to_target == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_GIF) &&
from_format == _gdk_win32_selection_cf (GDK_WIN32_CF_INDEX_GIF)))
{
/* No transmutation needed */
*set_data = g_memdup (data, length);
*set_data_length = length;
}
else if (to_target == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_UTF8_STRING) &&
from_format == CF_UNICODETEXT)
{
transmute_cf_unicodetext_to_utf8_string (data, length, set_data, set_data_length, NULL);
}
else if (to_target == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_UTF8_STRING) &&
from_format == CF_TEXT)
{
transmute_cf_text_to_utf8_string (data, length, set_data, set_data_length, NULL);
}
else if (to_target == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_IMAGE_BMP) &&
(from_format == CF_DIB || from_format == CF_DIBV5))
{
transmute_cf_dib_to_image_bmp (data, length, set_data, set_data_length, NULL);
}
else if (to_target == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_TEXT_URI_LIST) &&
from_format == _gdk_win32_selection_cf (GDK_WIN32_CF_INDEX_CFSTR_SHELLIDLIST))
{
transmute_cf_shell_id_list_to_text_uri_list (data, length, set_data, set_data_length, NULL);
}
else
{
g_warning ("Don't know how to transmute format 0x%x to target 0x%p", from_format, to_target);
}
}
void
transmute_selection_target (GdkAtom from_target,
UINT to_format,
const guchar *data,
gint length,
guchar **set_data,
gint *set_data_length)
{
if ((from_target == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_IMAGE_PNG) &&
to_format == _gdk_win32_selection_cf (GDK_WIN32_CF_INDEX_PNG)) ||
(from_target == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_IMAGE_JPEG) &&
to_format == _gdk_win32_selection_cf (GDK_WIN32_CF_INDEX_JFIF)) ||
(from_target == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_GIF) &&
to_format == _gdk_win32_selection_cf (GDK_WIN32_CF_INDEX_GIF)))
{
/* No conversion needed */
*set_data = g_memdup (data, length);
*set_data_length = length;
}
else if (from_target == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_UTF8_STRING) &&
to_format == CF_UNICODETEXT)
{
transmute_utf8_string_to_cf_unicodetext (data, length, set_data, set_data_length, NULL);
}
else if (from_target == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_UTF8_STRING) &&
to_format == CF_TEXT)
{
transmute_utf8_string_to_cf_text (data, length, set_data, set_data_length, NULL);
}
else if (from_target == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_IMAGE_BMP) &&
to_format == CF_DIB)
{
transmute_image_bmp_to_cf_dib (data, length, set_data, set_data_length, NULL);
}
else if (from_target == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_IMAGE_BMP) &&
to_format == CF_DIBV5)
{
transmute_image_bmp_to_cf_dib (data, length, set_data, set_data_length, NULL);
}
/*
else if (from_target == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_TEXT_URI_LIST) &&
to_format == _gdk_win32_selection_cf (GDK_WIN32_CF_INDEX_CFSTR_SHELLIDLIST))
{
transmute_text_uri_list_to_shell_id_list (data, length, set_data, set_data_length, NULL);
}
*/
else
{
g_warning ("Don't know how to transmute from target 0x%p to format 0x%x", from_target, to_format);
}
}
static GdkAtom
convert_clipboard_selection_to_targets_target (GdkWindow *requestor)
{
gint fmt;
int i;
int format_count = CountClipboardFormats ();
GArray *targets = g_array_sized_new (FALSE, FALSE, sizeof (GdkSelTargetFormat), format_count);
for (fmt = 0; 0 != (fmt = EnumClipboardFormats (fmt)); )
_gdk_win32_add_format_to_targets (fmt, targets, NULL);
GDK_NOTE (DND, {
g_print ("... ");
for (i = 0; i < targets->len; i++)
{
gchar *atom_name = gdk_atom_name (g_array_index (targets, GdkSelTargetFormat, i).target);
g_print ("%s", atom_name);
g_free (atom_name);
if (i < targets->len - 1)
g_print (", ");
}
g_print ("\n");
});
if (targets->len > 0)
{
gint len = targets->len;
GdkAtom *targets_only = g_new0 (GdkAtom, len);
for (i = 0; i < targets->len; i++)
targets_only[i] = g_array_index (targets, GdkSelTargetFormat, i).target;
g_array_free (targets, TRUE);
selection_property_store (requestor, GDK_SELECTION_TYPE_ATOM,
32, (guchar *) targets_only,
len * sizeof (GdkAtom));
return _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_GDK_SELECTION);
}
else
{
g_array_free (targets, TRUE);
return NULL;
}
}
static GdkAtom
convert_clipboard_selection_to_target (GdkWindow *requestor,
GdkAtom target)
{
UINT format;
HANDLE hdata;
guchar *ptr;
gint length;
gboolean transmute = FALSE;
GdkAtom result = _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_GDK_SELECTION);
gboolean found;
gchar *atom_name;
atom_name = gdk_atom_name (target);
for (format = 0, found = FALSE;
!found && 0 != (format = EnumClipboardFormats (format));
)
{
gboolean predef;
gchar *format_name = _gdk_win32_get_clipboard_format_name (format, &predef);
if (format_name == NULL)
continue;
found = g_strcmp0 (format_name, atom_name) == 0;
g_free (format_name);
}
g_free (atom_name);
if (format == 0)
{
gint i;
GArray *compat_formats = get_compatibility_formats_for_target (target);
for (i = 0; compat_formats != NULL && i < compat_formats->len; i++)
{
if (!IsClipboardFormatAvailable (g_array_index (compat_formats, GdkSelTargetFormat, i).format))
continue;
format = g_array_index (compat_formats, GdkSelTargetFormat, i).format;
transmute = g_array_index (compat_formats, GdkSelTargetFormat, i).transmute;
break;
}
}
if (format == 0)
return NULL;
if ((hdata = GetClipboardData (format)) == NULL)
return NULL;
if ((ptr = GlobalLock (hdata)) != NULL)
{
guchar *data = NULL;
gint data_len = 0;
length = GlobalSize (hdata);
GDK_NOTE (DND, g_print ("... format 0x%x: %d bytes\n", format, length));
if (transmute)
{
transmute_selection_format (format, target, ptr, length, &data, &data_len);
}
else
{
data = g_memdup (ptr, length);
data_len = length;
}
if (data)
selection_property_store (requestor, target,
8, data, data_len);
else
result = NULL;
GlobalUnlock (hdata);
}
return result;
}
static GdkAtom
convert_selection_with_opened_clipboard (GdkDisplay *display,
GdkWindow *requestor,
GdkAtom target,
guint32 time)
{
GdkAtom property;
if (target == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_TARGETS))
property = convert_clipboard_selection_to_targets_target (requestor);
else
property = convert_clipboard_selection_to_target (requestor, target);
return property;
}
static void
announce_delayrendered_targets_with_opened_clipboard (GdkWin32Selection *win32_sel)
{
gint i;
/* Announce the formats we support, but don't actually put any data out there.
* Other processes will send us WM_RENDERFORMAT to get the data.
*/
for (i = 0; i < win32_sel->clipboard_selection_targets->len; i++)
{
GdkSelTargetFormat *fmt = &g_array_index (win32_sel->clipboard_selection_targets, GdkSelTargetFormat, i);
/* Some calls here may be duplicates, but we don't really care */
if (fmt->format != 0)
SetClipboardData (fmt->format, NULL);
}
}
static gboolean
open_clipboard_timeout (gpointer data)
{
GList *tmp_list, *next;
GdkWin32ClipboardQueueInfo *info;
GdkWin32Selection *win32_sel = _gdk_win32_selection_get ();
GDK_NOTE (DND, g_print ("Open clipboard timeout ticks\n"));
/* Clear out old and invalid entries */
for (tmp_list = clipboard_queue; tmp_list; tmp_list = next)
{
info = (GdkWin32ClipboardQueueInfo *) tmp_list->data;
next = g_list_next (tmp_list);
if (GDK_WINDOW_DESTROYED (info->requestor) ||
info->idle_time >= CLIPBOARD_IDLE_ABORT_TIME)
{
clipboard_queue = g_list_remove_link (clipboard_queue, tmp_list);
g_list_free (tmp_list);
switch (info->action)
{
case GDK_WIN32_CLIPBOARD_QUEUE_ACTION_TARGETS:
break;
case GDK_WIN32_CLIPBOARD_QUEUE_ACTION_CONVERT:
generate_selection_notify (info->requestor, info->selection, info->target, NULL, info->time);
break;
}
g_clear_object (&info->requestor);
g_slice_free (GdkWin32ClipboardQueueInfo, info);
}
}
if (clipboard_queue == NULL)
{
GDK_NOTE (DND, g_print ("Stopping open clipboard timer\n"));
if (win32_sel->clipboard_opened_for != INVALID_HANDLE_VALUE)
{
API_CALL (CloseClipboard, ());
win32_sel->clipboard_opened_for = INVALID_HANDLE_VALUE;
GDK_NOTE (DND, g_print ("Closed clipboard @ %s:%d\n", __FILE__, __LINE__));
}
return FALSE;
}
for (tmp_list = clipboard_queue; tmp_list; tmp_list = next)
{
GdkAtom property;
info = (GdkWin32ClipboardQueueInfo *) tmp_list->data;
next = g_list_next (tmp_list);
/* CONVERT works with any opened clipboard,
* but TARGETS needs to open the clipboard with the hande of the
* owner window.
*/
if (info->action == GDK_WIN32_CLIPBOARD_QUEUE_ACTION_TARGETS &&
win32_sel->clipboard_opened_for == NULL)
{
GDK_NOTE (DND, g_print ("Need to re-open clipboard, closing\n"));
API_CALL (CloseClipboard, ());
win32_sel->clipboard_opened_for = INVALID_HANDLE_VALUE;
}
if (win32_sel->clipboard_opened_for == INVALID_HANDLE_VALUE)
{
if (!OpenClipboard (GDK_WINDOW_HWND (info->requestor)))
{
info->idle_time += 1;
continue;
}
win32_sel->clipboard_opened_for = GDK_WINDOW_HWND (info->requestor);
GDK_NOTE (DND, g_print ("Opened clipboard for 0x%p @ %s:%d\n", win32_sel->clipboard_opened_for, __FILE__, __LINE__));
}
clipboard_queue = g_list_remove_link (clipboard_queue, tmp_list);
g_list_free (tmp_list);
switch (info->action)
{
case GDK_WIN32_CLIPBOARD_QUEUE_ACTION_CONVERT:
property = convert_selection_with_opened_clipboard (info->display,
info->requestor,
info->target,
info->time);
generate_selection_notify (info->requestor,
GDK_SELECTION_CLIPBOARD,
info->target,
property,
info->time);
break;
case GDK_WIN32_CLIPBOARD_QUEUE_ACTION_TARGETS:
announce_delayrendered_targets_with_opened_clipboard (win32_sel);
break;
default:
g_assert_not_reached ();
}
g_clear_object (&info->requestor);
g_slice_free (GdkWin32ClipboardQueueInfo, info);
}
if (clipboard_queue != NULL)
return TRUE;
if (win32_sel->clipboard_opened_for != INVALID_HANDLE_VALUE)
{
API_CALL (CloseClipboard, ());
win32_sel->clipboard_opened_for = INVALID_HANDLE_VALUE;
GDK_NOTE (DND, g_print ("Closed clipboard @ %s:%d\n", __FILE__, __LINE__));
}
GDK_NOTE (DND, g_print ("Stopping open clipboard timer\n"));
return FALSE;
}
static void
queue_open_clipboard (GdkWin32ClipboardQueueAction action,
GdkDisplay *display,
GdkWindow *requestor,
GdkAtom target,
guint32 time)
{
guint id;
GList *tmp_list, *next;
GdkWin32ClipboardQueueInfo *info;
GdkWin32Selection *win32_sel = _gdk_win32_selection_get ();
for (tmp_list = clipboard_queue; tmp_list; tmp_list = next)
{
info = (GdkWin32ClipboardQueueInfo *) tmp_list->data;
next = g_list_next (tmp_list);
if (info->action == action &&
info->requestor == requestor)
return;
}
info = g_slice_new (GdkWin32ClipboardQueueInfo);
info->display = display;
g_set_object (&info->requestor, requestor);
info->selection = GDK_SELECTION_CLIPBOARD;
info->target = target;
info->idle_time = 0;
info->time = time;
info->action = action;
GDK_NOTE (DND, g_print ("Queueing open clipboard\n"));
if (win32_sel->clipboard_opened_for == INVALID_HANDLE_VALUE &&
clipboard_queue == NULL)
{
id = gdk_threads_add_timeout_seconds (1, (GSourceFunc) open_clipboard_timeout, NULL);
g_source_set_name_by_id (id, "[gdk-win32] open_clipboard_timeout");
GDK_NOTE (DND, g_print ("Started open clipboard timer\n"));
}
clipboard_queue = g_list_append (clipboard_queue, info);
}
gboolean
_gdk_win32_display_set_selection_owner (GdkDisplay *display,
GdkWindow *owner,
GdkAtom selection,
guint32 time,
gboolean send_event)
{
HWND hwnd;
GdkWin32Selection *win32_sel = _gdk_win32_selection_get ();
g_return_val_if_fail (selection != NULL, FALSE);
GDK_NOTE (DND, {
gchar *sel_name = gdk_atom_name (selection);
g_print ("gdk_selection_owner_set_for_display: %p %s\n",
(owner ? GDK_WINDOW_HWND (owner) : NULL),
sel_name);
g_free (sel_name);
});
if (selection != GDK_SELECTION_CLIPBOARD)
{
if (owner != NULL)
g_hash_table_insert (win32_sel->sel_owner_table, selection, GDK_WINDOW_HWND (owner));
else
g_hash_table_remove (win32_sel->sel_owner_table, selection);
return TRUE;
}
/* Rest of this function handles the CLIPBOARD selection */
if (owner != NULL)
{
if (GDK_WINDOW_DESTROYED (owner))
return FALSE;
hwnd = GDK_WINDOW_HWND (owner);
}
else
hwnd = NULL;
if (win32_sel->clipboard_opened_for != hwnd &&
win32_sel->clipboard_opened_for != INVALID_HANDLE_VALUE)
{
API_CALL (CloseClipboard, ());
win32_sel->clipboard_opened_for = INVALID_HANDLE_VALUE;
GDK_NOTE (DND, g_print ("Closed clipboard @ %s:%d\n", __FILE__, __LINE__));
}
if (!OpenClipboard (hwnd))
{
if (GetLastError () != ERROR_ACCESS_DENIED)
WIN32_API_FAILED ("OpenClipboard");
return FALSE;
}
win32_sel->clipboard_opened_for = hwnd;
GDK_NOTE (DND, g_print ("Opened clipboard for 0x%p @ %s:%d\n", win32_sel->clipboard_opened_for, __FILE__, __LINE__));
win32_sel->ignore_destroy_clipboard = TRUE;
GDK_NOTE (DND, g_print ("... EmptyClipboard()\n"));
if (!API_CALL (EmptyClipboard, ()))
{
win32_sel->ignore_destroy_clipboard = FALSE;
API_CALL (CloseClipboard, ());
win32_sel->clipboard_opened_for = INVALID_HANDLE_VALUE;
GDK_NOTE (DND, g_print ("Closed clipboard @ %s:%d\n", __FILE__, __LINE__));
return FALSE;
}
win32_sel->ignore_destroy_clipboard = FALSE;
/* Any queued clipboard operations were just made pointless
* by EmptyClipboard().
*/
_gdk_win32_clear_clipboard_queue ();
/* This is kind of risky, but we don't close the clipboard
* to ensure that it's still open when GDK_SELECTION_REQUEST
* is handled.
*/
if (owner == NULL)
{
if (!API_CALL (CloseClipboard, ()))
return FALSE;
GDK_NOTE (DND, g_print ("Closed clipboard @ %s:%d\n", __FILE__, __LINE__));
win32_sel->clipboard_opened_for = INVALID_HANDLE_VALUE;
}
send_targets_request (time);
return TRUE;
}
GdkWindow*
_gdk_win32_display_get_selection_owner (GdkDisplay *display,
GdkAtom selection)
{
GdkWindow *window;
HWND selection_owner;
GdkWin32Selection *win32_sel = _gdk_win32_selection_get ();
g_return_val_if_fail (selection != NULL, NULL);
if (selection == GDK_SELECTION_CLIPBOARD)
selection_owner = GetClipboardOwner ();
else
selection_owner = g_hash_table_lookup (win32_sel->sel_owner_table, selection);
if (selection_owner)
window = gdk_win32_window_lookup_for_display (display,
selection_owner);
else
window = NULL;
GDK_NOTE (DND, {
gchar *sel_name = gdk_atom_name (selection);
g_print ("gdk_selection_owner_get: %s = %p\n",
sel_name,
(window ? GDK_WINDOW_HWND (window) : NULL));
g_free (sel_name);
});
return window;
}
static GdkAtom
convert_dnd_selection_to_target (GdkAtom target,
GdkWindow *requestor)
{
GdkWin32Selection *win32_sel = _gdk_win32_selection_get ();
UINT format;
gint i, with_transmute;
guchar *ptr;
gint length;
gboolean transmute = FALSE;
GdkWin32DragContext *context_win32;
FORMATETC fmt;
STGMEDIUM storage;
HRESULT hr;
GdkAtom result = _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_OLE2_DND);
g_assert (win32_sel->target_drag_context != NULL);
g_assert (win32_sel->dnd_data_object_target != NULL);
context_win32 = GDK_WIN32_DRAG_CONTEXT (win32_sel->target_drag_context);
fmt.ptd = NULL;
fmt.dwAspect = DVASPECT_CONTENT;
fmt.lindex = -1;
fmt.tymed = TYMED_HGLOBAL;
for (format = 0, with_transmute = 0; format == 0 && with_transmute < 2; with_transmute++)
{
for (i = 0;
i < context_win32->droptarget_format_target_map->len;
i++)
{
GdkSelTargetFormat selformat = g_array_index (context_win32->droptarget_format_target_map, GdkSelTargetFormat, i);
if (selformat.target != target ||
selformat.transmute != (with_transmute == 0 ? FALSE : TRUE))
continue;
fmt.cfFormat = selformat.format;
hr = IDataObject_QueryGetData (win32_sel->dnd_data_object_target, &fmt);
if (!SUCCEEDED (hr) || hr != S_OK)
continue;
format = selformat.format;
transmute = selformat.transmute;
break;
}
}
if (format == 0)
return NULL;
hr = IDataObject_GetData (win32_sel->dnd_data_object_target, &fmt, &storage);
if (!SUCCEEDED (hr) || hr != S_OK)
return NULL;
if ((ptr = GlobalLock (storage.hGlobal)) != NULL)
{
guchar *data = NULL;
gint data_len = 0;
SetLastError (0);
length = GlobalSize (storage.hGlobal);
if (GetLastError () == NO_ERROR)
{
if (transmute)
{
transmute_selection_format (format, target, ptr, length, &data, &data_len);
}
else
{
data = g_memdup (ptr, length);
data_len = length;
}
if (data)
selection_property_store (requestor, target, 8,
data, data_len);
else
result = NULL;
}
else
result = NULL;
GlobalUnlock (storage.hGlobal);
}
else
result = NULL;
ReleaseStgMedium (&storage);
return result;
}
void
_gdk_win32_display_convert_selection (GdkDisplay *display,
GdkWindow *requestor,
GdkAtom selection,
GdkAtom target,
guint32 time)
{
GdkWin32Selection *win32_sel = _gdk_win32_selection_get ();
GdkAtom property = _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_GDK_SELECTION);
g_return_if_fail (selection != NULL);
g_return_if_fail (requestor != NULL);
if (GDK_WINDOW_DESTROYED (requestor))
return;
GDK_NOTE (DND, {
gchar *sel_name = gdk_atom_name (selection);
gchar *tgt_name = gdk_atom_name (target);
g_print ("gdk_selection_convert: %p %s %s\n",
GDK_WINDOW_HWND (requestor),
sel_name, tgt_name);
g_free (sel_name);
g_free (tgt_name);
});
if (selection == GDK_SELECTION_CLIPBOARD)
{
if (win32_sel->clipboard_opened_for != INVALID_HANDLE_VALUE ||
OpenClipboard (GDK_WINDOW_HWND (requestor)))
{
if (win32_sel->clipboard_opened_for == INVALID_HANDLE_VALUE)
{
win32_sel->clipboard_opened_for = GDK_WINDOW_HWND (requestor);
GDK_NOTE (DND, g_print ("Opened clipboard for 0x%p @ %s:%d\n", win32_sel->clipboard_opened_for, __FILE__, __LINE__));
}
queue_open_clipboard (GDK_WIN32_CLIPBOARD_QUEUE_ACTION_CONVERT, display, requestor, target, time);
open_clipboard_timeout (NULL);
}
else
{
queue_open_clipboard (GDK_WIN32_CLIPBOARD_QUEUE_ACTION_CONVERT, display, requestor, target, time);
/* Do not generate a selection notify message */
return;
}
}
else if (selection == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_DROPFILES_DND))
{
/* This means he wants the names of the dropped files.
* gdk_dropfiles_filter already has stored the text/uri-list
* data temporarily in dropfiles_prop.
*/
if (win32_sel->dropfiles_prop != NULL)
{
selection_property_store
(requestor, win32_sel->dropfiles_prop->target, win32_sel->dropfiles_prop->bitness,
win32_sel->dropfiles_prop->data, win32_sel->dropfiles_prop->length);
g_clear_pointer (&win32_sel->dropfiles_prop, g_free);
}
}
else if (selection == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_OLE2_DND))
{
property = convert_dnd_selection_to_target (target, requestor);
}
else
property = NULL;
/* Generate a selection notify message so that we actually fetch the
* data (if property == GDK_SELECTION) or indicating failure (if
* property == NULL).
*/
generate_selection_notify (requestor, selection, target, property, time);
}
void
_gdk_win32_selection_property_change (GdkWin32Selection *win32_sel,
GdkWindow *window,
GdkAtom property,
GdkAtom type,
gint format,
GdkPropMode mode,
const guchar *data,
gint nelements)
{
if (property == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_GDK_SELECTION) &&
type == GDK_SELECTION_TYPE_ATOM) /* implies target == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_TARGETS) */
{
if (win32_sel->clipboard_opened_for == INVALID_HANDLE_VALUE &&
OpenClipboard (GDK_WINDOW_HWND (window)))
{
win32_sel->clipboard_opened_for = GDK_WINDOW_HWND (window);
GDK_NOTE (DND, g_print ("Opened clipboard for 0x%p @ %s:%d\n", win32_sel->clipboard_opened_for, __FILE__, __LINE__));
}
if (win32_sel->clipboard_opened_for == INVALID_HANDLE_VALUE)
{
queue_open_clipboard (GDK_WIN32_CLIPBOARD_QUEUE_ACTION_TARGETS, NULL, window, type, GDK_CURRENT_TIME);
return;
}
else
{
queue_open_clipboard (GDK_WIN32_CLIPBOARD_QUEUE_ACTION_TARGETS, NULL, window, type, GDK_CURRENT_TIME);
open_clipboard_timeout (NULL);
}
}
else if (mode == GDK_PROP_MODE_REPLACE &&
(win32_sel->property_change_data == NULL ||
win32_sel->property_change_format == 0))
{
g_warning ("Setting selection property with 0x%p == NULL or 0x%x == 0", win32_sel->property_change_data, win32_sel->property_change_format);
}
else if (mode == GDK_PROP_MODE_REPLACE &&
win32_sel->property_change_data != NULL &&
win32_sel->property_change_format != 0)
{
guchar *set_data = NULL;
gint set_data_length = 0;
gint byte_length = format / 8 * nelements;
if (win32_sel->property_change_transmute)
transmute_selection_target (type,
win32_sel->property_change_format,
data,
byte_length,
&set_data,
&set_data_length);
else
{
set_data_length = byte_length;
set_data = g_memdup (data, set_data_length);
}
if (set_data != NULL && set_data_length > 0)
{
HGLOBAL hdata;
if ((hdata = GlobalAlloc (GMEM_MOVEABLE, set_data_length)) != 0)
{
gchar *ucptr;
win32_sel->property_change_data->tymed = TYMED_HGLOBAL;
win32_sel->property_change_data->pUnkForRelease = NULL;
win32_sel->property_change_data->hGlobal = hdata;
ucptr = GlobalLock (hdata);
memcpy (ucptr, set_data, set_data_length);
GlobalUnlock (hdata);
}
else
{
WIN32_API_FAILED ("GlobalAlloc");
}
g_free (set_data);
}
}
else
{
gchar *prop_name = gdk_atom_name (property);
gchar *type_name = gdk_atom_name (type);
gchar *datastring = _gdk_win32_data_to_string (data, MIN (10, format*nelements/8));
g_warning ("Unsupported property change on window 0x%p, %s property %s, %d-bit, target 0x%x of %d bytes: %s",
window,
(mode == GDK_PROP_MODE_REPLACE ? "REPLACE" :
(mode == GDK_PROP_MODE_PREPEND ? "PREPEND" :
(mode == GDK_PROP_MODE_APPEND ? "APPEND" :
"???"))),
prop_name,
format,
type_name,
nelements,
datastring);
g_free (datastring);
g_free (prop_name);
g_free (type_name);
}
}
gint
_gdk_win32_display_get_selection_property (GdkDisplay *display,
GdkWindow *requestor,
guchar **data,
GdkAtom *ret_type,
gint *ret_format)
{
GdkWin32Selection *win32_sel = _gdk_win32_selection_get ();
GdkSelProp *prop;
g_return_val_if_fail (requestor != NULL, 0);
g_return_val_if_fail (GDK_IS_WINDOW (requestor), 0);
if (GDK_WINDOW_DESTROYED (requestor))
return 0;
GDK_NOTE (DND, g_print ("gdk_selection_property_get: %p",
GDK_WINDOW_HWND (requestor)));
prop = g_hash_table_lookup (win32_sel->sel_prop_table, GDK_WINDOW_HWND (requestor));
if (prop == NULL)
{
GDK_NOTE (DND, g_print (" (nothing)\n"));
*data = NULL;
return 0;
}
*data = g_malloc (prop->length + 1);
(*data)[prop->length] = '\0';
if (prop->length > 0)
memmove (*data, prop->data, prop->length);
GDK_NOTE (DND, {
gchar *type_name = gdk_atom_name (prop->target);
g_print (" %s format:%d length:%"G_GSIZE_FORMAT"\n", type_name, prop->bitness, prop->length);
g_free (type_name);
});
if (ret_type)
*ret_type = prop->target;
if (ret_format)
*ret_format = prop->bitness;
return prop->length;
}
void
_gdk_selection_property_delete (GdkWindow *window)
{
GDK_NOTE (DND, g_print ("_gdk_selection_property_delete: %p (no-op)\n",
GDK_WINDOW_HWND (window)));
#if 0
prop = g_hash_table_lookup (sel_prop_table, GDK_WINDOW_HWND (window));
if (prop != NULL)
{
g_free (prop->data);
g_free (prop);
g_hash_table_remove (sel_prop_table, GDK_WINDOW_HWND (window));
}
#endif
}
void
_gdk_win32_display_send_selection_notify (GdkDisplay *display,
GdkWindow *requestor,
GdkAtom selection,
GdkAtom target,
GdkAtom property,
guint32 time)
{
GDK_NOTE (DND, {
gchar *sel_name = gdk_atom_name (selection);
gchar *tgt_name = gdk_atom_name (target);
gchar *prop_name = gdk_atom_name (property);
g_print ("gdk_selection_send_notify_for_display: %p %s %s %s (no-op)\n",
requestor, sel_name, tgt_name, prop_name);
g_free (sel_name);
g_free (tgt_name);
g_free (prop_name);
});
}
/* It's hard to say whether implementing this actually is of any use
* on the Win32 platform? gtk calls only
* gdk_text_property_to_utf8_list_for_display().
*/
gint
gdk_text_property_to_text_list_for_display (GdkDisplay *display,
GdkAtom encoding,
gint format,
const guchar *text,
gint length,
gchar ***list)
{
gchar *result;
const gchar *charset;
gchar *source_charset;
GDK_NOTE (DND, {
gchar *enc_name = gdk_atom_name (encoding);
g_print ("gdk_text_property_to_text_list_for_display: %s %d %.20s %d\n",
enc_name, format, text, length);
g_free (enc_name);
});
if (!list)
return 0;
if (encoding == GDK_TARGET_STRING)
source_charset = g_strdup ("ISO-8859-1");
else if (encoding == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_UTF8_STRING))
source_charset = g_strdup ("UTF-8");
else
source_charset = gdk_atom_name (encoding);
g_get_charset (&charset);
result = g_convert ((const gchar *) text, length, charset, source_charset,
NULL, NULL, NULL);
g_free (source_charset);
if (!result)
return 0;
*list = g_new (gchar *, 1);
**list = result;
return 1;
}
void
gdk_free_text_list (gchar **list)
{
g_return_if_fail (list != NULL);
g_free (*list);
g_free (list);
}
static gint
make_list (const gchar *text,
gint length,
gboolean latin1,
gchar ***list)
{
GSList *strings = NULL;
gint n_strings = 0;
gint i;
const gchar *p = text;
const gchar *q;
GSList *tmp_list;
GError *error = NULL;
while (p < text + length)
{
gchar *str;
q = p;
while (*q && q < text + length)
q++;
if (latin1)
{
str = g_convert (p, q - p,
"UTF-8", "ISO-8859-1",
NULL, NULL, &error);
if (!str)
{
g_warning ("Error converting selection from STRING: %s",
error->message);
g_error_free (error);
}
}
else
str = g_strndup (p, q - p);
if (str)
{
strings = g_slist_prepend (strings, str);
n_strings++;
}
p = q + 1;
}
if (list)
*list = g_new (gchar *, n_strings + 1);
(*list)[n_strings] = NULL;
i = n_strings;
tmp_list = strings;
while (tmp_list)
{
if (list)
(*list)[--i] = tmp_list->data;
else
g_free (tmp_list->data);
tmp_list = tmp_list->next;
}
g_slist_free (strings);
return n_strings;
}
gint
_gdk_win32_display_text_property_to_utf8_list (GdkDisplay *display,
GdkAtom encoding,
gint format,
const guchar *text,
gint length,
gchar ***list)
{
g_return_val_if_fail (text != NULL, 0);
g_return_val_if_fail (length >= 0, 0);
if (encoding == GDK_TARGET_STRING)
{
return make_list ((gchar *)text, length, TRUE, list);
}
else if (encoding == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_UTF8_STRING))
{
return make_list ((gchar *)text, length, FALSE, list);
}
else
{
gchar *enc_name = gdk_atom_name (encoding);
g_warning ("gdk_text_property_to_utf8_list_for_display: encoding %s not handled\n", enc_name);
g_free (enc_name);
if (list)
*list = NULL;
return 0;
}
}
gchar *
_gdk_win32_display_utf8_to_string_target (GdkDisplay *display,
const gchar *str)
{
return _gdk_utf8_to_string_target_internal (str, strlen (str));
}
void
gdk_win32_selection_clear_targets (GdkDisplay *display,
GdkAtom selection)
{
GdkWin32Selection *win32_sel = _gdk_win32_selection_get ();
if (selection == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_OLE2_DND) ||
selection == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_LOCAL_DND_SELECTION))
{
g_array_set_size (win32_sel->dnd_selection_targets, 0);
}
else if (selection == GDK_SELECTION_CLIPBOARD)
{
g_array_set_size (win32_sel->clipboard_selection_targets, 0);
}
else if (selection == GDK_SELECTION_PRIMARY)
{
/* Do nothing */
}
else
{
gchar *sel_name = gdk_atom_name (selection);
g_warning ("Unsupported generic selection %s (0x%p)", sel_name, selection);
g_free (sel_name);
}
}
gint
_gdk_win32_add_target_to_selformats (GdkAtom target,
GArray *array)
{
gint added_count = 0;
gchar *target_name;
wchar_t *target_name_w;
GdkSelTargetFormat fmt;
gint i;
GArray *compatibility_formats;
gint starting_point;
for (i = 0; i < array->len; i++)
if (g_array_index (array, GdkSelTargetFormat, i).target == target)
break;
/* Don't put duplicates into the array */
if (i < array->len)
return added_count;
if (target == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_TARGETS) ||
target == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_COMPOUND_TEXT) || target == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_SAVE_TARGETS))
{
/* Add the "we don't really support transferring that to
* other processes" format, just to keep the taget around.
*/
fmt.target = target;
fmt.format = 0;
fmt.transmute = FALSE;
g_array_append_val (array, fmt);
added_count += 1;
return added_count;
}
/* Only check the newly-added pairs for duplicates,
* all the ones that exist right now have different targets.
*/
starting_point = array->len;
target_name = gdk_atom_name (target);
target_name_w = g_utf8_to_utf16 (target_name, -1, NULL, NULL, NULL);
g_free (target_name);
if (target_name_w == NULL)
return added_count;
fmt.format = RegisterClipboardFormatW (target_name_w);
GDK_NOTE (DND, g_print ("Registered clipboard format %S as 0x%x\n", target_name_w, fmt.format));
g_free (target_name_w);
fmt.target = target;
fmt.transmute = FALSE;
/* Add the "as-is" format */
g_array_append_val (array, fmt);
added_count += 1;
compatibility_formats = get_compatibility_formats_for_target (target);
for (i = 0; compatibility_formats != NULL && i < compatibility_formats->len; i++)
{
gint j;
fmt = g_array_index (compatibility_formats, GdkSelTargetFormat, i);
/* Don't put duplicates into the array */
for (j = starting_point; j < array->len; j++)
if (g_array_index (array, GdkSelTargetFormat, j).format == fmt.format)
break;
if (j < array->len)
continue;
/* Add a compatibility format */
g_array_append_val (array, fmt);
added_count += 1;
}
return added_count;
}
void
gdk_win32_display_clear_selection_targets (GdkDisplay *display,
GdkAtom selection)
{
}
/* This function is called from gtk_selection_add_target() and
* gtk_selection_add_targets() in gtkselection.c. It is this function
* that takes care of setting those clipboard formats for which we use
* delayed rendering (that is, all formats, as we use delayed rendering
* for everything). This function only registers the formats, but does
* not announce them as supported. That is handled as a special case
* in gdk_window_property_change().
*
* Implementation detail:
* This function will be called repeatedly, every time the PRIMARY selection changes.
* It will also be called immediately before the CLIPBOARD selection changes.
* We let GTK+ handle the PRIMARY selection internally and do nothing here
* (therefore it's not possible to middle-click-paste between processes,
* unless one process deliberately puts PRIMARY selection contents into
* CLIPBOARD selection, and the other process does paste on middle-click).
*/
void
gdk_win32_display_add_selection_targets (GdkDisplay *display,
GdkWindow *owner,
GdkAtom selection,
GdkAtom *targets,
guint ntargets)
{
GdkWin32Selection *win32_sel = _gdk_win32_selection_get ();
gint i;
GDK_NOTE (DND, {
gchar *sel_name = gdk_atom_name (selection);
g_print ("gdk_win32_selection_add_targets: %p: %s: ",
owner ? GDK_WINDOW_HWND (owner) : NULL,
sel_name);
g_free (sel_name);
for (i = 0; i < ntargets; i++)
{
gchar *tgt_name = gdk_atom_name (targets[i]);
g_print ("%s", tgt_name);
g_free (tgt_name);
if (i < ntargets - 1)
g_print (", ");
}
g_print ("\n");
});
if (selection == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_OLE2_DND) ||
selection == _gdk_win32_selection_atom (GDK_WIN32_ATOM_INDEX_LOCAL_DND_SELECTION) ||
selection == GDK_SELECTION_CLIPBOARD)
{
GArray *fmts = NULL;
gint added_count = 0;
if (selection == GDK_SELECTION_CLIPBOARD)
fmts = win32_sel->clipboard_selection_targets;
else
fmts = win32_sel->dnd_selection_targets;
for (i = 0; i < ntargets; i++)
added_count += _gdk_win32_add_target_to_selformats (targets[i], fmts);
/* Re-announce our list of supported formats */
if (added_count > 0)
send_targets_request (GDK_CURRENT_TIME);
}
else if (selection == GDK_SELECTION_PRIMARY)
{
/* Do nothing */
}
else
{
gchar *sel_name = gdk_atom_name (selection);
g_warning ("Unsupported generic selection %s (0x%p)", sel_name, selection);
g_free (sel_name);
}
}