mirror of
https://github.com/moparisthebest/Simba
synced 2024-12-17 21:13:22 -05:00
536 lines
14 KiB
ObjectPascal
536 lines
14 KiB
ObjectPascal
{
|
|
This file is part of the Mufasa Macro Library (MML)
|
|
Copyright (c) 2009 by Raymond van Venetië and Merlijn Wajer
|
|
|
|
MML is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
MML is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with MML. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
See the file COPYING, included in this distribution,
|
|
for details about the copyright.
|
|
|
|
Linux OS specific implementation for Mufasa Macro Library
|
|
}
|
|
{$mode objfpc}{$H+}
|
|
unit os_linux;
|
|
|
|
{
|
|
TODO's:
|
|
- Allow selecting a different X display
|
|
- Fix keyboard layout / SendString
|
|
}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, mufasatypes, xlib, x, xutil, IOManager, XKeyInput, ctypes, xtest,
|
|
syncobjs, mufasabase;
|
|
|
|
type
|
|
|
|
TNativeWindow = x.TWindow;
|
|
|
|
TKeyInput = class(TXKeyInput)
|
|
public
|
|
procedure Down(Key: Word);
|
|
procedure Up(Key: Word);
|
|
end;
|
|
|
|
{ TWindow }
|
|
|
|
TWindow = class(TWindow_Abstract)
|
|
public
|
|
constructor Create(display: PDisplay; screennum: integer; window: x.TWindow);
|
|
destructor Destroy; override;
|
|
procedure GetTargetDimensions(out w, h: integer); override;
|
|
procedure GetTargetPosition(out left, top: integer); override;
|
|
function ReturnData(xs, ys, width, height: Integer): TRetData; override;
|
|
procedure FreeReturnData; override;
|
|
|
|
function GetError: String; override;
|
|
function ReceivedError: Boolean; override;
|
|
procedure ResetError; override;
|
|
|
|
function TargetValid: boolean; override;
|
|
procedure ActivateClient; override;
|
|
procedure GetMousePosition(out x,y: integer); override;
|
|
procedure MoveMouse(x,y: integer); override;
|
|
procedure HoldMouse(x,y: integer; button: TClickType); override;
|
|
procedure ReleaseMouse(x,y: integer; button: TClickType); override;
|
|
function IsMouseButtonHeld( button : TClickType) : boolean;override;
|
|
|
|
procedure SendString(str: string); override;
|
|
procedure HoldKey(key: integer); override;
|
|
procedure ReleaseKey(key: integer); override;
|
|
function IsKeyHeld(key: integer): boolean; override;
|
|
function GetKeyCode(c : char) : integer;override;
|
|
|
|
function GetNativeWindow: TNativeWindow;
|
|
private
|
|
{ display is the connection to the X server }
|
|
display: PDisplay;
|
|
|
|
{ screen-number and selected window }
|
|
screennum: integer;
|
|
window: x.TWindow;
|
|
|
|
{ Reference to the XImage }
|
|
buffer: PXImage;
|
|
|
|
{ For memory-leak checks }
|
|
dirty: Boolean; //true if image loaded
|
|
|
|
{ KeyInput class }
|
|
keyinput: TKeyInput;
|
|
|
|
{ X Error Handler }
|
|
oldXHandler: TXErrorHandler;
|
|
end;
|
|
|
|
TIOManager = class(TIOManager_Abstract)
|
|
public
|
|
constructor Create;
|
|
constructor Create(plugin_dir: string);
|
|
function SetTarget(target: TNativeWindow): integer; overload;
|
|
procedure SetDesktop; override;
|
|
|
|
function GetProcesses: TSysProcArr; override;
|
|
procedure SetTargetEx(Proc: TSysProc); overload;
|
|
private
|
|
procedure NativeInit; override;
|
|
procedure NativeFree; override;
|
|
public
|
|
display: PDisplay;
|
|
screennum: integer;
|
|
desktop: x.TWindow;
|
|
end;
|
|
|
|
function MufasaXErrorHandler(para1:PDisplay; para2:PXErrorEvent):cint; cdecl;
|
|
|
|
implementation
|
|
|
|
uses GraphType, interfacebase, lcltype;
|
|
|
|
{ PROBLEM: .Create is called on the main thread. ErrorCS etc aren't
|
|
created on other threads. We will create them on the fly...
|
|
More info below...}
|
|
threadvar
|
|
xerror: string;
|
|
threadvar
|
|
ErrorCS: syncobjs.TCriticalSection;
|
|
|
|
|
|
{
|
|
This is extremely hacky, but also very useful.
|
|
We have to install a X error handler, because otherwise X
|
|
will terminate our entire app on error.
|
|
|
|
Since we want the right thread to recieve the right error, we have to
|
|
fiddle a bit with threadvars, mutexes / semaphores.
|
|
|
|
Another problem is that the script thread is initialised on the main thread.
|
|
This means that all (threadvar!) semaphores initialised on the mainthread
|
|
are NOT initialised on the script thread, which has yet to be started.
|
|
Therefore, we check if it hasn't been created yet.
|
|
|
|
** Horrible solution, but WFM **
|
|
|
|
This is the Handler function.
|
|
|
|
}
|
|
|
|
function MufasaXErrorHandler(para1:PDisplay; para2:PXErrorEvent):cint; cdecl;
|
|
|
|
begin
|
|
case para2^.error_code of
|
|
1: xerror := 'BadRequest';
|
|
2: xerror := 'BadValue';
|
|
3: xerror := 'BadWindow';
|
|
4: xerror := 'BadPixmap';
|
|
5: xerror := 'BadAtom';
|
|
6: xerror := 'BadCursor';
|
|
7: xerror := 'BadFont';
|
|
8: xerror := 'BadMatch';
|
|
9: xerror := 'BadDrawable';
|
|
10: xerror := 'BadAccess';
|
|
11: xerror := 'BadAlloc';
|
|
12: xerror := 'BadColor';
|
|
13: xerror := 'BadGC';
|
|
14: xerror := 'BadIDChoice';
|
|
15: xerror := 'BadName';
|
|
16: xerror := 'BadLength';
|
|
17: xerror := 'BadImplementation';
|
|
else
|
|
xerror := 'UNKNOWN';
|
|
end;
|
|
result := 0;
|
|
mDebugLn('X Error: ' + xerror);
|
|
mDebugLn('Error code: ' + inttostr(para2^.error_code));
|
|
mDebugLn('Display: ' + inttostr(LongWord(para2^.display)));
|
|
mDebugLn('Minor code: ' + inttostr(para2^.minor_code));
|
|
mDebugLn('Request code: ' + inttostr(para2^.request_code));
|
|
mDebugLn('Resource ID: ' + inttostr(para2^.resourceid));
|
|
mDebugLn('Serial: ' + inttostr(para2^.serial));
|
|
mDebugLn('Type: ' + inttostr(para2^._type));
|
|
end;
|
|
|
|
{ TKeyInput }
|
|
|
|
procedure TKeyInput.Down(Key: Word);
|
|
begin
|
|
DoDown(Key);
|
|
end;
|
|
|
|
procedure TKeyInput.Up(Key: Word);
|
|
begin
|
|
DoUp(Key);
|
|
end;
|
|
|
|
{ TWindow }
|
|
|
|
function TWindow.GetError: String;
|
|
begin
|
|
exit(xerror);
|
|
end;
|
|
|
|
function TWindow.ReceivedError: Boolean;
|
|
begin
|
|
result := xerror <> '';
|
|
end;
|
|
|
|
procedure TWindow.ResetError;
|
|
begin
|
|
xerror := '';
|
|
end;
|
|
|
|
{ See if the semaphores / CS are initialised }
|
|
constructor TWindow.Create(display: PDisplay; screennum: integer; window: x.TWindow);
|
|
begin
|
|
inherited Create;
|
|
self.display:= display;
|
|
self.screennum:= screennum;
|
|
self.window:= window;
|
|
self.dirty:= false;
|
|
self.keyinput:= TKeyInput.Create;
|
|
|
|
xerror := '';
|
|
|
|
{ XXX FIXME TODO O GOD WTF }
|
|
if not assigned(ErrorCS) then
|
|
ErrorCS := syncobjs.TCriticalSection.Create;
|
|
ErrorCS.Enter;
|
|
try
|
|
oldXHandler:=XSetErrorHandler(@MufasaXErrorHandler);
|
|
finally
|
|
ErrorCS.Leave;
|
|
end;
|
|
end;
|
|
|
|
destructor TWindow.Destroy;
|
|
var
|
|
erh: TXErrorHandler;
|
|
begin
|
|
FreeReturnData;
|
|
keyinput.Free;
|
|
|
|
{ XXX FIXME TODO O GOD WTF }
|
|
if not assigned(ErrorCS) then
|
|
ErrorCS := syncobjs.TCriticalSection.Create;
|
|
ErrorCS.Enter;
|
|
|
|
erh := XSetErrorHandler(oldXHandler);
|
|
try
|
|
if erh <> @MufasaXErrorHandler then
|
|
XSetErrorHandler(erh);
|
|
finally
|
|
ErrorCS.Leave;
|
|
end;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TWindow.GetNativeWindow: TNativeWindow;
|
|
begin
|
|
result := self.window;
|
|
end;
|
|
|
|
procedure TWindow.GetTargetDimensions(out w, h: integer);
|
|
var
|
|
Attrib: TXWindowAttributes;
|
|
begin
|
|
if XGetWindowAttributes(display, window, @Attrib) <> 0 Then
|
|
begin
|
|
W := Attrib.Width;
|
|
H := Attrib.Height;
|
|
end else
|
|
begin
|
|
W := -1;
|
|
H := -1;
|
|
end;
|
|
end;
|
|
|
|
procedure TWindow.GetTargetPosition(out left, top: integer);
|
|
var
|
|
Attrib: TXWindowAttributes;
|
|
begin
|
|
if XGetWindowAttributes(display, window, @Attrib) <> 0 Then
|
|
begin
|
|
left := Attrib.x;
|
|
top := attrib.y;
|
|
end else
|
|
begin
|
|
// XXX: this is tricky; what do we return when it doesn't exist?
|
|
// The window can very well be at -1, -1. We'll return 0 for now.
|
|
left := 0;
|
|
top := 0;
|
|
end;
|
|
end;
|
|
|
|
function TWindow.TargetValid: boolean;
|
|
var
|
|
Attrib: TXWindowAttributes;
|
|
begin
|
|
XGetWindowAttributes(display, window, @Attrib);
|
|
result := not ReceivedError;
|
|
end;
|
|
|
|
procedure TWindow.ActivateClient;
|
|
|
|
begin
|
|
XSetInputFocus(display,window,RevertToParent,CurrentTime);
|
|
XFlush(display);
|
|
if ReceivedError then
|
|
raise Exception.Create('Error: ActivateClient: ' + GetError);
|
|
end;
|
|
|
|
function TWindow.ReturnData(xs, ys, width, height: Integer): TRetData;
|
|
var
|
|
w,h: integer;
|
|
begin
|
|
GetTargetDimensions(w,h);
|
|
if (xs < 0) or (xs + width > w) or (ys < 0) or (ys + height > h) then
|
|
raise Exception.CreateFMT('TMWindow.ReturnData: The parameters passed are wrong; xs,ys %d,%d width,height %d,%d',[xs,ys,width,height]);
|
|
if dirty then
|
|
raise Exception.CreateFmt('ReturnData was called again without freeing'+
|
|
' the previously used data. Do not forget to'+
|
|
' call FreeReturnData', []);
|
|
|
|
buffer := XGetImage(display, window, xs, ys, width, height, AllPlanes, ZPixmap);
|
|
if buffer = nil then
|
|
begin
|
|
mDebugLn('ReturnData: XGetImage Error. Dumping data now:');
|
|
mDebugLn('xs, ys, width, height: ' + inttostr(xs) + ', ' + inttostr(ys) +
|
|
', ' + inttostr(width) + ', ' + inttostr(height));
|
|
Result.Ptr := nil;
|
|
Result.IncPtrWith := 0;
|
|
raise Exception.CreateFMT('TMWindow.ReturnData: ReturnData: XGetImage Error', []);
|
|
exit;
|
|
end;
|
|
Result.Ptr := PRGB32(buffer^.data);
|
|
Result.IncPtrWith := 0;
|
|
Result.RowLen := width;
|
|
dirty:= true;
|
|
//XSetErrorHandler(Old_Handler);
|
|
end;
|
|
|
|
procedure TWindow.FreeReturnData;
|
|
begin
|
|
if dirty then
|
|
begin
|
|
if (buffer <> nil) then
|
|
XDestroyImage(buffer);
|
|
buffer:= nil;
|
|
dirty:= false;
|
|
end;
|
|
end;
|
|
|
|
procedure TWindow.GetMousePosition(out x,y: integer);
|
|
var
|
|
b:integer;
|
|
root, child: twindow;
|
|
xmask: Cardinal;
|
|
|
|
begin
|
|
XQueryPointer(display,window,@root,@child,@b,@b,@x,@y,@xmask);
|
|
end;
|
|
|
|
procedure TWindow.MoveMouse(x,y: integer);
|
|
var
|
|
w,h: integer;
|
|
begin
|
|
GetTargetDimensions(w, h);
|
|
if (x < 0) or (y < 0) or (x > w) or (y > h) then
|
|
raise Exception.CreateFmt('SetMousePos: X, Y (%d, %d) is not valid (0,0,%d,%d)', [x, y, w, h]);
|
|
XWarpPointer(display, 0, window, 0, 0, 0, 0, X, Y);
|
|
XFlush(display);
|
|
end;
|
|
|
|
procedure TWindow.HoldMouse(x,y: integer; button: TClickType);
|
|
var
|
|
ButtonP: cuint;
|
|
_isPress: cbool;
|
|
|
|
begin
|
|
_isPress := cbool(1);
|
|
case button of
|
|
mouse_Left: ButtonP:= Button1;
|
|
mouse_Middle: ButtonP:= Button2;
|
|
mouse_Right: ButtonP:= Button3;
|
|
end;
|
|
XTestFakeButtonEvent(display, ButtonP, _isPress, CurrentTime);;
|
|
end;
|
|
|
|
procedure TWindow.ReleaseMouse(x,y: integer; button: TClickType);
|
|
var
|
|
ButtonP: cuint;
|
|
_isPress: cbool;
|
|
begin
|
|
_isPress := cbool(0);
|
|
case button of
|
|
mouse_Left: ButtonP:= Button1;
|
|
mouse_Middle: ButtonP:= Button2;
|
|
mouse_Right: ButtonP:= Button3;
|
|
end;
|
|
XTestFakeButtonEvent(display, ButtonP, _isPress, CurrentTime);
|
|
end;
|
|
|
|
function TWindow.IsMouseButtonHeld(button: TClickType): boolean;
|
|
var
|
|
b:integer;
|
|
root, child: twindow;
|
|
xmask: Cardinal;
|
|
ButtonP: cuint;
|
|
|
|
begin
|
|
XQueryPointer(display,window,@root,@child,@b,@b,@b,@b,@xmask);
|
|
case button of
|
|
mouse_Left: ButtonP:= Button1Mask;
|
|
mouse_Middle: ButtonP:= Button2Mask;
|
|
mouse_Right: ButtonP:= Button3Mask;
|
|
end;
|
|
result := xmask and ButtonP > 0;
|
|
end;
|
|
|
|
{ TODO: Check if this supports multiple keyboard layouts, probably not }
|
|
procedure TWindow.SendString(str: string);
|
|
var
|
|
I, L: Integer;
|
|
K: Byte;
|
|
HoldShift: Boolean;
|
|
begin
|
|
HoldShift := False;
|
|
L := Length(str);
|
|
for I := 1 to L do
|
|
begin
|
|
if (((str[I] >= 'A') and (str[I] <= 'Z')) or
|
|
((str[I] >= '!') and (str[I] <= '&')) or
|
|
((str[I] >= '(') and (str[I] <= '+')) or
|
|
(str[I] = ':') or
|
|
((str[I] >= '<') and (str[I] <= '@')) or
|
|
((str[I] >= '^') and (str[I] <= '_')) or
|
|
((str[I] >= '{') and (str[I] <= '~'))) then
|
|
begin
|
|
HoldKey(VK_SHIFT);
|
|
HoldShift := True;
|
|
end;
|
|
|
|
K := GetKeyCode(str[I]);
|
|
HoldKey(K);
|
|
Sleep(20);
|
|
ReleaseKey(K);
|
|
|
|
if (HoldShift) then
|
|
begin
|
|
HoldShift := False;
|
|
ReleaseKey(VK_SHIFT);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TWindow.HoldKey(key: integer);
|
|
begin
|
|
keyinput.Down(key);
|
|
end;
|
|
|
|
procedure TWindow.ReleaseKey(key: integer);
|
|
begin
|
|
keyinput.Up(key);
|
|
end;
|
|
|
|
function TWindow.IsKeyHeld(key: integer): boolean;
|
|
begin
|
|
raise Exception.CreateFmt('IsKeyDown isn''t implemented yet on Linux', []);
|
|
end;
|
|
|
|
function TWindow.GetKeyCode(c: char): integer;
|
|
begin
|
|
case C of
|
|
'0'..'9' :Result := VK_0 + Ord(C) - Ord('0');
|
|
'a'..'z' :Result := VK_A + Ord(C) - Ord('a');
|
|
'A'..'Z' :Result := VK_A + Ord(C) - Ord('A');
|
|
' ' : result := VK_SPACE;
|
|
else
|
|
Raise Exception.CreateFMT('GetSimpleKeyCode - char (%s) is not in A..z',[c]);
|
|
end
|
|
end;
|
|
|
|
{ ***implementation*** IOManager }
|
|
|
|
constructor TIOManager.Create;
|
|
begin
|
|
inherited Create;
|
|
end;
|
|
|
|
constructor TIOManager.Create(plugin_dir: string);
|
|
begin
|
|
inherited Create(plugin_dir);
|
|
end;
|
|
|
|
procedure TIOManager.NativeInit;
|
|
begin
|
|
display := XOpenDisplay(nil);
|
|
if display = nil then
|
|
raise Exception.Create('Could not open a connection to the X Display');
|
|
|
|
{ DefaultScreen }
|
|
screennum:= DefaultScreen(display);
|
|
|
|
{ Get the Desktop Window }
|
|
desktop:= RootWindow(display,screennum)
|
|
end;
|
|
|
|
procedure TIOManager.NativeFree;
|
|
begin
|
|
XCloseDisplay(display);
|
|
end;
|
|
|
|
procedure TIOManager.SetDesktop;
|
|
begin
|
|
SetBothTargets(TWindow.Create(display, screennum, desktop));
|
|
end;
|
|
|
|
function TIOManager.SetTarget(target: x.TWindow): integer;
|
|
begin
|
|
result := SetBothTargets(TWindow.Create(display, screennum, target))
|
|
end;
|
|
|
|
function TIOManager.GetProcesses: TSysProcArr;
|
|
begin
|
|
raise Exception.Create('GetProcesses: Not Implemented.');
|
|
end;
|
|
|
|
procedure TIOManager.SetTargetEx(Proc: TSysProc);
|
|
begin
|
|
raise Exception.Create('SetTargetEx: Not Implemented.');
|
|
end;
|
|
|
|
end.
|