2 //windows messages like system based on lcore tasks
\r
5 uses pgtypes,sysutils,bsearchtree,strings,syncobjs;
\r
14 hwnd=qword; //window handles are monotonically increasing 64 bit integers,
\r
15 //this should allow for a million windows per second for over half
\r
18 twndproc=function(ahWnd:HWND; auMsg:Integer; awParam:WPARAM; alParam:LPARAM):Integer; stdcall;
\r
23 lpfnwndproc : twndproc;
\r
24 cbclsextra : integer;
\r
25 cbwndextra : integer;
\r
26 hinstance : thinstance;
\r
29 hbrbackground : hbrush;
\r
30 lpszmenuname : pchar;
\r
31 lpszclassname : pchar;
\r
33 PWNDCLASS=^twndclass;
\r
37 tTIMERPROC = procedure (ahwnd:HWND; umsg:integer; idevent:taddrint;dwtime:taddrint);stdcall;
\r
57 THevent=TEventObject;
\r
59 WS_EX_TOOLWINDOW = $80;
\r
60 WS_POPUP = longint($80000000);
\r
65 INFINITE = syncobjs.infinite;
\r
66 function getwindowlongptr(ahwnd:hwnd;nindex:integer) : taddrint;
\r
67 function setwindowlongptr(ahwnd:hwnd;nindex:integer;dwNewLong : taddrint) : taddrint;
\r
68 function DefWindowProc(ahWnd:HWND; auMsg:Integer; awParam:WPARAM; alParam:LPARAM):Integer; stdcall;
\r
69 function RegisterClass(const lpWndClass:TWNDCLASS):ATOM;
\r
70 function CreateWindowEx(dwExStyle:DWORD; lpClassName:LPCSTR; lpWindowName:LPCSTR; dwStyle:DWORD; X:longint;Y:longint; nWidth:longint; nHeight:longint; hWndParent:HWND; hMenu:HMENU;hInstance:HINST; lpParam:LPVOID):HWND;
\r
71 function DestroyWindow(ahWnd:HWND):WINBOOL;
\r
72 function PostMessage(hWnd:HWND; Msg:UINT; wParam:WPARAM; lParam:LPARAM):WINBOOL;
\r
73 function PeekMessage(var lpMsg: TMsg; hWnd: HWND; wMsgFilterMin, wMsgFilterMax, wRemoveMsg: UINT): WINBOOL;
\r
74 function DispatchMessage(const lpMsg: TMsg): Longint;
\r
75 function GetMessage(var lpMsg: TMsg; hWnd: HWND; wMsgFilterMin, wMsgFilterMax: UINT): WINBOOL;
\r
76 function SetEvent(hEvent:THevent):WINBOOL;
\r
77 function CreateEvent(lpEventAttributes:PSECURITYATTRIBUTES; bManualReset:WINBOOL; bInitialState:WINBOOL; lpName:pchar):tHevent;
\r
78 function terminatethread(threadhandle : tthreadid;dummy:integer) : boolean;
\r
79 function waitforsingleevent(event:thevent;timeout:cardinal) : twaitresult;
\r
80 function SetTimer(ahWnd:HWND; nIDEvent:taddrint; uElapse:UINT; lpTimerFunc:tTIMERPROC):UINT;
\r
81 function KillTimer(ahWnd:HWND; uIDEvent:taddrint):WINBOOL;
\r
87 baseunix,unix,lcore;//,safewriteln;
\r
91 tmessageintransit = class
\r
93 next : tmessageintransit;
\r
97 messagequeue : tmessageintransit;
\r
98 messageevent : teventobject;
\r
100 lcorethread : boolean;
\r
101 nexttimer : ttimeval;
\r
102 threadid : integer;
\r
106 extrawindowmemory : pointer;
\r
107 threadid : tthreadid;
\r
108 windowproc : twndproc;
\r
112 structurelock : tcriticalsection;
\r
113 threaddata : thashtable;
\r
114 windowclasses : thashtable;
\r
115 lcorelinkpipesend : integer;
\r
116 lcorelinkpiperecv : tlasio;
\r
117 windows : thashtable;
\r
118 //I would rather things crash immediately
\r
119 //if they use an insufficiant size type
\r
120 //than crash after over four billion
\r
121 //windows have been made ;)
\r
122 nextwindowhandle : qword = $100000000;
\r
123 {$i ltimevalstuff.inc}
\r
125 //findthreaddata should only be called while holding the structurelock
\r
126 function findthreaddata(threadid : integer) : tthreaddata;
\r
128 result := tthreaddata(findtree(@threaddata,inttostr(threadid)));
\r
129 if result = nil then begin
\r
130 result := tthreaddata.create;
\r
131 result.messageevent := teventobject.create(nil,false,false,inttostr(taddrint(result)));
\r
132 result.nexttimer := tv_invalidtimebig;
\r
133 result.threadid := threadid;
\r
134 addtree(@threaddata,inttostr(threadid),result);
\r
138 //deletethreaddataifunused should only be called while holding the structurelock
\r
139 procedure deletethreaddataifunused(athreaddata : tthreaddata);
\r
141 //writeln('in deletethreaddataifunused');
\r
142 if (athreaddata <> nil) then if (athreaddata.waiting=false) and (athreaddata.messagequeue=nil) and (athreaddata.lcorethread=false) and (athreaddata.nexttimer.tv_sec=tv_invalidtimebig.tv_sec) and (athreaddata.nexttimer.tv_usec=tv_invalidtimebig.tv_usec) then begin
\r
143 //writeln('threaddata is unused, freeing messageevent');
\r
144 athreaddata.messageevent.free;
\r
145 //writeln('freeing thread data object');
\r
147 //writeln('deleting thread data object from hashtable');
\r
148 deltree(@threaddata,inttostr(athreaddata.threadid));
\r
149 //writeln('finished deleting thread data');
\r
151 //writeln('thread data is not unused');
\r
155 function getwindowlongptr(ahwnd:hwnd;nindex:integer) : taddrint;
\r
159 structurelock.acquire;
\r
161 window := findtree(@windows,inttostr(ahwnd));
\r
162 if window <> nil then begin
\r
163 result := paddrint(taddrint(window.extrawindowmemory)+nindex)^;
\r
168 structurelock.release;
\r
172 function setwindowlongptr(ahwnd:hwnd;nindex:integer;dwNewLong : taddrint) : taddrint;
\r
176 structurelock.acquire;
\r
178 window := findtree(@windows,inttostr(ahwnd));
\r
179 if window <> nil then begin
\r
180 result := paddrint(taddrint(window.extrawindowmemory)+nindex)^;
\r
181 paddrint(taddrint(window.extrawindowmemory)+nindex)^ := dwnewlong;
\r
186 structurelock.release;
\r
192 function DefWindowProc(ahWnd:HWND; auMsg:Integer; awParam:WPARAM; alParam:LPARAM):Integer; stdcall;
\r
197 function strdup(s:pchar) : pchar;
\r
199 //swriteln('in strdup, about to allocate memory');
\r
200 result := getmem(strlen(s)+1);
\r
201 //swriteln('about to copy string');
\r
203 //swriteln('leaving strdup');
\r
206 function RegisterClass(const lpWndClass:TWNDCLASS):ATOM;
\r
208 storedwindowclass:pwndclass;
\r
210 structurelock.acquire;
\r
212 //swriteln('in registerclass, about to check for duplicate window class');
\r
213 storedwindowclass := findtree(@windowclasses, lpwndclass.lpszclassname);
\r
214 if storedwindowclass <> nil then begin
\r
216 if comparebyte(storedwindowclass^,lpwndclass,sizeof(twndclass)-sizeof(pchar)-sizeof(pchar)) <> 0 then begin
\r
217 //swriteln('duplicate window class registered with different settings');
\r
218 raise exception.create('duplicate window class registered with different settings');
\r
220 //swriteln('duplicate window class registered with same settings, tollerated');
\r
223 //swriteln('about to allocate memory for new windowclass');
\r
224 storedwindowclass := getmem(sizeof(twndclass));
\r
225 //swriteln('about to copy windowclass from parameter');
\r
226 move(lpwndclass,storedwindowclass^,sizeof(twndclass));
\r
227 //swriteln('about to copy strings');
\r
228 if lpwndclass.lpszmenuname <> nil then storedwindowclass.lpszmenuname := strdup(lpwndclass.lpszmenuname);
\r
229 if lpwndclass.lpszclassname <> nil then storedwindowclass.lpszclassname := strdup(lpwndclass.lpszclassname);
\r
230 //swriteln('about to add result to list of windowclasses');
\r
231 addtree(@windowclasses,lpwndclass.lpszclassname,storedwindowclass);
\r
233 //swriteln('about to return result');
\r
234 result := storedwindowclass;
\r
235 //swriteln('leaving registerclass');
\r
237 structurelock.release;
\r
241 function CreateWindowEx(dwExStyle:DWORD; lpClassName:LPCSTR; lpWindowName:LPCSTR; dwStyle:DWORD; X:longint;Y:longint; nWidth:longint; nHeight:longint; hWndParent:HWND; hMenu:HMENU;hInstance:HINST; lpParam:LPVOID):HWND;
\r
243 wndclass : pwndclass;
\r
244 tm : tthreadmanager;
\r
247 structurelock.acquire;
\r
249 window := twindow.create;
\r
250 window.hwnd := nextwindowhandle;
\r
251 result := window.hwnd;
\r
252 nextwindowhandle := nextwindowhandle + 1;
\r
253 addtree(@windows,inttostr(window.hwnd),window);
\r
254 wndclass := findtree(@windowclasses,lpclassname);
\r
255 window.extrawindowmemory := getmem(wndclass.cbwndextra);
\r
257 getthreadmanager(tm);
\r
258 window.threadid := tm.GetCurrentThreadId;
\r
259 window.windowproc := wndclass.lpfnwndproc;
\r
261 structurelock.release;
\r
264 function DestroyWindow(ahWnd:HWND):WINBOOL;
\r
267 windowthreaddata : tthreaddata;
\r
268 currentmessage : tmessageintransit;
\r
269 prevmessage : tmessageintransit;
\r
271 //writeln('started to destroy window');
\r
272 structurelock.acquire;
\r
274 window := twindow(findtree(@windows,inttostr(ahwnd)));
\r
275 if window <> nil then begin
\r
276 freemem(window.extrawindowmemory);
\r
277 //writeln('aboute to delete window from windows structure');
\r
278 deltree(@windows,inttostr(ahwnd));
\r
279 //writeln('deleted window from windows structure');
\r
280 windowthreaddata := tthreaddata(findtree(@threaddata,inttostr(window.threadid)));
\r
282 if windowthreaddata <> nil then begin
\r
283 //writeln('found thread data scanning for messages to clean up');
\r
284 currentmessage := windowthreaddata.messagequeue;
\r
285 prevmessage := nil;
\r
286 while currentmessage <> nil do begin
\r
287 while (currentmessage <> nil) and (currentmessage.msg.hwnd = ahwnd) do begin
\r
288 if prevmessage = nil then begin
\r
289 windowthreaddata.messagequeue := currentmessage.next;
\r
291 prevmessage.next := currentmessage.next;
\r
293 currentmessage.free;
\r
294 if prevmessage = nil then begin
\r
295 currentmessage := windowthreaddata.messagequeue;
\r
297 currentmessage := prevmessage.next;
\r
300 if currentmessage <> nil then begin
\r
301 prevmessage := currentmessage;
\r
302 currentmessage := currentmessage.next;
\r
305 //writeln('deleting thread data structure if it is unused');
\r
306 deletethreaddataifunused(windowthreaddata);
\r
308 //writeln('there is no thread data to search for messages to cleanup');
\r
310 //writeln('freeing window');
\r
317 structurelock.release;
\r
319 //writeln('window destroyed');
\r
324 function PostMessage(hWnd:HWND; Msg:UINT; wParam:WPARAM; lParam:LPARAM):WINBOOL;
\r
326 threaddata : tthreaddata;
\r
327 message : tmessageintransit;
\r
328 messagequeueend : tmessageintransit;
\r
331 structurelock.acquire;
\r
333 window := findtree(@windows,inttostr(hwnd));
\r
334 if window <> nil then begin
\r
335 threaddata := findthreaddata(window.threadid);
\r
336 message := tmessageintransit.create;
\r
337 message.msg.hwnd := hwnd;
\r
338 message.msg.message := msg;
\r
339 message.msg.wparam := wparam;
\r
340 message.msg.lparam := lparam;
\r
341 if threaddata.lcorethread then begin
\r
342 //swriteln('posting message to lcore thread');
\r
343 fdwrite(lcorelinkpipesend,message,sizeof(message));
\r
345 //writeln('posting message to non lcore thread');
\r
346 if threaddata.messagequeue = nil then begin
\r
347 threaddata.messagequeue := message;
\r
349 messagequeueend := threaddata.messagequeue;
\r
350 while messagequeueend.next <> nil do begin
\r
351 messagequeueend := messagequeueend.next;
\r
353 messagequeueend.next := message;
\r
356 //writeln('message added to queue');
\r
357 if threaddata.waiting then threaddata.messageevent.setevent;
\r
364 structurelock.release;
\r
369 function gettickcount : dword;
\r
375 result64 := (tv.tv_sec*1000)+(tv.tv_usec div 1000);
\r
376 result := result64;
\r
379 function DispatchMessage(const lpMsg: TMsg): Longint;
\r
381 timerproc : ttimerproc;
\r
383 windowproc : twndproc;
\r
385 ////swriteln('in dispatchmessage, msg.hwnd='+inttohex(taddrint(lpmsg.hwnd),16));
\r
386 if (lpmsg.lparam <> 0) and (lpmsg.message = WM_TIMER) then begin
\r
387 timerproc := ttimerproc(lpmsg.lparam);
\r
388 timerproc(lpmsg.hwnd,lpmsg.message,lpmsg.wparam,gettickcount);
\r
391 structurelock.acquire;
\r
393 window := findtree(@windows,inttostr(lpmsg.hwnd));
\r
394 //we have to get the window procedure while the structurelock
\r
395 //is still held as the window could be destroyed from another thread
\r
397 windowproc := window.windowproc;
\r
399 structurelock.release;
\r
401 if window <> nil then begin
\r
402 result := windowproc(lpmsg.hwnd,lpmsg.message,lpmsg.wparam,lpmsg.lparam);
\r
409 procedure processtimers;
\r
413 function GetMessageinternal(var lpMsg: TMsg; hWnd: HWND; wMsgFilterMin, wMsgFilterMax: UINT; wremovemsg : UINT;peek:boolean): WINBOOL;
\r
415 tm : tthreadmanager;
\r
416 threaddata : tthreaddata;
\r
417 message : tmessageintransit;
\r
419 timeouttv : ttimeval;
\r
423 if hwnd <> 0 then raise exception.create('getting messages for an individual window is not supported');
\r
424 if (wmsgfiltermin <> 0) or (wmsgfiltermax <> 0) then raise exception.create('message filtering is not supported');
\r
425 structurelock.acquire;
\r
428 getthreadmanager(tm);
\r
429 threaddata := findthreaddata(tm.GetCurrentThreadId);
\r
430 if threaddata.lcorethread then raise exception.create('get/peek message cannot be used in the lcore thread');
\r
431 message := threaddata.messagequeue;
\r
432 gettimeofday(nowtv);
\r
433 while (not peek) and (message=nil) and (not tv_compare(nowtv,threaddata.nexttimer)) do begin
\r
434 threaddata.waiting := true;
\r
435 structurelock.release;
\r
436 if (threaddata.nexttimer.tv_sec = TV_invalidtimebig.tv_sec) and (threaddata.nexttimer.tv_usec = TV_invalidtimebig.tv_usec) then begin
\r
437 threaddata.messageevent.waitfor(INFINITE);
\r
440 timeouttv := threaddata.nexttimer;
\r
441 timeoutms := (timeouttv.tv_sec * 1000)+(timeouttv.tv_usec div 1000);
\r
442 //i'm assuming the timeout is in milliseconds
\r
443 if (timeoutms > maxlongint) then timeoutms := maxlongint;
\r
444 threaddata.messageevent.waitfor(timeoutms);
\r
447 structurelock.acquire;
\r
448 threaddata.waiting := false;
\r
449 message := threaddata.messagequeue;
\r
450 gettimeofday(nowtv);
\r
452 if (message=nil) and tv_compare(nowtv,threaddata.nexttimer) then begin
\r
455 message := threaddata.messagequeue;
\r
456 if message <> nil then begin
\r
457 lpmsg := message.msg;
\r
458 if wremovemsg=PM_REMOVE then begin
\r
459 threaddata.messagequeue := message.next;
\r
465 deletethreaddataifunused(threaddata);
\r
467 structurelock.release;
\r
471 function GetMessage(var lpMsg: TMsg; hWnd: HWND; wMsgFilterMin, wMsgFilterMax: UINT): WINBOOL;
\r
473 result := getmessageinternal(lpmsg,hwnd,wmsgfiltermin,wmsgfiltermax,PM_REMOVE,false);
\r
476 function PeekMessage(var lpMsg: TMsg; hWnd: HWND; wMsgFilterMin, wMsgFilterMax, wRemoveMsg: UINT): WINBOOL;
\r
478 result := getmessageinternal(lpmsg,hwnd,wmsgfiltermin,wmsgfiltermax,PM_REMOVE,true);
\r
481 function SetEvent(hEvent:THevent):WINBOOL;
\r
487 function CreateEvent(lpEventAttributes:PSECURITYATTRIBUTES; bManualReset:WINBOOL; bInitialState:WINBOOL; lpName:pchar):tHevent;
\r
489 result := teventobject.create(lpeventattributes,bmanualreset,binitialstate,lpname);
\r
492 function terminatethread(threadhandle:tthreadid;dummy : integer) : boolean;
\r
494 tm : tthreadmanager;
\r
496 getthreadmanager(tm);
\r
497 tm.killthread(threadhandle);
\r
501 function waitforsingleevent(event:thevent;timeout:cardinal) : twaitresult;
\r
503 result := event.waitfor(timeout);
\r
506 procedure removefrombuffer(n : integer; var buffer:string);
\r
508 if n=length(buffer) then begin
\r
511 uniquestring(buffer);
\r
512 move(buffer[n+1],buffer[1],length(buffer)-n);
\r
513 setlength(buffer,length(buffer)-n);
\r
519 procedure available(sender:tobject;error:word);
\r
525 procedure tsc.available(sender:tobject;error:word);
\r
527 message : tmessageintransit;
\r
528 messagebytes : array[1..sizeof(tmessageintransit)] of char absolute message;
\r
531 //swriteln('received data on lcorelinkpipe');
\r
532 recvbuf := recvbuf + lcorelinkpiperecv.receivestr;
\r
533 while length(recvbuf) >= sizeof(tmessageintransit) do begin
\r
534 for i := 1 to sizeof(tmessageintransit) do begin
\r
535 messagebytes[i] := recvbuf[i];
\r
537 dispatchmessage(message.msg);
\r
539 removefrombuffer(sizeof(tmessageintransit),recvbuf);
\r
545 tm : tthreadmanager;
\r
546 threaddata : tthreaddata;
\r
547 pipeends : tfildes;
\r
550 structurelock := tcriticalsection.create;
\r
551 getthreadmanager(tm);
\r
552 threaddata := findthreaddata(tm.GetCurrentThreadId);
\r
553 threaddata.lcorethread := true;
\r
555 lcorelinkpipesend := pipeends[1];
\r
556 lcorelinkpiperecv := tlasio.create(nil);
\r
557 lcorelinkpiperecv.dup(pipeends[0]);
\r
558 lcorelinkpiperecv.ondataavailable := sc.available;
\r
563 lcorethreadtimers : thashtable;
\r
565 tltimerformsg = class(tltimer)
\r
569 procedure timer(sender : tobject);
\r
572 procedure tltimerformsg.timer(sender : tobject);
\r
576 ////swriteln('in tltimerformsg.timer');
\r
577 fillchar(msg,sizeof(msg),0);
\r
578 msg.message := WM_TIMER;
\r
582 dispatchmessage(msg);
\r
585 function SetTimer(ahWnd:HWND; nIDEvent:taddrint; uElapse:UINT; lpTimerFunc:tTIMERPROC):UINT;
\r
587 threaddata : tthreaddata;
\r
588 ltimer : tltimerformsg;
\r
589 tm : tthreadmanager;
\r
592 structurelock.acquire;
\r
594 window := findtree(@windows,inttostr(ahwnd));
\r
595 if window= nil then raise exception.create('invalid window');
\r
596 threaddata := findthreaddata(window.threadid);
\r
598 structurelock.release;
\r
600 if threaddata.lcorethread then begin
\r
601 getthreadmanager(tm);
\r
602 if tm.GetCurrentThreadId <> window.threadid then raise exception.create('timers on the lcore thread may only be added and removed from the lcore thread');
\r
603 if ahwnd = 0 then raise exception.create('timers on the lcore thread must be associated with a window handle');
\r
604 if @lptimerfunc <> nil then raise exception.create('seperate timer functions are not supported');
\r
606 //remove preexisting timer with same ID
\r
607 killtimer(ahwnd,nIDEvent);
\r
609 ltimer := tltimerformsg.create(nil);
\r
610 ltimer.interval := uelapse;
\r
611 ltimer.id := nidevent;
\r
612 ltimer.hwnd := ahwnd;
\r
613 ltimer.enabled := true;
\r
614 ltimer.ontimer := ltimer.timer;
\r
616 addtree(@lcorethreadtimers,inttostr(taddrint(ahwnd))+' '+inttostr(nIDEvent),ltimer);
\r
618 result := nidevent;
\r
620 raise exception.create('settimer not implemented for threads other than the lcore thread');
\r
624 function KillTimer(ahWnd:HWND; uIDEvent:taddrint):WINBOOL;
\r
626 threaddata : tthreaddata;
\r
627 ltimer : tltimerformsg;
\r
628 tm : tthreadmanager;
\r
631 structurelock.acquire;
\r
633 window := findtree(@windows,inttostr(ahwnd));
\r
634 if window= nil then raise exception.create('invalid window');
\r
635 threaddata := findthreaddata(window.threadid);
\r
637 structurelock.release;
\r
639 if threaddata.lcorethread then begin
\r
640 getthreadmanager(tm);
\r
641 if tm.GetCurrentThreadId <> window.threadid then raise exception.create('timers on the lcore thread may only be added and remove from the lcore thread');
\r
642 if ahwnd = 0 then raise exception.create('timers on the lcore thread must be associated with a window handle');
\r
643 ltimer := tltimerformsg(findtree(@lcorethreadtimers,inttostr(taddrint(ahwnd))+' '+inttostr(uIDEvent)));
\r
644 if ltimer <> nil then begin
\r
645 deltree(@lcorethreadtimers,inttostr(taddrint(ahwnd))+' '+inttostr(uIDEvent));
\r
652 raise exception.create('settimer not implemented for threads other than the lcore thread');
\r