mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-06-01 14:12:37 +02:00
365 lines
10 KiB
ObjectPascal
365 lines
10 KiB
ObjectPascal
unit customdrawn_x11proc;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
{$I customdrawndefines.inc}
|
|
|
|
interface
|
|
|
|
uses
|
|
// rtl+ftl
|
|
Types, Classes, SysUtils,
|
|
fpimage, fpcanvas, ctypes,
|
|
X, XLib,
|
|
BaseUnix,Unix,
|
|
// Custom Drawn Canvas
|
|
IntfGraphics, lazcanvas,
|
|
//
|
|
GraphType, Controls, LCLMessageGlue, WSControls, LCLType, LCLProc,
|
|
customdrawnproc;
|
|
|
|
type
|
|
TX11WindowInfo = class(TCDForm)
|
|
public
|
|
Window: X.TWindow;
|
|
// Used and valid only during event processing
|
|
XEvent: PXEvent;
|
|
// X11 extra objects
|
|
Attr: XLib.TXWindowAttributes;
|
|
Colormap: TColormap;
|
|
GC: TGC;
|
|
ColorDepth: Byte;
|
|
{$ifdef CD_X11_SmartPaint}
|
|
Valid: Boolean;
|
|
Moved: Boolean;
|
|
{$endif}
|
|
end;
|
|
|
|
{$ifdef CD_X11_UseNewTimer}
|
|
TWSTimerProc = procedure of object;
|
|
|
|
{ TCDX11Timer }
|
|
TCDX11Timer = class (TObject)
|
|
Next: TCDX11Timer;
|
|
Previous: TCDX11Timer;
|
|
Interval: Integer;
|
|
Expires: TDateTime;
|
|
func: TWSTimerProc;
|
|
constructor create (WSInterval: Integer; WSfunc: TWSTimerProc);
|
|
procedure Insert;
|
|
procedure Remove;
|
|
procedure Expired;
|
|
destructor destroy;
|
|
end;
|
|
|
|
{ TCDX11TimerThread }
|
|
|
|
TCDX11TimerThread = class(TThread)
|
|
private
|
|
rfds: TFDset;
|
|
Timeout: cint;
|
|
retval,ByteRec: integer;
|
|
protected
|
|
procedure Execute; override;
|
|
public
|
|
X11TimerPipeIn,X11TimerPipeOut: Integer; // Pipe to Timer
|
|
MainLoopPipeIn,MainLoopPipeOut: Integer; // Pipe to Main Loop
|
|
constructor Create(CreateSuspended: Boolean; const StackSize: SizeUInt=DefaultStackSize
|
|
);
|
|
end;
|
|
|
|
var
|
|
X11TimerThread: TCDX11TimerThread;
|
|
|
|
{$endif}
|
|
|
|
const
|
|
fpFD_SETSIZE = 1024; // As defined in typesizes.h
|
|
KMsToDateTime = 86400000; // # of milliseconds in a day
|
|
|
|
function RectToXRect(const ARect: TRect): TXRectangle;
|
|
function XRectToRect(const ARect: TXRectangle): TRect;
|
|
function XButtonToMouseButton(const XButton: cint; var MouseButton: TMouseButton): Boolean;
|
|
function GetXEventName(Event: LongInt): String;
|
|
|
|
implementation
|
|
{$ifdef CD_X11_UseNewTimer}
|
|
uses CustomDrawnInt;
|
|
|
|
{$endif}
|
|
|
|
function RectToXRect(const ARect: TRect): TXRectangle;
|
|
begin
|
|
Result.x := ARect.Left;
|
|
Result.y := ARect.Top;
|
|
Result.width := ARect.Right - ARect.Left;
|
|
Result.height := ARect.Bottom - ARect.Top;
|
|
end;
|
|
|
|
function XRectToRect(const ARect: TXRectangle): TRect;
|
|
begin
|
|
Result.Left := ARect.x;
|
|
Result.Top := ARect.y;
|
|
Result.Right := ARect.x + ARect.width;
|
|
Result.Bottom := ARect.y + ARect.height;
|
|
end;
|
|
|
|
{ Returns True if the button is indeed a mouse button
|
|
and False if it's the mouse wheel }
|
|
function XButtonToMouseButton(const XButton: cint; var MouseButton: TMouseButton): Boolean;
|
|
const
|
|
ButtonTable: array[1..3] of TMouseButton = (mbLeft, mbMiddle, mbRight);
|
|
begin
|
|
Result := False;
|
|
|
|
if (XButton > 3) or (XButton < 1) then Exit;
|
|
|
|
MouseButton := ButtonTable[XButton];
|
|
|
|
Result := True;
|
|
end;
|
|
|
|
function GetXEventName(Event: LongInt): String;
|
|
const
|
|
EventNames: array[2..34] of String = (
|
|
'KeyPress', 'KeyRelease', 'ButtonPress', 'ButtonRelease', 'MotionNotify',
|
|
'EnterNotify', 'LeaveNotify', 'FocusIn', 'FocusOut', 'KeymapNotify',
|
|
'Expose', 'GraphicsExpose', 'NoExpose', 'VisibilityNotify', 'CreateNotify',
|
|
'DestroyNotify', 'UnmapNotify', 'MapNotify', 'MapRequest', 'ReparentNotify',
|
|
'ConfigureNotify', 'ConfigureRequest', 'GravityNotify', 'ResizeRequest',
|
|
'CirculateNotify', 'CirculateRequest', 'PropertyNotify', 'SelectionClear',
|
|
'SelectionRequest', 'SelectionNotify', 'ColormapNotify', 'ClientMessage',
|
|
'MappingNotify');
|
|
begin
|
|
if (Event >= Low(EventNames)) and (Event <= High(EventNames)) then
|
|
Result := EventNames[Event]
|
|
else
|
|
Result := '#' + IntToStr(Event);
|
|
end;
|
|
|
|
{$ifdef CD_X11_UseNewTimer}
|
|
|
|
{ TCDX11TimerThread }
|
|
|
|
procedure TCDX11TimerThread.Execute;
|
|
var
|
|
Answ: array [0..80] of byte;
|
|
Answlen: Integer;
|
|
ANextTime: TDateTime absolute answ;
|
|
NextToExpire,TNow,TDiff: TDateTime;
|
|
HeadTimer: TCDX11Timer;
|
|
begin
|
|
retval:= AssignPipe(X11TimerPipeIn,X11TimerPipeOut);
|
|
retval:= AssignPipe(MainLoopPipeIn,MainLoopPipeOut);
|
|
WriteLn('TimerThread: Started!');
|
|
NextToExpire:= Now+10; // Ten days in future - high enough
|
|
Repeat
|
|
TNow := Now;
|
|
if NextToExpire > TNow+7 then // no timers until next week,
|
|
//or List Head just processed
|
|
Timeout:= -1 // wait until the first timer is activated
|
|
else if CDWidgetSet.XTimerListHead = nil then
|
|
Timeout:= -1
|
|
else begin
|
|
// Pick up timer which will expire first
|
|
// We must recalculate each time, because a message in between
|
|
// may have interrupted our timeout.
|
|
HeadTimer := CDWidgetSet.XTimerListHead;
|
|
NextToExpire:= HeadTimer.Expires;
|
|
// Compute how many ms from now
|
|
TDiff:= NextToExpire-Now;
|
|
Timeout:= DateTimeToMilliseconds(Tdiff);
|
|
// if already expired (we're late) handle right now
|
|
if Timeout <=0 then Timeout:= 0;
|
|
end;
|
|
// Wait for a message telling that the timer list has changed,
|
|
// until our current timer (if any) expires
|
|
fpFD_ZERO(rfds);
|
|
fpFD_SET(X11TimerPipeIn,rfds);
|
|
retval:= fpSelect(fpFD_SETSIZE,@rfds,nil,nil,Timeout);
|
|
if (retval <> 0) then begin // We've received a message
|
|
ByteRec := FileRead(X11TimerPipeIn,Answ,sizeof(Answ));
|
|
// Debugln doesn't like to be executed in a thread which isn't the MT
|
|
// and after a number of writes crashes with a DISK FULL error!
|
|
//WriteLn('TimerThread: Got message!');
|
|
if ByteRec >=SizeOf(ANextTime) then begin
|
|
if ANextTime < NextToExpire then NextToExpire:=ANextTime;
|
|
end;
|
|
//WriteLn(Format('TimerThread: Message received - NextTime= %s',[DateTimeToStr(ANextTime)]));
|
|
end
|
|
else begin // A Timer has expired - Send a message to Main Loop
|
|
// message content is irrelevant. We put Timeout for debug
|
|
ANextTime:= Timeout;
|
|
FileWrite(MainLoopPipeOut,Answ,sizeOf(Timeout));
|
|
// we don't want to send twice a messages for the same timer
|
|
// When timer is processed, the list is updateded and we will receive
|
|
// a new message. So we set NextToExpire to a value larger than any
|
|
// expectable value
|
|
NextToExpire:= TNow+10;
|
|
end;
|
|
until Terminated;
|
|
end;
|
|
|
|
constructor TCDX11TimerThread.Create(CreateSuspended: Boolean;
|
|
const StackSize: SizeUInt);
|
|
var
|
|
thisTM: TThreadManager;
|
|
begin
|
|
GetThreadManager(thisTM);
|
|
if not Assigned(thisTM.InitManager) then begin
|
|
Raise Exception.Create
|
|
('You must define UseCThread (-dUseCThreads in Project Options-> Compiler Options) in order to run this program!');
|
|
end;
|
|
inherited Create (CreateSuspended);
|
|
{Priority := 99; // it would be nice to assign priority and policy
|
|
Policy := SCHED_RR;} // but it depends on application rights to do so
|
|
FreeOnTerminate := True;
|
|
// Pipes do not yet exist. Better make it clear
|
|
MainLoopPipeIn:= -1;
|
|
MainLoopPipeOut:= -1;
|
|
X11TimerPipeIn:= -1;
|
|
X11TimerPipeOut:= -1;
|
|
end;
|
|
|
|
{ TCDX11Timer }
|
|
|
|
constructor TCDX11Timer.create(WSInterval: Integer; WSfunc: TWSTimerProc);
|
|
{$ifdef Verbose_CD_X11_Timer}
|
|
var
|
|
lTInterval: Integer;
|
|
TDiff,TNow: TDateTime;
|
|
{$endif}
|
|
begin
|
|
{$ifdef TimerUseCThreads}
|
|
if X11TimerThread.Suspended then begin
|
|
X11TimerThread.Suspended:= False; // Activate Timer Thread
|
|
end;
|
|
{$endif}
|
|
Interval:= WSInterval; // Interval in ms
|
|
Func:= WSfunc; // OnTimeEvent
|
|
Expires:= Now + Interval/KMsToDateTime; //
|
|
{$ifdef Verbose_CD_X11_Timer}
|
|
TNow:= Now;
|
|
TDiff:= Expires - TNow;
|
|
lTInterval:=DateTimeToMilliseconds(Tdiff);
|
|
DebugLn(Format('X11_Timer create: Interval= %d, Calculated=%d',[Interval,lTInterval]));
|
|
{$endif}
|
|
Previous:= Nil;
|
|
Next:= Nil;
|
|
end;
|
|
|
|
procedure TCDX11Timer.Insert;
|
|
var
|
|
lTimer,PTimer,NTimer: TCDX11Timer;
|
|
ABuffer: array[0..15] of byte;
|
|
ExpireTime: TDateTime absolute ABuffer;
|
|
begin
|
|
{$ifdef Verbose_CD_X11_Timer}
|
|
DebugLn(Format('TCDX11Timer Insert: Interval := %d',[Interval]));
|
|
{$endif}
|
|
if CDWidgetSet.XTimerListHead = nil then begin// The list is empty
|
|
CDWidgetSet.XTimerListHead:= self;
|
|
Previous:=Nil; // This is the first and only timer
|
|
Next:=Nil;
|
|
end
|
|
else begin
|
|
PTimer:=nil; // previous in list
|
|
NTimer:=nil; // Next in list
|
|
lTimer := CDWidgetSet.XTimerListHead;
|
|
while lTimer.Expires <= Expires do begin
|
|
PTimer := ltimer;
|
|
if not assigned(lTimer.Next) then Break
|
|
else lTimer:= lTimer.Next;
|
|
end;
|
|
if PTimer<>nil then begin //We're not the first one
|
|
Previous := PTimer;
|
|
NTimer := PTimer.Next;
|
|
if Assigned(NTimer) then begin
|
|
Next := NTimer;
|
|
NTimer.Previous := self;
|
|
end
|
|
else Next := Nil;
|
|
PTimer.Next := self;
|
|
end
|
|
else begin // we're in first place. previous first becomes Next
|
|
NTimer := CDWidgetSet.XTimerListHead;
|
|
CDWidgetSet.XTimerListHead := Self;
|
|
NTimer.Previous := Self;
|
|
Next:= NTimer;
|
|
Previous := nil;
|
|
end;
|
|
end;
|
|
{$ifdef TimerUseCThreads}
|
|
ExpireTime := Expires; // Copy Expire time to Buffer and send to TimerThread
|
|
FileWrite(X11TimerThread.X11TimerPipeOut,ABuffer,SizeOf(ExpireTime));
|
|
{$endif}
|
|
{$ifdef Verbose_CD_X11_Timer}
|
|
lTimer := CDWidgetSet.XTimerListHead;
|
|
while lTimer <> Nil do begin
|
|
DebugLn(Format('TCDX11Timer Insert results: Interval := %d',[lTimer.Interval]));
|
|
lTimer:= lTimer.Next;
|
|
end;
|
|
{$endif}
|
|
end;
|
|
|
|
procedure TCDX11Timer.remove;
|
|
begin
|
|
{$ifdef Verbose_CD_X11_Timer}
|
|
DebugLn(Format('TCDX11Timer Remove: Interval := %d',[Interval]));
|
|
{$endif}
|
|
if Previous <> Nil then begin
|
|
if Next <> Nil then begin
|
|
Previous.Next := Next;
|
|
Next.Previous := Previous;
|
|
end
|
|
else Previous.Next:= Nil;
|
|
end
|
|
else begin
|
|
CDWidgetSet.XTimerListHead := Next;
|
|
if Next <> nil then begin
|
|
Next.Previous:= Nil;
|
|
end;
|
|
end;
|
|
Previous:= Nil;
|
|
Next := Nil;
|
|
end;
|
|
|
|
procedure TCDX11Timer.Expired;
|
|
var
|
|
TNow: TDateTime;
|
|
{$ifdef Verbose_CD_X11_Timer}
|
|
lInterval,lTInterval: Integer;
|
|
TDiff: TDateTime;
|
|
{$endif}
|
|
begin
|
|
TNow:= Now;
|
|
Expires:= Expires+Interval/KMsToDateTime; // don't leak
|
|
while Expires <= TNow do begin // but if we're late, let's skip some! Bad kludge
|
|
Expires:= Expires+Interval/KMsToDateTime;
|
|
end;
|
|
{$ifdef Verbose_CD_X11_Timer}
|
|
TNow:= Now;
|
|
TDiff:= Expires - TNow;
|
|
lTInterval:=DateTimeToMilliseconds(Tdiff);
|
|
DebugLn(Format('X11_Timer Expired: Interval= %d, Calculated=%d',[Interval,lTInterval]));
|
|
{$endif}
|
|
Remove; // Remove from list Head
|
|
if func <> nil then
|
|
func(); // Execute OnTimer
|
|
Insert; // And insert again in right place
|
|
end;
|
|
|
|
destructor TCDX11Timer.destroy;
|
|
begin
|
|
{$ifdef Verbose_CD_X11_Timer}
|
|
DebugLn(Format('TCDX11Timer Destroy: Interval := %d',[Interval]));
|
|
{$endif}
|
|
remove;
|
|
//Free;
|
|
end;
|
|
|
|
{$endif}
|
|
|
|
end.
|
|
|