forked from AuroraMiddleware/gtk
8ee4de804c
* Remove clipdrop->dnd_target_state, it's not used anymore * Remove non-functioning _gdk_dropfiles_store(), store dropfiles list in GdkWin32Drop instead * Fix multiple comment typos * Fix _gdk_win32_get_clipboard_format_name_as_interned_mimetype() to leave names that look like mime/types alone * Refactor _gdk_win32_add_w32format_to_pairs() to populate GdkContentFormatsBuilder directly, instead of making a GList * Rename context -> drag (still using GdkDragContext type, but [almost?] all variables and comments say "drag" now) * Rename GdkDropContext -> GdkDrop * Rename some parameter names for clarity * Rewrite local protocol to look more like OLE2 protocol instead of mirroring the structure of the X11 API. * Add handle_events field to GdkWin32DragContext, to shut off event handling (temporary fix until GTK is patched up) * Remove _gdk_win32_drag_context_find() - the drag object is stored in GdkDrop instead. Use _gdk_win32_find_drag_for_dest_surface() to get it initially. * Remove target_ctx_for_window, droptarget context is stored in the surface instead. * Call gdk_drag_context_set_cursor() just like wayland backend does (slightly broken for now) * Clean up the action choosing code (filter source actions by using keyboard state, pass that to GTK, get all actions supported by GTK in response, match them up with filtered source actions, return the result, falling back to COPY in case of multiple actions) * Check drag_win32->protocol instead of the use_ole2_dnd variable where possible * Remove protocol checks from functions that are only used by the local protocol * Use event state to manufacture the keyboard state for WM_MOUSEMOVE * Change function names printed by GDK_NOTE to name the actual functions, not their theoretical generic GDK stack ancestors * Consistently use drag_win32 and drop_win32 variables instead of a mix of that and win32_drag/win32_drop * Return FALSE from button handler to ensure that GTK gets the button event to break implicit grab * Emit leave event on failed idroptarget_drop() calls
3118 lines
104 KiB
C
3118 lines
104 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+ has two clipboards - normal clipboard and primary clipboard
|
|
Primary clipboard is only handled
|
|
internally by GTK+ (it's not portable to Windows).
|
|
|
|
("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 the old g(t|d)k_selection_convert() APIs,
|
|
which are completely unrelated)
|
|
|
|
For Clipboard:
|
|
GTK+ calls one of the gdk_clipboard_set* () functions (either supplying
|
|
its own content provider, or giving a GTyped data for which GDK will
|
|
create a content provider automatically).
|
|
That function associates the content provider with the clipboard and calls
|
|
S: gdk_clipboard_claim(),
|
|
to claim ownership. GDK first calls the backend implementation of
|
|
that function, then the
|
|
S: gdk_clipboard_real_claim()
|
|
implementation.
|
|
The "real" function does some mundane bookkeeping, whereas the backend
|
|
implementation advertises the formats supported by the clipboard,
|
|
if the call says that the claim is local. Non-local (remote) claims
|
|
are there just to tell GDK that some other process owns the clipboard
|
|
and claims to provide data in particular formats.
|
|
No data is sent anywhere.
|
|
|
|
The content provider has a callback, which will be invoked every time
|
|
the data from this provider is needed.
|
|
|
|
GTK+ might also call gdk_clipboard_store_async(), which instructs
|
|
the backend to put the data into the OS clipboard manager (if
|
|
supported and available) so that it remains available for other
|
|
processes after the clipboard owner terminates.
|
|
|
|
When something needs to be obtained from clipboard, GTK+ calls
|
|
C: gdk_clipboard_read_async () -> gdk_clipboard_read_internal (),
|
|
providing it with a string-array of mime/types, which is internally
|
|
converted into a GdkContentFormats object.
|
|
That function creates a task.
|
|
|
|
Then, if the clipboard is local, it calls
|
|
C: gdk_clipboard_read_local_async(),
|
|
which matches given formats to the content provider formats and, if there's a match,
|
|
creates a pipe, calls
|
|
C: gdk_clipboard_write_async()
|
|
on the write end, and sets the read end as a return value of the task, which will
|
|
later be given to the caller-specified callback.
|
|
|
|
If the clipboard isn't local, it calls
|
|
C: read_async()
|
|
of the backend clipboard stream class.
|
|
The backend starts creating a stream (somehow) and sets up the callback to return that
|
|
stream via the task, once the stream is created.
|
|
Either way, the caller-specified callback is invoked, and it gets the read end
|
|
of a stream by calling
|
|
C: gdk_clipboard_read_finish(),
|
|
then reads the data from the stream and unrefs the stream once it is done.
|
|
IRL applications use wrappers, which create an extra task that gets the
|
|
stream, reads from it asynchronously and turns the bytes that it reads into
|
|
some kind of application-specific object type. GDK comes pre-equipped with
|
|
functions that read arbitrary GTypes (as long as they are serializable),
|
|
texts (strings) or textures (GdkPixbufs) this way.
|
|
|
|
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:
|
|
|
|
Any operations that require OpenClipboard()/CloseClipboard() combo (i.e.
|
|
almost everything, except for WM_RENDERFORMAT handling) is offloaded into
|
|
separate thread, which tries to to complete any operations in the queue.
|
|
Each operation routine usually starts with a timeout check (all operations
|
|
time out after 30 seconds), then a check for clipboard status (to abort
|
|
any operations that became obsolete due to clipboard status being changed -
|
|
i.e. retrieving clipboard contents is aborted if clipboard contents change
|
|
before the operation can be completed), then an attempt to OpenClipboard().
|
|
Failure to OpenClipboard() leads to the queue processing stopping, and
|
|
resuming from the beginning after another 1-second delay.
|
|
A success in OpenClipboard() allows the operation to continue.
|
|
The thread remembers the fact that it has clipboard open, and does
|
|
not try to close & reopen it, unless that is strictly necessary.
|
|
The clipboard is closed after each queue processing run.
|
|
|
|
GTK+ calls one of the gdk_clipboard_set* () functions (either supplying
|
|
its own content provider, or giving a GTyped data for which GDK will
|
|
create a content provider automatically).
|
|
That function associates the content provider with the clipboard and calls
|
|
S: gdk_clipboard_claim(),
|
|
to claim ownership. GDK first calls the backend implementation of
|
|
that function,
|
|
S: gdk_win32_clipboard_claim(),
|
|
which maps the supported GDK contentformats to W32 data formats and
|
|
caches this mapping, then the
|
|
S: gdk_clipboard_real_claim()
|
|
implementation.
|
|
The "real" function does some mundane bookkeeping, whereas the backend
|
|
implementation advertises the formats supported by the clipboard,
|
|
if the call says that the claim is local. Non-local (remote) claims
|
|
are there just to tell GDK that some other process owns the clipboard
|
|
and claims to provide data in particular formats.
|
|
For the local claims gdk_win32_clipboard_claim() queues a clipboard
|
|
advertise operation (see above).
|
|
That operation will call EmptyClipboard() to claim the ownership,
|
|
then call SetClipboardData() with NULL value for each W32 data format
|
|
supported, advertising the W32 data formats to other processes.
|
|
No data is sent anywhere.
|
|
|
|
The content provider has a callback, which will be invoked every time
|
|
the data from this provider is needed.
|
|
|
|
GTK+ might also call gdk_clipboard_store_async(), which instructs
|
|
the W32 backend to put the data into the OS clipboard manager by
|
|
sending WM_RENDERALLFORMATS to itself and then handling it normally.
|
|
|
|
Every time W32 backend gets WM_DRAWCLIPBOARD or WM_CLIPBOARDUPDATE,
|
|
it calls GetUpdatedClipboardFormats() and GetClipboardSequenceNumber()
|
|
and caches the results of both. These calls do not require the clipboard
|
|
to be opened.
|
|
After that it would call
|
|
C: gdk_win32_clipboard_claim_remote()
|
|
to indicate that some other process owns the clipboard and supports
|
|
the formats from the cached list. If the process is the owner,
|
|
the remote claim is not performed (it's assumed that a local claim
|
|
was already made when a clipboard content provider is set, so no need
|
|
to do that either).
|
|
Note: clipboard sequence number changes with each SetClipboardData() call.
|
|
Specifically, a process that uses delayed rendering (like GDK does)
|
|
must call SetClipboardData() with NULL value every time the data changes,
|
|
even if its format remains the same.
|
|
The cached remote formats are then mapped into GDK contentformats.
|
|
This map is separate from the one that maps supported GDK contentformats
|
|
to W32 formats for locally-claimed clipboards.
|
|
|
|
When something needs to be obtained from clipboard, GTK+ calls
|
|
C: gdk_clipboard_read_async () -> gdk_clipboard_read_internal (),
|
|
providing it with a string-array of mime/types, which is internally
|
|
converted into a GdkContentFormats object.
|
|
That function creates a task.
|
|
|
|
Then, if the clipboard is local, it calls
|
|
C: gdk_clipboard_read_local_async(),
|
|
which matches given formats to the content provider formats and, if there's a match,
|
|
creates a pipe, calls
|
|
C: gdk_clipboard_write_async()
|
|
on the write end, and sets the read end as a return value of the task, which will
|
|
later be given to the caller-specified callback.
|
|
|
|
If the clipboard isn't local, it calls
|
|
C: read_async()
|
|
of the W32 backend clipboard stream class.
|
|
It then queues a retrieve operation (see above).
|
|
The retrieve operation goes over formats available on the clipboard,
|
|
and picks the first one that matches the list supplied with the retrieve
|
|
operation (that is, it gives priority to formats at the top of the clipboard
|
|
format list, even if such formats are at the bottom of the list of formats
|
|
supported by the application; this is due to the fact that formats at the
|
|
top of the clipboard format list are usually "raw" or "native" and assumed
|
|
to not to be transmuted by the clipboard owner from some other format,
|
|
and thus it is better to use these, if the requesting application can handle
|
|
them). It then calls GetClipboardData(), which either causes
|
|
a WM_RENDERFORMAT to be sent to the server (for delayed rendering),
|
|
or it just grabs the data from the OS.
|
|
|
|
Server-side GDK catches WM_RENDERFORMAT, figures out a contentformat
|
|
to request (it has an earlier advertisement cached in the thread, so
|
|
there's no need to ask the main thread for anything), and
|
|
creates a render request, then sends it to the main thread.
|
|
After that it keeps polling the queue until the request comes back.
|
|
The main thread render handler creates an output stream that
|
|
writes the data into a global memory buffer, then calls
|
|
S: gdk_clipboard_write_async()
|
|
to write the data.
|
|
The callback finishes that up with
|
|
S: gdk_clipboard_write_finish(),
|
|
which sends the render request back to the clipborad thread,
|
|
along with the data. The clipboard thread then calls
|
|
S: SetClipboardData()
|
|
with the clipboard handle provided by the OS on behalf of the client.
|
|
|
|
Once the data handle is available, the clipboard thread creates a stream
|
|
that reads from a copy of that data (after transmutation, if necessary),
|
|
and sends that stream back to the main thread. The data is kept in a client-side
|
|
memory buffer (owned by the stream), the HGLOBAL given by the OS is not held
|
|
around for this to happen.
|
|
The stream is then returned through the task to the caller.
|
|
|
|
Either way, the caller-specified callback is invoked, and it gets the read end
|
|
of a stream by calling
|
|
C: gdk_clipboard_read_finish(),
|
|
then reads the data from the stream and unrefs the stream once it is done.
|
|
The local buffer that backed the stream is freed with the stream.
|
|
IRL applications use wrappers, which create an extra task that gets the
|
|
stream, reads from it asynchronously and turns the bytes that it reads into
|
|
some kind of application-specific object type. GDK comes pre-equipped with
|
|
functions that read arbitrary GTypes (as long as they are serializable),
|
|
texts (strings) or textures (GdkPixbufs) this way.
|
|
|
|
If data must be stored on the clipboard, because the application is quitting,
|
|
GTK+ will call
|
|
S: gdk_clipboard_store_async()
|
|
on all the clipboards it owns. This creates multiple write stream (one for each
|
|
format being stored), each backed by a HGLOBAL memory object. Once all memory
|
|
objects are written, the backend queues a store operation, passing along
|
|
all these HGLOBAL objects. The clipboard thread processes that by sending
|
|
WM_RENDERALLFORMATS to the window, then signals the task that it's done.
|
|
|
|
When clipboard owner changes, the old owner receives WM_DESTROYCLIPBOARD message,
|
|
the clipboard thread schedules a call to gdk_clipboard_claim_remote()
|
|
in the main thread, with an empty list of formats,
|
|
to indicate that the clipboard is now owned by a remote process.
|
|
Later the OS will send WM_DRAWCLIPBOARD or WM_CLIPBOARDUPDATE to indicate
|
|
the new clipboard contents (see above).
|
|
|
|
DND:
|
|
GDK-Win32:
|
|
DnD server runs in a separate thread, and schedules calls to be
|
|
made in the main thread in response to the DnD thread being invoked
|
|
by the OS (using OLE2 mechanism).
|
|
The DnD thread normally just idles, until the main thread tells it
|
|
to call DoDragDrop(), at which point it enters the DoDragDrop() call
|
|
(which means that its OLE2 DnD callbacks get invoked repeatedly by the OS
|
|
in response to user actions), and doesn't leave it until the DnD
|
|
operation is finished.
|
|
|
|
Otherwise it's similar to how the clipboard works. Only the DnD server
|
|
(drag source) works in a thread. DnD client (drop target) works normally.
|
|
*/
|
|
|
|
#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 "gdkdisplay.h"
|
|
#include "gdkprivate-win32.h"
|
|
#include "gdkclipboardprivate.h"
|
|
#include "gdkclipboard-win32.h"
|
|
#include "gdkclipdrop-win32.h"
|
|
#include "gdkhdataoutputstream-win32.h"
|
|
#include "gdk/gdkdndprivate.h"
|
|
#include "gdkwin32dnd.h"
|
|
#include "gdkwin32dnd-private.h"
|
|
#include "gdkwin32.h"
|
|
#include "gdkintl.h"
|
|
|
|
#define HIDA_GetPIDLFolder(pida) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[0])
|
|
#define HIDA_GetPIDLItem(pida, i) (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[i+1])
|
|
|
|
#define CLIPBOARD_OPERATION_TIMEOUT (G_USEC_PER_SEC * 30)
|
|
|
|
/* GetClipboardData() times out after 30 seconds.
|
|
* Try to reply (even if it's a no-action reply due to a timeout)
|
|
* before that happens.
|
|
*/
|
|
#define CLIPBOARD_RENDER_TIMEOUT (G_USEC_PER_SEC * 29)
|
|
|
|
gboolean _gdk_win32_transmute_windows_data (UINT from_w32format,
|
|
const gchar *to_contentformat,
|
|
HANDLE hdata,
|
|
guchar **set_data,
|
|
gsize *set_data_length);
|
|
|
|
/* Just to avoid calling RegisterWindowMessage() every time */
|
|
static UINT thread_wakeup_message;
|
|
|
|
typedef enum _GdkWin32ClipboardThreadQueueItemType GdkWin32ClipboardThreadQueueItemType;
|
|
|
|
enum _GdkWin32ClipboardThreadQueueItemType
|
|
{
|
|
GDK_WIN32_CLIPBOARD_THREAD_QUEUE_ITEM_ADVERTISE = 1,
|
|
GDK_WIN32_CLIPBOARD_THREAD_QUEUE_ITEM_RETRIEVE = 2,
|
|
GDK_WIN32_CLIPBOARD_THREAD_QUEUE_ITEM_STORE = 3,
|
|
};
|
|
|
|
typedef struct _GdkWin32ClipboardThreadQueueItem GdkWin32ClipboardThreadQueueItem;
|
|
|
|
struct _GdkWin32ClipboardThreadQueueItem
|
|
{
|
|
GdkWin32ClipboardThreadQueueItemType item_type;
|
|
gint64 start_time;
|
|
gint64 end_time;
|
|
gpointer opaque_task;
|
|
};
|
|
|
|
typedef struct _GdkWin32ClipboardThreadAdvertise GdkWin32ClipboardThreadAdvertise;
|
|
|
|
struct _GdkWin32ClipboardThreadAdvertise
|
|
{
|
|
GdkWin32ClipboardThreadQueueItem parent;
|
|
GArray *pairs; /* of GdkWin32ContentFormatPair */
|
|
gboolean unset;
|
|
};
|
|
|
|
typedef struct _GdkWin32ClipboardThreadRetrieve GdkWin32ClipboardThreadRetrieve;
|
|
|
|
struct _GdkWin32ClipboardThreadRetrieve
|
|
{
|
|
GdkWin32ClipboardThreadQueueItem parent;
|
|
GArray *pairs; /* of GdkWin32ContentFormatPair */
|
|
gint64 sequence_number;
|
|
};
|
|
|
|
typedef struct _GdkWin32ClipboardStorePrepElement GdkWin32ClipboardStorePrepElement;
|
|
|
|
struct _GdkWin32ClipboardStorePrepElement
|
|
{
|
|
UINT w32format;
|
|
const gchar *contentformat;
|
|
HANDLE handle;
|
|
GOutputStream *stream;
|
|
};
|
|
|
|
typedef struct _GdkWin32ClipboardStorePrep GdkWin32ClipboardStorePrep;
|
|
|
|
struct _GdkWin32ClipboardStorePrep
|
|
{
|
|
GTask *store_task;
|
|
GArray *elements; /* of GdkWin32ClipboardStorePrepElement */
|
|
};
|
|
|
|
typedef struct _GdkWin32ClipboardThreadStore GdkWin32ClipboardThreadStore;
|
|
|
|
struct _GdkWin32ClipboardThreadStore
|
|
{
|
|
GdkWin32ClipboardThreadQueueItem parent;
|
|
GArray *elements; /* of GdkWin32ClipboardStorePrepElement */
|
|
};
|
|
|
|
typedef struct _GdkWin32ClipboardThreadRender GdkWin32ClipboardThreadRender;
|
|
|
|
struct _GdkWin32ClipboardThreadRender
|
|
{
|
|
/* The handle that the main thread prepares for us.
|
|
* We just give it to SetClipboardData ().
|
|
* NULL means that the rendering failed.
|
|
*/
|
|
HANDLE main_thread_data_handle;
|
|
|
|
/* The format that is being requested of us */
|
|
GdkWin32ContentFormatPair pair;
|
|
};
|
|
|
|
typedef struct _GdkWin32ClipboardThread GdkWin32ClipboardThread;
|
|
|
|
struct _GdkWin32ClipboardThread
|
|
{
|
|
/* A hidden window that owns our clipboard
|
|
* and receives clipboard-related messages.
|
|
*/
|
|
HWND clipboard_window;
|
|
|
|
/* We receive instructions from the main thread in this queue */
|
|
GAsyncQueue *input_queue;
|
|
|
|
/* Last observer owner of the clipboard, as reported by the OS.
|
|
* This is compared to GetClipboardOwner() return value to see
|
|
* whether the owner changed.
|
|
*/
|
|
HWND stored_hwnd_owner;
|
|
|
|
/* The last time we saw an owner change event.
|
|
* Any requests made before this time are invalid and
|
|
* fail automatically.
|
|
*/
|
|
gint64 owner_change_time;
|
|
|
|
/* The handle that was given to OpenClipboard().
|
|
* NULL is a valid handle,
|
|
* INVALID_HANDLE_VALUE means that the clipboard is closed.
|
|
*/
|
|
HWND clipboard_opened_for;
|
|
|
|
HWND hwnd_next_viewer;
|
|
|
|
/* We can't peek the queue or "unpop" queue items,
|
|
* so the items that we can't act upon (yet) got
|
|
* to be stored *somewhere*.
|
|
*/
|
|
GList *dequeued_items;
|
|
|
|
/* Wakeup timer id (1 if timer is set, 0 otherwise) */
|
|
UINT wakeup_timer;
|
|
|
|
/* The formats that the main thread claims to provide */
|
|
GArray *cached_advertisement; /* of GdkWin32ContentFormatPair */
|
|
|
|
/* We receive rendered clipboard data in this queue.
|
|
* Contains GdkWin32ClipboardThreadRender structs.
|
|
*/
|
|
GAsyncQueue *render_queue;
|
|
|
|
/* Set to TRUE when we're calling EmptyClipboard () */
|
|
gboolean ignore_destroy_clipboard;
|
|
};
|
|
|
|
/* The code is much more secure if we don't rely on the OS to keep
|
|
* this around for us.
|
|
*/
|
|
static GdkWin32ClipboardThread *clipboard_thread_data = NULL;
|
|
|
|
typedef struct _GdkWin32ClipboardThreadResponse GdkWin32ClipboardThreadResponse;
|
|
|
|
struct _GdkWin32ClipboardThreadResponse
|
|
{
|
|
GdkWin32ClipboardThreadQueueItemType item_type;
|
|
GError *error;
|
|
gpointer opaque_task;
|
|
GInputStream *input_stream;
|
|
};
|
|
|
|
gboolean
|
|
_gdk_win32_format_uses_hdata (UINT w32format)
|
|
{
|
|
switch (w32format)
|
|
{
|
|
case CF_DIB:
|
|
case CF_DIBV5:
|
|
case CF_DIF:
|
|
case CF_DSPBITMAP:
|
|
case CF_DSPENHMETAFILE:
|
|
case CF_DSPMETAFILEPICT:
|
|
case CF_DSPTEXT:
|
|
case CF_OEMTEXT:
|
|
case CF_RIFF:
|
|
case CF_SYLK:
|
|
case CF_TEXT:
|
|
case CF_TIFF:
|
|
case CF_UNICODETEXT:
|
|
case CF_WAVE:
|
|
return TRUE;
|
|
default:
|
|
if (w32format >= 0xC000)
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
/* This function is called in the main thread */
|
|
static gboolean
|
|
clipboard_window_created (gpointer user_data)
|
|
{
|
|
GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
|
|
|
|
clipdrop->clipboard_window = (HWND) user_data;
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
/* This function is called in the main thread */
|
|
static gboolean
|
|
clipboard_owner_changed (gpointer user_data)
|
|
{
|
|
GdkDisplay *display = gdk_display_get_default ();
|
|
GdkClipboard *clipboard = gdk_display_get_clipboard (display);
|
|
gdk_win32_clipboard_claim_remote (GDK_WIN32_CLIPBOARD (clipboard));
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
typedef struct _GdkWin32ClipboardRenderAndStream GdkWin32ClipboardRenderAndStream;
|
|
|
|
struct _GdkWin32ClipboardRenderAndStream
|
|
{
|
|
GdkWin32ClipboardThreadRender *render;
|
|
GdkWin32HDataOutputStream *stream;
|
|
};
|
|
|
|
static void
|
|
clipboard_render_hdata_ready (GObject *clipboard,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
GError *error = NULL;
|
|
GdkWin32ClipboardRenderAndStream render_and_stream = *(GdkWin32ClipboardRenderAndStream *) user_data;
|
|
GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
|
|
|
|
g_free (user_data);
|
|
|
|
if (!gdk_clipboard_write_finish (GDK_CLIPBOARD (clipboard), result, &error))
|
|
{
|
|
HANDLE handle;
|
|
gboolean is_hdata;
|
|
GDK_NOTE(CLIPBOARD, g_printerr ("%p: failed to write HData-backed stream: %s\n", clipboard, error->message));
|
|
g_error_free (error);
|
|
g_output_stream_close (G_OUTPUT_STREAM (render_and_stream.stream), NULL, NULL);
|
|
handle = gdk_win32_hdata_output_stream_get_handle (render_and_stream.stream, &is_hdata);
|
|
|
|
if (is_hdata)
|
|
API_CALL (GlobalFree, (handle));
|
|
else
|
|
API_CALL (CloseHandle, (handle));
|
|
|
|
render_and_stream.render->main_thread_data_handle = NULL;
|
|
}
|
|
else
|
|
{
|
|
g_output_stream_close (G_OUTPUT_STREAM (render_and_stream.stream), NULL, NULL);
|
|
render_and_stream.render->main_thread_data_handle = gdk_win32_hdata_output_stream_get_handle (render_and_stream.stream, NULL);
|
|
}
|
|
|
|
g_async_queue_push (clipdrop->clipboard_render_queue, render_and_stream.render);
|
|
g_object_unref (render_and_stream.stream);
|
|
}
|
|
|
|
/* This function is called in the main thread */
|
|
static gboolean
|
|
clipboard_render (gpointer user_data)
|
|
{
|
|
GdkWin32ClipboardThreadRender *render = (GdkWin32ClipboardThreadRender *) user_data;
|
|
GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
|
|
GdkDisplay *display = gdk_display_get_default ();
|
|
GdkClipboard *clipboard = gdk_display_get_clipboard (display);
|
|
GError *error = NULL;
|
|
GOutputStream *stream = gdk_win32_hdata_output_stream_new (&render->pair, &error);
|
|
GdkWin32ClipboardRenderAndStream *render_and_stream;
|
|
|
|
if (stream == NULL)
|
|
{
|
|
GDK_NOTE (SELECTION, g_printerr ("%p: failed create a HData-backed stream: %s\n", clipboard, error->message));
|
|
g_error_free (error);
|
|
render->main_thread_data_handle = NULL;
|
|
g_async_queue_push (clipdrop->clipboard_render_queue, render);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
render_and_stream = g_new0 (GdkWin32ClipboardRenderAndStream, 1);
|
|
render_and_stream->render = render;
|
|
render_and_stream->stream = GDK_WIN32_HDATA_OUTPUT_STREAM (stream);
|
|
|
|
gdk_clipboard_write_async (GDK_CLIPBOARD (clipboard),
|
|
render->pair.contentformat,
|
|
stream,
|
|
G_PRIORITY_DEFAULT,
|
|
NULL,
|
|
clipboard_render_hdata_ready,
|
|
render_and_stream);
|
|
|
|
/* Keep our reference to the stream, don't unref it */
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
/* This function is called in the main thread */
|
|
static gboolean
|
|
clipboard_thread_response (gpointer user_data)
|
|
{
|
|
GdkWin32ClipboardThreadResponse *response = (GdkWin32ClipboardThreadResponse *) user_data;
|
|
GTask *task = (GTask *) response->opaque_task;
|
|
|
|
if (task != NULL)
|
|
{
|
|
if (response->error)
|
|
g_task_return_error (task, response->error);
|
|
else if (response->input_stream)
|
|
g_task_return_pointer (task, response->input_stream, g_object_unref);
|
|
else
|
|
g_task_return_boolean (task, TRUE);
|
|
|
|
g_object_unref (task);
|
|
}
|
|
|
|
g_free (response);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
free_prep_element (GdkWin32ClipboardStorePrepElement *el)
|
|
{
|
|
if (el->handle)
|
|
{
|
|
if (_gdk_win32_format_uses_hdata (el->w32format))
|
|
GlobalFree (el->handle);
|
|
else
|
|
CloseHandle (el->handle);
|
|
}
|
|
|
|
if (el->stream)
|
|
g_object_unref (el->stream);
|
|
}
|
|
|
|
static void
|
|
free_queue_item (GdkWin32ClipboardThreadQueueItem *item)
|
|
{
|
|
GdkWin32ClipboardThreadAdvertise *adv;
|
|
GdkWin32ClipboardThreadRetrieve *retr;
|
|
GdkWin32ClipboardThreadStore *store;
|
|
gint i;
|
|
|
|
switch (item->item_type)
|
|
{
|
|
case GDK_WIN32_CLIPBOARD_THREAD_QUEUE_ITEM_ADVERTISE:
|
|
adv = (GdkWin32ClipboardThreadAdvertise *) item;
|
|
if (adv->pairs)
|
|
g_array_free (adv->pairs, TRUE);
|
|
break;
|
|
case GDK_WIN32_CLIPBOARD_THREAD_QUEUE_ITEM_RETRIEVE:
|
|
retr = (GdkWin32ClipboardThreadRetrieve *) item;
|
|
if (retr->pairs)
|
|
g_array_free (retr->pairs, TRUE);
|
|
break;
|
|
case GDK_WIN32_CLIPBOARD_THREAD_QUEUE_ITEM_STORE:
|
|
store = (GdkWin32ClipboardThreadStore *) item;
|
|
for (i = 0; i < store->elements->len; i++)
|
|
{
|
|
GdkWin32ClipboardStorePrepElement *el = &g_array_index (store->elements, GdkWin32ClipboardStorePrepElement, i);
|
|
free_prep_element (el);
|
|
}
|
|
g_array_free (store->elements, TRUE);
|
|
break;
|
|
}
|
|
|
|
g_free (item);
|
|
}
|
|
|
|
static void
|
|
send_response (GdkWin32ClipboardThreadQueueItemType request_type,
|
|
gpointer opaque_task,
|
|
GError *error)
|
|
{
|
|
GdkWin32ClipboardThreadResponse *response = g_new0 (GdkWin32ClipboardThreadResponse, 1);
|
|
response->error = error;
|
|
response->opaque_task = opaque_task;
|
|
response->item_type = request_type;
|
|
g_idle_add_full (G_PRIORITY_DEFAULT, clipboard_thread_response, response, NULL);
|
|
}
|
|
|
|
static void
|
|
send_input_stream (GdkWin32ClipboardThreadQueueItemType request_type,
|
|
gpointer opaque_task,
|
|
GInputStream *stream)
|
|
{
|
|
GdkWin32ClipboardThreadResponse *response = g_new0 (GdkWin32ClipboardThreadResponse, 1);
|
|
response->input_stream = stream;
|
|
response->opaque_task = opaque_task;
|
|
response->item_type = request_type;
|
|
g_idle_add_full (G_PRIORITY_DEFAULT, clipboard_thread_response, response, NULL);
|
|
}
|
|
|
|
static DWORD
|
|
try_open_clipboard (HWND hwnd)
|
|
{
|
|
if (clipboard_thread_data->clipboard_opened_for == hwnd)
|
|
return NO_ERROR;
|
|
|
|
if (clipboard_thread_data->clipboard_opened_for != INVALID_HANDLE_VALUE)
|
|
{
|
|
API_CALL (CloseClipboard, ());
|
|
clipboard_thread_data->clipboard_opened_for = INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
if (!OpenClipboard (hwnd))
|
|
return GetLastError ();
|
|
|
|
clipboard_thread_data->clipboard_opened_for = hwnd;
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
static gboolean
|
|
process_advertise (GdkWin32ClipboardThreadAdvertise *adv)
|
|
{
|
|
DWORD error_code;
|
|
gint i;
|
|
|
|
if (g_get_monotonic_time () > adv->parent.end_time)
|
|
{
|
|
GDK_NOTE (CLIPBOARD, g_printerr ("An advertise task timed out\n"));
|
|
send_response (adv->parent.item_type,
|
|
adv->parent.opaque_task,
|
|
g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
_("Cannot claim clipboard ownership. OpenClipboard() timed out.")));
|
|
return FALSE;
|
|
}
|
|
|
|
if (clipboard_thread_data->owner_change_time > adv->parent.start_time)
|
|
{
|
|
GDK_NOTE (CLIPBOARD, g_printerr ("An advertise task timed out due to ownership change\n"));
|
|
send_response (adv->parent.item_type,
|
|
adv->parent.opaque_task,
|
|
g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
_("Cannot claim clipboard ownership. Another process claimed it before us.")));
|
|
return FALSE;
|
|
}
|
|
|
|
error_code = try_open_clipboard (adv->unset ? NULL : clipboard_thread_data->clipboard_window);
|
|
|
|
if (error_code == ERROR_ACCESS_DENIED)
|
|
return TRUE;
|
|
|
|
if (G_UNLIKELY (error_code != NO_ERROR))
|
|
{
|
|
send_response (adv->parent.item_type,
|
|
adv->parent.opaque_task,
|
|
g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
_("Cannot claim clipboard ownership. OpenClipboard() failed: 0x%lx."), error_code));
|
|
return FALSE;
|
|
}
|
|
|
|
clipboard_thread_data->ignore_destroy_clipboard = TRUE;
|
|
if (!EmptyClipboard ())
|
|
{
|
|
clipboard_thread_data->ignore_destroy_clipboard = FALSE;
|
|
error_code = GetLastError ();
|
|
send_response (adv->parent.item_type,
|
|
adv->parent.opaque_task,
|
|
g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
_("Cannot claim clipboard ownership. EmptyClipboard() failed: 0x%lx."), error_code));
|
|
return FALSE;
|
|
}
|
|
|
|
clipboard_thread_data->ignore_destroy_clipboard = FALSE;
|
|
|
|
if (adv->unset)
|
|
return FALSE;
|
|
|
|
for (i = 0; i < adv->pairs->len; i++)
|
|
{
|
|
GdkWin32ContentFormatPair *pair = &g_array_index (adv->pairs, GdkWin32ContentFormatPair, i);
|
|
|
|
SetClipboardData (pair->w32format, NULL);
|
|
}
|
|
|
|
if (clipboard_thread_data->cached_advertisement)
|
|
g_array_free (clipboard_thread_data->cached_advertisement, TRUE);
|
|
|
|
clipboard_thread_data->cached_advertisement = adv->pairs;
|
|
|
|
/* To enure that we don't free it later on */
|
|
adv->pairs = NULL;
|
|
|
|
send_response (adv->parent.item_type,
|
|
adv->parent.opaque_task,
|
|
NULL);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
process_store (GdkWin32ClipboardThreadStore *store)
|
|
{
|
|
DWORD error_code;
|
|
gint i;
|
|
|
|
if (g_get_monotonic_time () > store->parent.end_time)
|
|
{
|
|
GDK_NOTE (CLIPBOARD, g_printerr ("A store task timed out\n"));
|
|
send_response (store->parent.item_type,
|
|
store->parent.opaque_task,
|
|
g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
_("Cannot set clipboard data. OpenClipboard() timed out.")));
|
|
return FALSE;
|
|
}
|
|
|
|
if (clipboard_thread_data->owner_change_time > store->parent.start_time)
|
|
{
|
|
GDK_NOTE (CLIPBOARD, g_printerr ("A store task timed out due to ownership change\n"));
|
|
send_response (store->parent.item_type,
|
|
store->parent.opaque_task,
|
|
g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
_("Cannot set clipboard data. Another process claimed clipboard ownership.")));
|
|
return FALSE;
|
|
}
|
|
|
|
error_code = try_open_clipboard (clipboard_thread_data->clipboard_window);
|
|
|
|
if (error_code == ERROR_ACCESS_DENIED)
|
|
return TRUE;
|
|
|
|
if (G_UNLIKELY (error_code != NO_ERROR))
|
|
{
|
|
send_response (store->parent.item_type,
|
|
store->parent.opaque_task,
|
|
g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
_("Cannot set clipboard data. OpenClipboard() failed: 0x%lx."), error_code));
|
|
return FALSE;
|
|
}
|
|
|
|
/* It's possible for another process to claim ownership
|
|
* between between us entering this function and us opening the clipboard.
|
|
* So check the ownership one last time.
|
|
* Unlike the advertisement routine above, here we don't want to
|
|
* claim clipboard ownership - we want to store stuff in the clipboard
|
|
* that we already own, otherwise we're just killing stuff that some other
|
|
* process put in there, which is not nice.
|
|
*/
|
|
if (GetClipboardOwner () != clipboard_thread_data->clipboard_window)
|
|
{
|
|
send_response (store->parent.item_type,
|
|
store->parent.opaque_task,
|
|
g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
_("Cannot set clipboard data. Another process claimed clipboard ownership.")));
|
|
return FALSE;
|
|
}
|
|
|
|
for (i = 0; i < store->elements->len; i++)
|
|
{
|
|
GdkWin32ClipboardStorePrepElement *el = &g_array_index (store->elements, GdkWin32ClipboardStorePrepElement, i);
|
|
if (el->handle != NULL && el->w32format != 0)
|
|
if (SetClipboardData (el->w32format, el->handle))
|
|
el->handle = NULL; /* the OS now owns the handle, don't free it later on */
|
|
}
|
|
|
|
send_response (store->parent.item_type,
|
|
store->parent.opaque_task,
|
|
NULL);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gpointer
|
|
grab_data_from_hdata (GdkWin32ClipboardThreadRetrieve *retr,
|
|
HANDLE hdata,
|
|
gsize *data_len)
|
|
{
|
|
gpointer ptr;
|
|
SIZE_T length;
|
|
guchar *data;
|
|
|
|
ptr = GlobalLock (hdata);
|
|
if (ptr == NULL)
|
|
{
|
|
DWORD error_code = GetLastError ();
|
|
send_response (retr->parent.item_type,
|
|
retr->parent.opaque_task,
|
|
g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
_("Cannot get clipboard data. GlobalLock(0x%p) failed: 0x%lx."), hdata, error_code));
|
|
return NULL;
|
|
}
|
|
|
|
length = GlobalSize (hdata);
|
|
if (length == 0 && GetLastError () != NO_ERROR)
|
|
{
|
|
DWORD error_code = GetLastError ();
|
|
send_response (retr->parent.item_type,
|
|
retr->parent.opaque_task,
|
|
g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
_("Cannot get clipboard data. GlobalSize(0x%p) failed: 0x%lx."), hdata, error_code));
|
|
GlobalUnlock (hdata);
|
|
return NULL;
|
|
}
|
|
|
|
data = g_try_malloc (length);
|
|
|
|
if (data == NULL)
|
|
{
|
|
send_response (retr->parent.item_type,
|
|
retr->parent.opaque_task,
|
|
g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
_("Cannot get clipboard data. Failed to allocate %lu bytes to store the data."), length));
|
|
GlobalUnlock (hdata);
|
|
return NULL;
|
|
}
|
|
|
|
memcpy (data, ptr, length);
|
|
*data_len = length;
|
|
|
|
GlobalUnlock (hdata);
|
|
|
|
return data;
|
|
}
|
|
|
|
static gboolean
|
|
process_retrieve (GdkWin32ClipboardThreadRetrieve *retr)
|
|
{
|
|
DWORD error_code;
|
|
gint i;
|
|
UINT fmt, fmt_to_use;
|
|
HANDLE hdata;
|
|
GdkWin32ContentFormatPair *pair;
|
|
guchar *data;
|
|
gsize data_len;
|
|
GInputStream *stream;
|
|
|
|
if (g_get_monotonic_time () > retr->parent.end_time)
|
|
{
|
|
GDK_NOTE (CLIPBOARD, g_printerr ("A retrieve task timed out\n"));
|
|
send_response (retr->parent.item_type,
|
|
retr->parent.opaque_task,
|
|
g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
_("Cannot get clipboard data. OpenClipboard() timed out.")));
|
|
return FALSE;
|
|
}
|
|
|
|
if (clipboard_thread_data->owner_change_time > retr->parent.start_time)
|
|
{
|
|
GDK_NOTE (CLIPBOARD, g_printerr ("A retrieve task timed out due to ownership change\n"));
|
|
send_response (retr->parent.item_type,
|
|
retr->parent.opaque_task,
|
|
g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
_("Cannot get clipboard data. Clipboard ownership changed.")));
|
|
return FALSE;
|
|
}
|
|
|
|
if (GetClipboardSequenceNumber () > retr->sequence_number)
|
|
{
|
|
GDK_NOTE (CLIPBOARD, g_printerr ("A retrieve task timed out due to data change\n"));
|
|
send_response (retr->parent.item_type,
|
|
retr->parent.opaque_task,
|
|
g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
_("Cannot get clipboard data. Clipboard data changed before we could get it.")));
|
|
return FALSE;
|
|
}
|
|
|
|
if (clipboard_thread_data->clipboard_opened_for == INVALID_HANDLE_VALUE)
|
|
error_code = try_open_clipboard (clipboard_thread_data->clipboard_window);
|
|
else
|
|
error_code = try_open_clipboard (clipboard_thread_data->clipboard_opened_for);
|
|
|
|
if (error_code == ERROR_ACCESS_DENIED)
|
|
return TRUE;
|
|
|
|
if (G_UNLIKELY (error_code != NO_ERROR))
|
|
{
|
|
send_response (retr->parent.item_type,
|
|
retr->parent.opaque_task,
|
|
g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
_("Cannot get clipboard data. OpenClipboard() failed: 0x%lx."), error_code));
|
|
return FALSE;
|
|
}
|
|
|
|
for (fmt_to_use = 0, pair = NULL, fmt = 0;
|
|
fmt_to_use == 0 && 0 != (fmt = EnumClipboardFormats (fmt));
|
|
)
|
|
{
|
|
for (i = 0; i < retr->pairs->len; i++)
|
|
{
|
|
pair = &g_array_index (retr->pairs, GdkWin32ContentFormatPair, i);
|
|
|
|
if (pair->w32format != fmt)
|
|
continue;
|
|
|
|
fmt_to_use = fmt;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!fmt_to_use)
|
|
{
|
|
send_response (retr->parent.item_type,
|
|
retr->parent.opaque_task,
|
|
g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
_("Cannot get clipboard data. No compatible transfer format found.")));
|
|
return FALSE;
|
|
}
|
|
|
|
if ((hdata = GetClipboardData (fmt_to_use)) == NULL)
|
|
{
|
|
error_code = GetLastError ();
|
|
send_response (retr->parent.item_type,
|
|
retr->parent.opaque_task,
|
|
g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
_("Cannot get clipboard data. GetClipboardData() failed: 0x%lx."), error_code));
|
|
return FALSE;
|
|
}
|
|
|
|
if (!pair->transmute)
|
|
{
|
|
if (_gdk_win32_format_uses_hdata (pair->w32format))
|
|
{
|
|
data = grab_data_from_hdata (retr, hdata, &data_len);
|
|
|
|
if (data == NULL)
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
data_len = sizeof (HANDLE);
|
|
data = g_malloc (data_len);
|
|
memcpy (data, &hdata, data_len);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_gdk_win32_transmute_windows_data (pair->w32format, pair->contentformat, hdata, &data, &data_len);
|
|
|
|
if (data == NULL)
|
|
return FALSE;
|
|
}
|
|
|
|
stream = g_memory_input_stream_new_from_data (data, data_len, g_free);
|
|
g_object_set_data (G_OBJECT (stream), "gdk-clipboard-stream-contenttype", (gpointer) pair->contentformat);
|
|
|
|
GDK_NOTE (CLIPBOARD, g_printerr ("reading clipboard data from a %" G_GSIZE_FORMAT "-byte buffer\n",
|
|
data_len));
|
|
send_input_stream (retr->parent.item_type,
|
|
retr->parent.opaque_task,
|
|
stream);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
process_clipboard_queue ()
|
|
{
|
|
GdkWin32ClipboardThreadQueueItem *placeholder;
|
|
GList *p;
|
|
gboolean try_again;
|
|
GList *p_next;
|
|
|
|
for (p = clipboard_thread_data->dequeued_items, p_next = NULL; p; p = p_next)
|
|
{
|
|
placeholder = (GdkWin32ClipboardThreadQueueItem *) p->data;
|
|
p_next = p->next;
|
|
|
|
switch (placeholder->item_type)
|
|
{
|
|
case GDK_WIN32_CLIPBOARD_THREAD_QUEUE_ITEM_ADVERTISE:
|
|
try_again = process_advertise ((GdkWin32ClipboardThreadAdvertise *) placeholder);
|
|
break;
|
|
case GDK_WIN32_CLIPBOARD_THREAD_QUEUE_ITEM_RETRIEVE:
|
|
try_again = process_retrieve ((GdkWin32ClipboardThreadRetrieve *) placeholder);
|
|
break;
|
|
case GDK_WIN32_CLIPBOARD_THREAD_QUEUE_ITEM_STORE:
|
|
try_again = process_store ((GdkWin32ClipboardThreadStore *) placeholder);
|
|
break;
|
|
}
|
|
|
|
if (try_again)
|
|
return FALSE;
|
|
|
|
clipboard_thread_data->dequeued_items = g_list_delete_link (clipboard_thread_data->dequeued_items, p);
|
|
free_queue_item (placeholder);
|
|
}
|
|
|
|
while ((placeholder = g_async_queue_try_pop (clipboard_thread_data->input_queue)) != NULL)
|
|
{
|
|
switch (placeholder->item_type)
|
|
{
|
|
case GDK_WIN32_CLIPBOARD_THREAD_QUEUE_ITEM_ADVERTISE:
|
|
try_again = process_advertise ((GdkWin32ClipboardThreadAdvertise *) placeholder);
|
|
break;
|
|
case GDK_WIN32_CLIPBOARD_THREAD_QUEUE_ITEM_RETRIEVE:
|
|
try_again = process_retrieve ((GdkWin32ClipboardThreadRetrieve *) placeholder);
|
|
break;
|
|
case GDK_WIN32_CLIPBOARD_THREAD_QUEUE_ITEM_STORE:
|
|
try_again = process_store ((GdkWin32ClipboardThreadStore *) placeholder);
|
|
break;
|
|
}
|
|
|
|
if (!try_again)
|
|
{
|
|
free_queue_item (placeholder);
|
|
continue;
|
|
}
|
|
|
|
clipboard_thread_data->dequeued_items = g_list_append (clipboard_thread_data->dequeued_items, placeholder);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
discard_render (GdkWin32ClipboardThreadRender *render,
|
|
gboolean dont_touch_the_handle)
|
|
{
|
|
GdkWin32ClipboardThreadRender render_copy = *render;
|
|
|
|
g_free (render);
|
|
|
|
if (dont_touch_the_handle || render_copy.main_thread_data_handle == NULL)
|
|
return;
|
|
|
|
if (_gdk_win32_format_uses_hdata (render_copy.pair.w32format))
|
|
API_CALL (GlobalFree, (render_copy.main_thread_data_handle));
|
|
else
|
|
API_CALL (CloseHandle, (render_copy.main_thread_data_handle));
|
|
}
|
|
|
|
static LRESULT
|
|
inner_clipboard_window_procedure (HWND hwnd,
|
|
UINT message,
|
|
WPARAM wparam,
|
|
LPARAM lparam)
|
|
{
|
|
if (message == thread_wakeup_message ||
|
|
message == WM_TIMER)
|
|
{
|
|
gboolean queue_is_empty = FALSE;
|
|
|
|
if (clipboard_thread_data == NULL)
|
|
{
|
|
g_warning ("Clipboard thread got an actionable message with no thread data");
|
|
return DefWindowProcW (hwnd, message, wparam, lparam);
|
|
}
|
|
|
|
queue_is_empty = process_clipboard_queue ();
|
|
|
|
if (queue_is_empty && clipboard_thread_data->wakeup_timer)
|
|
{
|
|
API_CALL (KillTimer, (clipboard_thread_data->clipboard_window, clipboard_thread_data->wakeup_timer));
|
|
clipboard_thread_data->wakeup_timer = 0;
|
|
}
|
|
|
|
/* Close the clipboard after each queue run, if it's open.
|
|
* It would be wrong to keep it open, even if we would
|
|
* need it again a second later.
|
|
* queue_is_empty == FALSE implies that the clipboard
|
|
* is closed already, but it's better to be sure.
|
|
*/
|
|
if (clipboard_thread_data->clipboard_opened_for != INVALID_HANDLE_VALUE)
|
|
{
|
|
API_CALL (CloseClipboard, ());
|
|
clipboard_thread_data->clipboard_opened_for = INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
if (queue_is_empty ||
|
|
clipboard_thread_data->wakeup_timer != 0)
|
|
return 0;
|
|
|
|
if (SetTimer (clipboard_thread_data->clipboard_window, 1, 1000, NULL))
|
|
clipboard_thread_data->wakeup_timer = 1;
|
|
else
|
|
g_critical ("Failed to set a timer for the clipboard window 0x%p: %lu",
|
|
clipboard_thread_data->clipboard_window,
|
|
GetLastError ());
|
|
}
|
|
|
|
switch (message)
|
|
{
|
|
case WM_DESTROY: /* remove us from chain */
|
|
{
|
|
if (clipboard_thread_data == NULL)
|
|
{
|
|
g_warning ("Clipboard thread got an actionable message with no thread data");
|
|
return DefWindowProcW (hwnd, message, wparam, lparam);
|
|
}
|
|
|
|
ChangeClipboardChain (hwnd, clipboard_thread_data->hwnd_next_viewer);
|
|
PostQuitMessage (0);
|
|
return 0;
|
|
}
|
|
case WM_CHANGECBCHAIN:
|
|
{
|
|
HWND hwndRemove = (HWND) wparam; /* handle of window being removed */
|
|
HWND hwndNext = (HWND) lparam; /* handle of next window in chain */
|
|
|
|
if (clipboard_thread_data == NULL)
|
|
{
|
|
g_warning ("Clipboard thread got an actionable message with no thread data");
|
|
return DefWindowProcW (hwnd, message, wparam, lparam);
|
|
}
|
|
|
|
if (hwndRemove == clipboard_thread_data->hwnd_next_viewer)
|
|
clipboard_thread_data->hwnd_next_viewer = hwndNext == hwnd ? NULL : hwndNext;
|
|
else if (clipboard_thread_data->hwnd_next_viewer != NULL)
|
|
return SendMessage (clipboard_thread_data->hwnd_next_viewer, message, wparam, lparam);
|
|
|
|
return 0;
|
|
}
|
|
case WM_DESTROYCLIPBOARD:
|
|
{
|
|
return 0;
|
|
}
|
|
case WM_CLIPBOARDUPDATE:
|
|
case WM_DRAWCLIPBOARD:
|
|
{
|
|
HWND hwnd_owner;
|
|
HWND hwnd_opener;
|
|
/*
|
|
GdkEvent *event;
|
|
*/
|
|
if (clipboard_thread_data == NULL)
|
|
{
|
|
g_warning ("Clipboard thread got an actionable message with no thread data");
|
|
return DefWindowProcW (hwnd, message, wparam, lparam);
|
|
}
|
|
|
|
hwnd_owner = GetClipboardOwner ();
|
|
|
|
if ((hwnd_owner == NULL) &&
|
|
(GetLastError () != ERROR_SUCCESS))
|
|
WIN32_API_FAILED ("GetClipboardOwner");
|
|
|
|
hwnd_opener = GetOpenClipboardWindow ();
|
|
|
|
GDK_NOTE (DND, g_print (" drawclipboard owner: %p; opener %p ", hwnd_owner, hwnd_opener));
|
|
|
|
#ifdef G_ENABLE_DEBUG
|
|
if (_gdk_debug_flags & GDK_DEBUG_DND)
|
|
{
|
|
/* FIXME: grab and print clipboard formats without opening the clipboard
|
|
if (clipboard_thread_data->clipboard_opened_for != INVALID_HANDLE_VALUE ||
|
|
OpenClipboard (hwnd))
|
|
{
|
|
UINT nFormat = 0;
|
|
|
|
while ((nFormat = EnumClipboardFormats (nFormat)) != 0)
|
|
g_print ("%s ", _gdk_win32_cf_to_string (nFormat));
|
|
|
|
if (clipboard_thread_data->clipboard_opened_for == INVALID_HANDLE_VALUE)
|
|
CloseClipboard ();
|
|
}
|
|
else
|
|
{
|
|
WIN32_API_FAILED ("OpenClipboard");
|
|
}
|
|
*/
|
|
}
|
|
#endif
|
|
|
|
GDK_NOTE (DND, g_print (" \n"));
|
|
|
|
if (clipboard_thread_data->stored_hwnd_owner != hwnd_owner)
|
|
{
|
|
clipboard_thread_data->stored_hwnd_owner = hwnd_owner;
|
|
clipboard_thread_data->owner_change_time = g_get_monotonic_time ();
|
|
|
|
if (hwnd_owner != clipboard_thread_data->clipboard_window)
|
|
{
|
|
if (clipboard_thread_data->cached_advertisement)
|
|
g_array_free (clipboard_thread_data->cached_advertisement, TRUE);
|
|
|
|
clipboard_thread_data->cached_advertisement = NULL;
|
|
}
|
|
|
|
API_CALL (PostMessage, (clipboard_thread_data->clipboard_window, thread_wakeup_message, 0, 0));
|
|
|
|
if (hwnd_owner != clipboard_thread_data->clipboard_window)
|
|
g_idle_add_full (G_PRIORITY_DEFAULT, clipboard_owner_changed, NULL, NULL);
|
|
}
|
|
|
|
if (clipboard_thread_data->hwnd_next_viewer != NULL)
|
|
return SendMessage (clipboard_thread_data->hwnd_next_viewer, message, wparam, lparam);
|
|
|
|
/* clear error to avoid confusing SetClipboardViewer() return */
|
|
SetLastError (0);
|
|
|
|
return 0;
|
|
}
|
|
case WM_RENDERALLFORMATS:
|
|
{
|
|
if (clipboard_thread_data == NULL)
|
|
{
|
|
g_warning ("Clipboard thread got an actionable message with no thread data");
|
|
return DefWindowProcW (hwnd, message, wparam, lparam);
|
|
}
|
|
|
|
if (clipboard_thread_data->cached_advertisement == NULL)
|
|
return DefWindowProcW (hwnd, message, wparam, lparam);
|
|
|
|
if (API_CALL (OpenClipboard, (hwnd)))
|
|
{
|
|
gint i;
|
|
GdkWin32ContentFormatPair *pair;
|
|
|
|
for (pair = NULL, i = 0;
|
|
i < clipboard_thread_data->cached_advertisement->len;
|
|
i++)
|
|
{
|
|
pair = &g_array_index (clipboard_thread_data->cached_advertisement, GdkWin32ContentFormatPair, i);
|
|
|
|
if (pair->w32format != 0)
|
|
SendMessage (hwnd, WM_RENDERFORMAT, pair->w32format, 0);
|
|
}
|
|
|
|
API_CALL (CloseClipboard, ());
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
case WM_RENDERFORMAT:
|
|
{
|
|
gint i;
|
|
GdkWin32ClipboardThreadRender *render;
|
|
GdkWin32ClipboardThreadRender *returned_render;
|
|
GdkWin32ContentFormatPair *pair;
|
|
|
|
|
|
GDK_NOTE (EVENTS, g_print (" %s", _gdk_win32_cf_to_string (wparam)));
|
|
|
|
if (clipboard_thread_data == NULL)
|
|
{
|
|
g_warning ("Clipboard thread got an actionable message with no thread data");
|
|
return DefWindowProcW (hwnd, message, wparam, lparam);
|
|
}
|
|
|
|
if (clipboard_thread_data->cached_advertisement == NULL)
|
|
return DefWindowProcW (hwnd, message, wparam, lparam);
|
|
|
|
for (pair = NULL, i = 0;
|
|
i < clipboard_thread_data->cached_advertisement->len;
|
|
i++)
|
|
{
|
|
pair = &g_array_index (clipboard_thread_data->cached_advertisement, GdkWin32ContentFormatPair, i);
|
|
|
|
if (pair->w32format == wparam)
|
|
break;
|
|
|
|
pair = NULL;
|
|
}
|
|
|
|
if (pair == NULL)
|
|
{
|
|
GDK_NOTE (EVENTS, g_print (" (contentformat not found)"));
|
|
return DefWindowProcW (hwnd, message, wparam, lparam);
|
|
}
|
|
|
|
/* Clear the queue */
|
|
while ((render = g_async_queue_try_pop (clipboard_thread_data->render_queue)) != NULL)
|
|
discard_render (render, FALSE);
|
|
|
|
render = g_new0 (GdkWin32ClipboardThreadRender, 1);
|
|
render->pair = *pair;
|
|
g_idle_add_full (G_PRIORITY_DEFAULT, clipboard_render, render, NULL);
|
|
returned_render = g_async_queue_timeout_pop (clipboard_thread_data->render_queue, CLIPBOARD_RENDER_TIMEOUT);
|
|
|
|
/* We should get back the same pointer, ignore everything else. */
|
|
while (returned_render != NULL && returned_render != render)
|
|
{
|
|
discard_render (returned_render, FALSE);
|
|
/* Technically, we should use timed pop here as well,
|
|
* as it's *possible* for a late render struct
|
|
* to come down the queue just after we cleared
|
|
* the queue, but before our idle function
|
|
* triggered the actual render to be pushed.
|
|
* If you get many "Clipboard rendering timed out" warnings,
|
|
* this is probably why.
|
|
*/
|
|
returned_render = g_async_queue_try_pop (clipboard_thread_data->render_queue);
|
|
}
|
|
|
|
/* Just in case */
|
|
render = NULL;
|
|
|
|
if (returned_render == NULL)
|
|
{
|
|
g_warning ("Clipboard rendering timed out");
|
|
}
|
|
else if (returned_render->main_thread_data_handle)
|
|
{
|
|
BOOL set_data_succeeded;
|
|
/* The requestor is holding the clipboard, no
|
|
* OpenClipboard() is required/possible
|
|
*/
|
|
GDK_NOTE (DND,
|
|
g_print (" SetClipboardData (%s, %p)",
|
|
_gdk_win32_cf_to_string (wparam),
|
|
returned_render->main_thread_data_handle));
|
|
|
|
SetLastError (0);
|
|
set_data_succeeded = (SetClipboardData (wparam, returned_render->main_thread_data_handle) != NULL);
|
|
|
|
if (!set_data_succeeded)
|
|
WIN32_API_FAILED ("SetClipboardData");
|
|
|
|
discard_render (returned_render, set_data_succeeded);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
default:
|
|
/* Otherwise call DefWindowProcW(). */
|
|
GDK_NOTE (EVENTS, g_print (" DefWindowProcW"));
|
|
return DefWindowProcW (hwnd, message, wparam, lparam);
|
|
}
|
|
}
|
|
|
|
LRESULT CALLBACK
|
|
_clipboard_window_procedure (HWND hwnd,
|
|
UINT message,
|
|
WPARAM wparam,
|
|
LPARAM lparam)
|
|
{
|
|
LRESULT retval;
|
|
|
|
GDK_NOTE (EVENTS, g_print ("clipboard thread %s %p",
|
|
_gdk_win32_message_to_string (message), hwnd));
|
|
retval = inner_clipboard_window_procedure (hwnd, message, wparam, lparam);
|
|
|
|
GDK_NOTE (EVENTS, g_print (" => %" G_GINT64_FORMAT "\n", (gint64) retval));
|
|
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* Creates a hidden window and adds it to the clipboard chain
|
|
*/
|
|
static gboolean
|
|
register_clipboard_notification ()
|
|
{
|
|
WNDCLASS wclass = { 0, };
|
|
ATOM klass;
|
|
|
|
wclass.lpszClassName = "GdkClipboardNotification";
|
|
wclass.lpfnWndProc = _clipboard_window_procedure;
|
|
wclass.hInstance = _gdk_dll_hinstance;
|
|
wclass.cbWndExtra = sizeof (GdkWin32ClipboardThread *);
|
|
|
|
klass = RegisterClass (&wclass);
|
|
if (!klass)
|
|
return FALSE;
|
|
|
|
clipboard_thread_data->clipboard_window = CreateWindow (MAKEINTRESOURCE (klass),
|
|
NULL, WS_POPUP,
|
|
0, 0, 0, 0, NULL, NULL,
|
|
_gdk_dll_hinstance, NULL);
|
|
|
|
if (clipboard_thread_data->clipboard_window == NULL)
|
|
goto failed;
|
|
|
|
SetLastError (0);
|
|
clipboard_thread_data->hwnd_next_viewer = SetClipboardViewer (clipboard_thread_data->clipboard_window);
|
|
|
|
if (clipboard_thread_data->hwnd_next_viewer == NULL && GetLastError() != NO_ERROR)
|
|
{
|
|
DestroyWindow (clipboard_thread_data->clipboard_window);
|
|
goto failed;
|
|
}
|
|
|
|
g_idle_add_full (G_PRIORITY_DEFAULT, clipboard_window_created, (gpointer) clipboard_thread_data->clipboard_window, NULL);
|
|
|
|
/* FIXME: http://msdn.microsoft.com/en-us/library/ms649033(v=VS.85).aspx */
|
|
/* This is only supported by Vista, and not yet by mingw64 */
|
|
/* if (AddClipboardFormatListener (hwnd) == FALSE) */
|
|
/* goto failed; */
|
|
|
|
return TRUE;
|
|
|
|
failed:
|
|
g_critical ("Failed to install clipboard viewer");
|
|
UnregisterClass (MAKEINTRESOURCE (klass), _gdk_dll_hinstance);
|
|
return FALSE;
|
|
}
|
|
|
|
static gpointer
|
|
_gdk_win32_clipboard_thread_main (gpointer data)
|
|
{
|
|
MSG msg;
|
|
GAsyncQueue *queue = (GAsyncQueue *) data;
|
|
GAsyncQueue *render_queue = (GAsyncQueue *) g_async_queue_pop (queue);
|
|
|
|
g_assert (clipboard_thread_data == NULL);
|
|
|
|
clipboard_thread_data = g_new0 (GdkWin32ClipboardThread, 1);
|
|
clipboard_thread_data->input_queue = queue;
|
|
clipboard_thread_data->render_queue = render_queue;
|
|
clipboard_thread_data->clipboard_opened_for = INVALID_HANDLE_VALUE;
|
|
|
|
if (!register_clipboard_notification ())
|
|
{
|
|
g_async_queue_unref (queue);
|
|
g_clear_pointer (&clipboard_thread_data, g_free);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
while (GetMessage (&msg, NULL, 0, 0))
|
|
{
|
|
TranslateMessage (&msg);
|
|
DispatchMessage (&msg);
|
|
}
|
|
|
|
/* Just in case, as this should only happen when we shut down */
|
|
DestroyWindow (clipboard_thread_data->clipboard_window);
|
|
CloseHandle (clipboard_thread_data->clipboard_window);
|
|
g_async_queue_unref (queue);
|
|
g_clear_pointer (&clipboard_thread_data, g_free);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
G_DEFINE_TYPE (GdkWin32Clipdrop, gdk_win32_clipdrop, G_TYPE_OBJECT)
|
|
|
|
static void
|
|
gdk_win32_clipdrop_class_init (GdkWin32ClipdropClass *klass)
|
|
{
|
|
}
|
|
|
|
void
|
|
_gdk_win32_clipdrop_init (void)
|
|
{
|
|
_win32_main_thread = g_thread_self ();
|
|
_win32_clipdrop = GDK_WIN32_CLIPDROP (g_object_new (GDK_TYPE_WIN32_CLIPDROP, NULL));
|
|
}
|
|
|
|
static void
|
|
gdk_win32_clipdrop_init (GdkWin32Clipdrop *win32_clipdrop)
|
|
{
|
|
GArray *atoms;
|
|
GArray *cfs;
|
|
GSList *pixbuf_formats;
|
|
GSList *rover;
|
|
int i;
|
|
GArray *comp;
|
|
GdkWin32ContentFormatPair fmt;
|
|
HMODULE user32;
|
|
|
|
thread_wakeup_message = RegisterWindowMessage ("GDK_WORKER_THREAD_WEAKEUP");
|
|
|
|
user32 = LoadLibrary ("user32.dll");
|
|
win32_clipdrop->GetUpdatedClipboardFormats = GetProcAddress (user32, "GetUpdatedClipboardFormats");
|
|
FreeLibrary (user32);
|
|
|
|
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_clipdrop->known_atoms = atoms;
|
|
win32_clipdrop->known_clipboard_formats = cfs;
|
|
|
|
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_GDK_SELECTION) = g_intern_static_string ("GDK_SELECTION");
|
|
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_CLIPBOARD_MANAGER) = g_intern_static_string ("CLIPBOARD_MANAGER");
|
|
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_WM_TRANSIENT_FOR) = g_intern_static_string ("WM_TRANSIENT_FOR");
|
|
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_TARGETS) = g_intern_static_string ("TARGETS");
|
|
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_DELETE) = g_intern_static_string ("DELETE");
|
|
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_SAVE_TARGETS) = g_intern_static_string ("SAVE_TARGETS");
|
|
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_TEXT_PLAIN_UTF8) = g_intern_static_string ("text/plain;charset=utf-8");
|
|
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_TEXT_PLAIN) = g_intern_static_string ("text/plain");
|
|
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_TEXT_URI_LIST) = g_intern_static_string ("text/uri-list");
|
|
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_TEXT_HTML) = g_intern_static_string ("text/html");
|
|
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_PNG) = g_intern_static_string ("image/png");
|
|
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_JPEG) = g_intern_static_string ("image/jpeg");
|
|
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_BMP) = g_intern_static_string ("image/bmp");
|
|
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_GIF) = g_intern_static_string ("image/gif");
|
|
|
|
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_LOCAL_DND_SELECTION) = g_intern_static_string ("LocalDndSelection");
|
|
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_DROPFILES_DND) = g_intern_static_string ("DROPFILES_DND");
|
|
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_OLE2_DND) = g_intern_static_string ("OLE2_DND");
|
|
|
|
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_PNG)= g_intern_static_string ("PNG");
|
|
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_JFIF) = g_intern_static_string ("JFIF");
|
|
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_GIF) = g_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) = g_intern_static_string ("application/x.windows.CF_DIB");
|
|
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_CFSTR_SHELLIDLIST) = g_intern_static_string ("application/x.windows.Shell IDList Array");
|
|
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_CF_UNICODETEXT) = g_intern_static_string ("application/x.windows.CF_UNICODETEXT");
|
|
_gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_CF_TEXT) = g_intern_static_string ("application/x.windows.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 contentformat "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_TEXT_PLAIN_UTF8) = RegisterClipboardFormatA ("text/plain;charset=utf-8");
|
|
|
|
win32_clipdrop->active_source_drags = g_hash_table_new_full (NULL, NULL, (GDestroyNotify) g_object_unref, NULL);
|
|
|
|
pixbuf_formats = gdk_pixbuf_get_formats ();
|
|
|
|
win32_clipdrop->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_clipdrop->n_known_pixbuf_formats++;
|
|
}
|
|
|
|
win32_clipdrop->known_pixbuf_formats = g_new (const gchar *, win32_clipdrop->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_clipdrop->known_pixbuf_formats[i++] = g_intern_string (*mime_type);
|
|
}
|
|
|
|
g_slist_free (pixbuf_formats);
|
|
|
|
win32_clipdrop->compatibility_w32formats = 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 (GdkWin32ContentFormatPair), 3);
|
|
fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_TEXT_PLAIN_UTF8);
|
|
|
|
fmt.w32format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_TEXT_PLAIN_UTF8);
|
|
fmt.transmute = FALSE;
|
|
g_array_append_val (comp, fmt);
|
|
|
|
fmt.w32format = CF_UNICODETEXT;
|
|
fmt.transmute = TRUE;
|
|
g_array_append_val (comp, fmt);
|
|
|
|
fmt.w32format = CF_TEXT;
|
|
g_array_append_val (comp, fmt);
|
|
|
|
g_hash_table_replace (win32_clipdrop->compatibility_w32formats, (gpointer) fmt.contentformat, comp);
|
|
|
|
|
|
comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkWin32ContentFormatPair), 3);
|
|
fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_PNG);
|
|
|
|
fmt.w32format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_IMAGE_PNG);
|
|
fmt.transmute = FALSE;
|
|
g_array_append_val (comp, fmt);
|
|
|
|
fmt.w32format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_PNG);
|
|
g_array_append_val (comp, fmt);
|
|
|
|
fmt.w32format = CF_DIB;
|
|
fmt.transmute = TRUE;
|
|
g_array_append_val (comp, fmt);
|
|
|
|
g_hash_table_replace (win32_clipdrop->compatibility_w32formats, (gpointer) fmt.contentformat, comp);
|
|
|
|
|
|
comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkWin32ContentFormatPair), 4);
|
|
fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_JPEG);
|
|
|
|
fmt.w32format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_IMAGE_JPEG);
|
|
fmt.transmute = FALSE;
|
|
g_array_append_val (comp, fmt);
|
|
|
|
fmt.w32format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_JFIF);
|
|
g_array_append_val (comp, fmt);
|
|
|
|
fmt.w32format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_PNG);
|
|
fmt.transmute = TRUE;
|
|
g_array_append_val (comp, fmt);
|
|
|
|
fmt.w32format = CF_DIB;
|
|
g_array_append_val (comp, fmt);
|
|
|
|
g_hash_table_replace (win32_clipdrop->compatibility_w32formats, (gpointer) fmt.contentformat, comp);
|
|
|
|
|
|
comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkWin32ContentFormatPair), 4);
|
|
fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_GIF);
|
|
|
|
fmt.w32format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_IMAGE_GIF);
|
|
fmt.transmute = FALSE;
|
|
g_array_append_val (comp, fmt);
|
|
|
|
fmt.w32format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_GIF);
|
|
g_array_append_val (comp, fmt);
|
|
|
|
fmt.w32format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_PNG);
|
|
fmt.transmute = TRUE;
|
|
g_array_append_val (comp, fmt);
|
|
|
|
fmt.w32format = CF_DIB;
|
|
g_array_append_val (comp, fmt);
|
|
|
|
g_hash_table_replace (win32_clipdrop->compatibility_w32formats, (gpointer) fmt.contentformat, comp);
|
|
|
|
|
|
comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkWin32ContentFormatPair), 2);
|
|
fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_BMP);
|
|
|
|
fmt.w32format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_IMAGE_BMP);
|
|
fmt.transmute = FALSE;
|
|
g_array_append_val (comp, fmt);
|
|
|
|
fmt.w32format = CF_DIB;
|
|
fmt.transmute = TRUE;
|
|
g_array_append_val (comp, fmt);
|
|
|
|
g_hash_table_replace (win32_clipdrop->compatibility_w32formats, (gpointer) fmt.contentformat, comp);
|
|
|
|
|
|
/* Not implemented, but definitely possible
|
|
comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkWin32ContentFormatPair), 2);
|
|
fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_TEXT_URI_LIST);
|
|
|
|
fmt.w32format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_TEXT_URI_LIST);
|
|
fmt.transmute = FALSE;
|
|
g_array_append_val (comp, fmt);
|
|
|
|
fmt.w32format = _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_clipdrop->compatibility_w32formats, fmt.contentformat, comp);
|
|
*/
|
|
|
|
win32_clipdrop->compatibility_contentformats = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) g_array_unref);
|
|
|
|
comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkWin32ContentFormatPair), 2);
|
|
fmt.w32format = CF_TEXT;
|
|
fmt.transmute = FALSE;
|
|
|
|
fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_CF_TEXT);
|
|
g_array_append_val (comp, fmt);
|
|
|
|
fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_TEXT_PLAIN_UTF8);
|
|
fmt.transmute = TRUE;
|
|
g_array_append_val (comp, fmt);
|
|
|
|
g_hash_table_replace (win32_clipdrop->compatibility_contentformats, GINT_TO_POINTER (CF_TEXT), comp);
|
|
|
|
|
|
comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkWin32ContentFormatPair), 2);
|
|
fmt.w32format = CF_UNICODETEXT;
|
|
fmt.transmute = FALSE;
|
|
|
|
fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_CF_UNICODETEXT);
|
|
g_array_append_val (comp, fmt);
|
|
|
|
fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_TEXT_PLAIN_UTF8);
|
|
fmt.transmute = TRUE;
|
|
g_array_append_val (comp, fmt);
|
|
|
|
g_hash_table_replace (win32_clipdrop->compatibility_contentformats, GINT_TO_POINTER (CF_UNICODETEXT), comp);
|
|
|
|
|
|
comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkWin32ContentFormatPair), 3);
|
|
fmt.w32format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_PNG);
|
|
fmt.transmute = FALSE;
|
|
|
|
fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_PNG);
|
|
g_array_append_val (comp, fmt);
|
|
|
|
fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_PNG);
|
|
g_array_append_val (comp, fmt);
|
|
|
|
fmt.contentformat = _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_clipdrop->compatibility_contentformats, GINT_TO_POINTER (_gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_PNG)), comp);
|
|
|
|
|
|
comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkWin32ContentFormatPair), 4);
|
|
fmt.w32format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_JFIF);
|
|
fmt.transmute = FALSE;
|
|
|
|
fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_JFIF);
|
|
g_array_append_val (comp, fmt);
|
|
|
|
fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_JPEG);
|
|
g_array_append_val (comp, fmt);
|
|
|
|
fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_PNG);
|
|
fmt.transmute = TRUE;
|
|
g_array_append_val (comp, fmt);
|
|
|
|
fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_BMP);
|
|
g_array_append_val (comp, fmt);
|
|
|
|
g_hash_table_replace (win32_clipdrop->compatibility_contentformats, GINT_TO_POINTER (_gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_JFIF)), comp);
|
|
|
|
|
|
comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkWin32ContentFormatPair), 4);
|
|
fmt.w32format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_GIF);
|
|
fmt.transmute = FALSE;
|
|
|
|
fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_GIF);
|
|
g_array_append_val (comp, fmt);
|
|
|
|
fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_GIF);
|
|
g_array_append_val (comp, fmt);
|
|
|
|
fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_PNG);
|
|
fmt.transmute = TRUE;
|
|
g_array_append_val (comp, fmt);
|
|
|
|
fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_IMAGE_BMP);
|
|
g_array_append_val (comp, fmt);
|
|
|
|
g_hash_table_replace (win32_clipdrop->compatibility_contentformats, GINT_TO_POINTER (_gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_GIF)), comp);
|
|
|
|
|
|
comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkWin32ContentFormatPair), 3);
|
|
fmt.w32format = CF_DIB;
|
|
fmt.transmute = FALSE;
|
|
|
|
fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_CF_DIB);
|
|
g_array_append_val (comp, fmt);
|
|
|
|
fmt.contentformat = _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_clipdrop->compatibility_contentformats, GINT_TO_POINTER (CF_DIB), comp);
|
|
|
|
|
|
comp = g_array_sized_new (FALSE, FALSE, sizeof (GdkWin32ContentFormatPair), 3);
|
|
fmt.w32format = _gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_CFSTR_SHELLIDLIST);
|
|
fmt.transmute = FALSE;
|
|
|
|
fmt.contentformat = _gdk_atom_array_index (atoms, GDK_WIN32_ATOM_INDEX_CFSTR_SHELLIDLIST);
|
|
g_array_append_val (comp, fmt);
|
|
|
|
fmt.contentformat = _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_clipdrop->compatibility_contentformats, GINT_TO_POINTER (_gdk_cf_array_index (cfs, GDK_WIN32_CF_INDEX_CFSTR_SHELLIDLIST)), comp);
|
|
|
|
win32_clipdrop->clipboard_open_thread_queue = g_async_queue_new ();
|
|
win32_clipdrop->clipboard_render_queue = g_async_queue_new ();
|
|
/* Out of sheer laziness, we just push the extra queue through the
|
|
* main queue, instead of allocating a struct with two queue
|
|
* pointers and then passing *that* to the thread.
|
|
*/
|
|
g_async_queue_push (win32_clipdrop->clipboard_open_thread_queue, g_async_queue_ref (win32_clipdrop->clipboard_render_queue));
|
|
win32_clipdrop->clipboard_open_thread = g_thread_new ("GDK Win32 Clipboard Thread",
|
|
_gdk_win32_clipboard_thread_main,
|
|
g_async_queue_ref (win32_clipdrop->clipboard_open_thread_queue));
|
|
|
|
win32_clipdrop->dnd_queue = g_async_queue_new ();
|
|
win32_clipdrop->dnd_thread = g_thread_new ("GDK Win32 DnD Thread",
|
|
_gdk_win32_dnd_thread_main,
|
|
g_async_queue_ref (win32_clipdrop->dnd_queue));
|
|
win32_clipdrop->dnd_thread_id = GPOINTER_TO_UINT (g_async_queue_pop (win32_clipdrop->dnd_queue));
|
|
}
|
|
|
|
#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);
|
|
|
|
/* TODO: 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 (®istered_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 megabyte.
|
|
*/
|
|
if (gcfn_result == 0 || registered_name_w_len > 1024 * 1024)
|
|
{
|
|
gcfn_result = 0;
|
|
g_clear_pointer (®istered_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;
|
|
}
|
|
|
|
/* This turns an arbitrary string into a string that
|
|
* *looks* like a mime/type, such as:
|
|
* "application/x.windows.FOO_BAR" from "FOO_BAR".
|
|
* Does nothing for strings that already look like a mime/type
|
|
* (no spaces, one slash, with at least one char on each side of the slash).
|
|
*/
|
|
const gchar *
|
|
_gdk_win32_get_clipboard_format_name_as_interned_mimetype (gchar *w32format_name)
|
|
{
|
|
gchar *mime_type;
|
|
const gchar *result;
|
|
gchar *space = strchr (w32format_name, ' ');
|
|
gchar *slash = strchr (w32format_name, '/');
|
|
|
|
if (space == NULL &&
|
|
slash > w32format_name &&
|
|
slash[1] != '\0' &&
|
|
strchr (&slash[1], '/') == NULL)
|
|
return g_intern_string (w32format_name);
|
|
|
|
mime_type = g_strdup_printf ("application/x.windows.%s", w32format_name);
|
|
result = g_intern_string (mime_type);
|
|
g_free (mime_type);
|
|
|
|
return result;
|
|
}
|
|
|
|
static GArray *
|
|
get_compatibility_w32formats_for_contentformat (const gchar *contentformat)
|
|
{
|
|
GArray *result = NULL;
|
|
gint i;
|
|
GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
|
|
|
|
result = g_hash_table_lookup (clipdrop->compatibility_w32formats, contentformat);
|
|
|
|
if (result != NULL)
|
|
return result;
|
|
|
|
for (i = 0; i < clipdrop->n_known_pixbuf_formats; i++)
|
|
{
|
|
if (contentformat != clipdrop->known_pixbuf_formats[i])
|
|
continue;
|
|
|
|
/* Any format known to gdk-pixbuf can be presented as PNG or BMP */
|
|
result = g_hash_table_lookup (clipdrop->compatibility_w32formats,
|
|
_gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_IMAGE_PNG));
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static GArray *
|
|
_gdk_win32_get_compatibility_contentformats_for_w32format (UINT w32format)
|
|
{
|
|
GArray *result = NULL;
|
|
GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
|
|
|
|
result = g_hash_table_lookup (clipdrop->compatibility_contentformats, GINT_TO_POINTER (w32format));
|
|
|
|
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;
|
|
}
|
|
|
|
/* Turn W32 format into a GDK content format and add it
|
|
* to the array of W32 format <-> GDK content format pairs
|
|
* and/or to a GDK contentformat builder.
|
|
* Also add compatibility GDK content formats for that W32 format.
|
|
* The added content format string is always interned.
|
|
* Ensures that duplicates are not added to the pairs array
|
|
* (builder already takes care of that for itself).
|
|
*/
|
|
void
|
|
_gdk_win32_add_w32format_to_pairs (UINT w32format,
|
|
GArray *pairs,
|
|
GdkContentFormatsBuilder *builder)
|
|
{
|
|
gboolean predef;
|
|
gchar *w32format_name = _gdk_win32_get_clipboard_format_name (w32format, &predef);
|
|
const gchar *interned_w32format_name;
|
|
GdkWin32ContentFormatPair pair;
|
|
GArray *comp_pairs;
|
|
gint i, j;
|
|
|
|
if (w32format_name != NULL)
|
|
{
|
|
interned_w32format_name = _gdk_win32_get_clipboard_format_name_as_interned_mimetype (w32format_name);
|
|
GDK_NOTE (DND, g_print ("Maybe add as-is format %s (%s) (0x%p)\n", w32format_name, interned_w32format_name, interned_w32format_name));
|
|
g_free (w32format_name);
|
|
|
|
if (pairs && interned_w32format_name != 0)
|
|
{
|
|
for (j = 0; j < pairs->len; j++)
|
|
if (g_array_index (pairs, GdkWin32ContentFormatPair, j).contentformat == interned_w32format_name)
|
|
break;
|
|
if (j == pairs->len)
|
|
{
|
|
pair.w32format = w32format;
|
|
pair.contentformat = interned_w32format_name;
|
|
pair.transmute = FALSE;
|
|
g_array_append_val (pairs, pair);
|
|
}
|
|
}
|
|
if (builder != NULL && interned_w32format_name != 0)
|
|
gdk_content_formats_builder_add_mime_type (builder, interned_w32format_name);
|
|
}
|
|
|
|
comp_pairs = _gdk_win32_get_compatibility_contentformats_for_w32format (w32format);
|
|
|
|
if (pairs != NULL && comp_pairs != NULL)
|
|
for (i = 0; i < comp_pairs->len; i++)
|
|
{
|
|
pair = g_array_index (comp_pairs, GdkWin32ContentFormatPair, i);
|
|
|
|
for (j = 0; j < pairs->len; j++)
|
|
if (g_array_index (pairs, GdkWin32ContentFormatPair, j).contentformat == pair.contentformat &&
|
|
g_array_index (pairs, GdkWin32ContentFormatPair, j).w32format == pair.w32format)
|
|
break;
|
|
|
|
if (j == pairs->len)
|
|
g_array_append_val (pairs, pair);
|
|
}
|
|
|
|
if (builder != NULL && comp_pairs != NULL)
|
|
for (i = 0; i < comp_pairs->len; i++)
|
|
{
|
|
pair = g_array_index (comp_pairs, GdkWin32ContentFormatPair, i);
|
|
|
|
gdk_content_formats_builder_add_mime_type (builder, pair.contentformat);
|
|
}
|
|
}
|
|
|
|
static void
|
|
transmute_cf_unicodetext_to_utf8_string (const guchar *data,
|
|
gint length,
|
|
guchar **set_data,
|
|
gsize *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,
|
|
gsize *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,
|
|
gsize *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,
|
|
gsize *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,
|
|
gsize *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,
|
|
gsize *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,
|
|
gsize *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;
|
|
}
|
|
|
|
gboolean
|
|
_gdk_win32_transmute_windows_data (UINT from_w32format,
|
|
const gchar *to_contentformat,
|
|
HANDLE hdata,
|
|
guchar **set_data,
|
|
gsize *set_data_length)
|
|
{
|
|
const guchar *data;
|
|
SIZE_T length;
|
|
|
|
/* FIXME: error reporting */
|
|
|
|
if ((data = GlobalLock (hdata)) == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
length = GlobalSize (hdata);
|
|
|
|
if ((to_contentformat == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_IMAGE_PNG) &&
|
|
from_w32format == _gdk_win32_clipdrop_cf (GDK_WIN32_CF_INDEX_PNG)) ||
|
|
(to_contentformat == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_IMAGE_JPEG) &&
|
|
from_w32format == _gdk_win32_clipdrop_cf (GDK_WIN32_CF_INDEX_JFIF)) ||
|
|
(to_contentformat == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_GIF) &&
|
|
from_w32format == _gdk_win32_clipdrop_cf (GDK_WIN32_CF_INDEX_GIF)))
|
|
{
|
|
/* No transmutation needed */
|
|
*set_data = g_memdup (data, length);
|
|
*set_data_length = length;
|
|
}
|
|
else if (to_contentformat == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_TEXT_PLAIN_UTF8) &&
|
|
from_w32format == CF_UNICODETEXT)
|
|
{
|
|
transmute_cf_unicodetext_to_utf8_string (data, length, set_data, set_data_length, NULL);
|
|
}
|
|
else if (to_contentformat == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_TEXT_PLAIN_UTF8) &&
|
|
from_w32format == CF_TEXT)
|
|
{
|
|
transmute_cf_text_to_utf8_string (data, length, set_data, set_data_length, NULL);
|
|
}
|
|
else if (to_contentformat == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_IMAGE_BMP) &&
|
|
(from_w32format == CF_DIB || from_w32format == CF_DIBV5))
|
|
{
|
|
transmute_cf_dib_to_image_bmp (data, length, set_data, set_data_length, NULL);
|
|
}
|
|
else if (to_contentformat == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_TEXT_URI_LIST) &&
|
|
from_w32format == _gdk_win32_clipdrop_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 W32 format 0x%x to content format 0x%p (%s)",
|
|
from_w32format, to_contentformat, to_contentformat);
|
|
return FALSE;
|
|
}
|
|
|
|
GlobalUnlock (hdata);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
_gdk_win32_transmute_contentformat (const gchar *from_contentformat,
|
|
UINT to_w32format,
|
|
const guchar *data,
|
|
gint length,
|
|
guchar **set_data,
|
|
gsize *set_data_length)
|
|
{
|
|
if ((from_contentformat == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_IMAGE_PNG) &&
|
|
to_w32format == _gdk_win32_clipdrop_cf (GDK_WIN32_CF_INDEX_PNG)) ||
|
|
(from_contentformat == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_IMAGE_JPEG) &&
|
|
to_w32format == _gdk_win32_clipdrop_cf (GDK_WIN32_CF_INDEX_JFIF)) ||
|
|
(from_contentformat == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_GIF) &&
|
|
to_w32format == _gdk_win32_clipdrop_cf (GDK_WIN32_CF_INDEX_GIF)))
|
|
{
|
|
/* No conversion needed */
|
|
*set_data = g_memdup (data, length);
|
|
*set_data_length = length;
|
|
}
|
|
else if (from_contentformat == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_TEXT_PLAIN_UTF8) &&
|
|
to_w32format == CF_UNICODETEXT)
|
|
{
|
|
transmute_utf8_string_to_cf_unicodetext (data, length, set_data, set_data_length, NULL);
|
|
}
|
|
else if (from_contentformat == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_TEXT_PLAIN_UTF8) &&
|
|
to_w32format == CF_TEXT)
|
|
{
|
|
transmute_utf8_string_to_cf_text (data, length, set_data, set_data_length, NULL);
|
|
}
|
|
else if (from_contentformat == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_IMAGE_BMP) &&
|
|
to_w32format == CF_DIB)
|
|
{
|
|
transmute_image_bmp_to_cf_dib (data, length, set_data, set_data_length, NULL);
|
|
}
|
|
else if (from_contentformat == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_IMAGE_BMP) &&
|
|
to_w32format == CF_DIBV5)
|
|
{
|
|
transmute_image_bmp_to_cf_dib (data, length, set_data, set_data_length, NULL);
|
|
}
|
|
/*
|
|
else if (from_contentformat == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_TEXT_URI_LIST) &&
|
|
to_w32format == _gdk_win32_clipdrop_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_contentformat, to_w32format);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
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 == g_intern_static_string ("STRING"))
|
|
{
|
|
return make_list ((gchar *)text, length, TRUE, list);
|
|
}
|
|
else if (encoding == _gdk_win32_clipdrop_atom (GDK_WIN32_ATOM_INDEX_TEXT_PLAIN_UTF8))
|
|
{
|
|
return make_list ((gchar *)text, length, FALSE, list);
|
|
}
|
|
else
|
|
{
|
|
const char *enc_name = (const char *)encoding;
|
|
|
|
g_warning ("gdk_text_property_to_utf8_list_for_display: encoding %s not handled\n", enc_name);
|
|
|
|
if (list)
|
|
*list = NULL;
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
gchar *
|
|
_gdk_win32_display_utf8_to_string_target (GdkDisplay *display,
|
|
const gchar *str)
|
|
{
|
|
return g_strdup (str);
|
|
}
|
|
|
|
gint
|
|
_gdk_win32_add_contentformat_to_pairs (const gchar *contentformat,
|
|
GArray *array)
|
|
{
|
|
gint added_count = 0;
|
|
wchar_t *contentformat_w;
|
|
GdkWin32ContentFormatPair fmt;
|
|
gint i;
|
|
GArray *comp_pairs;
|
|
gint starting_point;
|
|
const wchar_t *prefix = L"application/x.windows.";
|
|
size_t prefix_len = wcslen (prefix);
|
|
size_t offset = 0;
|
|
|
|
for (i = 0; i < array->len; i++)
|
|
if (g_array_index (array, GdkWin32ContentFormatPair, i).contentformat == contentformat)
|
|
break;
|
|
|
|
/* Don't put duplicates into the array */
|
|
if (i < array->len)
|
|
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;
|
|
|
|
contentformat_w = g_utf8_to_utf16 (contentformat, -1, NULL, NULL, NULL);
|
|
|
|
if (contentformat_w == NULL)
|
|
return added_count;
|
|
|
|
if (wcsnicmp (contentformat_w, prefix, prefix_len) == 0)
|
|
offset = prefix_len;
|
|
else
|
|
offset = 0;
|
|
|
|
fmt.w32format = RegisterClipboardFormatW (&contentformat_w[offset]);
|
|
GDK_NOTE (DND, g_print ("Registered clipboard format %S as 0x%x\n", &contentformat_w[offset], fmt.w32format));
|
|
g_free (contentformat_w);
|
|
fmt.contentformat = contentformat;
|
|
fmt.transmute = FALSE;
|
|
|
|
/* Add the "as-is" pair */
|
|
g_array_append_val (array, fmt);
|
|
added_count += 1;
|
|
|
|
comp_pairs = get_compatibility_w32formats_for_contentformat (contentformat);
|
|
for (i = 0; comp_pairs != NULL && i < comp_pairs->len; i++)
|
|
{
|
|
gint j;
|
|
|
|
fmt = g_array_index (comp_pairs, GdkWin32ContentFormatPair, i);
|
|
|
|
/* Don't put duplicates into the array */
|
|
for (j = starting_point; j < array->len; j++)
|
|
if (g_array_index (array, GdkWin32ContentFormatPair, j).w32format == fmt.w32format)
|
|
break;
|
|
|
|
if (j < array->len)
|
|
continue;
|
|
|
|
/* Add a compatibility pair */
|
|
g_array_append_val (array, fmt);
|
|
added_count += 1;
|
|
}
|
|
|
|
return added_count;
|
|
}
|
|
|
|
void
|
|
_gdk_win32_advertise_clipboard_contentformats (GTask *task,
|
|
GdkContentFormats *contentformats)
|
|
{
|
|
GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
|
|
GdkWin32ClipboardThreadAdvertise *adv = g_new0 (GdkWin32ClipboardThreadAdvertise, 1);
|
|
const char * const *mime_types;
|
|
gsize mime_types_len;
|
|
gsize i;
|
|
|
|
g_assert (clipdrop->clipboard_window != NULL);
|
|
|
|
adv->parent.item_type = GDK_WIN32_CLIPBOARD_THREAD_QUEUE_ITEM_ADVERTISE;
|
|
adv->parent.start_time = g_get_monotonic_time ();
|
|
adv->parent.end_time = adv->parent.start_time + CLIPBOARD_OPERATION_TIMEOUT;
|
|
adv->parent.opaque_task = task;
|
|
|
|
if (contentformats == NULL)
|
|
{
|
|
adv->unset = TRUE;
|
|
}
|
|
else
|
|
{
|
|
adv->unset = FALSE;
|
|
|
|
adv->pairs = g_array_new (FALSE, FALSE, sizeof (GdkWin32ContentFormatPair));
|
|
mime_types = gdk_content_formats_get_mime_types (contentformats, &mime_types_len);
|
|
|
|
for (i = 0; i < mime_types_len; i++)
|
|
_gdk_win32_add_contentformat_to_pairs (mime_types[i], adv->pairs);
|
|
}
|
|
|
|
g_async_queue_push (clipdrop->clipboard_open_thread_queue, adv);
|
|
API_CALL (PostMessage, (clipdrop->clipboard_window, thread_wakeup_message, 0, 0));
|
|
|
|
return;
|
|
}
|
|
|
|
void
|
|
_gdk_win32_retrieve_clipboard_contentformats (GTask *task,
|
|
GdkContentFormats *contentformats)
|
|
{
|
|
GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
|
|
GdkWin32ClipboardThreadRetrieve *retr = g_new0 (GdkWin32ClipboardThreadRetrieve, 1);
|
|
const char * const *mime_types;
|
|
gsize mime_types_len;
|
|
gsize i;
|
|
|
|
g_assert (clipdrop->clipboard_window != NULL);
|
|
|
|
retr->parent.item_type = GDK_WIN32_CLIPBOARD_THREAD_QUEUE_ITEM_RETRIEVE;
|
|
retr->parent.start_time = g_get_monotonic_time ();
|
|
retr->parent.end_time = retr->parent.start_time + CLIPBOARD_OPERATION_TIMEOUT;
|
|
retr->parent.opaque_task = task;
|
|
retr->pairs = g_array_new (FALSE, FALSE, sizeof (GdkWin32ContentFormatPair));
|
|
retr->sequence_number = GetClipboardSequenceNumber ();
|
|
|
|
mime_types = gdk_content_formats_get_mime_types (contentformats, &mime_types_len);
|
|
|
|
for (i = 0; i < mime_types_len; i++)
|
|
_gdk_win32_add_contentformat_to_pairs (mime_types[i], retr->pairs);
|
|
|
|
g_async_queue_push (clipdrop->clipboard_open_thread_queue, retr);
|
|
API_CALL (PostMessage, (clipdrop->clipboard_window, thread_wakeup_message, 0, 0));
|
|
|
|
return;
|
|
}
|
|
|
|
typedef struct _GdkWin32ClipboardHDataPrepAndStream GdkWin32ClipboardHDataPrepAndStream;
|
|
|
|
struct _GdkWin32ClipboardHDataPrepAndStream
|
|
{
|
|
GdkWin32ClipboardStorePrep *prep;
|
|
GdkWin32HDataOutputStream *stream;
|
|
};
|
|
|
|
static void
|
|
clipboard_store_hdata_ready (GObject *clipboard,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
GError *error = NULL;
|
|
gint i;
|
|
gboolean no_other_streams;
|
|
GdkWin32ClipboardHDataPrepAndStream *prep_and_stream = (GdkWin32ClipboardHDataPrepAndStream *) user_data;
|
|
GdkWin32ClipboardStorePrep *prep = prep_and_stream->prep;
|
|
GdkWin32HDataOutputStream *stream = prep_and_stream->stream;
|
|
GdkWin32ClipboardThreadStore *store;
|
|
GdkWin32Clipdrop *clipdrop;
|
|
|
|
g_clear_pointer (&prep_and_stream, g_free);
|
|
|
|
if (!gdk_clipboard_write_finish (GDK_CLIPBOARD (clipboard), result, &error))
|
|
{
|
|
HANDLE handle;
|
|
gboolean is_hdata;
|
|
|
|
GDK_NOTE(CLIPBOARD, g_printerr ("Failed to write stream: %s\n", error->message));
|
|
g_error_free (error);
|
|
for (i = 0; i < prep->elements->len; i++)
|
|
free_prep_element (&g_array_index (prep->elements, GdkWin32ClipboardStorePrepElement, i));
|
|
g_free (prep);
|
|
g_output_stream_close (G_OUTPUT_STREAM (stream), NULL, NULL);
|
|
handle = gdk_win32_hdata_output_stream_get_handle (stream, &is_hdata);
|
|
|
|
if (is_hdata)
|
|
API_CALL (GlobalFree, (handle));
|
|
else
|
|
API_CALL (CloseHandle, (handle));
|
|
|
|
g_object_unref (stream);
|
|
|
|
return;
|
|
}
|
|
|
|
for (i = 0, no_other_streams = TRUE; i < prep->elements->len; i++)
|
|
{
|
|
GdkWin32ClipboardStorePrepElement *el = &g_array_index (prep->elements, GdkWin32ClipboardStorePrepElement, i);
|
|
|
|
if (el->stream == G_OUTPUT_STREAM (stream))
|
|
{
|
|
g_output_stream_close (el->stream, NULL, NULL);
|
|
el->handle = gdk_win32_hdata_output_stream_get_handle (GDK_WIN32_HDATA_OUTPUT_STREAM (el->stream), NULL);
|
|
g_object_unref (el->stream);
|
|
el->stream = NULL;
|
|
}
|
|
else if (el->stream != NULL)
|
|
no_other_streams = FALSE;
|
|
}
|
|
|
|
if (!no_other_streams)
|
|
return;
|
|
|
|
clipdrop = _gdk_win32_clipdrop_get ();
|
|
|
|
store = g_new0 (GdkWin32ClipboardThreadStore, 1);
|
|
|
|
store->parent.start_time = g_get_monotonic_time ();
|
|
store->parent.end_time = store->parent.start_time + CLIPBOARD_OPERATION_TIMEOUT;
|
|
store->parent.item_type = GDK_WIN32_CLIPBOARD_THREAD_QUEUE_ITEM_STORE;
|
|
store->parent.opaque_task = prep->store_task;
|
|
store->elements = prep->elements;
|
|
|
|
g_async_queue_push (clipdrop->clipboard_open_thread_queue, store);
|
|
API_CALL (PostMessage, (clipdrop->clipboard_window, thread_wakeup_message, 0, 0));
|
|
|
|
g_free (prep);
|
|
}
|
|
|
|
gboolean
|
|
_gdk_win32_store_clipboard_contentformats (GdkClipboard *cb,
|
|
GTask *task,
|
|
GdkContentFormats *contentformats)
|
|
{
|
|
GArray *pairs; /* of GdkWin32ContentFormatPair */
|
|
const char * const *mime_types;
|
|
gsize n_mime_types;
|
|
gsize i;
|
|
GdkWin32Clipdrop *clipdrop = _gdk_win32_clipdrop_get ();
|
|
GdkWin32ClipboardStorePrep *prep;
|
|
|
|
g_assert (clipdrop->clipboard_window != NULL);
|
|
|
|
mime_types = gdk_content_formats_get_mime_types (contentformats, &n_mime_types);
|
|
|
|
pairs = g_array_sized_new (FALSE,
|
|
FALSE,
|
|
sizeof (GdkWin32ContentFormatPair),
|
|
n_mime_types);
|
|
|
|
for (i = 0; i < n_mime_types; i++)
|
|
_gdk_win32_add_contentformat_to_pairs (mime_types[i], pairs);
|
|
|
|
if (pairs->len <= 0)
|
|
{
|
|
g_array_free (pairs, TRUE);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
prep = g_new0 (GdkWin32ClipboardStorePrep, 1);
|
|
prep->elements = g_array_sized_new (FALSE, TRUE, sizeof (GdkWin32ClipboardStorePrepElement), pairs->len);
|
|
prep->store_task = task;
|
|
|
|
for (i = 0; i < pairs->len; i++)
|
|
{
|
|
GdkWin32ClipboardStorePrepElement el;
|
|
GdkWin32ContentFormatPair *pair = &g_array_index (pairs, GdkWin32ContentFormatPair, i);
|
|
|
|
el.stream = gdk_win32_hdata_output_stream_new (pair, NULL);
|
|
|
|
if (!el.stream)
|
|
continue;
|
|
|
|
el.w32format = pair->w32format;
|
|
el.contentformat = pair->contentformat;
|
|
el.handle = NULL;
|
|
g_array_append_val (prep->elements, el);
|
|
}
|
|
|
|
for (i = 0; i < prep->elements->len; i++)
|
|
{
|
|
GdkWin32ClipboardStorePrepElement *el = &g_array_index (prep->elements, GdkWin32ClipboardStorePrepElement, i);
|
|
GdkWin32ClipboardHDataPrepAndStream *prep_and_stream = g_new0 (GdkWin32ClipboardHDataPrepAndStream, 1);
|
|
prep_and_stream->prep = prep;
|
|
prep_and_stream->stream = GDK_WIN32_HDATA_OUTPUT_STREAM (el->stream);
|
|
|
|
gdk_clipboard_write_async (GDK_CLIPBOARD (cb),
|
|
el->contentformat,
|
|
el->stream,
|
|
G_PRIORITY_DEFAULT,
|
|
NULL,
|
|
clipboard_store_hdata_ready,
|
|
prep_and_stream);
|
|
}
|
|
|
|
g_array_free (pairs, TRUE);
|
|
|
|
return TRUE;
|
|
}
|