瀏覽代碼

display: Beginnings of a clipboard API

Only includes a Windows implementation, other platforms will follow later.

See #515
rdb 3 年之前
父節點
當前提交
241f476d46

+ 3 - 0
panda/src/display/CMakeLists.txt

@@ -1,5 +1,6 @@
 set(P3DISPLAY_HEADERS
   standardMunger.I standardMunger.h
+  clipboard.h
   config_display.h
   callbackGraphicsWindow.I callbackGraphicsWindow.h
   drawableRegion.I drawableRegion.h
@@ -7,6 +8,7 @@ set(P3DISPLAY_HEADERS
   displayRegionCullCallbackData.I displayRegionCullCallbackData.h
   displayRegionDrawCallbackData.I displayRegionDrawCallbackData.h
   frameBufferProperties.I frameBufferProperties.h
+  internalClipboard.h
   get_x11.h pre_x11_include.h post_x11_include.h
   graphicsEngine.I graphicsEngine.h
   graphicsOutput.I graphicsOutput.h
@@ -56,6 +58,7 @@ set(P3DISPLAY_SOURCES
   graphicsWindowProc.cxx
   graphicsWindowProcCallbackData.cxx
   graphicsDevice.cxx
+  internalClipboard.cxx
   mouseAndKeyboard.cxx
   nativeWindowHandle.cxx
   parasiteBuffer.cxx

+ 75 - 0
panda/src/display/clipboard.h

@@ -0,0 +1,75 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file clipboard.h
+ * @author rdb
+ * @date 2022-01-17
+ */
+
+#ifndef CLIPBOARD_H
+#define CLIPBOARD_H
+
+#include "pandabase.h"
+#include "asyncFuture.h"
+#include "vector_uchar.h"
+
+/**
+ * Abstract base class for clipboard implementations.
+ */
+class EXPCL_PANDA_DISPLAY Clipboard {
+PUBLISHED:
+  /**
+   * Returns a future that resolves to the contents of the clipboard in text
+   * format, or an empty string if it did not contain any text.
+   *
+   * If the operating system refuses access to the clipboard, the future will
+   * be cancelled.
+   */
+  virtual PT(AsyncFuture) request_text()=0;
+
+  /**
+   * Returns a future that resolves to the contents of the clipboard as binary
+   * data of the given MIME type, or null if it did not contain any data of
+   * this MIME type.
+   *
+   * If the operating system refuses access to the clipboard, the future may
+   * be cancelled.
+   */
+  virtual PT(AsyncFuture) request_data(const std::string &mime_type)=0;
+
+  /**
+   * Empties the contents of the clipboard.
+   */
+  virtual void clear()=0;
+
+  /**
+   * Replaces the contents of the clipboard with the given text string.
+   *
+   * It is not guaranteed that the change is applied \em immediately, but it
+   * \em is guaranteed that successive calls to request_text() return this
+   * data, unless the clipboard is overwritten after the call to set_text().
+   */
+  virtual void set_text(const std::string &text)=0;
+
+  /**
+   * Writes arbitrary data to the clipboard, the format indicated by a MIME
+   * type.  Returns false if the OS does not support data of the given type.
+   *
+   * Which types are supported is determined by what the operating system and
+   * what other programs support.  For example, "image/bmp" generally works
+   * well on Windows, "image/tiff" is accepted but is not recognized by most
+   * applications, and "image/webp" is rejected entirely.
+   *
+   * It is not guaranteed that the change is applied \em immediately, but it
+   * \em is guaranteed that successive calls to request_text() return this
+   * data, unless the clipboard is overwritten by someone else.
+   */
+  virtual bool set_data(const std::string &mime_type, const vector_uchar &data)=0;
+};
+
+#endif

+ 11 - 0
panda/src/display/graphicsWindow.cxx

@@ -14,6 +14,7 @@
 #include "graphicsWindow.h"
 #include "graphicsPipe.h"
 #include "config_display.h"
+#include "internalClipboard.h"
 #include "mouseButton.h"
 #include "keyboardButton.h"
 #include "lightMutexHolder.h"
@@ -723,6 +724,16 @@ get_touch_info(int index){
   return TouchInfo();
 }
 
+/**
+ * Returns an instance of a Clipboard object.
+ */
+Clipboard *GraphicsWindow::
+get_clipboard() const {
+  // In absence of any specific implementation, we use a process-local
+  // clipboard.
+  return InternalClipboard::get_global_ptr();
+}
+
 /**
  * Returns whether this window supports adding of Windows proc handlers.
  *

+ 7 - 0
panda/src/display/graphicsWindow.h

@@ -33,6 +33,8 @@
 #include "windowHandle.h"
 #include "touchInfo.h"
 
+class Clipboard;
+
 /**
  * A window, fullscreen or on a desktop, into which a graphics device sends
  * its output for interactive display.
@@ -115,6 +117,11 @@ public:
   virtual int get_num_touches();
   virtual TouchInfo get_touch_info(int index);
 
+  virtual Clipboard *get_clipboard() const;
+
+PUBLISHED:
+  MAKE_PROPERTY(clipboard, get_clipboard);
+
 public:
   virtual void request_open();
   virtual void request_close();

+ 98 - 0
panda/src/display/internalClipboard.cxx

@@ -0,0 +1,98 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file internalClipboard.cxx
+ * @author rdb
+ * @date 2022-01-18
+ */
+
+#include "internalClipboard.h"
+#include "lightMutexHolder.h"
+#include "string_utils.h"
+
+/**
+ * Returns a future that resolves to the contents of the clipboard in text
+ * format, or an empty string if it did not contain any text.
+ *
+ * If the operating system refuses access to the clipboard, the future will be
+ * cancelled.
+ */
+PT(AsyncFuture) InternalClipboard::
+request_text() {
+  PT(AsyncFuture) fut = new AsyncFuture;
+  LightMutexHolder holder(_lock);
+  std::string text;
+  if (_type == T_text) {
+    text = _text_or_mime_type;
+  }
+  fut->set_result(text);
+  return fut;
+}
+
+/**
+ * Returns a future that resolves to the contents of the clipboard as binary
+ * data of the given MIME type, or null if it did not contain any data of this
+ * MIME type.
+ *
+ * If the operating system refuses access to the clipboard, the future will be
+ * cancelled.
+ */
+PT(AsyncFuture) InternalClipboard::
+request_data(const std::string &mime_type) {
+  PT(AsyncFuture) fut = new AsyncFuture;
+  LightMutexHolder holder(_lock);
+  if (_type == T_data && cmp_nocase(mime_type, _text_or_mime_type) == 0) {
+    fut->set_result(_data);
+  } else {
+    fut->set_result(nullptr, nullptr);
+  }
+  return fut;
+}
+
+/**
+ * Clears any data currently in the clipboard.
+ */
+void InternalClipboard::
+clear() {
+  LightMutexHolder holder(_lock);
+  _type = T_empty;
+  _text_or_mime_type.clear();
+  _data.clear();
+}
+
+/**
+ * Writes a text string into the clipboard.
+ */
+void InternalClipboard::
+set_text(const std::string &text) {
+  LightMutexHolder holder(_lock);
+  _type = T_text;
+  _text_or_mime_type = text;
+  _data.clear();
+}
+
+/**
+ * Writes arbitrary data into the clipboard.
+ */
+bool InternalClipboard::
+set_data(const std::string &mime_type, const vector_uchar &data) {
+  LightMutexHolder holder(_lock);
+  _type = T_data;
+  _text_or_mime_type = mime_type;
+  _data = data;
+  return true;
+}
+
+/**
+ *
+ */
+Clipboard *InternalClipboard::
+get_global_ptr() {
+  static InternalClipboard clipboard;
+  return &clipboard;
+}

+ 46 - 0
panda/src/display/internalClipboard.h

@@ -0,0 +1,46 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file internalClipboard.h
+ * @author rdb
+ * @date 2022-01-18
+ */
+
+#ifndef INTERNALCLIPBOARD_H
+#define INTERNALCLIPBOARD_H
+
+#include "clipboard.h"
+
+/**
+ * A simple clipboard implementation that is used in absence of an OS-provided
+ * clipboard implementation.  This one is internal to the current process only.
+ */
+class EXPCL_PANDA_DISPLAY InternalClipboard final : public Clipboard {
+public:
+  virtual PT(AsyncFuture) request_text() override;
+  virtual PT(AsyncFuture) request_data(const std::string &mime_type) override;
+
+  virtual void clear() override;
+  virtual void set_text(const std::string &text) override;
+  virtual bool set_data(const std::string &mime_type, const vector_uchar &data) override;
+
+  static Clipboard *get_global_ptr();
+
+private:
+  LightMutex _lock;
+  enum Type {
+    T_empty,
+    T_text,
+    T_data,
+  };
+  Type _type = T_empty;
+  std::string _text_or_mime_type;
+  vector_uchar _data;
+};
+
+#endif

+ 1 - 0
panda/src/display/p3display_composite2.cxx

@@ -4,6 +4,7 @@
 #include "graphicsWindowProc.cxx"
 #include "graphicsWindowProcCallbackData.cxx"
 #include "graphicsWindowInputDevice.cxx"
+#include "internalClipboard.cxx"
 #include "mouseAndKeyboard.cxx"
 #include "nativeWindowHandle.cxx"
 #include "parasiteBuffer.cxx"

+ 2 - 0
panda/src/windisplay/CMakeLists.txt

@@ -4,6 +4,7 @@ endif()
 
 set(P3WINDISPLAY_HEADERS
   config_windisplay.h
+  winClipboard.h
   winGraphicsPipe.I winGraphicsPipe.h
   winGraphicsWindow.I winGraphicsWindow.h
   winDetectDx.h
@@ -11,6 +12,7 @@ set(P3WINDISPLAY_HEADERS
 
 set(P3WINDISPLAY_SOURCES
   config_windisplay.cxx winGraphicsPipe.cxx
+  winClipboard.cxx
   winGraphicsWindow.cxx
   winDetectDx9.cxx
 )

+ 1 - 0
panda/src/windisplay/p3windisplay_composite1.cxx

@@ -1,3 +1,4 @@
 #include "config_windisplay.cxx"
+#include "winClipboard.cxx"
 #include "winGraphicsWindow.cxx"
 #include "winGraphicsPipe.cxx"

+ 391 - 0
panda/src/windisplay/winClipboard.cxx

@@ -0,0 +1,391 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file winClipboard.cxx
+ * @author rdb
+ * @date 2022-01-18
+ */
+
+#include "winClipboard.h"
+#include "datagram.h"
+#include "genericThread.h"
+#include "mutexHolder.h"
+#include "string_utils.h"
+#include "textEncoder.h"
+
+/**
+ *
+ */
+WinClipboard::
+WinClipboard(HWND hwnd) :
+  _hwnd(hwnd),
+  _lock("clipboard"),
+  _cvar(_lock) {
+}
+
+/**
+ * Shuts down the clipboard thread.
+ */
+WinClipboard::
+~WinClipboard() {
+  {
+    MutexHolder holder(_lock);
+    if (_write_thread == nullptr) {
+      return;
+    }
+
+    if (windisplay_cat.is_debug()) {
+      windisplay_cat.debug()
+        << "Shutting down clipboard thread.\n";
+    }
+
+    _thread_state = TS_shutdown;
+  }
+
+  // Wake up the thread to let it process the TS_shutdown message.
+  // Wait for it to finish, otherwise it might access deleted structures.
+  _cvar.notify();
+  _write_thread->join();
+  _write_thread.clear();
+}
+
+/**
+ * Returns a future that resolves to the contents of the clipboard in text
+ * format, or an empty string if it did not contain any text.
+ *
+ * If the operating system refuses access to the clipboard, the future will be
+ * cancelled.
+ */
+PT(AsyncFuture) WinClipboard::
+request_text() {
+  MutexHolder holder(_lock);
+  return do_request(CF_UNICODETEXT);
+}
+
+/**
+ * Returns a future that resolves to the contents of the clipboard as binary
+ * data of the given MIME type, or null if it did not contain any data of this
+ * MIME type.
+ *
+ * If the operating system refuses access to the clipboard, the future will be
+ * cancelled.
+ */
+PT(AsyncFuture) WinClipboard::
+request_data(const std::string &mime_type) {
+  MutexHolder holder(_lock);
+
+  UINT format = 0;
+  if (cmp_nocase(mime_type, "image/bmp") == 0) {
+    format = CF_DIBV5;
+  }
+  else if (cmp_nocase(mime_type, "image/tiff") == 0) {
+    format = CF_TIFF;
+  }
+  else if (cmp_nocase(mime_type, "audio/wav") == 0) {
+    format = CF_WAVE;
+  }
+  else if (cmp_nocase(mime_type, "image/png") == 0) {
+    format = RegisterClipboardFormatA("PNG");
+  }
+  else if (cmp_nocase(mime_type, "image/gif") == 0) {
+    format = RegisterClipboardFormatA("GIF");
+  }
+  else if (cmp_nocase(mime_type, "image/jpeg") == 0) {
+    format = RegisterClipboardFormatA("JFIF");
+  }
+  else if (cmp_nocase(mime_type, "text/html") == 0) {
+    format = RegisterClipboardFormatA("HTML Format");
+  }
+  else if (cmp_nocase(mime_type, "text/rtf") == 0) {
+    format = RegisterClipboardFormatA("Rich Text Format");
+  }
+
+  return do_request(format);
+}
+
+/**
+ * Clears any data currently in the clipboard.
+ */
+void WinClipboard::
+clear() {
+  write(0, 0);
+}
+
+/**
+ * Writes a text string into the clipboard.
+ */
+void WinClipboard::
+set_text(const std::string &text) {
+  TextEncoder encoder;
+  encoder.set_text(text);
+  const std::wstring &wtext = encoder.get_wtext();
+
+  HGLOBAL data = GlobalAlloc(GMEM_MOVEABLE, (wtext.size() + 1) * sizeof(wchar_t));
+  nassertv_always(data != nullptr);
+
+  {
+    wchar_t *buf = (wchar_t *)GlobalLock(data);
+    nassertv_always(buf != nullptr);
+    memcpy(buf, wtext.data(), wtext.size() * sizeof(wchar_t));
+    buf[wtext.size()] = 0;
+    GlobalUnlock(data);
+  }
+
+  write(CF_UNICODETEXT, data);
+}
+
+/**
+ * Writes arbitrary data into the clipboard.
+ */
+bool WinClipboard::
+set_data(const std::string &mime_type, const vector_uchar &data) {
+  UINT format = 0;
+  const unsigned char *ptr = data.data();
+  size_t size = data.size();
+
+  if (cmp_nocase(mime_type, "image/bmp") == 0) {
+    // Is it a v5 structure?
+    if (data.size() < sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)) {
+      windisplay_cat.error()
+        << "Refusing to put invalid image/bmp data on clipboard.\n";
+      return nullptr;
+    }
+
+    BITMAPINFOHEADER *info_header = (BITMAPINFOHEADER *)(data.data() + sizeof(BITMAPFILEHEADER));
+    switch (info_header->biSize) {
+    case sizeof(BITMAPINFOHEADER):
+      format = CF_DIB;
+      break;
+
+    case sizeof(BITMAPV5HEADER):
+      format = CF_DIBV5;
+      break;
+
+    default:
+      windisplay_cat.error()
+        << "Refusing to put image/bmp data with unrecognized header on clipboard.\n";
+      return false;
+    }
+
+    // Remove the header.
+    ptr += 14;
+    size -= 14;
+  }
+  else if (cmp_nocase(mime_type, "image/tiff") == 0) {
+    format = CF_TIFF;
+  }
+  else if (cmp_nocase(mime_type, "audio/wav") == 0) {
+    format = CF_WAVE;
+  }
+  else if (cmp_nocase(mime_type, "image/png") == 0) {
+    format = RegisterClipboardFormatA("PNG");
+  }
+  else if (cmp_nocase(mime_type, "image/gif") == 0) {
+    format = RegisterClipboardFormatA("GIF");
+  }
+  else if (cmp_nocase(mime_type, "image/jpeg") == 0) {
+    format = RegisterClipboardFormatA("JFIF");
+  }
+  else if (cmp_nocase(mime_type, "text/html") == 0) {
+    format = RegisterClipboardFormatA("HTML Format");
+  }
+  else if (cmp_nocase(mime_type, "text/rtf") == 0) {
+    format = RegisterClipboardFormatA("Rich Text Format");
+  }
+
+  if (format == 0) {
+    // Unsupported format.
+    return false;
+  }
+
+  HGLOBAL handle = GlobalAlloc(GMEM_MOVEABLE, size);
+  nassertr_always(handle != nullptr, false);
+
+  {
+    wchar_t *buf = (wchar_t *)GlobalLock(handle);
+    nassertr_always(buf != nullptr, false);
+    memcpy(buf, ptr, size);
+    GlobalUnlock(handle);
+  }
+
+  write(format, handle);
+  return true;
+}
+
+/**
+ * Assumes the lock is held.
+ */
+PT(AsyncFuture) WinClipboard::
+do_request(UINT format) {
+  PT(AsyncFuture) fut = new AsyncFuture;
+
+  // If there is a pending write to the clipboard, return that instead,
+  // so that we don't get issues with reads/writes being out of order.
+  if (_pending_format == format) {
+    process_data(fut, format, _pending_data);
+    return fut;
+  }
+
+  if (format == 0 || _thread_state != TS_wait) {
+    process_data(fut, format, nullptr);
+    return fut;
+  }
+
+  // The write thread should wait for the writes to succeed.
+  ++_pending_requests;
+
+  PT(Thread) thread = new GenericThread("clipboard", "clipboard", [this, fut, format] {
+    // Unfortunately there is no WaitClipboard call, so we have to do an
+    // inefficient retry loop.
+    if (!OpenClipboard(_hwnd)) {
+      Sleep(0);
+      while (!OpenClipboard(_hwnd)) {
+        Sleep(1);
+      }
+    }
+
+    process_data(fut, format, GetClipboardData(format));
+    CloseClipboard();
+
+    _lock.lock();
+    if (--_pending_requests == 0 && _thread_state != TS_wait) {
+      // Wake up the writer thread, which was waiting for us to be done.
+      _lock.unlock();
+      _cvar.notify();
+    } else {
+      _lock.unlock();
+    }
+  });
+
+  thread->start(TP_normal, false);
+  return fut;
+}
+
+/**
+ * Processes the result of reading from the clipboard.
+ */
+void WinClipboard::
+process_data(AsyncFuture *fut, UINT format, HANDLE data) {
+  if (format == 0 || data == nullptr) {
+    if (format == CF_UNICODETEXT) {
+      fut->set_result(std::string());
+    } else {
+      fut->set_result(nullptr);
+    }
+    return;
+  }
+
+  const char *buf = (const char *)GlobalLock(data);
+  size_t size = GlobalSize(data);
+
+  if (format == CF_UNICODETEXT) {
+    TextEncoder encoder;
+    encoder.set_wtext(std::wstring((const wchar_t *)buf, wcsnlen((const wchar_t *)buf, size)));
+    fut->set_result(encoder.get_text());
+  }
+  else if (format == CF_DIBV5) {
+    uint32_t offset = 14;
+
+    BITMAPINFOHEADER *info_header = (BITMAPINFOHEADER *)buf;
+    offset += info_header->biSize;
+    if (info_header->biBitCount <= 8) {
+      offset += (1 << info_header->biBitCount) * 4;
+    }
+
+    // Insert a BMP header before the DIB data.
+    Datagram dg;
+    dg.append_data("BM", 2);
+    dg.add_uint32(size + 14);
+    dg.add_uint16(0);
+    dg.add_uint16(0);
+    dg.add_uint32(offset);
+
+    vector_uchar data((unsigned char *)dg.get_data(),
+                      (unsigned char *)dg.get_data() + dg.get_length());
+    data.insert(data.end(), buf, buf + size);
+    fut->set_result(std::move(data));
+  }
+  else {
+    fut->set_result(vector_uchar((const unsigned char *)buf, (const unsigned char *)buf + size));
+  }
+
+  GlobalUnlock(data);
+}
+
+/**
+ * Schedules that the given data should be written to the clipboard.
+ */
+void WinClipboard::
+write(UINT format, HANDLE data) {
+  {
+    MutexHolder holder(_lock);
+    _thread_state = TS_write;
+
+    if (_pending_data != nullptr) {
+      GlobalFree(_pending_data);
+    }
+    _pending_data = data;
+    _pending_format = format;
+
+    if (_write_thread == nullptr) {
+      _write_thread = new GenericThread("clipboard", "clipboard", [this] { write_thread_main(); });
+      _write_thread->start(TP_normal, true);
+
+      // No need to notify(), the thread will start writing as soon as
+      // we release the lock (or as soon as it begins to run).
+      return;
+    }
+  }
+  _cvar.notify();
+}
+
+/**
+ *
+ */
+void WinClipboard::
+write_thread_main() {
+  MutexHolder holder(_lock);
+
+  while (true) {
+    // If there are any clipboard reads pending, we continue to wait (a reader
+    // thread will wake us up when it's done), to avoid out-of-order issues.
+    while (_thread_state == TS_wait || _pending_requests > 0) {
+      _cvar.wait();
+    }
+
+    if (_thread_state == TS_shutdown) {
+      return;
+    }
+
+    // We have a pending change, and there are no read threads active.
+    // Try to unlock the clipboard.  There is no risk of any read threads
+    // being started as long as _thread_state is not TS_wait.
+    while (!OpenClipboard(_hwnd)) {
+      _lock.unlock();
+      Sleep(1);
+      _lock.lock();
+    }
+
+    nassertd(_thread_state == TS_write && _pending_requests == 0) continue;
+
+    // We got the system clipboard lock.  We always have to call EmptyClipboard
+    // to take ownership.
+    EmptyClipboard();
+
+    if (_pending_data != nullptr) {
+      if (SetClipboardData(_pending_format, _pending_data) != nullptr) {
+        _thread_state = TS_wait;
+        GlobalFree(_pending_data);
+        _pending_data = nullptr;
+        _pending_format = 0;
+      }
+    }
+
+    CloseClipboard();
+  }
+}

+ 65 - 0
panda/src/windisplay/winClipboard.h

@@ -0,0 +1,65 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file winClipboard.h
+ * @author rdb
+ * @date 2022-01-18
+ */
+
+#ifndef WINCLIPBOARD_H
+#define WINCLIPBOARD_H
+
+#include "config_display.h"
+#include "clipboard.h"
+#include "pmutex.h"
+
+#ifndef WIN32_LEAN_AND_MEAN
+  #define WIN32_LEAN_AND_MEAN 1
+#endif
+#include <windows.h>
+
+/**
+ * Clipboard implementation that is used in absence of an OS-provided
+ * clipboard.  This one is local to the current process only.
+ */
+class EXPCL_PANDAWIN WinClipboard final : public Clipboard {
+public:
+  WinClipboard(HWND hwnd);
+  ~WinClipboard();
+
+  virtual PT(AsyncFuture) request_text() override;
+  virtual PT(AsyncFuture) request_data(const std::string &mime_type) override;
+
+  virtual void clear() override;
+  virtual void set_text(const std::string &text) override;
+  virtual bool set_data(const std::string &mime_type, const vector_uchar &data) override;
+
+private:
+  enum ThreadState {
+    TS_wait,
+    TS_shutdown,
+    TS_write,
+  };
+
+  PT(AsyncFuture) do_request(UINT format);
+  static void process_data(AsyncFuture *fut, UINT format, HANDLE data);
+  void write(UINT format, HANDLE data);
+  void write_thread_main();
+
+  const HWND _hwnd;
+  PT(Thread) _write_thread;
+
+  Mutex _lock;
+  ConditionVar _cvar;
+  ThreadState _thread_state = TS_wait;
+  UINT _pending_format = 0;
+  HANDLE _pending_data = nullptr;
+  unsigned int _pending_requests = 0;
+};
+
+#endif

+ 17 - 0
panda/src/windisplay/winGraphicsWindow.cxx

@@ -14,6 +14,7 @@
 #include "winGraphicsWindow.h"
 #include "config_windisplay.h"
 #include "winGraphicsPipe.h"
+#include "winClipboard.h"
 
 #include "graphicsPipe.h"
 #include "keyboardButton.h"
@@ -1216,6 +1217,11 @@ open_graphic_window() {
     _parent_window_handle = nullptr;
   }
 
+  if (_clipboard != nullptr) {
+    delete _clipboard;
+    _clipboard = nullptr;
+  }
+
   if (!_hparent) { // This can be a regular window or a fullscreen window
     _hWnd = CreateWindowW(wclass._name.c_str(), title.c_str(), window_style,
                           metrics.x, metrics.y,
@@ -3325,3 +3331,14 @@ get_touch_info(int index) {
   ret.set_flags(ti.dwFlags);
   return ret;
 }
+
+/**
+ * Returns an instance of a Clipboard object.
+ */
+Clipboard *WinGraphicsWindow::
+get_clipboard() const {
+  if (_clipboard == nullptr && _hWnd != 0) {
+    _clipboard = new WinClipboard(_hWnd);
+  }
+  return _clipboard;
+}

+ 6 - 0
panda/src/windisplay/winGraphicsWindow.h

@@ -52,6 +52,8 @@ typedef struct tagTOUCHINPUT {
 } TOUCHINPUT, *PTOUCHINPUT;
 #endif
 
+class WinClipboard;
+
 /**
  * An abstract base class for glGraphicsWindow and dxGraphicsWindow (and, in
  * general, graphics windows that interface with the Microsoft Windows API).
@@ -98,6 +100,8 @@ public:
   virtual int get_num_touches();
   virtual TouchInfo get_touch_info(int index);
 
+  virtual Clipboard *get_clipboard() const;
+
 protected:
   void trigger_flip();
   virtual void close_window();
@@ -200,6 +204,8 @@ private:
   UINT _num_touches;
   TOUCHINPUT _touches[MAX_TOUCHES];
 
+  mutable WinClipboard *_clipboard = nullptr;
+
 private:
   // We need this map to support per-window calls to window_proc().
   typedef std::map<HWND, WinGraphicsWindow *> WindowHandles;