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