如何处理剪贴板复制粘贴(底层)





5.00/5 (2投票s)
一个解释如何在 X11、WinAPI 和 Cocoa 中设置和处理剪贴板复制粘贴的教程。
概述
- 剪贴板粘贴
- X11(初始化原子、转换部分、获取数据)
- Win32(打开剪贴板、获取数据、转换数据、关闭剪贴板)
- Cocoa(设置数据类型、获取粘贴板、获取数据、转换数据)
- 剪贴板复制
- X11(初始化原子、转换部分、处理请求、发送数据)
- Win32(设置全局对象、转换数据、打开剪贴板、转换字符串、发送数据、关闭剪贴板)
- Cocoa(创建数据类型数组、声明类型、转换字符串、发送数据)
剪贴板粘贴
X11
要处理剪贴板,您必须通过 XInternAtom
创建一些原子。 X 原子 用于通过 X11 请求或发送特定数据或属性。
您需要三个原子,
- UTF8_STRING:UTF-8 字符串的原子。
- CLIPBOARD:用于获取剪贴板数据的原子。
- XSEL_DATA:用于获取选择数据的原子。
const Atom UTF8_STRING = XInternAtom(display, "UTF8_STRING", True);
const Atom CLIPBOARD = XInternAtom(display, "CLIPBOARD", 0);
const Atom XSEL_DATA = XInternAtom(display, "XSEL_DATA", 0);
现在,要获取剪贴板数据,您必须使用 XConvertSelection
请求将剪贴板部分转换为 UTF8。
使用 XSync
将请求发送到服务器。
XConvertSelection(display, CLIPBOARD, UTF8_STRING, XSEL_DATA, window, CurrentTime);
XSync(display, 0);
选择将被转换并作为 XSelectionNotify
事件发送回客户端。您可以使用 XNextEvent
获取下一个事件,它应该是 SelectionNotify
事件。
XEvent event;
XNextEvent(display, &event);
检查事件是否为 SelectionNotify
事件,并使用 .selection
确保类型是 CLIPBOARD
。此外,请确保 .property
不为 0 且可以检索。
if (event.type == SelectionNotify && event.xselection.selection == CLIPBOARD && event.xselection.property != 0) {
您可以通过使用选择属性的 XGetWindowProperty
获取转换后的数据。
int format;
unsigned long N, size;
char* data, * s = NULL;
Atom target;
XGetWindowProperty(event.xselection.display, event.xselection.requestor,
event.xselection.property, 0L, (~0L), 0, AnyPropertyType, &target,
&format, &size, &N, (unsigned char**) &data);
通过检查 target
确保数据格式正确
if (target == UTF8_STRING || target == XA_STRING) {
数据存储在 data
中,使用完后,请使用 XFree
释放它。
您还可以通过 XDeleteProperty
删除属性。
XFree(data);
}
XDeleteProperty(event.xselection.display, event.xselection.requestor, event.xselection.property);
}
winapi
首先,打开剪贴板 OpenClipboard
。
if (OpenClipboard(NULL) == 0)
return 0;
通过 GetClipboardData
以 utf16 字符串获取剪贴板数据
如果数据为 NULL,您应该使用 CloseClipboard
关闭剪贴板
HANDLE hData = GetClipboardData(CF_UNICODETEXT);
if (hData == NULL) {
CloseClipboard();
return 0;
}
接下来,您需要将 utf16 数据转换回 utf8。
首先通过 GlobalLock
锁定 utf8 数据的内存。
wchar_t* wstr = (wchar_t*) GlobalLock(hData);
使用 setlocale
确保数据格式为 utf8。
使用 wcstombs
获取 UTF-8 版本的长度。
setlocale(LC_ALL, "en_US.UTF-8");
size_t textLen = wcstombs(NULL, wstr, 0);
如果长度有效,则使用 wcstombs
转换数据。
if (textLen) {
char* text = (char*) malloc((textLen * sizeof(char)) + 1);
wcstombs(text, wstr, (textLen) + 1);
text[textLen] = '\0';
free(text);
}
请务必使用 GlobalUnlock
释放剩余的全局数据,并使用 CloseClipboard
关闭剪贴板。
GlobalUnlock(hData);
CloseClipboard();
cocoa
Cocoa 使用 NSPasteboardTypeString
请求字符串数据。如果您不使用 Objective-C,则必须自行定义此项。
NSPasteboardType const NSPasteboardTypeString = "public.utf8-plain-text";
尽管这是 C 字符串,Cocoa 使用 NSStrings,但您可以通过 stringWithUTF8String
将 C 字符串转换为 NSString。
NSString* dataType = objc_msgSend_class_char(objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), (char*)NSPasteboardTypeString);
现在我们将使用 generalPasteboard
获取默认的粘贴板对象。
NSPasteboard* pasteboard = objc_msgSend_id((id)objc_getClass("NSPasteboard"), sel_registerName("generalPasteboard"));
然后您可以使用 stringForType
获取粘贴板的字符串数据,其中包含 dataType
。
但是,它会给您一个 NSString,可以使用 UTF8String
进行转换。
NSString* clip = ((id(*)(id, SEL, const char*))objc_msgSend)(pasteboard, sel_registerName("stringForType:"), dataType);
const char* str = ((const char* (*)(id, SEL)) objc_msgSend) (clip, sel_registerName("UTF8String"));
剪贴板复制
X11
要复制到剪贴板,您还需要一些原子。
- SAVE_TARGETS:请求转换为某个部分(用于复制)。
- TARGETS:处理一个请求的目标
- MULTIPLE:当有多个请求目标时
- ATOM_PAIR:获取支持的数据类型。
- CLIPBOARD_MANAGER:访问剪贴板管理器中的数据。
const Atom SAVE_TARGETS = XInternAtom((Display*) display, "SAVE_TARGETS", False);
const Atom TARGETS = XInternAtom((Display*) display, "TARGETS", False);
const Atom MULTIPLE = XInternAtom((Display*) display, "MULTIPLE", False);
const Atom ATOM_PAIR = XInternAtom((Display*) display, "ATOM_PAIR", False);
const Atom CLIPBOARD_MANAGER = XInternAtom((Display*) display, "CLIPBOARD_MANAGER", False);
我们可以请求一个剪贴板部分。首先,通过 XSetSelectionOwner
将该部分的所有者设置为客户端窗口。接下来使用 XConvertSelection
请求转换后的部分。
XSetSelectionOwner((Display*) display, CLIPBOARD, (Window) window, CurrentTime);
XConvertSelection((Display*) display, CLIPBOARD_MANAGER, SAVE_TARGETS, None, (Window) window, CurrentTime);
其余代码将存在于事件循环中。如果愿意,您可以从主事件循环创建一个外部事件循环,或将其添加到主事件循环中。
我们将处理 SelectionRequest
,以便将剪贴板选择更新为字符串数据。
if (event.type == SelectionRequest) {
const XSelectionRequestEvent* request = &event.xselectionrequest;
在 SelectionNotify 事件结束时,将向请求者发送响应。结构应该在这里创建并根据请求数据进行修改。
XEvent reply = { SelectionNotify };
reply.xselection.property = 0;
我们将处理的第一个目标是 TARGETS
,当请求者想知道支持哪些目标时。
if (request->target == TARGETS) {
我将创建一个支持目标的数组
const Atom targets[] = { TARGETS,
MULTIPLE,
UTF8_STRING,
XA_STRING };
此数组可以使用 XChangeProperty
传递。
我还会更改选择属性,以便请求者知道我们更改了哪些属性。
XChangeProperty(display,
request->requestor,
request->property,
4,
32,
PropModeReplace,
(unsigned char*) targets,
sizeof(targets) / sizeof(targets[0]));
reply.xselection.property = request->property;
}
接下来,我将处理 MULTIPLE
目标。
if (request->target == MULTIPLE) {
我们将首先通过 XGetWindowProperty
获取支持的目标
Atom* targets = NULL;
Atom actualType = 0;
int actualFormat = 0;
unsigned long count = 0, bytesAfter = 0;
XGetWindowProperty(display, request->requestor, request->property, 0, LONG_MAX, False, ATOM_PAIR, &actualType, &actualFormat, &count, &bytesAfter, (unsigned char **) &targets);
现在我们将遍历支持的目标。如果支持的目标与我们支持的目标之一匹配,我们可以使用 XChangeProperty
传递数据。
如果目标未使用,则第二个参数应设置为 None,将其标记为未使用。
unsigned long i;
for (i = 0; i < count; i += 2) {
if (targets[i] == UTF8_STRING || targets[i] == XA_STRING) {
XChangeProperty((Display*) display,
request->requestor,
targets[i + 1],
targets[i],
8,
PropModeReplace,
(unsigned char*) text,
sizeof(text));
XFlush(display);
} else {
targets[i + 1] = None;
}
}
您可以使用 XChangeProperty
将支持目标的最终数组传递给请求者。这告诉请求者要从它发送的原始列表中期望哪些目标。
当调用 XFlush
时,消息将尽快发送出去。
您可以使用 XFree
释放目标数组的副本。
XChangeProperty((Display*) display,
request->requestor,
request->property,
ATOM_PAIR,
32,
PropModeReplace,
(unsigned char*) targets,
count);
XFlush(display);
XFree(targets);
reply.xselection.property = request->property;
}
对于事件的最后一步,通过 XSendEvent
将选择发送回请求者。
然后使用 XFlush
刷新队列。
reply.xselection.display = request->display;
reply.xselection.requestor = request->requestor;
reply.xselection.selection = request->selection;
reply.xselection.target = request->target;
reply.xselection.time = request->time;
XSendEvent((Display*) display, request->requestor, False, 0, &reply);
XFlush(display);
}
winapi
首先使用 GlobalAlloc
为您的数据和 utf-8 缓冲区分配全局内存
HANDLE object = GlobalAlloc(GMEM_MOVEABLE, (1 + textLen) * sizeof(WCHAR));
WCHAR* buffer = (WCHAR*) GlobalLock(object);
接下来,您可以使用 MultiByteToWideChar
将字符串转换为宽字符串。
MultiByteToWideChar(CP_UTF8, 0, text, -1, buffer, textLen);
现在解锁全局对象并打开剪贴板
GlobalUnlock(object);
OpenClipboard(NULL);
要更新剪贴板数据,首先通过 EmptyClipboard
清除剪贴板上的内容,然后可以使用 SetClipboardData
将数据设置为 utf8 对象。
最后,使用 CloseClipboard
关闭剪贴板。
EmptyClipboard();
SetClipboardData(CF_UNICODETEXT, object);
CloseClipboard();
cocoa
首先创建一个您想要放入剪贴板的数据类型数组,并使用 initWithObjects
将其转换为 NSArray。
NSPasteboardType ntypes[] = { dataType };
NSArray* array = ((id (*)(id, SEL, void*, NSUInteger))objc_msgSend)
(NSAlloc(objc_getClass("NSArray")), sel_registerName("initWithObjects:count:"), ntypes, 1);
使用 declareTypes
将数组声明为支持的数据类型。
您还可以使用 NSRelease
释放 NSArray。
((NSInteger(*)(id, SEL, id, void*))objc_msgSend) (pasteboard, sel_registerName("declareTypes:owner:"), array, NULL);
NSRelease(array);
您可以将要复制的字符串通过 stringWithUTF8String
转换为 NSString,然后使用 setString
将剪贴板字符串设置为该 NSString。
NSString* nsstr = objc_msgSend_class_char(objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), text);
((bool (*)(id, SEL, id, NSPasteboardType))objc_msgSend) (pasteboard, sel_registerName("setString:forType:"), nsstr, dataType);
完整示例
X11
// compile with:
// gcc x11.c -lX11
#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <X11/Xatom.h>
int main(void) {
Display* display = XOpenDisplay(NULL);
Window window = XCreateSimpleWindow(display, RootWindow(display, DefaultScreen(display)), 10, 10, 200, 200, 1,
BlackPixel(display, DefaultScreen(display)), WhitePixel(display, DefaultScreen(display)));
XSelectInput(display, window, ExposureMask | KeyPressMask);
const Atom UTF8_STRING = XInternAtom(display, "UTF8_STRING", True);
const Atom CLIPBOARD = XInternAtom(display, "CLIPBOARD", 0);
const Atom XSEL_DATA = XInternAtom(display, "XSEL_DATA", 0);
const Atom SAVE_TARGETS = XInternAtom((Display*) display, "SAVE_TARGETS", False);
const Atom TARGETS = XInternAtom((Display*) display, "TARGETS", False);
const Atom MULTIPLE = XInternAtom((Display*) display, "MULTIPLE", False);
const Atom ATOM_PAIR = XInternAtom((Display*) display, "ATOM_PAIR", False);
const Atom CLIPBOARD_MANAGER = XInternAtom((Display*) display, "CLIPBOARD_MANAGER", False);
// input
XConvertSelection(display, CLIPBOARD, UTF8_STRING, XSEL_DATA, window, CurrentTime);
XSync(display, 0);
XEvent event;
XNextEvent(display, &event);
if (event.type == SelectionNotify && event.xselection.selection == CLIPBOARD && event.xselection.property != 0) {
int format;
unsigned long N, size;
char* data, * s = NULL;
Atom target;
XGetWindowProperty(event.xselection.display, event.xselection.requestor,
event.xselection.property, 0L, (~0L), 0, AnyPropertyType, &target,
&format, &size, &N, (unsigned char**) &data);
if (target == UTF8_STRING || target == XA_STRING) {
printf("paste: %s\n", data);
XFree(data);
}
XDeleteProperty(event.xselection.display, event.xselection.requestor, event.xselection.property);
}
// output
char text[] = "new string\0";
XSetSelectionOwner((Display*) display, CLIPBOARD, (Window) window, CurrentTime);
XConvertSelection((Display*) display, CLIPBOARD_MANAGER, SAVE_TARGETS, None, (Window) window, CurrentTime);
Bool running = True;
while (running) {
XNextEvent(display, &event);
if (event.type == SelectionRequest) {
const XSelectionRequestEvent* request = &event.xselectionrequest;
XEvent reply = { SelectionNotify };
reply.xselection.property = 0;
if (request->target == TARGETS) {
const Atom targets[] = { TARGETS,
MULTIPLE,
UTF8_STRING,
XA_STRING };
XChangeProperty(display,
request->requestor,
request->property,
4,
32,
PropModeReplace,
(unsigned char*) targets,
sizeof(targets) / sizeof(targets[0]));
reply.xselection.property = request->property;
}
if (request->target == MULTIPLE) {
Atom* targets = NULL;
Atom actualType = 0;
int actualFormat = 0;
unsigned long count = 0, bytesAfter = 0;
XGetWindowProperty(display, request->requestor, request->property, 0, LONG_MAX, False, ATOM_PAIR, &actualType, &actualFormat, &count, &bytesAfter, (unsigned char **) &targets);
unsigned long i;
for (i = 0; i < count; i += 2) {
Bool found = False;
if (targets[i] == UTF8_STRING || targets[i] == XA_STRING) {
XChangeProperty((Display*) display,
request->requestor,
targets[i + 1],
targets[i],
8,
PropModeReplace,
(unsigned char*) text,
sizeof(text));
XFlush(display);
running = False;
} else {
targets[i + 1] = None;
}
}
XChangeProperty((Display*) display,
request->requestor,
request->property,
ATOM_PAIR,
32,
PropModeReplace,
(unsigned char*) targets,
count);
XFlush(display);
XFree(targets);
reply.xselection.property = request->property;
}
reply.xselection.display = request->display;
reply.xselection.requestor = request->requestor;
reply.xselection.selection = request->selection;
reply.xselection.target = request->target;
reply.xselection.time = request->time;
XSendEvent((Display*) display, request->requestor, False, 0, &reply);
XFlush(display);
}
}
XCloseDisplay(display);
}
Winapi
// compile with:
// gcc win32.c
#include <windows.h>
#include <locale.h>
#include <stdio.h>
int main() {
// output
if (OpenClipboard(NULL) == 0)
return 0;
HANDLE hData = GetClipboardData(CF_UNICODETEXT);
if (hData == NULL) {
CloseClipboard();
return 0;
}
wchar_t* wstr = (wchar_t*) GlobalLock(hData);
setlocale(LC_ALL, "en_US.UTF-8");
size_t textLen = wcstombs(NULL, wstr, 0);
if (textLen) {
char* text = (char*) malloc((textLen * sizeof(char)) + 1);
wcstombs(text, wstr, (textLen) + 1);
text[textLen] = '\0';
printf("paste: %s\n", text);
free(text);
}
GlobalUnlock(hData);
CloseClipboard();
// input
char text[] = "new text\0";
HANDLE object = GlobalAlloc(GMEM_MOVEABLE, (sizeof(text) / sizeof(char)) * sizeof(WCHAR));
WCHAR* buffer = (WCHAR*) GlobalLock(object);
if (!buffer) {
GlobalFree(object);
return 0;
}
MultiByteToWideChar(CP_UTF8, 0, text, -1, buffer, (sizeof(text) / sizeof(char)));
GlobalUnlock(object);
if (OpenClipboard(NULL) == 0) {
GlobalFree(object);
return 0;
}
EmptyClipboard();
SetClipboardData(CF_UNICODETEXT, object);
CloseClipboard();
}
Cocoa
// compile with:
// gcc cocoa.c -framework Foundation -framework AppKit
#include <objc/runtime.h>
#include <objc/message.h>
#include <CoreVideo/CVDisplayLink.h>
#include <ApplicationServices/ApplicationServices.h>
#ifdef __arm64__
/* ARM just uses objc_msgSend */
#define abi_objc_msgSend_stret objc_msgSend
#define abi_objc_msgSend_fpret objc_msgSend
#else /* __i386__ */
/* x86 just uses abi_objc_msgSend_fpret and (NSColor *)objc_msgSend_id respectively */
#define abi_objc_msgSend_stret objc_msgSend_stret
#define abi_objc_msgSend_fpret objc_msgSend_fpret
#endif
typedef void NSPasteboard;
typedef void NSString;
typedef void NSArray;
typedef void NSApplication;
typedef const char* NSPasteboardType;
typedef unsigned long NSUInteger;
typedef long NSInteger;
#define NSAlloc(nsclass) objc_msgSend_id((id)nsclass, sel_registerName("alloc"))
#define objc_msgSend_bool ((BOOL (*)(id, SEL))objc_msgSend)
#define objc_msgSend_void ((void (*)(id, SEL))objc_msgSend)
#define objc_msgSend_void_id ((void (*)(id, SEL, id))objc_msgSend)
#define objc_msgSend_uint ((NSUInteger (*)(id, SEL))objc_msgSend)
#define objc_msgSend_void_bool ((void (*)(id, SEL, BOOL))objc_msgSend)
#define objc_msgSend_void_int ((void (*)(id, SEL, int))objc_msgSend)
#define objc_msgSend_bool_void ((BOOL (*)(id, SEL))objc_msgSend)
#define objc_msgSend_void_SEL ((void (*)(id, SEL, SEL))objc_msgSend)
#define objc_msgSend_id ((id (*)(id, SEL))objc_msgSend)
#define objc_msgSend_id_id ((id (*)(id, SEL, id))objc_msgSend)
#define objc_msgSend_id_bool ((BOOL (*)(id, SEL, id))objc_msgSend)
#define objc_msgSend_class_char ((id (*)(Class, SEL, char*))objc_msgSend)
void NSRelease(id obj) {
objc_msgSend_void(obj, sel_registerName("release"));
}
int main() {
/* input */
NSPasteboardType const NSPasteboardTypeString = "public.utf8-plain-text";
NSString* dataType = objc_msgSend_class_char(objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), (char*)NSPasteboardTypeString);
NSPasteboard* pasteboard = objc_msgSend_id((id)objc_getClass("NSPasteboard"), sel_registerName("generalPasteboard"));
NSString* clip = ((id(*)(id, SEL, const char*))objc_msgSend)(pasteboard, sel_registerName("stringForType:"), dataType);
const char* str = ((const char* (*)(id, SEL)) objc_msgSend) (clip, sel_registerName("UTF8String"));
printf("paste: %s\n", str);
char text[] = "new string\0";
NSPasteboardType ntypes[] = { dataType };
NSArray* array = ((id (*)(id, SEL, void*, NSUInteger))objc_msgSend)
(NSAlloc(objc_getClass("NSArray")), sel_registerName("initWithObjects:count:"), ntypes, 1);
((NSInteger(*)(id, SEL, id, void*))objc_msgSend) (pasteboard, sel_registerName("declareTypes:owner:"), array, NULL);
NSRelease(array);
NSString* nsstr = objc_msgSend_class_char(objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), text);
((bool (*)(id, SEL, id, NSPasteboardType))objc_msgSend) (pasteboard, sel_registerName("setString:forType:"), nsstr, dataType);
}