/* GDK HData Output Stream - a stream backed by a global memory buffer
*
* Copyright (C) 2018 Руслан Ижбулатов
*
* 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.1 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 .
*
* Author: Руслан Ижбулатов
*/
#include "config.h"
#include
#include "gdkprivate-win32.h"
#include "gdkhdataoutputstream-win32.h"
#include "gdkclipboard-win32.h"
#include "gdkdisplay-win32.h"
#include "gdkintl.h"
#include "gdkwin32display.h"
#include "gdkwin32surface.h"
#include "gdkinternals.h"
typedef struct _GdkWin32HDataOutputStreamPrivate GdkWin32HDataOutputStreamPrivate;
struct _GdkWin32HDataOutputStreamPrivate
{
HANDLE handle;
guchar *data;
gsize data_allocated_size;
gsize data_length;
GdkWin32ContentFormatPair pair;
guint handle_is_buffer : 1;
guint closed : 1;
};
G_DEFINE_TYPE_WITH_PRIVATE (GdkWin32HDataOutputStream, gdk_win32_hdata_output_stream, G_TYPE_OUTPUT_STREAM);
static gssize
write_stream (GdkWin32HDataOutputStream *stream,
GdkWin32HDataOutputStreamPrivate *priv,
const void *buffer,
gsize count,
GError **error)
{
gsize spillover = (priv->data_length + count) - priv->data_allocated_size;
gsize to_copy = count;
if (priv->closed)
{
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
_("writing a closed stream"));
return -1;
}
if (spillover > 0 && !priv->handle_is_buffer)
{
guchar *new_data;
HANDLE new_handle = GlobalReAlloc (priv->handle, priv->data_allocated_size + spillover, 0);
if (new_handle != NULL)
{
new_data = g_try_realloc (priv->data, priv->data_allocated_size + spillover);
if (new_data != NULL)
{
priv->handle = new_handle;
priv->data = new_data;
priv->data_allocated_size += spillover;
}
else
{
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
_("g_try_realloc () failed"));
return -1;
}
}
else
{
DWORD error_code = GetLastError ();
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"%s%lu", _("GlobalReAlloc() failed: "), error_code);
return -1;
}
}
if (priv->handle_is_buffer)
{
to_copy = MIN (count, priv->data_allocated_size - priv->data_length);
if (to_copy == 0)
{
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
_("Ran out of buffer space (buffer size is fixed)"));
return -1;
}
memcpy (&((guchar *) priv->handle)[priv->data_length], buffer, to_copy);
}
else
memcpy (&priv->data[priv->data_length], buffer, to_copy);
priv->data_length += to_copy;
return to_copy;
}
static gssize
gdk_win32_hdata_output_stream_write (GOutputStream *output_stream,
const void *buffer,
gsize count,
GCancellable *cancellable,
GError **error)
{
GdkWin32HDataOutputStream *stream = GDK_WIN32_HDATA_OUTPUT_STREAM (output_stream);
GdkWin32HDataOutputStreamPrivate *priv = gdk_win32_hdata_output_stream_get_instance_private (stream);
gssize result = write_stream (stream, priv, buffer, count, error);
if (result != -1)
GDK_NOTE(SELECTION, g_printerr ("CLIPBOARD: wrote %zd bytes, %u total now\n",
result, priv->data_length));
return result;
}
static void
gdk_win32_hdata_output_stream_write_async (GOutputStream *output_stream,
const void *buffer,
gsize count,
int io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GdkWin32HDataOutputStream *stream = GDK_WIN32_HDATA_OUTPUT_STREAM (output_stream);
GdkWin32HDataOutputStreamPrivate *priv = gdk_win32_hdata_output_stream_get_instance_private (stream);
GTask *task;
gssize result;
GError *error = NULL;
task = g_task_new (stream, cancellable, callback, user_data);
g_task_set_source_tag (task, gdk_win32_hdata_output_stream_write_async);
g_task_set_priority (task, io_priority);
result = write_stream (stream, priv, buffer, count, &error);
if (result != -1)
{
GDK_NOTE (SELECTION, g_printerr ("CLIPBOARD async wrote %zd bytes, %u total now\n",
result, priv->data_length));
g_task_return_int (task, result);
}
else
g_task_return_error (task, error);
g_object_unref (task);
return;
}
static gssize
gdk_win32_hdata_output_stream_write_finish (GOutputStream *stream,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (g_task_is_valid (result, stream), -1);
g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == gdk_win32_hdata_output_stream_write_async, -1);
return g_task_propagate_int (G_TASK (result), error);
}
static gboolean
gdk_win32_hdata_output_stream_close (GOutputStream *output_stream,
GCancellable *cancellable,
GError **error)
{
GdkWin32HDataOutputStream *stream = GDK_WIN32_HDATA_OUTPUT_STREAM (output_stream);
GdkWin32HDataOutputStreamPrivate *priv = gdk_win32_hdata_output_stream_get_instance_private (stream);
guchar *hdata;
if (priv->closed)
return TRUE;
if (priv->pair.transmute)
{
guchar *transmuted_data = NULL;
gsize transmuted_data_length;
if (priv->handle_is_buffer)
{
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
_("Can’t transmute a single handle"));
return FALSE;
}
if (!_gdk_win32_transmute_contentformat (priv->pair.contentformat,
priv->pair.w32format,
priv->data,
priv->data_length,
&transmuted_data,
&transmuted_data_length))
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
_("Failed to transmute %zu bytes of data from %s to %u"),
priv->data_length,
priv->pair.contentformat,
priv->pair.w32format);
return FALSE;
}
else
{
HANDLE new_handle;
new_handle = GlobalReAlloc (priv->handle, transmuted_data_length, 0);
if (new_handle == NULL)
{
DWORD error_code = GetLastError ();
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"%s%lu", _("GlobalReAlloc() failed: "), error_code);
return FALSE;
}
priv->handle = new_handle;
priv->data_length = transmuted_data_length;
g_clear_pointer (&priv->data, g_free);
priv->data = transmuted_data;
}
}
if (!priv->handle_is_buffer)
{
hdata = GlobalLock (priv->handle);
if (hdata == NULL)
{
DWORD error_code = GetLastError ();
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"%s%lu", _("GlobalLock() failed: "), error_code);
return FALSE;
}
memcpy (hdata, priv->data, priv->data_length);
GlobalUnlock (priv->handle);
g_clear_pointer (&priv->data, g_free);
}
priv->closed = 1;
return TRUE;
}
static void
gdk_win32_hdata_output_stream_close_async (GOutputStream *stream,
int io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
GError *error = NULL;
task = g_task_new (stream, cancellable, callback, user_data);
g_task_set_source_tag (task, gdk_win32_hdata_output_stream_close_async);
g_task_set_priority (task, io_priority);
if (gdk_win32_hdata_output_stream_close (stream, NULL, &error))
g_task_return_boolean (task, TRUE);
else
g_task_return_error (task, error);
g_object_unref (task);
}
static gboolean
gdk_win32_hdata_output_stream_close_finish (GOutputStream *stream,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (g_task_is_valid (result, stream), FALSE);
g_return_val_if_fail (g_async_result_is_tagged (result, gdk_win32_hdata_output_stream_close_async), FALSE);
return g_task_propagate_boolean (G_TASK (result), error);
}
static void
gdk_win32_hdata_output_stream_finalize (GObject *object)
{
GdkWin32HDataOutputStream *stream = GDK_WIN32_HDATA_OUTPUT_STREAM (object);
GdkWin32HDataOutputStreamPrivate *priv = gdk_win32_hdata_output_stream_get_instance_private (stream);
g_clear_pointer (&priv->data, g_free);
/* We deliberately don't close the memory object,
* as it will be used elsewhere (it's a shame that
* MS global memory handles are not refcounted and
* not duplicateable).
* Except when the stream isn't closed, which means
* that the caller never bothered to get the handle.
*/
if (!priv->closed && priv->handle)
{
if (_gdk_win32_format_uses_hdata (priv->pair.w32format))
GlobalFree (priv->handle);
else
CloseHandle (priv->handle);
}
G_OBJECT_CLASS (gdk_win32_hdata_output_stream_parent_class)->finalize (object);
}
static void
gdk_win32_hdata_output_stream_class_init (GdkWin32HDataOutputStreamClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GOutputStreamClass *output_stream_class = G_OUTPUT_STREAM_CLASS (klass);
object_class->finalize = gdk_win32_hdata_output_stream_finalize;
output_stream_class->write_fn = gdk_win32_hdata_output_stream_write;
output_stream_class->close_fn = gdk_win32_hdata_output_stream_close;
output_stream_class->write_async = gdk_win32_hdata_output_stream_write_async;
output_stream_class->write_finish = gdk_win32_hdata_output_stream_write_finish;
output_stream_class->close_async = gdk_win32_hdata_output_stream_close_async;
output_stream_class->close_finish = gdk_win32_hdata_output_stream_close_finish;
}
static void
gdk_win32_hdata_output_stream_init (GdkWin32HDataOutputStream *stream)
{
}
GOutputStream *
gdk_win32_hdata_output_stream_new (GdkWin32ContentFormatPair *pair,
GError **error)
{
GdkWin32HDataOutputStream *stream;
GdkWin32HDataOutputStreamPrivate *priv;
HANDLE handle;
gboolean hmem;
hmem = _gdk_win32_format_uses_hdata (pair->w32format);
if (hmem)
{
handle = GlobalAlloc (GMEM_MOVEABLE | GMEM_ZEROINIT, 0);
if (handle == NULL)
{
DWORD error_code = GetLastError ();
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"%s%lu", _("GlobalAlloc() failed: "), error_code);
return NULL;
}
}
stream = g_object_new (GDK_TYPE_WIN32_HDATA_OUTPUT_STREAM, NULL);
priv = gdk_win32_hdata_output_stream_get_instance_private (stream);
priv->pair = *pair;
if (hmem)
{
priv->handle = handle;
}
else
{
priv->data_allocated_size = sizeof (priv->handle);
priv->handle_is_buffer = 1;
}
return G_OUTPUT_STREAM (stream);
}
HANDLE
gdk_win32_hdata_output_stream_get_handle (GdkWin32HDataOutputStream *stream,
gboolean *is_hdata)
{
GdkWin32HDataOutputStreamPrivate *priv;
priv = gdk_win32_hdata_output_stream_get_instance_private (stream);
if (!priv->closed)
return NULL;
if (is_hdata)
*is_hdata = _gdk_win32_format_uses_hdata (priv->pair.w32format);
return priv->handle;
}