Send message for WSAAsyncSelect sockets directly from the server,

instead of using the service thread. Finished separation of ws2_32.
This commit is contained in:
Alexandre Julliard 2002-03-23 20:43:52 +00:00
parent 0307f6dc7b
commit 81f2a7307b
10 changed files with 185 additions and 260 deletions

View File

@ -5,7 +5,9 @@ SRCDIR = @srcdir@
VPATH = @srcdir@
MODULE = ws2_32.dll
ALTNAMES = winsock.dll
IMPORTS = user32.dll kernel32.dll ntdll.dll
LDDLLFLAGS = @LDDLLFLAGS@
SYMBOLFILE = $(MODULE).tmp.o
C_SRCS = \
async.c \

View File

@ -104,9 +104,7 @@
#include "wsipx.h"
#include "wine/winsock16.h"
#include "winnt.h"
#include "services.h"
#include "wine/server.h"
#include "file.h"
#include "wine/debug.h"
@ -140,14 +138,10 @@ typedef struct /* WSAAsyncSelect() control struct */
#define WS_MAX_SOCKETS_PER_PROCESS 128 /* reasonable guess */
#define WS_MAX_UDP_DATAGRAM 1024
#define WS_ACCEPT_QUEUE 6
#define PROCFS_NETDEV_FILE "/proc/net/dev" /* Points to the file in the /proc fs
that lists the network devices.
Do we need an #ifdef LINUX for this? */
static volatile HANDLE accept_old[WS_ACCEPT_QUEUE], accept_new[WS_ACCEPT_QUEUE];
static void *he_buffer; /* typecast for Win16/32 ws_hostent */
static SEGPTR he_buffer_seg;
static void *se_buffer; /* typecast for Win16/32 ws_servent */
@ -224,23 +218,6 @@ static int _px_tcp_ops[] = {
*/
static int opentype = 0;
/* Permutation of 0..FD_MAX_EVENTS - 1 representing the order in which we post
* messages if there are multiple events. Used in WINSOCK_DoAsyncEvent. The
* problem is if there is both a FD_CONNECT event and, say, an FD_READ event
* available on the same socket, we want to notify the app of the connect event
* first. Otherwise it may discard the read event because it thinks it hasn't
* connected yet.
*/
static const int event_bitorder[FD_MAX_EVENTS] = {
FD_CONNECT_BIT,
FD_ACCEPT_BIT,
FD_OOB_BIT,
FD_WRITE_BIT,
FD_READ_BIT,
FD_CLOSE_BIT,
6, 7, 8, 9 /* leftovers */
};
/* set last error code from NT status without mapping WSA errors */
inline static unsigned int set_error( unsigned int err )
{
@ -255,11 +232,11 @@ inline static unsigned int set_error( unsigned int err )
static char* check_buffer(int size);
static int _get_sock_fd(SOCKET s)
inline static int _get_sock_fd(SOCKET s)
{
int fd = FILE_GetUnixHandle( s, GENERIC_READ );
if (fd == -1)
FIXME("handle %d is not a socket (GLE %ld)\n",s,GetLastError());
int fd;
if (set_error( wine_server_handle_to_fd( s, GENERIC_READ, &fd, NULL, NULL ) )) return -1;
return fd;
}
@ -284,7 +261,6 @@ static int _is_blocking(SOCKET s)
{
req->handle = s;
req->service = FALSE;
req->s_event = 0;
req->c_event = 0;
wine_server_call( req );
ret = (reply->state & FD_WINE_NONBLOCKING) == 0;
@ -300,7 +276,6 @@ static unsigned int _get_sock_mask(SOCKET s)
{
req->handle = s;
req->service = FALSE;
req->s_event = 0;
req->c_event = 0;
wine_server_call( req );
ret = reply->mask;
@ -324,7 +299,6 @@ static int _get_sock_error(SOCKET s, unsigned int bit)
{
req->handle = s;
req->service = FALSE;
req->s_event = 0;
req->c_event = 0;
wine_server_set_reply( req, events, sizeof(events) );
wine_server_call( req );
@ -790,20 +764,6 @@ static struct ws_protoent* check_buffer_pe(int size)
#define SUPPORTED_PF(pf) ((pf)==WS_AF_INET)
#endif
static void ws2_async_accept(SOCKET s, SOCKET as)
{
int q;
/* queue socket for WSAAsyncSelect */
for (q=0; q<WS_ACCEPT_QUEUE; q++)
if (InterlockedCompareExchange((LONG *)&accept_old[q], s, 0) == 0)
break;
if (q<WS_ACCEPT_QUEUE)
accept_new[q] = as;
else
ERR("accept queue too small\n");
/* now signal our AsyncSelect handler */
_enable_event(s, FD_WINE_SERVEVENT, 0, 0);
}
/**********************************************************************/
@ -965,10 +925,7 @@ SOCKET WINAPI WS_accept(SOCKET s, struct WS_sockaddr *addr,
SERVER_END_REQ;
if (as)
{
unsigned omask = _get_sock_mask( s );
WS_getpeername(as, addr, addrlen32);
if (omask & FD_WINE_SERVEVENT)
ws2_async_accept(s, as);
return as;
}
}
@ -2727,7 +2684,6 @@ int WINAPI WSAEnumNetworkEvents(SOCKET s, WSAEVENT hEvent, LPWSANETWORKEVENTS lp
{
req->handle = s;
req->service = TRUE;
req->s_event = 0;
req->c_event = hEvent;
wine_server_set_reply( req, lpEvent->iErrorCode, sizeof(lpEvent->iErrorCode) );
if (!(ret = wine_server_call(req))) lpEvent->lNetworkEvents = reply->pmask & reply->mask;
@ -2752,6 +2708,8 @@ int WINAPI WSAEventSelect(SOCKET s, WSAEVENT hEvent, LONG lEvent)
req->handle = s;
req->mask = lEvent;
req->event = hEvent;
req->window = 0;
req->msg = 0;
ret = wine_server_call( req );
}
SERVER_END_REQ;
@ -2760,117 +2718,27 @@ int WINAPI WSAEventSelect(SOCKET s, WSAEVENT hEvent, LONG lEvent)
return SOCKET_ERROR;
}
/***********************************************************************
* WINSOCK_DoAsyncSelect
*/
VOID CALLBACK WINSOCK_DoAsyncEvent( ULONG_PTR ptr )
{
ws_select_info *info = (ws_select_info*)ptr;
unsigned int i, j, pmask, orphan = FALSE;
int errors[FD_MAX_EVENTS];
TRACE("socket %08x, event %08x\n", info->sock, info->event);
SetLastError(0);
SERVER_START_REQ( get_socket_event )
{
req->handle = info->sock;
req->service = TRUE;
req->s_event = info->event; /* <== avoid race conditions */
req->c_event = info->event;
wine_server_set_reply( req, errors, sizeof(errors) );
set_error( wine_server_call(req) );
pmask = reply->pmask;
}
SERVER_END_REQ;
if ( (GetLastError() == WSAENOTSOCK) || (GetLastError() == WSAEINVAL) )
{
/* orphaned event (socket closed or something) */
pmask = FD_WINE_SERVEVENT;
orphan = TRUE;
}
/* check for accepted sockets that needs to inherit WSAAsyncSelect */
if (pmask & FD_WINE_SERVEVENT) {
int q;
for (q=0; q<WS_ACCEPT_QUEUE; q++)
if (accept_old[q] == info->sock) {
/* there's only one service thread per process, no lock necessary */
HANDLE as = accept_new[q];
if (as) {
accept_new[q] = 0;
accept_old[q] = 0;
WSAAsyncSelect(as, info->hWnd, info->uMsg, info->lEvent);
}
}
pmask &= ~FD_WINE_SERVEVENT;
}
/* dispatch network events, but use the order in the event_bitorder
* array.
*/
for (i=0; i<FD_MAX_EVENTS; i++) {
j = event_bitorder[i];
if (pmask & (1<<j)) {
TRACE("post: event bit %d, error %d\n", j, errors[j]);
PostMessageA(info->hWnd, info->uMsg, info->sock,
WSAMAKESELECTREPLY(1<<j, errors[j]));
}
}
/* cleanup */
if (orphan)
{
TRACE("orphaned event, self-destructing\n");
/* SERVICE_Delete closes the event object */
SERVICE_Delete( info->service );
WS_FREE(info);
}
}
/***********************************************************************
* WSAAsyncSelect (WS2_32.101)
*/
INT WINAPI WSAAsyncSelect(SOCKET s, HWND hWnd, UINT uMsg, LONG lEvent)
{
int fd = _get_sock_fd(s);
int ret;
TRACE("%04x, hWnd %04x, uMsg %08x, event %08x\n",
(SOCKET16)s, (HWND16)hWnd, uMsg, (unsigned)lEvent );
if (fd != -1)
{
close(fd);
if( lEvent )
{
ws_select_info *info = (ws_select_info*)WS_ALLOC(sizeof(ws_select_info));
if( info )
{
HANDLE hObj = CreateEventA( NULL, TRUE, FALSE, NULL );
INT err;
TRACE("%x, hWnd %x, uMsg %08x, event %08lx\n", s, hWnd, uMsg, lEvent );
info->sock = s;
info->event = hObj;
info->hWnd = hWnd;
info->uMsg = uMsg;
info->lEvent = lEvent;
info->service = SERVICE_AddObject( hObj, WINSOCK_DoAsyncEvent, (ULONG_PTR)info );
err = WSAEventSelect( s, hObj, lEvent | FD_WINE_SERVEVENT );
if (err) {
/* SERVICE_Delete closes the event object */
SERVICE_Delete( info->service );
WS_FREE(info);
return err;
}
return 0; /* success */
}
else SetLastError(WSAENOBUFS);
}
else
SERVER_START_REQ( set_socket_event )
{
WSAEventSelect(s, 0, 0);
return 0;
req->handle = s;
req->mask = lEvent;
req->event = 0;
req->window = hWnd;
req->msg = uMsg;
ret = wine_server_call( req );
}
}
else SetLastError(WSAEINVAL);
SERVER_END_REQ;
if (!ret) return 0;
SetLastError(WSAEINVAL);
return SOCKET_ERROR;
}

View File

@ -910,6 +910,8 @@ struct set_socket_event_request
handle_t handle;
unsigned int mask;
handle_t event;
user_handle_t window;
unsigned int msg;
};
struct set_socket_event_reply
{
@ -923,7 +925,6 @@ struct get_socket_event_request
struct request_header __header;
handle_t handle;
int service;
handle_t s_event;
handle_t c_event;
};
struct get_socket_event_reply
@ -3127,6 +3128,6 @@ union generic_reply
struct get_window_properties_reply get_window_properties_reply;
};
#define SERVER_PROTOCOL_VERSION 74
#define SERVER_PROTOCOL_VERSION 75
#endif /* __WINE_WINE_SERVER_PROTOCOL_H */

View File

@ -733,7 +733,6 @@ typedef struct WS(WSAData)
#define FD_WINE_NONBLOCKING 0x20000000
#define FD_WINE_CONNECTED 0x40000000
#define FD_WINE_RAW 0x80000000
#define FD_WINE_SERVEVENT 0x01000000
#define FD_WINE_INTERNAL 0xFFFF0000
#endif

View File

@ -680,6 +680,8 @@ enum fd_type
handle_t handle; /* handle to the socket */
unsigned int mask; /* event mask */
handle_t event; /* event object */
user_handle_t window; /* window to send the message to */
unsigned int msg; /* message to send */
@END
@ -687,7 +689,6 @@ enum fd_type
@REQ(get_socket_event)
handle_t handle; /* handle to the socket */
int service; /* clear pending? */
handle_t s_event; /* "expected" event object */
handle_t c_event; /* event to clear */
@REPLY
unsigned int mask; /* event mask */

View File

@ -684,6 +684,37 @@ void queue_cleanup_window( struct thread *thread, user_handle_t win )
}
}
/* post a message to a window; used by socket handling */
void post_message( user_handle_t win, unsigned int message,
unsigned int wparam, unsigned int lparam )
{
struct message *msg;
struct thread *thread = get_window_thread( win );
if (!thread) return;
if (thread->queue && (msg = mem_alloc( sizeof(*msg) )))
{
msg->type = MSG_POSTED;
msg->win = get_user_full_handle( win );
msg->msg = message;
msg->wparam = wparam;
msg->lparam = lparam;
msg->time = get_tick_count();
msg->x = 0;
msg->y = 0;
msg->info = 0;
msg->result = NULL;
msg->data = NULL;
msg->data_size = 0;
append_message( &thread->queue->msg_list[POST_MESSAGE], msg );
set_queue_bits( thread->queue, QS_POSTMESSAGE );
}
release_object( thread );
}
/* get the message queue of the current thread */
DECL_HANDLER(get_msg_queue)
{

View File

@ -50,6 +50,7 @@
#include "handle.h"
#include "thread.h"
#include "request.h"
#include "user.h"
#include "async.h"
/* To avoid conflicts with the Unix socket headers. Plus we only need a few
@ -67,6 +68,9 @@ struct sock
unsigned int pmask; /* pending events */
unsigned int flags; /* socket flags */
struct event *event; /* event object */
user_handle_t window; /* window to send the message to */
unsigned int message; /* message to send */
unsigned int wparam; /* message wparam (socket handle) */
int errors[FD_MAX_EVENTS]; /* event errors */
struct async_queue read_q; /* Queue for asynchronous reads */
struct async_queue write_q; /* Queue for asynchronous writes */
@ -99,10 +103,29 @@ static const struct object_ops sock_ops =
sock_destroy /* destroy */
};
/* Permutation of 0..FD_MAX_EVENTS - 1 representing the order in which
* we post messages if there are multiple events. Used to send
* messages. The problem is if there is both a FD_CONNECT event and,
* say, an FD_READ event available on the same socket, we want to
* notify the app of the connect event first. Otherwise it may
* discard the read event because it thinks it hasn't connected yet.
*/
static const int event_bitorder[FD_MAX_EVENTS] =
{
FD_CONNECT_BIT,
FD_ACCEPT_BIT,
FD_OOB_BIT,
FD_WRITE_BIT,
FD_READ_BIT,
FD_CLOSE_BIT,
6, 7, 8, 9 /* leftovers */
};
static void sock_reselect( struct sock *sock )
{
int ev = sock_get_poll_events( &sock->obj );
struct pollfd pfd;
if (debug_level)
fprintf(stderr,"sock_reselect(%d): new mask %x\n", sock->obj.fd, ev);
@ -115,14 +138,36 @@ static void sock_reselect( struct sock *sock )
}
/* update condition mask */
set_select_events( &sock->obj, ev );
}
/* check whether condition is satisfied already */
pfd.fd = sock->obj.fd;
pfd.events = ev;
pfd.revents = 0;
poll( &pfd, 1, 0 );
if (pfd.revents)
sock_poll_event( &sock->obj, pfd.revents);
/* wake anybody waiting on the socket event or send the associated message */
static void sock_wake_up( struct sock *sock )
{
unsigned int events = sock->pmask & sock->mask;
int i;
if (!events) return;
if (sock->event)
{
if (debug_level) fprintf(stderr, "signalling events %x ptr %p\n", events, sock->event );
set_event( sock->event );
}
if (sock->window)
{
if (debug_level) fprintf(stderr, "signalling events %x win %x\n", events, sock->window );
for (i = 0; i < FD_MAX_EVENTS; i++)
{
int event = event_bitorder[i];
if (sock->pmask & (1 << event))
{
unsigned int lparam = (1 << event) | (sock->errors[event] << 16);
post_message( sock->window, sock->message, sock->wparam, lparam );
}
}
sock->pmask = 0;
sock_reselect( sock );
}
}
inline static int sock_error(int s)
@ -137,7 +182,7 @@ inline static int sock_error(int s)
static void sock_poll_event( struct object *obj, int event )
{
struct sock *sock = (struct sock *)obj;
unsigned int emask;
assert( sock->obj.ops == &sock_ops );
if (debug_level)
fprintf(stderr, "socket %d select event: %x\n", sock->obj.fd, event);
@ -233,14 +278,9 @@ static void sock_poll_event( struct object *obj, int event )
set_select_events( &sock->obj, -1 );
else
sock_reselect( sock );
/* wake up anyone waiting for whatever just happened */
emask = sock->pmask & sock->mask;
if (debug_level && emask)
fprintf(stderr, "socket %d pending events: %x\n", sock->obj.fd, emask);
if (emask && sock->event) {
if (debug_level) fprintf(stderr, "signalling event ptr %p\n", sock->event);
set_event(sock->event);
}
if (sock->pmask & sock->mask) sock_wake_up( sock );
/* if anyone is stupid enough to wait on the socket object itself,
* maybe we should wake them up too, just in case? */
@ -326,17 +366,7 @@ static void sock_destroy( struct object *obj )
destroy_async_queue ( &sock->read_q );
destroy_async_queue ( &sock->write_q );
}
if (sock->event)
{
/* if the service thread was waiting for the event object,
* we should now signal it, to let the service thread
* object detect that it is now orphaned... */
if (sock->mask & FD_WINE_SERVEVENT)
set_event( sock->event );
/* we're through with it */
release_object( sock->event );
}
if (sock->event) release_object( sock->event );
}
/* create a new and unconnected socket */
@ -361,6 +391,9 @@ static struct object *create_socket( int family, int type, int protocol, unsigne
sock->pmask = 0;
sock->flags = flags;
sock->event = NULL;
sock->window = 0;
sock->message = 0;
sock->wparam = 0;
sock_reselect( sock );
clear_error();
if (sock->flags & WSA_FLAG_OVERLAPPED)
@ -372,7 +405,7 @@ static struct object *create_socket( int family, int type, int protocol, unsigne
}
/* accept a socket (creates a new fd) */
static struct object *accept_socket( handle_t handle )
static struct sock *accept_socket( handle_t handle )
{
struct sock *acceptsock;
struct sock *sock;
@ -411,8 +444,10 @@ static struct object *accept_socket( handle_t handle )
acceptsock->hmask = 0;
acceptsock->pmask = 0;
acceptsock->event = NULL;
if (sock->event && !(sock->mask & FD_WINE_SERVEVENT))
acceptsock->event = (struct event *)grab_object( sock->event );
acceptsock->window = sock->window;
acceptsock->message = sock->message;
acceptsock->wparam = 0;
if (sock->event) acceptsock->event = (struct event *)grab_object( sock->event );
acceptsock->flags = sock->flags;
if ( acceptsock->flags & WSA_FLAG_OVERLAPPED )
{
@ -420,13 +455,12 @@ static struct object *accept_socket( handle_t handle )
init_async_queue ( &acceptsock->write_q );
}
sock_reselect( acceptsock );
clear_error();
sock->pmask &= ~FD_ACCEPT;
sock->hmask &= ~FD_ACCEPT;
sock_reselect( sock );
release_object( sock );
return &acceptsock->obj;
return acceptsock;
}
/* set the last error depending on errno */
@ -515,13 +549,15 @@ DECL_HANDLER(create_socket)
/* accept a socket */
DECL_HANDLER(accept_socket)
{
struct object *obj;
struct sock *sock;
reply->handle = 0;
if ((obj = accept_socket( req->lhandle )) != NULL)
if ((sock = accept_socket( req->lhandle )) != NULL)
{
reply->handle = alloc_handle( current->process, obj, req->access, req->inherit );
release_object( obj );
reply->handle = alloc_handle( current->process, &sock->obj, req->access, req->inherit );
sock->wparam = reply->handle; /* wparam for message is the socket handle */
sock_reselect( sock );
release_object( &sock->obj );
}
}
@ -529,16 +565,19 @@ DECL_HANDLER(accept_socket)
DECL_HANDLER(set_socket_event)
{
struct sock *sock;
struct event *oevent;
unsigned int omask;
struct event *old_event;
sock=(struct sock*)get_handle_obj(current->process,req->handle,GENERIC_READ|GENERIC_WRITE|SYNCHRONIZE,&sock_ops);
if (!sock)
if (!(sock = (struct sock*)get_handle_obj( current->process, req->handle,
GENERIC_READ|GENERIC_WRITE|SYNCHRONIZE, &sock_ops)))
return;
oevent = sock->event;
omask = sock->mask;
old_event = sock->event;
sock->mask = req->mask;
sock->event = get_event_obj( current->process, req->event, EVENT_MODIFY_STATE );
sock->event = NULL;
sock->window = req->window;
sock->message = req->msg;
sock->wparam = req->handle; /* wparam is the socket handle */
if (req->event) sock->event = get_event_obj( current->process, req->event, EVENT_MODIFY_STATE );
if (debug_level && sock->event) fprintf(stderr, "event ptr: %p\n", sock->event);
sock_reselect( sock );
if (sock->mask)
@ -548,19 +587,9 @@ DECL_HANDLER(set_socket_event)
it is possible that FD_CONNECT or FD_ACCEPT network events has happened
before a WSAEventSelect() was done on it.
(when dealing with Asynchronous socket) */
if (sock->pmask & sock->mask)
set_event(sock->event);
if (sock->pmask & sock->mask) sock_wake_up( sock );
if (oevent)
{
if ((oevent != sock->event) && (omask & FD_WINE_SERVEVENT))
/* if the service thread was waiting for the old event object,
* we should now signal it, to let the service thread
* object detect that it is now orphaned... */
set_event( oevent );
/* we're through with it */
release_object( oevent );
}
if (old_event) release_object( old_event ); /* we're through with it */
release_object( &sock->obj );
}
@ -584,27 +613,20 @@ DECL_HANDLER(get_socket_event)
set_reply_data( sock->errors, min( get_reply_max_size(), sizeof(sock->errors) ));
if (req->service)
{
handle_t s_event = req->s_event;
if (s_event)
{
struct event *sevent = get_event_obj(current->process, req->s_event, 0);
if (sevent == sock->event) s_event = 0;
release_object( sevent );
}
if (!s_event)
{
if (req->c_event)
{
struct event *cevent = get_event_obj(current->process, req->c_event, EVENT_MODIFY_STATE);
struct event *cevent = get_event_obj( current->process, req->c_event,
EVENT_MODIFY_STATE );
if (cevent)
{
reset_event( cevent );
release_object( cevent );
}
}
sock->pmask = 0;
sock_reselect( sock );
}
else set_error(WSAEINVAL);
}
release_object( &sock->obj );
}
@ -613,24 +635,14 @@ DECL_HANDLER(enable_socket_event)
{
struct sock *sock;
sock=(struct sock*)get_handle_obj(current->process,req->handle,GENERIC_READ|GENERIC_WRITE|SYNCHRONIZE,&sock_ops);
if (!sock)
if (!(sock = (struct sock*)get_handle_obj( current->process, req->handle,
GENERIC_READ|GENERIC_WRITE|SYNCHRONIZE, &sock_ops)))
return;
sock->pmask &= ~req->mask; /* is this safe? */
sock->hmask &= ~req->mask;
sock->state |= req->sstate;
sock->state &= ~req->cstate;
sock_reselect( sock );
/* service trigger */
if (req->mask & FD_WINE_SERVEVENT)
{
sock->pmask |= FD_WINE_SERVEVENT;
if (sock->event) {
if (debug_level) fprintf(stderr, "signalling service event ptr %p\n", sock->event);
set_event(sock->event);
}
}
release_object( &sock->obj );
}

View File

@ -856,14 +856,15 @@ static void dump_set_socket_event_request( const struct set_socket_event_request
{
fprintf( stderr, " handle=%d,", req->handle );
fprintf( stderr, " mask=%08x,", req->mask );
fprintf( stderr, " event=%d", req->event );
fprintf( stderr, " event=%d,", req->event );
fprintf( stderr, " window=%08x,", req->window );
fprintf( stderr, " msg=%08x", req->msg );
}
static void dump_get_socket_event_request( const struct get_socket_event_request *req )
{
fprintf( stderr, " handle=%d,", req->handle );
fprintf( stderr, " service=%d,", req->service );
fprintf( stderr, " s_event=%d,", req->s_event );
fprintf( stderr, " c_event=%d", req->c_event );
}

View File

@ -45,11 +45,14 @@ extern void *next_user_handle( user_handle_t *handle, enum user_object type );
extern void inc_queue_paint_count( struct thread *thread, int incr );
extern void queue_cleanup_window( struct thread *thread, user_handle_t win );
extern void post_message( user_handle_t win, unsigned int message,
unsigned int wparam, unsigned int lparam );
/* window functions */
extern void destroy_thread_windows( struct thread *thread );
extern int is_child_window( user_handle_t parent, user_handle_t child );
extern struct thread *get_window_thread( user_handle_t handle );
extern user_handle_t find_window_to_repaint( user_handle_t parent, struct thread *thread );
#endif /* __WINE_SERVER_USER_H */

View File

@ -329,6 +329,13 @@ int is_child_window( user_handle_t parent, user_handle_t child )
return 0;
}
/* return the thread owning a window */
struct thread *get_window_thread( user_handle_t handle )
{
struct window *win = get_user_object( handle, USER_WINDOW );
if (!win || !win->thread) return NULL;
return (struct thread *)grab_object( win->thread );
}
/* find a child of the specified window that needs repainting */
static struct window *find_child_to_repaint( struct window *parent, struct thread *thread )