{ MSEgui Copyright (c) 1999-2016 by Martin Schreiber

    See the file COPYING.MSE, included in this distribution,
    for details about the copyright.

    This program 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.
}
unit glhotkeys;

{$ifdef FPC}{$mode objfpc}{$h+}{$endif}
{$ifndef FPC}{$ifdef linux} {$define UNIX} {$endif}{$endif}

interface

uses
 Classes,mkeysym,mclasses,mseclasses,mseguiintf,mx,mxlib,sysutils;

type
 TGlobalHotKey = packed record
	// common
	Active		: bytebool; // hotkey on/off | выкп./вкл. горячую клавишу
	Key		: dword; // what key | название кнопки
	Ctrl		: bytebool; // need Ctrl pressed | с нажатым контролом
	Shift		: bytebool; // need Shift pressed | с нажатым шифтом
	Alt		: bytebool; // need Alt pressed | с нажатым альтом
	ActionNumber	: dword; // number of the calling action | номер действия, вызываемого горячей клавишей
	// xorg
	AnyMod		: bytebool; // key with any modifiers | кнопка с любыми модификаторами
	NumLock		: bytebool; // Mod2Mask
	CapsLock	: bytebool; // LockMask
	R0,R1,R2,R3,R4	: bytebool; // results of registrations | результаты регистраций
 end;

tglobalhotkeyprocedure = procedure(a : Int64) of object;

tglobalhotkeys = class(tmsecomponent)
public
 fHotKeys	: array of TGlobalHotKey;
 proc : tglobalhotkeyprocedure;
 procedure ResetHotKeys;
 procedure RegisterHotKeys;
 procedure UnRegisterHotKeys;
 function CheckForGlobalHK(k : TXKeyEvent) : boolean; virtual;
private
 fTest : Int64;
 procedure SetTest(Value : Int64);
 function GetTest : Int64;
published
 property Test: Int64 read GetTest write SetTest default 3;
public
 constructor create(aowner: tcomponent); override;
 destructor free;
end;

type
 // may be move this declaration in mseguiintf.pas to interface section ?
 XErrorHandler = function(Display: PDisplay; ErrorEvent: PXErrorEvent): Longint; cdecl;

var
 ghkehandler: xerrorhandler;

implementation

procedure tglobalhotkeys.SetTest(Value : Int64);
begin
fTest := Value;
end;

function tglobalhotkeys.GetTest : Int64;
begin
Exit(fTest);
end;

procedure tglobalhotkeys.ResetHotKeys;
var
 f : dword;
begin
for f := 0 to High(fHotKeys) do
 with fHotKeys[f] do begin
	Active		:= false;
	Key		:= 32;
	Ctrl		:= false;
	Shift		:= false;
	Alt		:= false;
	ActionNumber	:= f;
	// -----------------------
	AnyMod		:= false;
	NumLock		:= false;
	CapsLock	:= false;
 end;
end;

procedure UnRegisterHotKey(k : TGlobalHotKey);
var
 m : dword;
 appdisp: pdisplay;
begin
appdisp := msedisplay;
m := 0;
if k.AnyMod and k.R0 then begin
 if k.R0 then XUnGrabKey(Pointer(appdisp), k.Key, AnyModifier, gui_getrootwindow);
end else begin
 if k.Ctrl then  m := m or ControlMask;
 if k.Shift then m := m or ShiftMask;
 if k.Alt then   m := m or Mod1Mask;
  if k.R1 then XUnGrabKey(Pointer(appdisp), k.Key, m, gui_getrootwindow);
 if k.NumLock then
  if k.R2 then XUnGrabKey(Pointer(appdisp), k.Key, m or Mod2Mask, gui_getrootwindow);
 if k.CapsLock then
  if k.R3 then XUnGrabKey(Pointer(appdisp), k.Key, m or LockMask, gui_getrootwindow);
 if (k.NumLock and k.CapsLock) then
  if k.R4 then XUnGrabKey(Pointer(appdisp), k.Key, m or LockMask or Mod2Mask, gui_getrootwindow);
end;
end;

procedure tglobalhotkeys.RegisterHotKeys;
function e : bytebool;
begin
//Sleep(500);
Exit(ghklastf);
end;
var
 f : Int64;
//    u : UInt;
 m : dword;
 appdisp: pdisplay;
begin
// win
//   for f := 0 to max_hot_keys do begin
//   if tun.HotKeys[f].active then begin
//    u := 0;
//    if tun.HotKeys[f].ctrl then  u := u or MOD_CONTROL;
//    if tun.HotKeys[f].shift then u := u or MOD_SHIFT;
//    if tun.HotKeys[f].alt then   u := u or MOD_ALT;
//    tun.HotKeys[f].active := RegisterHotKey(app.hwnd, f, u, tun.HotKeys[f].key);
//    if not tun.HotKeys[f].active then MessageBox(app.hwnd, PCHar(err_not_reg_hot_key + hot_key_action_namws[f]), PCHar(app_name), 0);
//   end; {end if}
//   end; {next f}

appdisp := msedisplay;

ghkehandler:= xseterrorhandler({$ifdef FPC}@{$endif}globalhotkeyserrorhandler);
//ghkehandler:= xseterrorhandler(nil);

for f := 0 to High(fHotKeys) do begin
if fHotKeys[f].Active then begin
 m := 0;
 if fHotKeys[f].AnyMod then begin
  ghklastf := true;
  XGrabKey(Pointer(appdisp), fHotKeys[f].Key, AnyModifier, gui_getrootwindow, 1, GrabModeAsync, GrabModeAsync);
  XSync(Pointer(appdisp),true);
  fHotKeys[f].R0 := e;
  fHotKeys[f].Active := fHotKeys[f].R0;
 end else begin
  if fHotKeys[f].Ctrl then  m := m or ControlMask;
  if fHotKeys[f].Shift then m := m or ShiftMask;
  if fHotKeys[f].Alt then   m := m or Mod1Mask;
  ghklastf := true;
  XGrabKey(Pointer(appdisp), fHotKeys[f].Key, m, gui_getrootwindow, 1, GrabModeAsync, GrabModeAsync);
  XSync(Pointer(appdisp),true);
  fHotKeys[f].R1 := e;
  ghklastf := true;
  if fHotKeys[f].NumLock then
   XGrabKey(Pointer(appdisp), fHotKeys[f].Key, m or Mod2Mask, gui_getrootwindow, 0, GrabModeAsync, GrabModeAsync);
  XSync(Pointer(appdisp),true);
  fHotKeys[f].R2 := e;
  ghklastf := true;
  if fHotKeys[f].CapsLock then
   XGrabKey(Pointer(appdisp), fHotKeys[f].Key, m or LockMask, gui_getrootwindow, 0, GrabModeAsync, GrabModeAsync);
  XSync(Pointer(appdisp),true);
  fHotKeys[f].R3 := e;
 ghklastf := true;
  if (fHotKeys[f].NumLock and fHotKeys[f].CapsLock) then
   XGrabKey(Pointer(appdisp), fHotKeys[f].Key, m or LockMask or Mod2Mask, gui_getrootwindow, 0, GrabModeAsync, GrabModeAsync);
  XSync(Pointer(appdisp),true);
  fHotKeys[f].R4 := e;
  fHotKeys[f].Active := fHotKeys[f].R1 and fHotKeys[f].R2 and fHotKeys[f].R3 and fHotKeys[f].R4;
 end;
end; {end if}
 if not fHotKeys[f].Active then
 begin
	WriteLn('Register GlobalHotkey failed (holded by other application?) ', f);
	UnRegisterHotKey(fHotKeys[f]);
 end;
end; {next f}
// Need or not XSelectInput under mse?
XSelectInput(Pointer(appdisp), gui_getrootwindow, KeyPressMask);
end;

procedure tglobalhotkeys.UnRegisterHotKeys;
var
 f : Int64;
begin
// win
//   for f := 0 to max_hot_keys do begin
//   UnregisterHotKey(app.hwnd, f);
//   end; {next f}
for f := 0 to High(fHotKeys) do if fHotKeys[f].Active then UnRegisterHotKey(fHotKeys[f]);
end;

// CheckForGlobalHotKey
function tglobalhotkeys.CheckForGlobalHK(k : TXKeyEvent) : boolean;
var
 f : Int64;
 ctrl,shift,alt : bytebool;
begin
ctrl := (k.state and ControlMask) = ControlMask;
shift := (k.state and ShiftMask) = ShiftMask;
alt := (k.state and Mod1Mask) = Mod1Mask;
for f := 0 to High(fHotKeys) do begin
if ( ((fHotKeys[f].key = k.keycode) and (fHotKeys[f].Ctrl = Ctrl) and (fHotKeys[f].Shift = Shift) and (fHotKeys[f].Alt = Alt))
or ((fHotKeys[f].key = k.keycode) and fHotKeys[f].AnyMod) ) and fHotKeys[f].Active then begin
WriteLn('[#] Global Hotkey pressed');
proc(fHotKeys[f].ActionNumber);
Exit(true);
end;
end; {next f}
Exit(false);
end;

constructor tglobalhotkeys.Create(aowner: tcomponent);
begin
checkforglobalhotkey := @CheckForGlobalHK;
inherited;
end;

destructor tglobalhotkeys.Free;
begin
checkforglobalhotkey := nil;
inherited;
end;

end.