1 { Copyright (C) 2005 Bas Steendijk and Peter Green
\r
2 For conditions of distribution and use, see copyright notice in zlib_license.txt
\r
3 which is included in the package
\r
4 ----------------------------------------------------------------------------- }
\r
7 //windows messages like system based on lcore tasks
\r
10 uses pgtypes,sysutils,bsearchtree,strings,syncobjs;
\r
19 hwnd=qword; //window handles are monotonically increasing 64 bit integers,
\r
20 //this should allow for a million windows per second for over half
\r
23 twndproc=function(ahWnd:HWND; auMsg:Integer; awParam:WPARAM; alParam:LPARAM):Integer; stdcall;
\r
28 lpfnwndproc : twndproc;
\r
29 cbclsextra : integer;
\r
30 cbwndextra : integer;
\r
31 hinstance : thinstance;
\r
34 hbrbackground : hbrush;
\r
35 lpszmenuname : pchar;
\r
36 lpszclassname : pchar;
\r
38 PWNDCLASS=^twndclass;
\r
42 tTIMERPROC = procedure (ahwnd:HWND; umsg:integer; idevent:taddrint;dwtime:taddrint);stdcall;
\r
62 THevent=TEventObject;
\r
64 WS_EX_TOOLWINDOW = $80;
\r
65 WS_POPUP = longint($80000000);
\r
70 INFINITE = syncobjs.infinite;
\r
71 function getwindowlongptr(ahwnd:hwnd;nindex:integer) : taddrint;
\r
72 function setwindowlongptr(ahwnd:hwnd;nindex:integer;dwNewLong : taddrint) : taddrint;
\r
73 function DefWindowProc(ahWnd:HWND; auMsg:Integer; awParam:WPARAM; alParam:LPARAM):Integer; stdcall;
\r
74 function RegisterClass(const lpWndClass:TWNDCLASS):ATOM;
\r
75 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
76 function DestroyWindow(ahWnd:HWND):WINBOOL;
\r
77 function PostMessage(hWnd:HWND; Msg:UINT; wParam:WPARAM; lParam:LPARAM):WINBOOL;
\r
78 function PeekMessage(var lpMsg: TMsg; hWnd: HWND; wMsgFilterMin, wMsgFilterMax, wRemoveMsg: UINT): WINBOOL;
\r
79 function DispatchMessage(const lpMsg: TMsg): Longint;
\r
80 function GetMessage(var lpMsg: TMsg; hWnd: HWND; wMsgFilterMin, wMsgFilterMax: UINT): WINBOOL;
\r
81 function SetEvent(hEvent:THevent):WINBOOL;
\r
82 function CreateEvent(lpEventAttributes:PSECURITYATTRIBUTES; bManualReset:WINBOOL; bInitialState:WINBOOL; lpName:pchar):tHevent;
\r
83 function terminatethread(threadhandle : tthreadid;dummy:integer) : boolean;
\r
84 function waitforsingleevent(event:thevent;timeout:cardinal) : twaitresult;
\r
85 function SetTimer(ahWnd:HWND; nIDEvent:taddrint; uElapse:UINT; lpTimerFunc:tTIMERPROC):UINT;
\r
86 function KillTimer(ahWnd:HWND; uIDEvent:taddrint):WINBOOL;
\r
92 baseunix,unix,lcore,unixutil;//,safewriteln;
\r
96 tmessageintransit = class
\r
98 next : tmessageintransit;
\r
101 tthreaddata = class
\r
102 messagequeue : tmessageintransit;
\r
103 messageevent : teventobject;
\r
105 lcorethread : boolean;
\r
106 nexttimer : ttimeval;
\r
107 threadid : integer;
\r
111 extrawindowmemory : pointer;
\r
112 threadid : tthreadid;
\r
113 windowproc : twndproc;
\r
117 structurelock : tcriticalsection;
\r
118 threaddata : thashtable;
\r
119 windowclasses : thashtable;
\r
120 lcorelinkpipesend : integer;
\r
121 lcorelinkpiperecv : tlasio;
\r
122 windows : thashtable;
\r
123 //I would rather things crash immediately
\r
124 //if they use an insufficiant size type
\r
125 //than crash after over four billion
\r
126 //windows have been made ;)
\r
127 nextwindowhandle : qword = $100000000;
\r
128 {$i ltimevalstuff.inc}
\r
130 //findthreaddata should only be called while holding the structurelock
\r
131 function findthreaddata(threadid : integer) : tthreaddata;
\r
133 result := tthreaddata(findtree(@threaddata,inttostr(threadid)));
\r
134 if result = nil then begin
\r
135 result := tthreaddata.create;
\r
136 result.messageevent := teventobject.create(nil,false,false,inttostr(taddrint(result)));
\r
137 result.nexttimer := tv_invalidtimebig;
\r
138 result.threadid := threadid;
\r
139 addtree(@threaddata,inttostr(threadid),result);
\r
143 //deletethreaddataifunused should only be called while holding the structurelock
\r
144 procedure deletethreaddataifunused(athreaddata : tthreaddata);
\r
146 //writeln('in deletethreaddataifunused');
\r
147 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
148 //writeln('threaddata is unused, freeing messageevent');
\r
149 athreaddata.messageevent.free;
\r
150 //writeln('freeing thread data object');
\r
152 //writeln('deleting thread data object from hashtable');
\r
153 deltree(@threaddata,inttostr(athreaddata.threadid));
\r
154 //writeln('finished deleting thread data');
\r
156 //writeln('thread data is not unused');
\r
160 function getwindowlongptr(ahwnd:hwnd;nindex:integer) : taddrint;
\r
164 structurelock.acquire;
\r
166 window := findtree(@windows,inttostr(ahwnd));
\r
167 if window <> nil then begin
\r
168 result := paddrint(taddrint(window.extrawindowmemory)+nindex)^;
\r
173 structurelock.release;
\r
177 function setwindowlongptr(ahwnd:hwnd;nindex:integer;dwNewLong : taddrint) : taddrint;
\r
181 structurelock.acquire;
\r
183 window := findtree(@windows,inttostr(ahwnd));
\r
184 if window <> nil then begin
\r
185 result := paddrint(taddrint(window.extrawindowmemory)+nindex)^;
\r
186 paddrint(taddrint(window.extrawindowmemory)+nindex)^ := dwnewlong;
\r
191 structurelock.release;
\r
197 function DefWindowProc(ahWnd:HWND; auMsg:Integer; awParam:WPARAM; alParam:LPARAM):Integer; stdcall;
\r
202 function strdup(s:pchar) : pchar;
\r
204 //swriteln('in strdup, about to allocate memory');
\r
205 result := getmem(strlen(s)+1);
\r
206 //swriteln('about to copy string');
\r
208 //swriteln('leaving strdup');
\r
211 function RegisterClass(const lpWndClass:TWNDCLASS):ATOM;
\r
213 storedwindowclass:pwndclass;
\r
215 structurelock.acquire;
\r
217 //swriteln('in registerclass, about to check for duplicate window class');
\r
218 storedwindowclass := findtree(@windowclasses, lpwndclass.lpszclassname);
\r
219 if storedwindowclass <> nil then begin
\r
221 if comparebyte(storedwindowclass^,lpwndclass,sizeof(twndclass)-sizeof(pchar)-sizeof(pchar)) <> 0 then begin
\r
222 //swriteln('duplicate window class registered with different settings');
\r
223 raise exception.create('duplicate window class registered with different settings');
\r
225 //swriteln('duplicate window class registered with same settings, tollerated');
\r
228 //swriteln('about to allocate memory for new windowclass');
\r
229 storedwindowclass := getmem(sizeof(twndclass));
\r
230 //swriteln('about to copy windowclass from parameter');
\r
231 move(lpwndclass,storedwindowclass^,sizeof(twndclass));
\r
232 //swriteln('about to copy strings');
\r
233 if lpwndclass.lpszmenuname <> nil then storedwindowclass.lpszmenuname := strdup(lpwndclass.lpszmenuname);
\r
234 if lpwndclass.lpszclassname <> nil then storedwindowclass.lpszclassname := strdup(lpwndclass.lpszclassname);
\r
235 //swriteln('about to add result to list of windowclasses');
\r
236 addtree(@windowclasses,lpwndclass.lpszclassname,storedwindowclass);
\r
238 //swriteln('about to return result');
\r
239 result := storedwindowclass;
\r
240 //swriteln('leaving registerclass');
\r
242 structurelock.release;
\r
246 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
248 wndclass : pwndclass;
\r
249 tm : tthreadmanager;
\r
252 structurelock.acquire;
\r
254 window := twindow.create;
\r
255 window.hwnd := nextwindowhandle;
\r
256 result := window.hwnd;
\r
257 nextwindowhandle := nextwindowhandle + 1;
\r
258 addtree(@windows,inttostr(window.hwnd),window);
\r
259 wndclass := findtree(@windowclasses,lpclassname);
\r
260 window.extrawindowmemory := getmem(wndclass.cbwndextra);
\r
262 getthreadmanager(tm);
\r
263 window.threadid := tm.GetCurrentThreadId;
\r
264 window.windowproc := wndclass.lpfnwndproc;
\r
266 structurelock.release;
\r
269 function DestroyWindow(ahWnd:HWND):WINBOOL;
\r
272 windowthreaddata : tthreaddata;
\r
273 currentmessage : tmessageintransit;
\r
274 prevmessage : tmessageintransit;
\r
276 //writeln('started to destroy window');
\r
277 structurelock.acquire;
\r
279 window := twindow(findtree(@windows,inttostr(ahwnd)));
\r
280 if window <> nil then begin
\r
281 freemem(window.extrawindowmemory);
\r
282 //writeln('aboute to delete window from windows structure');
\r
283 deltree(@windows,inttostr(ahwnd));
\r
284 //writeln('deleted window from windows structure');
\r
285 windowthreaddata := tthreaddata(findtree(@threaddata,inttostr(window.threadid)));
\r
287 if windowthreaddata <> nil then begin
\r
288 //writeln('found thread data scanning for messages to clean up');
\r
289 currentmessage := windowthreaddata.messagequeue;
\r
290 prevmessage := nil;
\r
291 while currentmessage <> nil do begin
\r
292 while (currentmessage <> nil) and (currentmessage.msg.hwnd = ahwnd) do begin
\r
293 if prevmessage = nil then begin
\r
294 windowthreaddata.messagequeue := currentmessage.next;
\r
296 prevmessage.next := currentmessage.next;
\r
298 currentmessage.free;
\r
299 if prevmessage = nil then begin
\r
300 currentmessage := windowthreaddata.messagequeue;
\r
302 currentmessage := prevmessage.next;
\r
305 if currentmessage <> nil then begin
\r
306 prevmessage := currentmessage;
\r
307 currentmessage := currentmessage.next;
\r
310 //writeln('deleting thread data structure if it is unused');
\r
311 deletethreaddataifunused(windowthreaddata);
\r
313 //writeln('there is no thread data to search for messages to cleanup');
\r
315 //writeln('freeing window');
\r
322 structurelock.release;
\r
324 //writeln('window destroyed');
\r
329 function PostMessage(hWnd:HWND; Msg:UINT; wParam:WPARAM; lParam:LPARAM):WINBOOL;
\r
331 threaddata : tthreaddata;
\r
332 message : tmessageintransit;
\r
333 messagequeueend : tmessageintransit;
\r
336 structurelock.acquire;
\r
338 window := findtree(@windows,inttostr(hwnd));
\r
339 if window <> nil then begin
\r
340 threaddata := findthreaddata(window.threadid);
\r
341 message := tmessageintransit.create;
\r
342 message.msg.hwnd := hwnd;
\r
343 message.msg.message := msg;
\r
344 message.msg.wparam := wparam;
\r
345 message.msg.lparam := lparam;
\r
346 if threaddata.lcorethread then begin
\r
347 //swriteln('posting message to lcore thread');
\r
348 fdwrite(lcorelinkpipesend,message,sizeof(message));
\r
350 //writeln('posting message to non lcore thread');
\r
351 if threaddata.messagequeue = nil then begin
\r
352 threaddata.messagequeue := message;
\r
354 messagequeueend := threaddata.messagequeue;
\r
355 while messagequeueend.next <> nil do begin
\r
356 messagequeueend := messagequeueend.next;
\r
358 messagequeueend.next := message;
\r
361 //writeln('message added to queue');
\r
362 if threaddata.waiting then threaddata.messageevent.setevent;
\r
369 structurelock.release;
\r
374 function gettickcount : dword;
\r
380 result64 := (tv.tv_sec*1000)+(tv.tv_usec div 1000);
\r
381 result := result64;
\r
384 function DispatchMessage(const lpMsg: TMsg): Longint;
\r
386 timerproc : ttimerproc;
\r
388 windowproc : twndproc;
\r
390 ////swriteln('in dispatchmessage, msg.hwnd='+inttohex(taddrint(lpmsg.hwnd),16));
\r
391 if (lpmsg.lparam <> 0) and (lpmsg.message = WM_TIMER) then begin
\r
392 timerproc := ttimerproc(lpmsg.lparam);
\r
393 timerproc(lpmsg.hwnd,lpmsg.message,lpmsg.wparam,gettickcount);
\r
396 structurelock.acquire;
\r
398 window := findtree(@windows,inttostr(lpmsg.hwnd));
\r
399 //we have to get the window procedure while the structurelock
\r
400 //is still held as the window could be destroyed from another thread
\r
402 windowproc := window.windowproc;
\r
404 structurelock.release;
\r
406 if window <> nil then begin
\r
407 result := windowproc(lpmsg.hwnd,lpmsg.message,lpmsg.wparam,lpmsg.lparam);
\r
414 procedure processtimers;
\r
418 function GetMessageinternal(var lpMsg: TMsg; hWnd: HWND; wMsgFilterMin, wMsgFilterMax: UINT; wremovemsg : UINT;peek:boolean): WINBOOL;
\r
420 tm : tthreadmanager;
\r
421 threaddata : tthreaddata;
\r
422 message : tmessageintransit;
\r
424 timeouttv : ttimeval;
\r
428 if hwnd <> 0 then raise exception.create('getting messages for an individual window is not supported');
\r
429 if (wmsgfiltermin <> 0) or (wmsgfiltermax <> 0) then raise exception.create('message filtering is not supported');
\r
430 structurelock.acquire;
\r
433 getthreadmanager(tm);
\r
434 threaddata := findthreaddata(tm.GetCurrentThreadId);
\r
435 if threaddata.lcorethread then raise exception.create('get/peek message cannot be used in the lcore thread');
\r
436 message := threaddata.messagequeue;
\r
437 gettimeofday(nowtv);
\r
438 while (not peek) and (message=nil) and (not tv_compare(nowtv,threaddata.nexttimer)) do begin
\r
439 threaddata.waiting := true;
\r
440 structurelock.release;
\r
441 if (threaddata.nexttimer.tv_sec = TV_invalidtimebig.tv_sec) and (threaddata.nexttimer.tv_usec = TV_invalidtimebig.tv_usec) then begin
\r
442 threaddata.messageevent.waitfor(INFINITE);
\r
445 timeouttv := threaddata.nexttimer;
\r
446 timeoutms := (timeouttv.tv_sec * 1000)+(timeouttv.tv_usec div 1000);
\r
447 //i'm assuming the timeout is in milliseconds
\r
448 if (timeoutms > maxlongint) then timeoutms := maxlongint;
\r
449 threaddata.messageevent.waitfor(timeoutms);
\r
452 structurelock.acquire;
\r
453 threaddata.waiting := false;
\r
454 message := threaddata.messagequeue;
\r
455 gettimeofday(nowtv);
\r
457 if (message=nil) and tv_compare(nowtv,threaddata.nexttimer) then begin
\r
460 message := threaddata.messagequeue;
\r
461 if message <> nil then begin
\r
462 lpmsg := message.msg;
\r
463 if wremovemsg=PM_REMOVE then begin
\r
464 threaddata.messagequeue := message.next;
\r
470 deletethreaddataifunused(threaddata);
\r
472 structurelock.release;
\r
476 function GetMessage(var lpMsg: TMsg; hWnd: HWND; wMsgFilterMin, wMsgFilterMax: UINT): WINBOOL;
\r
478 result := getmessageinternal(lpmsg,hwnd,wmsgfiltermin,wmsgfiltermax,PM_REMOVE,false);
\r
481 function PeekMessage(var lpMsg: TMsg; hWnd: HWND; wMsgFilterMin, wMsgFilterMax, wRemoveMsg: UINT): WINBOOL;
\r
483 result := getmessageinternal(lpmsg,hwnd,wmsgfiltermin,wmsgfiltermax,PM_REMOVE,true);
\r
486 function SetEvent(hEvent:THevent):WINBOOL;
\r
492 function CreateEvent(lpEventAttributes:PSECURITYATTRIBUTES; bManualReset:WINBOOL; bInitialState:WINBOOL; lpName:pchar):tHevent;
\r
494 result := teventobject.create(lpeventattributes,bmanualreset,binitialstate,lpname);
\r
497 function terminatethread(threadhandle:tthreadid;dummy : integer) : boolean;
\r
499 tm : tthreadmanager;
\r
501 getthreadmanager(tm);
\r
502 tm.killthread(threadhandle);
\r
506 function waitforsingleevent(event:thevent;timeout:cardinal) : twaitresult;
\r
508 result := event.waitfor(timeout);
\r
511 procedure removefrombuffer(n : integer; var buffer:string);
\r
513 if n=length(buffer) then begin
\r
516 uniquestring(buffer);
\r
517 move(buffer[n+1],buffer[1],length(buffer)-n);
\r
518 setlength(buffer,length(buffer)-n);
\r
524 procedure available(sender:tobject;error:word);
\r
530 procedure tsc.available(sender:tobject;error:word);
\r
532 message : tmessageintransit;
\r
533 messagebytes : array[1..sizeof(tmessageintransit)] of char absolute message;
\r
536 //swriteln('received data on lcorelinkpipe');
\r
537 recvbuf := recvbuf + lcorelinkpiperecv.receivestr;
\r
538 while length(recvbuf) >= sizeof(tmessageintransit) do begin
\r
539 for i := 1 to sizeof(tmessageintransit) do begin
\r
540 messagebytes[i] := recvbuf[i];
\r
542 dispatchmessage(message.msg);
\r
544 removefrombuffer(sizeof(tmessageintransit),recvbuf);
\r
550 tm : tthreadmanager;
\r
551 threaddata : tthreaddata;
\r
552 pipeends : tfildes;
\r
555 structurelock := tcriticalsection.create;
\r
556 getthreadmanager(tm);
\r
557 threaddata := findthreaddata(tm.GetCurrentThreadId);
\r
558 threaddata.lcorethread := true;
\r
560 lcorelinkpipesend := pipeends[1];
\r
561 lcorelinkpiperecv := tlasio.create(nil);
\r
562 lcorelinkpiperecv.dup(pipeends[0]);
\r
563 lcorelinkpiperecv.ondataavailable := sc.available;
\r
568 lcorethreadtimers : thashtable;
\r
570 tltimerformsg = class(tltimer)
\r
574 procedure timer(sender : tobject);
\r
577 procedure tltimerformsg.timer(sender : tobject);
\r
581 ////swriteln('in tltimerformsg.timer');
\r
582 fillchar(msg,sizeof(msg),0);
\r
583 msg.message := WM_TIMER;
\r
587 dispatchmessage(msg);
\r
590 function SetTimer(ahWnd:HWND; nIDEvent:taddrint; uElapse:UINT; lpTimerFunc:tTIMERPROC):UINT;
\r
592 threaddata : tthreaddata;
\r
593 ltimer : tltimerformsg;
\r
594 tm : tthreadmanager;
\r
597 structurelock.acquire;
\r
599 window := findtree(@windows,inttostr(ahwnd));
\r
600 if window= nil then raise exception.create('invalid window');
\r
601 threaddata := findthreaddata(window.threadid);
\r
603 structurelock.release;
\r
605 if threaddata.lcorethread then begin
\r
606 getthreadmanager(tm);
\r
607 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
608 if ahwnd = 0 then raise exception.create('timers on the lcore thread must be associated with a window handle');
\r
609 if @lptimerfunc <> nil then raise exception.create('seperate timer functions are not supported');
\r
611 //remove preexisting timer with same ID
\r
612 killtimer(ahwnd,nIDEvent);
\r
614 ltimer := tltimerformsg.create(nil);
\r
615 ltimer.interval := uelapse;
\r
616 ltimer.id := nidevent;
\r
617 ltimer.hwnd := ahwnd;
\r
618 ltimer.enabled := true;
\r
619 ltimer.ontimer := ltimer.timer;
\r
621 addtree(@lcorethreadtimers,inttostr(taddrint(ahwnd))+' '+inttostr(nIDEvent),ltimer);
\r
623 result := nidevent;
\r
625 raise exception.create('settimer not implemented for threads other than the lcore thread');
\r
629 function KillTimer(ahWnd:HWND; uIDEvent:taddrint):WINBOOL;
\r
631 threaddata : tthreaddata;
\r
632 ltimer : tltimerformsg;
\r
633 tm : tthreadmanager;
\r
636 structurelock.acquire;
\r
638 window := findtree(@windows,inttostr(ahwnd));
\r
639 if window= nil then raise exception.create('invalid window');
\r
640 threaddata := findthreaddata(window.threadid);
\r
642 structurelock.release;
\r
644 if threaddata.lcorethread then begin
\r
645 getthreadmanager(tm);
\r
646 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
647 if ahwnd = 0 then raise exception.create('timers on the lcore thread must be associated with a window handle');
\r
648 ltimer := tltimerformsg(findtree(@lcorethreadtimers,inttostr(taddrint(ahwnd))+' '+inttostr(uIDEvent)));
\r
649 if ltimer <> nil then begin
\r
650 deltree(@lcorethreadtimers,inttostr(taddrint(ahwnd))+' '+inttostr(uIDEvent));
\r
657 raise exception.create('settimer not implemented for threads other than the lcore thread');
\r