| /* 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 <http://www.gnu.org/licenses/>. |
| * |
| * Author: Руслан Ижбулатов <lrn1986@gmail.com> |
| */ |
| |
| #include "config.h" |
| |
| #include <Windows.h> |
| |
| #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; |
| } |