From f0fbf1a9d1345750c2bfce5858617b4095ce3ba7 Mon Sep 17 00:00:00 2001 From: Alexandre Julliard Date: Mon, 26 Sep 2016 15:09:27 +0900 Subject: [PATCH] user32: Cache clipboard data on the client side. Signed-off-by: Alexandre Julliard --- dlls/user32/clipboard.c | 181 ++++++++++++++++++++++++++++++++++++++++- dlls/user32/tests/clipboard.c | 18 ++-- include/wine/server_protocol.h | 10 ++- server/clipboard.c | 12 ++- server/protocol.def | 7 +- server/request.h | 9 +- server/trace.c | 12 ++- 7 files changed, 224 insertions(+), 25 deletions(-) diff --git a/dlls/user32/clipboard.c b/dlls/user32/clipboard.c index 3282fa08f4c..a841d7dbfb3 100644 --- a/dlls/user32/clipboard.c +++ b/dlls/user32/clipboard.c @@ -46,13 +46,34 @@ #include "user_private.h" #include "win.h" -#include "wine/debug.h" +#include "wine/list.h" #include "wine/unicode.h" #include "wine/server.h" +#include "wine/debug.h" WINE_DEFAULT_DEBUG_CHANNEL(clipboard); +struct cached_format +{ + struct list entry; /* entry in cache list */ + UINT format; /* format id */ + UINT seqno; /* sequence number when the data was set */ + HANDLE handle; /* original data handle */ +}; + +static struct list cached_formats = LIST_INIT( cached_formats ); +static struct list formats_to_free = LIST_INIT( formats_to_free ); + +static CRITICAL_SECTION clipboard_cs; +static CRITICAL_SECTION_DEBUG critsect_debug = +{ + 0, 0, &clipboard_cs, + { &critsect_debug.ProcessLocksList, &critsect_debug.ProcessLocksList }, + 0, 0, { (DWORD_PTR)(__FILE__ ": clipboard_cs") } +}; +static CRITICAL_SECTION clipboard_cs = { &critsect_debug, -1, 0, 0, 0, 0 }; + /* get a debug string for a format id */ static const char *debugstr_format( UINT id ) { @@ -200,6 +221,115 @@ static HANDLE unmarshal_data( UINT format, void *data, data_size_t size ) return handle; } +/* retrieve a data format from the cache */ +static struct cached_format *get_cached_format( UINT format ) +{ + struct cached_format *cache; + + LIST_FOR_EACH_ENTRY( cache, &cached_formats, struct cached_format, entry ) + if (cache->format == format) return cache; + return NULL; +} + +/* store data in the cache, or reuse the existing one if available */ +static HANDLE cache_data( UINT format, HANDLE data, data_size_t size, UINT seqno, + struct cached_format *cache ) +{ + if (cache) + { + if (seqno == cache->seqno) /* we can reuse the cached data */ + { + GlobalFree( data ); + return cache->handle; + } + /* cache entry is stale, remove it */ + list_remove( &cache->entry ); + list_add_tail( &formats_to_free, &cache->entry ); + } + + /* allocate new cache entry */ + if (!(cache = HeapAlloc( GetProcessHeap(), 0, sizeof(*cache) ))) + { + GlobalFree( data ); + return 0; + } + cache->format = format; + cache->seqno = seqno; + cache->handle = unmarshal_data( format, data, size ); + list_add_tail( &cached_formats, &cache->entry ); + return cache->handle; +} + +/* free a single cached format */ +static void free_cached_data( struct cached_format *cache ) +{ + void *ptr; + + switch (cache->format) + { + case CF_BITMAP: + case CF_DSPBITMAP: + case CF_PALETTE: + DeleteObject( cache->handle ); + break; + case CF_ENHMETAFILE: + case CF_DSPENHMETAFILE: + DeleteEnhMetaFile( cache->handle ); + break; + case CF_METAFILEPICT: + case CF_DSPMETAFILEPICT: + if ((ptr = GlobalLock( cache->handle ))) + { + DeleteMetaFile( ((METAFILEPICT *)ptr)->hMF ); + GlobalUnlock( cache->handle ); + } + GlobalFree( cache->handle ); + break; + default: + if ((ptr = GlobalLock( cache->handle )) && ptr != cache->handle) + { + GlobalUnlock( cache->handle ); + GlobalFree( cache->handle ); + } + break; + } + list_remove( &cache->entry ); + HeapFree( GetProcessHeap(), 0, cache ); +} + +/* clear global memory formats; special types are freed on EmptyClipboard */ +static void invalidate_memory_formats(void) +{ + struct cached_format *cache, *next; + + LIST_FOR_EACH_ENTRY_SAFE( cache, next, &cached_formats, struct cached_format, entry ) + { + switch (cache->format) + { + case CF_BITMAP: + case CF_DSPBITMAP: + case CF_PALETTE: + case CF_ENHMETAFILE: + case CF_DSPENHMETAFILE: + case CF_METAFILEPICT: + case CF_DSPMETAFILEPICT: + continue; + default: + free_cached_data( cache ); + break; + } + } +} + +/* free all the data in the cache */ +static void free_cached_formats(void) +{ + struct list *ptr; + + list_move_tail( &formats_to_free, &cached_formats ); + while ((ptr = list_head( &formats_to_free ))) + free_cached_data( LIST_ENTRY( ptr, struct cached_format, entry )); +} /* get the clipboard locale stored in the CF_LOCALE format */ static LCID get_clipboard_locale(void) @@ -518,18 +648,25 @@ INT WINAPI GetClipboardFormatNameA( UINT format, LPSTR buffer, INT maxlen ) BOOL WINAPI OpenClipboard( HWND hwnd ) { BOOL ret; + HWND owner; TRACE( "%p\n", hwnd ); USER_Driver->pUpdateClipboard(); + EnterCriticalSection( &clipboard_cs ); + SERVER_START_REQ( open_clipboard ) { req->window = wine_server_user_handle( hwnd ); ret = !wine_server_call_err( req ); + owner = wine_server_ptr_handle( reply->owner ); } SERVER_END_REQ; + if (ret && !WIN_IsCurrentProcess( owner )) invalidate_memory_formats(); + + LeaveCriticalSection( &clipboard_cs ); return ret; } @@ -572,12 +709,17 @@ BOOL WINAPI EmptyClipboard(void) if (owner) SendMessageTimeoutW( owner, WM_DESTROYCLIPBOARD, 0, 0, SMTO_ABORTIFHUNG, 5000, NULL ); + EnterCriticalSection( &clipboard_cs ); + SERVER_START_REQ( empty_clipboard ) { ret = !wine_server_call_err( req ); } SERVER_END_REQ; + if (ret) free_cached_formats(); + + LeaveCriticalSection( &clipboard_cs ); return ret; } @@ -696,6 +838,7 @@ BOOL WINAPI ChangeClipboardChain( HWND hwnd, HWND next ) */ HANDLE WINAPI SetClipboardData( UINT format, HANDLE data ) { + struct cached_format *cache = NULL; void *ptr = NULL; data_size_t size = 0; HANDLE handle = data, retval = 0; @@ -707,19 +850,37 @@ HANDLE WINAPI SetClipboardData( UINT format, HANDLE data ) { if (!(handle = marshal_data( format, data, &size ))) return 0; if (!(ptr = GlobalLock( handle ))) goto done; + if (!(cache = HeapAlloc( GetProcessHeap(), 0, sizeof(*cache) ))) goto done; + cache->format = format; + cache->handle = data; } + EnterCriticalSection( &clipboard_cs ); + SERVER_START_REQ( set_clipboard_data ) { req->format = format; req->lcid = GetUserDefaultLCID(); wine_server_add_data( req, ptr, size ); - ret = !wine_server_call_err( req ); + if ((ret = !wine_server_call_err( req ))) + { + if (cache) cache->seqno = reply->seqno; + } } SERVER_END_REQ; if (ret) + { + /* free the previous entry if any */ + struct cached_format *prev; + + if ((prev = get_cached_format( format ))) free_cached_data( prev ); + if (cache) list_add_tail( &cached_formats, &cache->entry ); retval = data; + } + else HeapFree( GetProcessHeap(), 0, cache ); + + LeaveCriticalSection( &clipboard_cs ); done: if (ptr) GlobalUnlock( ptr ); @@ -828,8 +989,9 @@ BOOL WINAPI GetUpdatedClipboardFormats( UINT *formats, UINT size, UINT *out_size */ HANDLE WINAPI GetClipboardData( UINT format ) { + struct cached_format *cache; NTSTATUS status; - UINT from; + UINT from, data_seqno; HWND owner; HANDLE data; UINT size = 1024; @@ -839,23 +1001,34 @@ HANDLE WINAPI GetClipboardData( UINT format ) { if (!(data = GlobalAlloc( GMEM_FIXED, size ))) return 0; + EnterCriticalSection( &clipboard_cs ); + cache = get_cached_format( format ); + SERVER_START_REQ( get_clipboard_data ) { req->format = format; + if (cache) + { + req->cached = 1; + req->seqno = cache->seqno; + } wine_server_set_reply( req, data, size ); status = wine_server_call( req ); from = reply->from; size = reply->total; + data_seqno = reply->seqno; owner = wine_server_ptr_handle( reply->owner ); } SERVER_END_REQ; if (!status && size) { - data = unmarshal_data( format, data, size ); + data = cache_data( format, data, size, data_seqno, cache ); + LeaveCriticalSection( &clipboard_cs ); TRACE( "%s returning %p\n", debugstr_format( format ), data ); return data; } + LeaveCriticalSection( &clipboard_cs ); GlobalFree( data ); if (status == STATUS_BUFFER_OVERFLOW) continue; /* retry with the new size */ diff --git a/dlls/user32/tests/clipboard.c b/dlls/user32/tests/clipboard.c index ef98575a0a1..90e1aaad13a 100644 --- a/dlls/user32/tests/clipboard.c +++ b/dlls/user32/tests/clipboard.c @@ -1755,11 +1755,11 @@ static void test_handles( HWND hwnd ) /* and now they are freed, unless we are the owner */ if (!is_owner) { - todo_wine ok( is_freed( htext ), "expected freed mem %p\n", htext ); - todo_wine ok( is_freed( htext2 ), "expected freed mem %p\n", htext2 ); - todo_wine ok( is_freed( htext3 ), "expected freed mem %p\n", htext3 ); - todo_wine ok( is_freed( htext4 ), "expected freed mem %p\n", htext4 ); - todo_wine ok( is_freed( hmoveable ), "expected freed mem %p\n", hmoveable ); + ok( is_freed( htext ), "expected freed mem %p\n", htext ); + ok( is_freed( htext2 ), "expected freed mem %p\n", htext2 ); + ok( is_freed( htext3 ), "expected freed mem %p\n", htext3 ); + ok( is_freed( htext4 ), "expected freed mem %p\n", htext4 ); + ok( is_freed( hmoveable ), "expected freed mem %p\n", hmoveable ); data = GetClipboardData( CF_TEXT ); ok( is_fixed( data ), "expected fixed mem %p\n", data ); @@ -1935,7 +1935,7 @@ static void test_handles_process( const char *str ) r = OpenClipboard( 0 ); ok( r, "gle %d\n", GetLastError() ); h = GetClipboardData( CF_TEXT ); - todo_wine_if( !h ) ok( is_fixed( h ), "expected fixed mem %p\n", h ); + ok( is_fixed( h ), "expected fixed mem %p\n", h ); ptr = GlobalLock( h ); ok( !strcmp( str, ptr ), "wrong data '%.5s'\n", ptr ); GlobalUnlock( h ); @@ -1945,13 +1945,13 @@ static void test_handles_process( const char *str ) if (ptr) ok( !strcmp( str, ptr ), "wrong data '%.5s'\n", ptr ); GlobalUnlock( h ); h = GetClipboardData( CF_GDIOBJFIRST + 3 ); - todo_wine_if( !h ) ok( is_fixed( h ), "expected fixed mem %p\n", h ); + ok( is_fixed( h ), "expected fixed mem %p\n", h ); ptr = GlobalLock( h ); ok( !strcmp( str, ptr ), "wrong data '%.5s'\n", ptr ); GlobalUnlock( h ); trace( "gdiobj %p\n", h ); h = GetClipboardData( CF_PRIVATEFIRST + 7 ); - todo_wine_if( !h ) ok( is_fixed( h ), "expected fixed mem %p\n", h ); + ok( is_fixed( h ), "expected fixed mem %p\n", h ); ptr = GlobalLock( h ); ok( !strcmp( str, ptr ), "wrong data '%.5s'\n", ptr ); GlobalUnlock( h ); @@ -1972,13 +1972,11 @@ static void test_handles_process( const char *str ) trace( "palette %p\n", h ); h = GetClipboardData( CF_METAFILEPICT ); ok( is_fixed( h ), "expected fixed mem %p\n", h ); - if (h) ok( GetObjectType( ((METAFILEPICT *)h)->hMF ) == OBJ_METAFILE, "wrong object %p\n", ((METAFILEPICT *)h)->hMF ); trace( "metafile %p\n", h ); h = GetClipboardData( CF_DSPMETAFILEPICT ); ok( is_fixed( h ), "expected fixed mem %p\n", h ); - if (h) ok( GetObjectType( ((METAFILEPICT *)h)->hMF ) == OBJ_METAFILE, "wrong object %p\n", ((METAFILEPICT *)h)->hMF ); trace( "metafile2 %p\n", h ); diff --git a/include/wine/server_protocol.h b/include/wine/server_protocol.h index 01ba97a724a..29f51468ce3 100644 --- a/include/wine/server_protocol.h +++ b/include/wine/server_protocol.h @@ -4468,7 +4468,7 @@ struct open_clipboard_request struct open_clipboard_reply { struct reply_header __header; - int owner; + user_handle_t owner; char __pad_12[4]; }; @@ -4538,6 +4538,8 @@ struct set_clipboard_data_request struct set_clipboard_data_reply { struct reply_header __header; + unsigned int seqno; + char __pad_12[4]; }; @@ -4546,15 +4548,17 @@ struct get_clipboard_data_request { struct request_header __header; unsigned int format; + int cached; + unsigned int seqno; }; struct get_clipboard_data_reply { struct reply_header __header; unsigned int from; user_handle_t owner; + unsigned int seqno; data_size_t total; /* VARARG(data,bytes); */ - char __pad_20[4]; }; @@ -6438,6 +6442,6 @@ union generic_reply struct terminate_job_reply terminate_job_reply; }; -#define SERVER_PROTOCOL_VERSION 521 +#define SERVER_PROTOCOL_VERSION 522 #endif /* __WINE_WINE_SERVER_PROTOCOL_H */ diff --git a/server/clipboard.c b/server/clipboard.c index dcae0158816..aa23b5f50b6 100644 --- a/server/clipboard.c +++ b/server/clipboard.c @@ -42,6 +42,7 @@ struct clip_format struct list entry; /* entry in format list */ unsigned int id; /* format id */ unsigned int from; /* for synthesized data, format to generate it from */ + unsigned int seqno; /* sequence number when the data was set */ data_size_t size; /* size of the data block */ void *data; /* data contents, or NULL for delay-rendered */ }; @@ -207,7 +208,7 @@ static int synthesize_formats( struct clipboard *clipboard ) void *data = memdup( &clipboard->lcid, sizeof(clipboard->lcid) ); if ((format = add_format( clipboard, CF_LOCALE ))) { - clipboard->seqno++; + format->seqno = clipboard->seqno++; format->data = data; format->size = sizeof(clipboard->lcid); } @@ -222,6 +223,7 @@ static int synthesize_formats( struct clipboard *clipboard ) else continue; if (!(format = add_format( clipboard, formats[i][0] ))) continue; format->from = from; + format->seqno = clipboard->seqno; total++; } return total; @@ -385,7 +387,7 @@ DECL_HANDLER(open_clipboard) clipboard->open_win = win; clipboard->open_thread = current; - reply->owner = (clipboard->owner_thread && clipboard->owner_thread->process == current->process); + reply->owner = clipboard->owner_win; } @@ -459,13 +461,15 @@ DECL_HANDLER(set_clipboard_data) } } - clipboard->seqno++; format->from = 0; + format->seqno = clipboard->seqno++; format->size = get_req_data_size(); format->data = data; if (req->format == CF_TEXT || req->format == CF_OEMTEXT || req->format == CF_UNICODETEXT) clipboard->lcid = req->lcid; + + reply->seqno = format->seqno; } @@ -489,7 +493,9 @@ DECL_HANDLER(get_clipboard_data) } reply->from = format->from; reply->total = format->size; + reply->seqno = format->seqno; if (!format->data && !format->from) reply->owner = clipboard->owner_win; + if (req->cached && req->seqno == format->seqno) return; /* client-side cache still valid */ if (format->size <= get_reply_max_size()) set_reply_data( format->data, format->size ); else set_error( STATUS_BUFFER_OVERFLOW ); } diff --git a/server/protocol.def b/server/protocol.def index f9840583534..8d867378895 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -3162,7 +3162,7 @@ enum caret_state @REQ(open_clipboard) user_handle_t window; /* clipboard window */ @REPLY - int owner; /* is the process already the owner? */ + user_handle_t owner; /* current clipboard owner */ @END @@ -3204,15 +3204,20 @@ enum caret_state unsigned int format; /* clipboard format of the data */ unsigned int lcid; /* locale id to use for synthesizing text formats */ VARARG(data,bytes); /* data contents */ +@REPLY + unsigned int seqno; /* sequence number for the set data */ @END /* Fetch a data format from the clipboard */ @REQ(get_clipboard_data) unsigned int format; /* clipboard format of the data */ + int cached; /* do we already have it in the client-side cache? */ + unsigned int seqno; /* sequence number for the data in the cache */ @REPLY unsigned int from; /* for synthesized data, format to generate it from */ user_handle_t owner; /* clipboard owner for delayed-rendered formats */ + unsigned int seqno; /* sequence number for the originally set data */ data_size_t total; /* total data size */ VARARG(data,bytes); /* data contents */ @END diff --git a/server/request.h b/server/request.h index 3c6e2981334..f3592de6a5c 100644 --- a/server/request.h +++ b/server/request.h @@ -2053,11 +2053,16 @@ C_ASSERT( sizeof(struct empty_clipboard_request) == 16 ); C_ASSERT( FIELD_OFFSET(struct set_clipboard_data_request, format) == 12 ); C_ASSERT( FIELD_OFFSET(struct set_clipboard_data_request, lcid) == 16 ); C_ASSERT( sizeof(struct set_clipboard_data_request) == 24 ); +C_ASSERT( FIELD_OFFSET(struct set_clipboard_data_reply, seqno) == 8 ); +C_ASSERT( sizeof(struct set_clipboard_data_reply) == 16 ); C_ASSERT( FIELD_OFFSET(struct get_clipboard_data_request, format) == 12 ); -C_ASSERT( sizeof(struct get_clipboard_data_request) == 16 ); +C_ASSERT( FIELD_OFFSET(struct get_clipboard_data_request, cached) == 16 ); +C_ASSERT( FIELD_OFFSET(struct get_clipboard_data_request, seqno) == 20 ); +C_ASSERT( sizeof(struct get_clipboard_data_request) == 24 ); C_ASSERT( FIELD_OFFSET(struct get_clipboard_data_reply, from) == 8 ); C_ASSERT( FIELD_OFFSET(struct get_clipboard_data_reply, owner) == 12 ); -C_ASSERT( FIELD_OFFSET(struct get_clipboard_data_reply, total) == 16 ); +C_ASSERT( FIELD_OFFSET(struct get_clipboard_data_reply, seqno) == 16 ); +C_ASSERT( FIELD_OFFSET(struct get_clipboard_data_reply, total) == 20 ); C_ASSERT( sizeof(struct get_clipboard_data_reply) == 24 ); C_ASSERT( FIELD_OFFSET(struct get_clipboard_formats_request, format) == 12 ); C_ASSERT( sizeof(struct get_clipboard_formats_request) == 16 ); diff --git a/server/trace.c b/server/trace.c index ddc6cc7223a..791641d39c1 100644 --- a/server/trace.c +++ b/server/trace.c @@ -3741,7 +3741,7 @@ static void dump_open_clipboard_request( const struct open_clipboard_request *re static void dump_open_clipboard_reply( const struct open_clipboard_reply *req ) { - fprintf( stderr, " owner=%d", req->owner ); + fprintf( stderr, " owner=%08x", req->owner ); } static void dump_close_clipboard_request( const struct close_clipboard_request *req ) @@ -3780,15 +3780,23 @@ static void dump_set_clipboard_data_request( const struct set_clipboard_data_req dump_varargs_bytes( ", data=", cur_size ); } +static void dump_set_clipboard_data_reply( const struct set_clipboard_data_reply *req ) +{ + fprintf( stderr, " seqno=%08x", req->seqno ); +} + static void dump_get_clipboard_data_request( const struct get_clipboard_data_request *req ) { fprintf( stderr, " format=%08x", req->format ); + fprintf( stderr, ", cached=%d", req->cached ); + fprintf( stderr, ", seqno=%08x", req->seqno ); } static void dump_get_clipboard_data_reply( const struct get_clipboard_data_reply *req ) { fprintf( stderr, " from=%08x", req->from ); fprintf( stderr, ", owner=%08x", req->owner ); + fprintf( stderr, ", seqno=%08x", req->seqno ); fprintf( stderr, ", total=%u", req->total ); dump_varargs_bytes( ", data=", cur_size ); } @@ -4973,7 +4981,7 @@ static const dump_func reply_dumpers[REQ_NB_REQUESTS] = { (dump_func)dump_close_clipboard_reply, (dump_func)dump_set_clipboard_info_reply, NULL, - NULL, + (dump_func)dump_set_clipboard_data_reply, (dump_func)dump_get_clipboard_data_reply, (dump_func)dump_get_clipboard_formats_reply, (dump_func)dump_enum_clipboard_formats_reply, -- 2.11.4.GIT