A friend and I were recently discussing alternative ways in which the Windows Clipboard could operate and after a while we decided that a FIFO Queue would be a useful addition to normal clipboard operations. For those of you wondering what a FIFO Queue is, think of it as a data type with two general methods, push and get. These push method will add another element, and the get method returns elements in the same order that they were pushed (Queue Wiki).
This functionality comes in use when you want to copy a series of different things and then paste them. It’s more efficient to copy everything at once and then paste everything at the end, then to constantly switch between copying and pasting. My software does allow you to use traditional copy and paste as my queue versions are ‘hotkeyed’ to ALT+C and ALT+V.
Implementing the software was actually a bit more tricky than I expected. Initially I was having issues with forcing the system to invoke a copy/paste and I attempted a variety of solutions. Some attempts include the use of keybd_event() to force a CONTORL+C/CONTROL+V key combination and the use of SendMessage() with WM_COPY and WM_PASTE (which is a more proper implementation) to cause a copy/paste.
There was also the issue of allowing for proper memory protection of data supplied to the SetClipboardData function. This occurred because the function expects to take ownership of the memory (Handle) that it gets, therefore we are forced to use a series of calls to GlobalAlloc, GlobalLock, and GlobalUnlock.
Due to these problems and other issues I contacted a friend named Napalm (Netcore2k.net) for some help with the code. Initially he just remarked on what needed to be changed, but after errors continued to surface he stepped in and coded a very substantial (and by substantial I mean almost all of) part of the copypaste() function. With his help I was able to get the software working and the only obvious improvement is to implement a clipboard viewer so that we can know exactly when the clipboard is updated. I’m not going to do this right now.
And here’s the code (note the heavy use of Hungarian notation by Napalm…).
// Queue Based Clipboard
// Creative Commons - Aaron Burrow and Napalm
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#define Q_COPY 100
#define Q_PASTE 101
typedef struct tagGUITHREADINFO {
DWORD cbSize;
DWORD flags;
HWND hwndActive;
HWND hwndFocus;
HWND hwndCapture;
HWND hwndMenuOwner;
HWND hwndMoveSize;
HWND hwndCaret;
RECT rcCaret;
} GUITHREADINFO, *LPGUITHREADINFO;
struct queue_model {
long size;
long current_elements;
long add_more;
char** contents;
};
void copypaste(struct queue_model* queue, int copyorpaste);
struct queue_model* queue_init(long q_size);
bool queue_put(struct queue_model* queue, char* item);
bool queue_get(struct queue_model* queue, char* item);
bool queue_get_item_length(struct queue_model* queue, size_t* length);
void queue_cleanup(struct queue_model* queue);
BOOL (WINAPI *GetGUIThreadInfo)(DWORD idThread, LPGUITHREADINFO lpgui);
int main(int argc, char** argv)
{
struct queue_model* queue;
MSG msg;
// Using old headers
*(FARPROC *)&GetGUIThreadInfo = GetProcAddress(GetModuleHandle("user32.dll"), "GetGUIThreadInfo");
if (!GetGUIThreadInfo) {
printf("Upgrade your Windows!\n");
return 1;
}
if (queue = queue_init(50)) {
if (RegisterHotKey(NULL, Q_COPY, MOD_ALT, 'C')) {
if (RegisterHotKey(NULL, Q_PASTE, MOD_ALT, 'V')) {
while (GetMessage(&msg, NULL, 0, 0)) {
if (msg.message == WM_HOTKEY) {
if (msg.wParam == Q_COPY)
copypaste(queue, 1);
else if (msg.wParam == Q_PASTE)
copypaste(queue, 0);
}
}
}
}
queue_cleanup(queue);
}
return 0;
}
void copypaste(struct queue_model* queue, int copyorpaste) // Napalm (Netcore2k.net) fixed a bunch of problems and coded even more
{
HGLOBAL hGlobal;
LPSTR lpData, lpSaved = NULL;
BOOL bSuccess;
UINT uLength, uSize;
// Save Clipboard
bSuccess = FALSE;
if(OpenClipboard(NULL)){
hGlobal = GetClipboardData(CF_TEXT);
if(hGlobal){
if((lpData = (LPSTR)GlobalLock(hGlobal)) != NULL){
uLength = strlen(lpData) + 1;
lpSaved = (LPSTR)HeapAlloc(GetProcessHeap(), 0, uLength);
if(lpSaved){
strcpy(lpSaved, lpData);
bSuccess = TRUE;
}
GlobalUnlock(hGlobal);
}
}
CloseClipboard();
}else{
printf("Error: OpenClipboard() failed!\n");
}
if(!bSuccess){
printf("Error: %d, failed to get clipboard data!\n", GetLastError());
return;
}
// Do actual work
if(bSuccess){
GUITHREADINFO guiThread;
DWORD dwResult;
UINT uMsgCopyPaste = (copyorpaste) ? WM_COPY : WM_PASTE;
if(!copyorpaste){
// Update clipboard with new paste data
bSuccess = FALSE;
if(OpenClipboard(NULL)){
if(queue_get_item_length(queue, &uSize)){
hGlobal = GlobalAlloc(GMEM_MOVEABLE, uSize + 1);
if((lpData = GlobalLock(hGlobal)) != NULL){
GlobalUnlock(hGlobal);
EmptyClipboard();
queue_get(queue, lpData);
if(SetClipboardData(CF_TEXT, lpData))
bSuccess = TRUE;
}
}
CloseClipboard();
}else{
printf("Error: OpenClipboard() failed!\n");
}
if(!bSuccess){
printf("Warning: Could not update clipboard with new data\n");
GlobalFree(hGlobal);
return;
}
}
guiThread.cbSize = sizeof(guiThread);
if(GetGUIThreadInfo(0, &guiThread)){
SendMessageTimeout(guiThread.hwndFocus, uMsgCopyPaste, 0, 0, SMTO_BLOCK, 100, &dwResult);
}else{
printf("Warning: GetGUIThreadInfo() failed!\n");
SendMessageTimeout(GetFocus(), uMsgCopyPaste, 0, 0, SMTO_BLOCK, 100, &dwResult);
}
// TODO: IMPORTANT! - use clipboard viewer api to see when the update actually occurs
Sleep(100);
if(copyorpaste){
// Copy new data from Clipboard
bSuccess = FALSE;
if(OpenClipboard(NULL)){
hGlobal = GetClipboardData(CF_TEXT);
if(hGlobal){
if((lpData = (LPSTR)GlobalLock(hGlobal)) != NULL){
queue_put(queue, lpData);
GlobalUnlock(hGlobal);
bSuccess = TRUE;
}
}else{
printf("Warning: %d, nothing to copy from clipboard!\n", GetLastError());
}
}else{
printf("Error: OpenClipboard() failed!\n");
}
if(!bSuccess){
// detect if saved data == new data
printf("Warning: No new data on clipboard\n");
}
}
}
// Restore Clipboard
bSuccess = FALSE;
if(OpenClipboard(NULL)){
hGlobal = GlobalAlloc(GMEM_MOVEABLE, uLength);
if((lpData = GlobalLock(hGlobal)) != NULL){
memcpy(lpData, lpSaved, uLength);
GlobalUnlock(hGlobal);
if(SetClipboardData(CF_TEXT, hGlobal))
bSuccess = TRUE;
}else{
printf("Error: Failed to lock\n");
}
CloseClipboard();
}else{
printf("Error: OpenClipboard() failed!\n");
}
if(!bSuccess){
printf("Error: %d, failed to restore clipboard data!\n", GetLastError());
}
if(lpSaved)
HeapFree(GetProcessHeap(), 0, lpSaved);
return;
}
// A return value of NULL indicates an error
struct queue_model* queue_init(long q_size)
{
struct queue_model* queue = NULL;
if (queue = malloc(sizeof(struct queue_model))) {
if (queue->contents = malloc(sizeof(q_size))) {
queue->size = q_size;
queue->current_elements = 0;
}
}
return queue;
}
bool queue_put(struct queue_model* queue, char* item)
{
if (queue->current_elements < queue->size) {
if (queue->contents[queue->current_elements] = malloc(strlen(item) + 1)) {
strcpy(queue->contents[queue->current_elements++], item);
return true;
}
}
return false;
}
bool queue_get(struct queue_model* queue, char* item)
{
if (queue->current_elements > 0) {
strcpy(item, queue->contents[0]);
memmove((void*)queue->contents, (void*)&(queue->contents[1]), --queue->current_elements * sizeof(char*));
return true;
}
return false;
}
bool queue_get_item_length(struct queue_model* queue, size_t* length)
{
if (queue->current_elements > 0) {
*length = strlen(queue->contents[0]);
return true;
}
return false;
}
void queue_cleanup(struct queue_model* queue)
{
for (int i = 0; i < queue->current_elements; i++)
free (queue->contents[i]);
free (queue);
return;
}
Here is some example output.
ALT+C [hello]
ALT+C [google]
ALT+C [bye]
ALT+V [hello]
ALT+V [google]
ALT+V [bye]
Until later.



