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
6 //this unit provides a rough approximation of windows messages on linux
\r
7 //it is usefull for multithreaded applications on linux to communicate back to
\r
8 //the main lcore thread
\r
9 //This unit is *nix only, on windows you should use the real thing
\r
12 //windows messages like system based on lcore tasks
\r
15 uses pgtypes,sysutils,bsearchtree,strings,syncobjs;
\r
18 {$if (fpc_version < 2) or ((fpc_version=2) and ((fpc_release < 2) or ((fpc_release = 2) and (fpc_patch < 2)) ))}
\r
19 {$error this code is only supported under fpc 2.2.2 and above due to bugs in the eventobject code in older versions}
\r
29 hwnd=qword; //window handles are monotonically increasing 64 bit integers,
\r
30 //this should allow for a million windows per second for over half
\r
33 twndproc=function(ahWnd:HWND; auMsg:Integer; awParam:WPARAM; alParam:LPARAM):Integer; stdcall;
\r
38 lpfnwndproc : twndproc;
\r
39 cbclsextra : integer;
\r
40 cbwndextra : integer;
\r
41 hinstance : thinstance;
\r
44 hbrbackground : hbrush;
\r
45 lpszmenuname : pchar;
\r
46 lpszclassname : pchar;
\r
48 PWNDCLASS=^twndclass;
\r
52 tTIMERPROC = procedure (ahwnd:HWND; umsg:integer; idevent:taddrint;dwtime:taddrint);stdcall;
\r
72 THevent=TEventObject;
\r
74 WS_EX_TOOLWINDOW = $80;
\r
75 WS_POPUP = longint($80000000);
\r
76 CW_USEDEFAULT=$80000000;
\r
81 INFINITE = syncobjs.infinite;
\r
83 function getwindowlongptr(ahwnd:hwnd;nindex:integer) : taddrint;
\r
84 function setwindowlongptr(ahwnd:hwnd;nindex:integer;dwNewLong : taddrint) : taddrint;
\r
85 function DefWindowProc(ahWnd:HWND; auMsg:Integer; awParam:WPARAM; alParam:LPARAM):Integer; stdcall;
\r
86 function RegisterClass(const lpWndClass:TWNDCLASS):ATOM;
\r
87 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
88 function DestroyWindow(ahWnd:HWND):WINBOOL;
\r
89 function PostMessage(hWnd:HWND; Msg:UINT; wParam:WPARAM; lParam:LPARAM):WINBOOL;
\r
90 function PeekMessage(var lpMsg: TMsg; hWnd: HWND; wMsgFilterMin, wMsgFilterMax, wRemoveMsg: UINT): WINBOOL;
\r
91 function DispatchMessage(const lpMsg: TMsg): Longint;
\r
92 function GetMessage(var lpMsg: TMsg; hWnd: HWND; wMsgFilterMin, wMsgFilterMax: UINT): WINBOOL;
\r
93 function SetEvent(hEvent:THevent):WINBOOL;
\r
94 function CreateEvent(lpEventAttributes:PSECURITYATTRIBUTES; bManualReset:WINBOOL; bInitialState:WINBOOL; lpName:pchar):tHevent;
\r
95 function terminatethread(threadhandle : tthreadid;dummy:integer) : boolean;
\r
96 function waitforsingleevent(event:thevent;timeout:cardinal) : twaitresult;
\r
97 function SetTimer(ahWnd:HWND; nIDEvent:taddrint; uElapse:UINT; lpTimerFunc:tTIMERPROC):UINT;
\r
98 function KillTimer(ahWnd:HWND; uIDEvent:taddrint):WINBOOL;
\r
104 baseunix,unix,lcore,unixutil,ltimevalstuff,sockets;//,safewriteln;
\r
109 tmessageintransit = class
\r
111 next : tmessageintransit;
\r
114 tthreaddata = class
\r
115 messagequeue : tmessageintransit;
\r
116 messageevent : teventobject;
\r
118 lcorethread : boolean;
\r
119 nexttimer : ttimeval;
\r
120 threadid : tthreadid;
\r
124 extrawindowmemory : pointer;
\r
125 threadid : tthreadid;
\r
126 windowproc : twndproc;
\r
130 structurelock : tcriticalsection;
\r
131 threaddata : thashtable;
\r
132 windowclasses : thashtable;
\r
133 lcorelinkpipesend : integer;
\r
134 lcorelinkpiperecv : tlasio;
\r
135 windows : thashtable;
\r
136 //I would rather things crash immediately
\r
137 //if they use an insufficiant size type
\r
138 //than crash after over four billion
\r
139 //windows have been made ;)
\r
140 nextwindowhandle : qword = $100000000;
\r
143 //findthreaddata should only be called while holding the structurelock
\r
144 function findthreaddata(threadid : tthreadid) : tthreaddata;
\r
146 result := tthreaddata(findtree(@threaddata,inttostr(taddrint(threadid))));
\r
147 if result = nil then begin
\r
148 result := tthreaddata.create;
\r
149 result.messageevent := teventobject.create(nil,false,false,inttostr(taddrint(result)));
\r
150 result.nexttimer := tv_invalidtimebig;
\r
151 result.threadid := threadid;
\r
152 addtree(@threaddata,inttostr(taddrint(threadid)),result);
\r
156 //deletethreaddataifunused should only be called while holding the structurelock
\r
157 procedure deletethreaddataifunused(athreaddata : tthreaddata);
\r
159 //writeln('in deletethreaddataifunused');
\r
160 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
161 //writeln('threaddata is unused, freeing messageevent');
\r
162 athreaddata.messageevent.free;
\r
163 //writeln('freeing thread data object');
\r
165 //writeln('deleting thread data object from hashtable');
\r
166 deltree(@threaddata,inttostr(taddrint(athreaddata.threadid)));
\r
167 //writeln('finished deleting thread data');
\r
169 //writeln('thread data is not unused');
\r
173 function getwindowlongptr(ahwnd:hwnd;nindex:integer) : taddrint;
\r
177 structurelock.acquire;
\r
179 window := findtree(@windows,inttostr(ahwnd));
\r
180 if window <> nil then begin
\r
181 result := paddrint(taddrint(window.extrawindowmemory)+nindex)^;
\r
186 structurelock.release;
\r
190 function setwindowlongptr(ahwnd:hwnd;nindex:integer;dwNewLong : taddrint) : taddrint;
\r
194 structurelock.acquire;
\r
196 window := findtree(@windows,inttostr(ahwnd));
\r
197 if window <> nil then begin
\r
198 result := paddrint(taddrint(window.extrawindowmemory)+nindex)^;
\r
199 paddrint(taddrint(window.extrawindowmemory)+nindex)^ := dwnewlong;
\r
204 structurelock.release;
\r
210 function DefWindowProc(ahWnd:HWND; auMsg:Integer; awParam:WPARAM; alParam:LPARAM):Integer; stdcall;
\r
215 function strdup(s:pchar) : pchar;
\r
217 //swriteln('in strdup, about to allocate memory');
\r
218 result := getmem(strlen(s)+1);
\r
219 //swriteln('about to copy string');
\r
221 //swriteln('leaving strdup');
\r
224 function RegisterClass(const lpWndClass:TWNDCLASS):ATOM;
\r
226 storedwindowclass:pwndclass;
\r
228 structurelock.acquire;
\r
230 //swriteln('in registerclass, about to check for duplicate window class');
\r
231 storedwindowclass := findtree(@windowclasses, lpwndclass.lpszclassname);
\r
232 if storedwindowclass <> nil then begin
\r
234 if comparebyte(storedwindowclass^,lpwndclass,sizeof(twndclass)-sizeof(pchar)-sizeof(pchar)) <> 0 then begin
\r
235 //swriteln('duplicate window class registered with different settings');
\r
236 raise exception.create('duplicate window class registered with different settings');
\r
238 //swriteln('duplicate window class registered with same settings, tollerated');
\r
241 //swriteln('about to allocate memory for new windowclass');
\r
242 storedwindowclass := getmem(sizeof(twndclass));
\r
243 //swriteln('about to copy windowclass from parameter');
\r
244 move(lpwndclass,storedwindowclass^,sizeof(twndclass));
\r
245 //swriteln('about to copy strings');
\r
246 if lpwndclass.lpszmenuname <> nil then storedwindowclass.lpszmenuname := strdup(lpwndclass.lpszmenuname);
\r
247 if lpwndclass.lpszclassname <> nil then storedwindowclass.lpszclassname := strdup(lpwndclass.lpszclassname);
\r
248 //swriteln('about to add result to list of windowclasses');
\r
249 addtree(@windowclasses,lpwndclass.lpszclassname,storedwindowclass);
\r
251 //swriteln('about to return result');
\r
252 result := storedwindowclass;
\r
253 //swriteln('leaving registerclass');
\r
255 structurelock.release;
\r
259 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
261 wndclass : pwndclass;
\r
262 tm : tthreadmanager;
\r
265 structurelock.acquire;
\r
267 window := twindow.create;
\r
268 window.hwnd := nextwindowhandle;
\r
269 result := window.hwnd;
\r
270 nextwindowhandle := nextwindowhandle + 1;
\r
271 addtree(@windows,inttostr(window.hwnd),window);
\r
272 wndclass := findtree(@windowclasses,lpclassname);
\r
273 window.extrawindowmemory := getmem(wndclass.cbwndextra);
\r
275 getthreadmanager(tm);
\r
276 window.threadid := tm.GetCurrentThreadId;
\r
277 window.windowproc := wndclass.lpfnwndproc;
\r
279 structurelock.release;
\r
282 function DestroyWindow(ahWnd:HWND):WINBOOL;
\r
285 windowthreaddata : tthreaddata;
\r
286 currentmessage : tmessageintransit;
\r
287 prevmessage : tmessageintransit;
\r
289 //writeln('started to destroy window');
\r
290 structurelock.acquire;
\r
292 window := twindow(findtree(@windows,inttostr(ahwnd)));
\r
293 if window <> nil then begin
\r
294 freemem(window.extrawindowmemory);
\r
295 //writeln('aboute to delete window from windows structure');
\r
296 deltree(@windows,inttostr(ahwnd));
\r
297 //writeln('deleted window from windows structure');
\r
298 windowthreaddata := tthreaddata(findtree(@threaddata,inttostr(taddrint(window.threadid))));
\r
300 if windowthreaddata <> nil then begin
\r
301 //writeln('found thread data scanning for messages to clean up');
\r
302 currentmessage := windowthreaddata.messagequeue;
\r
303 prevmessage := nil;
\r
304 while currentmessage <> nil do begin
\r
305 while (currentmessage <> nil) and (currentmessage.msg.hwnd = ahwnd) do begin
\r
306 if prevmessage = nil then begin
\r
307 windowthreaddata.messagequeue := currentmessage.next;
\r
309 prevmessage.next := currentmessage.next;
\r
311 currentmessage.free;
\r
312 if prevmessage = nil then begin
\r
313 currentmessage := windowthreaddata.messagequeue;
\r
315 currentmessage := prevmessage.next;
\r
318 if currentmessage <> nil then begin
\r
319 prevmessage := currentmessage;
\r
320 currentmessage := currentmessage.next;
\r
323 //writeln('deleting thread data structure if it is unused');
\r
324 deletethreaddataifunused(windowthreaddata);
\r
326 //writeln('there is no thread data to search for messages to cleanup');
\r
328 //writeln('freeing window');
\r
335 structurelock.release;
\r
337 //writeln('window destroyed');
\r
342 function PostMessage(hWnd:HWND; Msg:UINT; wParam:WPARAM; lParam:LPARAM):WINBOOL;
\r
344 threaddata : tthreaddata;
\r
345 message : tmessageintransit;
\r
346 messagequeueend : tmessageintransit;
\r
349 structurelock.acquire;
\r
351 window := findtree(@windows,inttostr(hwnd));
\r
352 if window <> nil then begin
\r
353 threaddata := findthreaddata(window.threadid);
\r
354 message := tmessageintransit.create;
\r
355 message.msg.hwnd := hwnd;
\r
356 message.msg.message := msg;
\r
357 message.msg.wparam := wparam;
\r
358 message.msg.lparam := lparam;
\r
359 if threaddata.lcorethread then begin
\r
360 //swriteln('posting message to lcore thread');
\r
361 fdwrite(lcorelinkpipesend,message,sizeof(message));
\r
363 //writeln('posting message to non lcore thread');
\r
364 if threaddata.messagequeue = nil then begin
\r
365 threaddata.messagequeue := message;
\r
367 messagequeueend := threaddata.messagequeue;
\r
368 while messagequeueend.next <> nil do begin
\r
369 messagequeueend := messagequeueend.next;
\r
371 messagequeueend.next := message;
\r
374 //writeln('message added to queue');
\r
375 if threaddata.waiting then threaddata.messageevent.setevent;
\r
382 structurelock.release;
\r
387 function gettickcount : dword;
\r
393 result64 := (tv.tv_sec*1000)+(tv.tv_usec div 1000);
\r
394 result := result64;
\r
397 function DispatchMessage(const lpMsg: TMsg): Longint;
\r
399 timerproc : ttimerproc;
\r
401 windowproc : twndproc;
\r
403 ////swriteln('in dispatchmessage, msg.hwnd='+inttohex(taddrint(lpmsg.hwnd),16));
\r
404 if (lpmsg.lparam <> 0) and (lpmsg.message = WM_TIMER) then begin
\r
405 timerproc := ttimerproc(lpmsg.lparam);
\r
406 timerproc(lpmsg.hwnd,lpmsg.message,lpmsg.wparam,gettickcount);
\r
409 structurelock.acquire;
\r
411 window := findtree(@windows,inttostr(lpmsg.hwnd));
\r
412 //we have to get the window procedure while the structurelock
\r
413 //is still held as the window could be destroyed from another thread
\r
415 if window <> nil then begin
\r
416 windowproc := window.windowproc;
\r
421 structurelock.release;
\r
423 if assigned(windowproc) then begin
\r
424 result := windowproc(lpmsg.hwnd,lpmsg.message,lpmsg.wparam,lpmsg.lparam);
\r
431 procedure processtimers;
\r
435 function GetMessageinternal(var lpMsg: TMsg; hWnd: HWND; wMsgFilterMin, wMsgFilterMax: UINT; wremovemsg : UINT;peek:boolean): WINBOOL;
\r
437 tm : tthreadmanager;
\r
438 threaddata : tthreaddata;
\r
439 message : tmessageintransit;
\r
441 timeouttv : ttimeval;
\r
445 if hwnd <> 0 then raise exception.create('getting messages for an individual window is not supported');
\r
446 if (wmsgfiltermin <> 0) or (wmsgfiltermax <> 0) then raise exception.create('message filtering is not supported');
\r
447 structurelock.acquire;
\r
450 getthreadmanager(tm);
\r
451 threaddata := findthreaddata(tm.GetCurrentThreadId);
\r
452 if threaddata.lcorethread then raise exception.create('get/peek message cannot be used in the lcore thread');
\r
453 message := threaddata.messagequeue;
\r
454 gettimeofday(nowtv);
\r
455 while (not peek) and (message=nil) and (not tv_compare(nowtv,threaddata.nexttimer)) do begin
\r
456 threaddata.waiting := true;
\r
457 structurelock.release;
\r
458 if (threaddata.nexttimer.tv_sec = TV_invalidtimebig.tv_sec) and (threaddata.nexttimer.tv_usec = TV_invalidtimebig.tv_usec) then begin
\r
459 threaddata.messageevent.waitfor(INFINITE);
\r
462 timeouttv := threaddata.nexttimer;
\r
463 timeoutms := (timeouttv.tv_sec * 1000)+(timeouttv.tv_usec div 1000);
\r
464 //i'm assuming the timeout is in milliseconds
\r
465 if (timeoutms > maxlongint) then timeoutms := maxlongint;
\r
466 threaddata.messageevent.waitfor(timeoutms);
\r
469 structurelock.acquire;
\r
470 threaddata.waiting := false;
\r
471 message := threaddata.messagequeue;
\r
472 gettimeofday(nowtv);
\r
474 if (message=nil) and tv_compare(nowtv,threaddata.nexttimer) then begin
\r
477 message := threaddata.messagequeue;
\r
478 if message <> nil then begin
\r
479 lpmsg := message.msg;
\r
480 if wremovemsg=PM_REMOVE then begin
\r
481 threaddata.messagequeue := message.next;
\r
487 deletethreaddataifunused(threaddata);
\r
489 structurelock.release;
\r
493 function GetMessage(var lpMsg: TMsg; hWnd: HWND; wMsgFilterMin, wMsgFilterMax: UINT): WINBOOL;
\r
495 result := getmessageinternal(lpmsg,hwnd,wmsgfiltermin,wmsgfiltermax,PM_REMOVE,false);
\r
498 function PeekMessage(var lpMsg: TMsg; hWnd: HWND; wMsgFilterMin, wMsgFilterMax, wRemoveMsg: UINT): WINBOOL;
\r
500 result := getmessageinternal(lpmsg,hwnd,wmsgfiltermin,wmsgfiltermax,wRemoveMsg,true);
\r
503 function SetEvent(hEvent:THevent):WINBOOL;
\r
509 function CreateEvent(lpEventAttributes:PSECURITYATTRIBUTES; bManualReset:WINBOOL; bInitialState:WINBOOL; lpName:pchar):tHevent;
\r
511 result := teventobject.create(lpeventattributes,bmanualreset,binitialstate,lpname);
\r
514 function terminatethread(threadhandle:tthreadid;dummy : integer) : boolean;
\r
516 tm : tthreadmanager;
\r
518 getthreadmanager(tm);
\r
519 tm.killthread(threadhandle);
\r
523 function waitforsingleevent(event:thevent;timeout:cardinal) : twaitresult;
\r
525 result := event.waitfor(timeout);
\r
528 procedure removefrombuffer(n : integer; var buffer:string);
\r
530 if n=length(buffer) then begin
\r
533 uniquestring(buffer);
\r
534 move(buffer[n+1],buffer[1],length(buffer)-n);
\r
535 setlength(buffer,length(buffer)-n);
\r
541 procedure available(sender:tobject;error:word);
\r
547 procedure tsc.available(sender:tobject;error:word);
\r
549 message : tmessageintransit;
\r
550 messagebytes : array[1..sizeof(tmessageintransit)] of char absolute message;
\r
553 //swriteln('received data on lcorelinkpipe');
\r
554 recvbuf := recvbuf + lcorelinkpiperecv.receivestr;
\r
555 while length(recvbuf) >= sizeof(tmessageintransit) do begin
\r
556 for i := 1 to sizeof(tmessageintransit) do begin
\r
557 messagebytes[i] := recvbuf[i];
\r
559 dispatchmessage(message.msg);
\r
561 removefrombuffer(sizeof(tmessageintransit),recvbuf);
\r
567 tm : tthreadmanager;
\r
568 threaddata : tthreaddata;
\r
569 pipeends : tfildes;
\r
572 structurelock := tcriticalsection.create;
\r
573 getthreadmanager(tm);
\r
574 threaddata := findthreaddata(tm.GetCurrentThreadId);
\r
575 threaddata.lcorethread := true;
\r
577 lcorelinkpipesend := pipeends[1];
\r
578 lcorelinkpiperecv := tlasio.create(nil);
\r
579 lcorelinkpiperecv.dup(pipeends[0]);
\r
580 lcorelinkpiperecv.ondataavailable := sc.available;
\r
585 lcorethreadtimers : thashtable;
\r
587 tltimerformsg = class(tltimer)
\r
591 procedure timer(sender : tobject);
\r
594 procedure tltimerformsg.timer(sender : tobject);
\r
598 ////swriteln('in tltimerformsg.timer');
\r
599 fillchar(msg,sizeof(msg),0);
\r
600 msg.message := WM_TIMER;
\r
604 dispatchmessage(msg);
\r
607 function SetTimer(ahWnd:HWND; nIDEvent:taddrint; uElapse:UINT; lpTimerFunc:tTIMERPROC):UINT;
\r
609 threaddata : tthreaddata;
\r
610 ltimer : tltimerformsg;
\r
611 tm : tthreadmanager;
\r
614 structurelock.acquire;
\r
616 window := findtree(@windows,inttostr(ahwnd));
\r
617 if window= nil then raise exception.create('invalid window');
\r
618 threaddata := findthreaddata(window.threadid);
\r
620 structurelock.release;
\r
622 if threaddata.lcorethread then begin
\r
623 getthreadmanager(tm);
\r
624 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
625 if ahwnd = 0 then raise exception.create('timers on the lcore thread must be associated with a window handle');
\r
626 if @lptimerfunc <> nil then raise exception.create('seperate timer functions are not supported');
\r
628 //remove preexisting timer with same ID
\r
629 killtimer(ahwnd,nIDEvent);
\r
631 ltimer := tltimerformsg.create(nil);
\r
632 ltimer.interval := uelapse;
\r
633 ltimer.id := nidevent;
\r
634 ltimer.hwnd := ahwnd;
\r
635 ltimer.enabled := true;
\r
636 ltimer.ontimer := ltimer.timer;
\r
638 addtree(@lcorethreadtimers,inttostr(taddrint(ahwnd))+' '+inttostr(nIDEvent),ltimer);
\r
640 result := nidevent;
\r
642 raise exception.create('settimer not implemented for threads other than the lcore thread');
\r
646 function KillTimer(ahWnd:HWND; uIDEvent:taddrint):WINBOOL;
\r
648 threaddata : tthreaddata;
\r
649 ltimer : tltimerformsg;
\r
650 tm : tthreadmanager;
\r
653 structurelock.acquire;
\r
655 window := findtree(@windows,inttostr(ahwnd));
\r
656 if window= nil then raise exception.create('invalid window');
\r
657 threaddata := findthreaddata(window.threadid);
\r
659 structurelock.release;
\r
661 if threaddata.lcorethread then begin
\r
662 getthreadmanager(tm);
\r
663 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
664 if ahwnd = 0 then raise exception.create('timers on the lcore thread must be associated with a window handle');
\r
665 ltimer := tltimerformsg(findtree(@lcorethreadtimers,inttostr(taddrint(ahwnd))+' '+inttostr(uIDEvent)));
\r
666 if ltimer <> nil then begin
\r
667 deltree(@lcorethreadtimers,inttostr(taddrint(ahwnd))+' '+inttostr(uIDEvent));
\r
674 raise exception.create('settimer not implemented for threads other than the lcore thread');
\r