unit main;

{
    Main unit.
    For GNU/Linux 64 bit version.
    Version: 1.
    Written on FreePascal (https://freepascal.org/).
    Copyright (C) 1995-2025  Artyomov Alexander
    http://self-made-free.ru/
    aralni@mail.ru

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as
    published by the Free Software Foundation, either version 3 of the
    License, or (at your option) any later version.

    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.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
}

{$ifdef FPC}{$mode objfpc}{$h+}{$endif}

{$RANGECHECKS ON}
{$GOTO ON}
{$CODEPAGE UTF8}
{$ASMMODE INTEL}

interface

uses
 msekeyboard, algor, classes, SysUtils, DBus, mselibc, msepointer, mseglob,
 mseguiglob,mseguiintf,mseapplication, msegui,msegraphutils,mseevent,mseclasses,
 mseforms,msesimplewidgets, msewidgets, msebitmap,msegraphics,time,msemenus,
 msethreadcomp, msestrings, msetypes, mseact,unix,baseunix,linux,msetimer,
 msedock,msedragglob,msewidgetgrid,msesyntaxedit,x,xlib,mxlib,mkeysym,queue,
 ttafunctions,syscall,lng,math,lnglist,menu_prog,glhkindex,lazutf8,wmtest,proglist;

procedure clipmon;

const
  DBUS_SERVICE = 'org.freedesktop.login1';
  DBUS_PATH = '/org/freedesktop/login1';
  DBUS_INTERFACE = 'org.freedesktop.login1.Manager';

type 
 TGlobalHotKey = packed record
	// common
	Active		: bytebool; // hotkey on/off | выкп./вкл. горячую клавишу
	KeyIdx		: TGhkIdx; // Key index (in array of XK_)
	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;

  tmainfo = class(tmainform)
   tpaintbox1: tpaintbox;
   tpaintbox2: tpaintbox;
   tpaintbox3: tpaintbox;
   tlabel2: tlabel;
   tpopupmenu1: tpopupmenu;
   tthreadcomp3: tthreadcomp;
   tthreadcomp4: tthreadcomp;
   tthreadcomp5: tthreadcomp;
   ttimer1: ttimer;
   tfacecomp1: tfacecomp;
   tfacecomp2: tfacecomp;
   tfacecomp3: tfacecomp;
   tfacecomp4: tfacecomp;
   tfacecomp5: tfacecomp;
   tfacecomp6: tfacecomp;
   tframecomp2: tframecomp;
   timagelist1: timagelist;
   timagelist2: timagelist;
   tbutton2: tbutton;
   tpopupmenu2: tpopupmenu;
   trichbutton1: trichbutton;
   tbutton3: tbutton;
   trichbutton2: trichbutton;
   tpopupmenu3: tpopupmenu;
    procedure onquit(Const Sender: TObject);
    procedure oncreate(Const Sender: TObject);
    procedure onmouseev(Const Sender: twidget; Var ainfo: mouseeventinfoty);
   procedure menuexit(const sender: TObject);
   procedure onmute(const sender: TObject);
   procedure soundplay(const sender: tthreadcomp);
   procedure scriptrun(const sender: tthreadcomp);
   procedure onfixation(const sender: TObject);
   procedure onnoact(const sender: TObject);
   procedure ShowP(a : Int64);
   procedure ShowO(a : Int64);
   procedure ShowEE(a : Int64);
   procedure ShowELE;
   procedure ShowFlash;
   procedure ShowClockPanel;
   procedure ShowAlarm;
   procedure ShowShutdown;
   procedure ShowSettings;
   procedure ShowNotebook;
   procedure ShowYearList;
   procedure ShowHelp;
   procedure onflash(const sender: TObject);
   procedure soundrec(const sender: tthreadcomp);
   procedure DisplayMuteNoact;
   procedure Display;
   procedure ChangeLang;
   procedure Action(i : Int64);
   procedure doubleclick_action;
   function CheckForGlobalHK(k : TXKeyEvent) : boolean; virtual;
   procedure RegisterHotKeys;
   procedure LoadHotKeysFromFile(fn: utf8string);
   procedure onclockpanel(const sender: TObject);
   procedure onneeds(const sender: TObject);
   procedure onele(const sender: TObject);
   procedure onsettings(const sender: TObject);
   procedure onaddev(const sender: TObject);
   procedure onnotebook(const sender: TObject);
   procedure onaddo(const sender: TObject);
   procedure onaddp(const sender: TObject);
   procedure onhelp(const sender: TObject);
   procedure onyearlist(const sender: TObject);
   procedure onbutton(const sender: TObject);
   procedure onshutdown(const sender: TObject);
   procedure onmenuprog(const sender: TObject);
   procedure onm2execute(const sender: TObject);
   procedure onproclist(const sender: TObject);
   procedure onproclistexec(const sender: TObject);
   procedure onpaintev(const sender: twidget; const acanvas: tcanvas);
   procedure onloopev(const sender: TObject);
   procedure onsetheight(avalue: Integer);
   procedure updatelang();
   procedure onmousereboot(const sender: twidget; var ainfo: mouseeventinfoty);
  public
	fHotKeys       : array of TGlobalHotKey;
procedure toabove;
    procedure InitializeInhibit;
    procedure ReleaseInhibit;
//    procedure HandleDBusSignal(msg: PDBusMessage);
    procedure InitializeSignalHandler;
//    procedure StartDBusLoop;
    procedure HandlePrepareForShutdown(msg: PDBusMessage);
    procedure HandlePrepareForSleep(msg: PDBusMessage);
    procedure HandleShutdown(msg: PDBusMessage);
    procedure HandleReboot(msg: PDBusMessage);
    procedure SendEndSessionResponse(IsReady: Boolean);
    procedure HandleEndSession(msg: PDBusMessage);
    procedure HandleQueryEndSession(msg: PDBusMessage);
  end;

  TTW = packed record
   tone, duration : Word;
  end;
  PTW=^TTW;

type
DBusBool = LongBool; // LongBool лучше подходит для логических значений
tvolume = 0..100;
TTu = packed record
    fmute, fnoact       : bytebool;
    al_dlg_mem_action   : byte;
    clip_mon, sync_snd  : bytebool;
    record_prog : byte;
    engtrue_hour_fmt    : bytebool;
    engtrue_calend_fmt  : bytebool;
    flash_accmulate     : bytebool;
    main_left, main_top, main_height : integer;
    ee_left, ee_top, ee_width, ee_height : integer;
    ee_maximize : bytebool;
    book_left, book_top, book_width, book_height : integer;
    book_maximize : bytebool;
    lang_code : shortstring;
    f_force_off, engtrue_calend_layout{f_pwr_off} : bytebool;
    main_fixation : bytebool;
    main_doubleclick_action : Int64;
    flash_left, flash_top, flash_width, flash_height : SmallInt;
    flash_maximize : bytebool;
    siz_clock      : SmallInt;  // width of clock panel
    am_pm : array[false..true] of string[255];
    small_screen : bytebool;
    volume : tvolume;
    volumeon : bytebool;
end;
PTu = ^TTu;
    TTun = packed object
	p : PTu;
  a : Int64;
  fFileName: utf8string;
  fs : Int64;
    protected
	procedure SetMute(Value : bytebool);
	procedure SetNoAct(Value : bytebool);
	procedure SetFixation(Value : bytebool);
	procedure SetLangCode(Value : shortstring);
	function GetLangCode : shortstring;
function r_m : bytebool;
function r_n : bytebool;
function r_f : bytebool;
    private
    public
	function map : boolean;
	function unmap : boolean;
	function Load : boolean;
	property Mute : bytebool read r_m write SetMute;
	property NoAct : bytebool read r_n write SetNoAct;
	property Fixation : bytebool read r_f write SetFixation;
	property LangCode : shortstring read GetLangCode write SetLangCode;	
    end;  
  
var
  mainfo: tmainfo;
  gt  : TThread;
  ispressed: Boolean = False;
  oripoint: pointty;
  year, month, day, hour, minute : LongInt;
  t : TSystemTime;
  firstrun : boolean = true;
  needclockpanel : tclockpanelaction = 0;
  needshutdown : tclockpanelaction = 0;
  needalarm : boolean = false;
  needeefoclose : boolean = false;
  needelefodisplay:boolean = false;
  needafterrec:bytebool = false;
  NeedDelSndFil : boolean = false;
  tun : TTun;
  menuprog: TMenuProg;
  mfiles,rfile : msestring;
  sf_queue : tqueue;
  shfiles : filenamearty;
  homedir : msestring;
lang : msestring;
ruenv : bytebool = false;
  workdir : msestring = '.gorg64';
  musicdir : msestring = 'music';
  sounddir : msestring = 'sound';
  flashdir : msestring = 'flash';
  flashfile : msestring = 'flash.txt';
  langdir : msestring = 'lang_s';
  flashfp : Text;
  flashhotlistfile : msestring = 'flashhotlist.txt';
  flashhotlistfp : Text;
  flashstrdividerfile : msestring = 'flash_str_divider.txt';
  scriptdir : msestring = 'script';
  menuprogdir  : msestring = 'menuprog';
  tunfile : msestring = 'tun.rwo';
  ghkfile : msestring = 'globalhotkeys.txt';
  gorgfile : msestring = 'gorg.txt';
  gorgfp : Text;
  hotlistfile : msestring = 'gorghotlist.txt';
  hotlistfp : Text;
  historyfile : msestring = 'gorghistory.txt';
  historyfp : Text;
  peoplesfile : msestring = 'peoples.txt';
  peoplesfp : Text;
  organizationsfile : msestring = 'organizations.txt';
  organizationsfp : Text;
  yearlistfile : msestring = 'yearlist.txt';
  yearlistfp : Text;
  processrecord : boolean = false;
  canspkplay : boolean = true;
  playsoundstate : Int64 = 0;
  mfilesar : filenamearty;
  arecord_cl : utf8string = '';
  arecord_fn : utf8string = 'arecord_commandline.txt';
  rec_cl : utf8string = '';
  rec_fn : utf8string = 'rec_commandline.txt';
  wavrec_cl : utf8string = '';
  wavrec_fn : utf8string = 'wavrec_commandline.txt';
  xmp_cl : utf8string = '';
  xmp_fn : utf8string = 'xmp_commandline.txt';
  mplayer_cl : utf8string = '';
  mplayer_fn : utf8string = 'mplayer_commandline.txt';
  tunfileok : boolean = false;
    inhibit_fd: cint; // Файловый дескриптор блокировки
    fConn: PDBusConnection; // Соединение с D-Bus
    shutdownRequested: Boolean; // Флаг завершения работы
DBUSThread: TThread;
appclosemutex:bytebool=false;

procedure StartDBusLoop;

procedure PlaySound(s : filenamearty);
procedure StopSnd;
function FilErr(I : integer; s : string; fhalt : boolean) : boolean;
procedure RecordSound(s : filenamety);
procedure RunScript(s : filenamearty);
procedure AppClose;
function inttomonth(y : Int64; m : TMonth) : string;
function LoadClFile(fn : utf8string; var cl : utf8string) : boolean;
procedure SaveClFile(fn, cl : utf8string);

// Обычная процедура для обработки сигналов
//procedure ShutdownSignalHandler(sig: cint); cdecl;
function DBusSignalHandler(conn: PDBusConnection; msg: PDBusMessage; user_data: Pointer): DBusHandlerResult; cdecl;

var
wm_state, wm_state_above : TAtom;
appdisp: pdisplay;

implementation

uses 
main_mfm,ee,flash,msg,clockpanel,ele,shutdown,settings,notebook,people,organization,yearlist;

function TTun.r_m : bytebool;
begin
Exit(p^.fmute);
end;
function TTun.r_n : bytebool;
begin
Exit(p^.fnoact);
end;
function TTun.r_f : bytebool;
begin
Exit(p^.main_fixation);
end;

function LoadClFile(fn : utf8string; var cl : utf8string) : boolean;
var
  fp : Text;
begin
Assign(fp, fn);
FileMode := 0;
{$I-}
ReSet(fp);
{$I+} if IOResult <> 0 then Exit(false);
{$I-}
ReadLn(fp, cl);
{$I+} if IOResult <> 0 then Exit(true);
{$I-}
Close(fp);
{$I+} if IOResult <> 0 then Exit(true);
Exit(false);
end;

procedure SaveClFile(fn, cl : utf8string);
var
  fp : Text;
begin
Assign(fp, fn);
FileMode := 1;
{$I-}
 rewrite(fp);
{$I+} FilErr(IOResult, '[Rewrite CL File]', false);
{$I-}
 write(fp, cl);
{$I+} FilErr(IOResult, '[Write CL File]', false);
 closefile(fp);
{$I+} FilErr(IOResult, '[Close CL File]', false);
end;

procedure StopSnd;
var tmp : filenamearty;
begin
sf_queue.Zero;
try
case playsoundstate of
//2 : fpSystem('killall gorg64_spkplay');
2 : fpSystem('gorg64_spkplay --stop');
3 : fpSystem('killall -s KILL xmp');
4 : fpSystem('killall -s KILL timidity');
5 : fpSystem('killall -s KILL mplayer');
end;
except end;
if processrecord then begin
try
case tun.p^.record_prog of
0: fpSystem('killall -s KILL arecord');
1: fpSystem('killall -s KILL rec');
2: fpSystem('killall wavrec');
end; {select}
except end;
end;
end;

procedure PlaySound(s : filenamearty);
var
  f : Int64;
begin
if tun.mute then exit;
if Length(s) < 1 then exit;
for f := 0 to High(s) do sf_queue.add(s[f]);
end;

procedure RunScript(s : filenamearty);
var
  f : Int64;
  tmp : msestring;  
begin
if tun.noact then exit;
if Length(s) > 0 then begin
SetLength(shfiles, Length(s));
for f := 0 to High(s) do shfiles[f] := expandfilename(scriptdir + '"' + s[f] + '" ');
end else begin exit; end;
mainfo.tthreadcomp4.run;
end;

procedure RecordSound(s : filenamety);
begin
if processrecord then exit;
processrecord := true;
rfile := s;
mainfo.tthreadcomp5.run;
//needrecord := true;
end;

procedure AppClose;
begin
if appclosemutex then begin WriteLn('AppClose: ALREADY CLOSING'); exit; end;
appclosemutex := true;
StopSnd;
Org.WorkGorgFile(true);
//fpSystem('gorg64_spkplay');
shutdownRequested := True;
mainfo.ReleaseInhibit;
mainfo.Close;
end;

function FilErr(I : integer; s : string; fhalt : boolean) : boolean;
begin result := i <> 0;
 if not result then exit;
 askok(PCHar('IOResult=' + IntToStr(i) + ' Error in file: ' + s), PCHar('GORG64'));
 if fhalt then halt;
end;

procedure TTun.SetFixation(Value : bytebool);
begin p^.main_fixation := value;
if p^.main_fixation then begin
   mainfo.tpopupmenu1.menu.items[2].caption := '[v] ' + str_fixation;
   end else begin
   mainfo.tpopupmenu1.menu.items[2].caption := str_fixation;
   end;
end;

procedure tmainfo.onquit(Const Sender: TObject);
begin
AppClose;
end;

function DetectLang : shortstring;
var
i,i2,i3 : integer;
SR : TSearchRec;
l, langsys, langdef : msestring;
begin
l := GetEnvironmentVariable('LANG');
l := system.copy(l,1,2);

i := 0;
i2 := 0;
i3 := 0;

if FindFirst(langdir + '*.txt', faArchive, SR) = 0 then
   begin
     repeat
      if l = copy(SR.Name,1,2) then
      begin
      langsys := SR.Name;
      i2 := i;
      end;      
      if 'en' = copy(SR.Name,1,2) then
      begin
      langdef := SR.Name;
      i3 := i;
      end; 
      inc(i); 
      until FindNext(SR) <> 0;
    FindClose(SR);
   end;   
   
   if langsys <> '' then result := system.copy(langsys,1,2)
   else result := system.copy(langdef,1,2);
  
end;

procedure tmainfo.ChangeLang;
var f : Int64;
i : integer = 0;
SR : TSearchRec;
begin
 if tunfileok = false then
 tun.LangCode := DetectLang;
 tunfileok := true;

if FindFirst(langdir + '*.txt', faArchive, SR) = 0 then
   begin
     repeat
      if system.copy(SR.Name,1,2) = tun.LangCode then
      lang := SR.Name;
      inc(i); 
      until FindNext(SR) <> 0;
    FindClose(SR);
   end;   
 
 if system.copy(lang,1,2) = 'ru' then 
 ruenv := true else ruenv := false;

lang := langdir + lang;

LoadLng(lang);

 for f := 1 to 12 do 
 begin
 case f of
  1: mon_names[f] := str_january;
  2: mon_names[f] := str_february;
  3: mon_names[f] := str_march;
  4: mon_names[f] := str_april;
  5: mon_names[f] := str_may;
  6: mon_names[f] := str_june;
  7: mon_names[f] := str_july;
  8: mon_names[f] := str_august;
  9: mon_names[f] := str_september;
  10: mon_names[f] := str_october;
  11: mon_names[f] := str_november;
  12 : mon_names[f] := str_december;  
 end;
//  writeln(mon_names[f]);
  end;
  
 for f := 1 to 12 do 
 begin
 case f of
  1: mon_names3[f] := lowercase(utf8copy(str_january,1,3));
  2: mon_names3[f] := lowercase(utf8copy(str_february,1,3));
  3: mon_names3[f] := lowercase(utf8copy(str_march,1,3));
  4: mon_names3[f] := lowercase(utf8copy(str_april,1,3));
  5: mon_names3[f] := lowercase(utf8copy(str_may,1,3));
  6: mon_names3[f] := lowercase(utf8copy(str_june,1,3));
  7:  if tun.LangCode = 'fr' then mon_names3[f] := 'jul' else 
      mon_names3[f] := lowercase(utf8copy(str_july,1,3));
  8: mon_names3[f] := lowercase(utf8copy(str_august,1,3));
  9: mon_names3[f] := lowercase(utf8copy(str_september,1,3));
  10: mon_names3[f] := lowercase(utf8copy(str_october,1,3));
  11: mon_names3[f] := lowercase(utf8copy(str_november,1,3)); 
  12 : mon_names3[f] := lowercase(utf8copy(str_december,1,3));
end;  
// writeln(mon_names3[f]);
end;
  
wdn2[7] := lowercase(utf8copy(str_sunday,1,3));
wdn[7] := str_sunday;

for f := 2 to 7 do 
begin
 case f of
  2: wdn2[f-1] := lowercase(utf8copy(str_monday,1,3));
  3: wdn2[f-1] := lowercase(utf8copy(str_tuesday,1,3));
  4: wdn2[f-1] := lowercase(utf8copy(str_wednesday,1,3));
  5: wdn2[f-1] := lowercase(utf8copy(str_thursday,1,3));
  6: wdn2[f-1] := lowercase(utf8copy(str_friday,1,3));
  7: wdn2[f-1] := lowercase(utf8copy(str_saturday,1,3));
end;
// writeln(wdn2[f-1]);
end;

for f := 2 to 7 do 
 case f of
  2: wdn[f-1] := str_monday;
  3: wdn[f-1] := str_tuesday;
  4: wdn[f-1] := str_wednesday;
  5: wdn[f-1] := str_thursday;
  6: wdn[f-1] := str_friday;
  7: wdn[f-1] := str_saturday;
end;

if str_editevents <> '' then mainfo.tpopupmenu1.menu.items[3].caption := str_editevents;
if str_addevent <> '' then mainfo.tpopupmenu1.menu.items[4].caption := str_addevent;
if str_notebook <> '' then mainfo.tpopupmenu1.menu.items[5].caption := str_notebook;
if str_addpeople <> '' then mainfo.tpopupmenu1.menu.items[6].caption := str_addpeople;
if str_addorganization <> '' then mainfo.tpopupmenu1.menu.items[7].caption := str_addorganization;
if str_help <> '' then mainfo.tpopupmenu1.menu.items[8].caption := str_help;
if str_settings <> '' then mainfo.tpopupmenu1.menu.items[9].caption := str_settings;
if str_flash <> '' then mainfo.tpopupmenu1.menu.items[10].caption := str_flash;
if str_clockpanel <> '' then mainfo.tpopupmenu1.menu.items[11].caption := str_clockpanel;
if str_yearlist <> '' then mainfo.tpopupmenu1.menu.items[12].caption := str_yearlist; 
if str_quit <> '' then mainfo.tpopupmenu1.menu.items[13].caption := str_quit;
end;

procedure tmainfo.toabove;
var rEvent: TXEvent;
begin
if (wm_state <> None) and (wm_state_above <> None) then begin
                fillbyte(revent, sizeof(txevent), 0);
                revent.xclient._type := ClientMessage;
                revent.xclient.window := window.winid;
                revent.xclient.serial := 0;
                revent.xclient.send_event := 1;
                revent.xclient.display := appdisp;
                revent.xclient.message_type := wm_state;
                revent.xclient.format := 32;
                revent.xclient.data.l[0] := 1;
                revent.xclient.data.l[1] := wm_state_above;
                revent.xclient.data.l[2] := 0;
                revent.xclient.data.l[3] := 0;
                revent.xclient.data.l[4] := 0;
                XSendEvent(appdisp, DefaultRootWindow(XLIB.PDisplay(appdisp)), False,
                        SubstructureRedirectMask or SubstructureNotifyMask, @revent);
end;
end;

procedure tmainfo.oncreate(Const Sender: TObject);
var
 f,ff: Int64;
begin
appdisp := msedisplay;
//WriteLn(window.winid);

//  if (wo_alwaysontop in options.options)
//  then setnetatomarrayitem (window.winid,net_wm_state, net_wm_alwaystofront)
//  else resetnetatomarrayitem (window.winid,net_wm_state, net_wm_alwaystofront);

wm_state := XInternAtom(appdisp, '_NET_WM_STATE', True);
wm_state_above := XInternAtom(appdisp, '_NET_WM_STATE_ABOVE', False);

if not wm_running then begin
//fpSystem('gorg64_runner fpwm');
fpSystem('gorg64_runner marco');
Sleep(3000);
end;

  WriteLn('Запуск Galaxy Organizer...');
  // Инициализация D-Bus
  fConn := dbus_bus_get(DBUS_BUS_SYSTEM, nil);
  if fConn = nil then
  begin
    WriteLn('Ошибка подключения к D-Bus.');
    Halt(1);
  end;
  // Инициализация блокировки
  InitializeInhibit;
  // Подписка на сигнал PrepareForShutdown
  InitializeSignalHandler;
  // Установка обработчика сигналов завершения
//  FpSignal(SIGTERM, @ShutdownSignalHandler);
//  FpSignal(SIGINT, @ShutdownSignalHandler);
  // Запуск цикла обработки событий D-Bus в отдельном потоке
// TThread.CreateAnonymousThread(@StartDBusLoop).Start;
  DBUSThread := TThread.CreateAnonymousThread(@StartDBusLoop);
  DBUSThread.Start;

SetExceptionMask(GetExceptionMask + [exZeroDivide] + [exInvalidOp] +
    [exDenormalized] + [exOverflow] + [exUnderflow] + [exPrecision]);

mse_radiuscorner := 30;
//tbutton1.visible := false;

{$ifdef ootb}
homedir := ExtractFilePath(ParamStr(0)) + 'data/';
workdir := ExtractFilePath(ParamStr(0))  + 'data/';
musicdir := ExtractFilePath(ParamStr(0)) + 'music/';
sounddir := ExtractFilePath(ParamStr(0)) + 'sound/';
flashdir := ExtractFilePath(ParamStr(0)) + 'flash/';
scriptdir := ExtractFilePath(ParamStr(0)) + 'script/';
bindir := ExtractFilePath(ParamStr(0)) + 'bin/';
langdir := ExtractFilePath(ParamStr(0)) + 'lang_s/';
menuprogdir := ExtractFilePath(ParamStr(0)) + 'menuprog/';
{$else}
homedir := GetEnvironmentVariable('HOME') + '/';
workdir := homedir + workdir + '/';
musicdir := workdir + musicdir + '/';
sounddir := workdir + sounddir + '/';
flashdir := workdir + flashdir + '/';
scriptdir := workdir + scriptdir + '/';
menuprogdir := workdir + menuprogdir + '/';
langdir := '/usr/share/doc/gorg64/lang_s/';
{$endif}

try
MkDir(workdir);MkDir(musicdir);MkDir(sounddir);MkDir(flashdir);MkDir(scriptdir);MkDir(menuprogdir);
if FileExists('/usr/share/doc/gorg64/files.tar.xz') then
fpSystem('tar -C ' + workdir + ' -xvJf /usr/share/doc/gorg64/files.tar.xz');
except
end;

tunfile := workdir + tunfile;
ghkfile := workdir + ghkfile;
flashfile := workdir + flashfile; AssignFile(flashfp, flashfile);
flashhotlistfile := workdir + flashhotlistfile;
flashstrdividerfile := workdir + flashstrdividerfile;
gorgfile := workdir + gorgfile; AssignFile(gorgfp, gorgfile);
hotlistfile := workdir + hotlistfile; AssignFile(hotlistfp, hotlistfile);
flashhotlistfile := workdir + flashhotlistfile; AssignFile(flashhotlistfp, flashhotlistfile);
historyfile := workdir + historyfile; AssignFile(historyfp, historyfile);
peoplesfile := workdir + peoplesfile; AssignFile(peoplesfp, peoplesfile);
organizationsfile := workdir + organizationsfile; AssignFile(organizationsfp, organizationsfile);
yearlistfile := workdir + yearlistfile;
arecord_fn := workdir + arecord_fn;
rec_fn := workdir + rec_fn;
xmp_fn := workdir + xmp_fn;
wavrec_fn := workdir + wavrec_fn;
mplayer_fn := workdir + mplayer_fn;
LoadClFile(arecord_fn, arecord_cl);
LoadClFile(rec_fn, rec_cl);
LoadClFile(wavrec_fn, wavrec_cl);
LoadClFile(xmp_fn, xmp_cl);
LoadClFile(mplayer_fn, mplayer_cl);

if fileexists(tunfile) then
 tunfileok := true;

Tun.Load;

ChangeLang;

DisplayMuteNoact;
Tun.SetFixation(Tun.fixation);
left := tun.p^.main_left; top := tun.p^.main_top;

if tun.p^.clip_mon then clipboardcopymonitor := @clipmon;

LoadHotKeysFromFile(ghkfile);
RegisterHotKeys;
checkforglobalhotkey := @CheckForGlobalHK;

menu_prog.user_dir := menuprogdir;
menuprog := TMenuProg.Create;
//tpopupmenu2.menu.submenu.count := Length(menuprog.a);
tpopupmenu2.menu.submenu.count := Length(ucat);
for f := 0 to High(ucat) do begin
tpopupmenu2.menu.submenu[f].Caption := ucat[f];
tpopupmenu2.menu.submenu[f].Tag := -1;
tpopupmenu2.menu.submenu[f].onexecute := nil; // @onm2execute;
tpopupmenu2.menu.submenu[f].submenu.count := Length(ulist[f]);
 for ff := 0 to High(ulist[f]) do begin
  tpopupmenu2.menu.submenu[f].submenu[ff].Caption := menuprog.a[ulist[f][ff]].Name;
  tpopupmenu2.menu.submenu[f].submenu[ff].Tag := ulist[f][ff];
  tpopupmenu2.menu.submenu[f].submenu[ff].onexecute := @onm2execute;
 end;
end;

peoples.Load;
organizations.Load;
LoadUnique;

mainfo.tthreadcomp3.run;

Org := TOrg.Create(True);
Org.Start;
end;

function inttomonth(y : Int64; m : TMonth) : string;
begin
if MonthWithDay(y, m) then Exit('{'+IntToStr(month)+'}') else Exit('('+IntToStr(month)+')');
end;

procedure tmainfo.Display;
begin
mainfo.tlabel2.Caption := IntToHour(hour) + ':' + IntToFix2Str(minute) + ' ' + mon_names3[month] + IntToMonth(year,month) + ' ' +
wdn2[WeekdayRu(year, month, day)] + ' ' + IntToStr(day) + ' ' + IntToStr(year) + ' [' + IntToStr(org.orgtodaycount) + ']';
end;

procedure tmainfo.doubleclick_action;
begin
Action(tun.p^.main_doubleclick_action);
end;

procedure tmainfo.Action(i : Int64);
begin
case i of
0: ShowEE(-1);
1: StopSnd;
2: tun.mute := not tun.mute;
3: tun.noact := not tun.noact;
4: ShowELE;
5: ShowFlash;
6: ShowClockPanel;
7: menuexit(self);
8: ShowYearList;
9: ShowNoteBook;
10: ShowP(-1);
11: ShowO(-1);
12: ShowSettings;
13: ShowHelp;
end;
end;

procedure tmainfo.onmouseev(Const Sender: twidget; Var ainfo: mouseeventinfoty);
begin
  if ainfo.eventkind = ek_buttonpress then
    begin
if ss_double in ainfo.shiftstate then begin
doubleclick_action;
Exit;
end;
      ispressed := True;
      oripoint  := ainfo.pos;
      tpaintbox1.cursor := cr_pointinghand;
    end;
  if ainfo.eventkind = ek_buttonrelease then
    begin
      ispressed := False;
      tpaintbox1.cursor := cr_default;
      if ainfo.button = mb_right then begin
      tpopupmenu1.Show(self, ainfo);
      end;
    end;
  if (ispressed = True) and (ainfo.eventkind = ek_mousemove) and (not tun.fixation) then
    begin
      left := left + ainfo.pos.x - oripoint.x;
      top  := top + ainfo.pos.y - oripoint.y;
      tun.p^.main_left := left; tun.p^.main_top := top;
    end;
end;

procedure tmainfo.menuexit(const sender: TObject);
begin
AppClose;
end;

procedure tmainfo.onmute(const sender: TObject);
begin
tun.mute := not tun.mute;
end;

procedure tmainfo.soundplay(const sender: tthreadcomp);
function vol : msestring;
begin
if not tun.p^.volumeon then Exit('');
result := '-volume ' + IntToStr(tun.p^.volume) + ' ';
end;
var
ext : msestring;
begin
while not application.terminated do begin
sf_queue.get;
if sf_queue.fvalid then begin
ext := mselowercase(ExtractFileExt(sf_queue.fstr));
if (ext = '.speaker') then begin
playsoundstate := 2;
fpSystem('gorg64_spkplay ' + musicdir + '"' +  sf_queue.fstr + '"');
end else begin
 if (ext = '.mod') or (ext = '.xm') or (ext = '.it') or (ext = '.s3m') or (ext = '.mptm') or (ext = '.dtm') then begin
playsoundstate := 3;
  fpSystem('xmp ' + xmp_cl + ' ' + sounddir + '"' +  sf_queue.fstr + '"');
 end else begin
  if (ext = '.mid') then begin
playsoundstate := 4;
   fpSystem('timidity ' + sounddir + '"' +  sf_queue.fstr + '"');
  end else begin
playsoundstate := 5;
   fpSystem('mplayer ' + vol + ' ' + mplayer_cl + ' ' + sounddir + '"' +  sf_queue.fstr + '"');
  end;
 end; 
end;
end;
playsoundstate := 0;
Sleep(800);
end;
end;

procedure tmainfo.scriptrun(const sender: tthreadcomp);
var f : Int64;
begin
for f := 0 to High(shfiles) do fpSystem('gorg64_runner /bin/bash ' + shfiles[f]);
end;

procedure tmainfo.onfixation(const sender: TObject);
begin
tun.fixation := not tun.fixation;
end;

procedure tmainfo.onnoact(const sender: TObject);
begin
tun.noact := not tun.noact;
end;

procedure TTun.SetMute(Value : bytebool);
begin
with p^ do begin
fmute := value;
if fmute then StopSnd;
end;
mainfo.DisplayMuteNoact;
end;

procedure TTun.SetNoact(Value : bytebool);
begin 
with p^ do begin
fnoact := value;
end;
mainfo.DisplayMuteNoact;  
end;

procedure tmainfo.DisplayMuteNoact;
begin
if tun.p^.fmute then
  begin
  tpopupmenu1.menu.items[0].caption := '[v] ' + str_mute;
  tpaintbox1.face.template := tfacecomp4;
  end else
  begin
  tpopupmenu1.menu.items[0].caption := str_mute;
  tpaintbox1.face.template := tfacecomp6;
  end;
  
if tun.p^.fnoact then
  begin
  tpopupmenu1.menu.items[1].caption := '[v] ' + str_noact;
  tpaintbox2.face.template := tfacecomp5;
  end else
  begin
  tpopupmenu1.menu.items[1].caption := str_noact;
  tpaintbox2.face.template := tfacecomp6;    
  end;
  
  tpaintbox1.invalidatewidget;
  tpaintbox2.invalidatewidget;
end;



procedure tmainfo.ShowP(a : Int64);
begin
if efpeoplefo then begin
peoplefo.bringtofront;
exit;
end else begin
pnum := a;
if pnum > -1 then edp := peoples.a[pnum] else edp.ReSet;
peoplefo := tpeoplefo.Create(nil);
efpeoplefo := true;
end;
end;
procedure tmainfo.ShowO(a : Int64);
begin
if eforganizationfo then begin
organizationfo.bringtofront;
exit;
end else begin
onum := a;
if onum > -1 then edo := organizations.a[onum] else edo.ReSet;
organizationfo := torganizationfo.Create(nil);
eforganizationfo := true;
end;
end;

procedure tmainfo.ShowEE(a : Int64);
begin
if efEEfo then begin
eefo.bringtofront;
exit;
end else begin
eefoindex := a;
eefo := teefo.Create(nil);
efEEfo := true;
end;
end;

procedure tmainfo.ShowELE;
begin
if efelefo then begin
elefo.bringtofront;
exit;
end else begin
elefo := telefo.Create(nil);
efelefo := true;
end;
end;

procedure tmainfo.ShowNotebook;
begin
if efnotebookfo then begin
notebookfo.bringtofront;
exit;
end else begin
notebookfo := tnotebookfo.Create(nil);
efnotebookfo := true;
end;
end;

procedure tmainfo.ShowSettings;
begin
if efsettingsfo then begin
settingsfo.bringtofront;
exit;
end else begin
settingsfo := tsettingsfo.Create(nil);
efsettingsfo := true;
end;
end;

procedure tmainfo.ShowShutdown;
begin
needclockpanel := 2;
if efshutdownfo then begin
shutdownfo.bringtofront;
exit;
end else begin
shutdownfo := tshutdownfo.Create(nil);
efshutdownfo := true;
end;
end;

procedure tmainfo.ShowFlash;
var
 str1: msestring;
begin
msewidgets.pastefromclipboard(str1);
if efFlash then begin
 if tun.p^.flash_accmulate then begin
 flashfo.ttextedit2.beginupdate;
flashfo.ttextedit2.settext(flashfo.ttextedit2.gettext + str_divider + str1);
 flashfo.ttextedit2.endupdate;
 exit;
 end else flashfo.bringtofront;
end else begin
flashfo := tflashfo.Create(nil);
efFlash := true;end;
flashfo.ttextedit2.beginupdate;
flashfo.ttextedit2.settext(str1);
flashfo.ttextedit2.endupdate;
end;
procedure tmainfo.onflash(const sender: TObject);
begin
ShowFlash;
end;

procedure tmainfo.ShowClockPanel;
begin
if tun.noact then exit;
if not efclockpanel then begin
clockpanelfo := tclockpanelfo.Create(nil);
efclockpanel := true;
end;
end;

procedure tmainfo.ShowHelp;
begin
fpSystem('gorg64_runner xdg-open "man:gorg64 >/dev/null 2>/dev/null" 2>/dev/null');
end;

procedure tmainfo.soundrec(const sender: tthreadcomp);
var tmp : filenamearty;
    stmp, noext : filenamety;
fp : textfile;
s:utf8string;
begin
//while not application.terminated do begin
//if needrecord then begin
if efeefo then eefo.trichbutton6.visible := false;
	if Length(rfile) > 4 then begin
noext := rfile; SetLength(noext, Length(rfile)-4);
try
//fpSystem('arecord -t wav  -f S16_LE -c1 -r44100 ' + rfile);
//fpSystem('arecord -t wav -f S16_LE -c2 -D hw:CARD=SB,DEV=0 ' + rfile);
case tun.p^.record_prog of
0: fpSystem('arecord -t wav ' + arecord_cl + ' ' + rfile);
1: fpSystem('rec ' + rec_cl + ' ' + rfile);
2: begin fpSystem('wavrec ' + rfile + wavrec_cl);
{
try
popen (fp, 'wavrec ' + rfile + wavrec_cl, 'r');
while not eof(fp) do begin
readln(fp,s);
WriteLn('> ',s);
end;
pclose(fp);
except
WriteLn('Wavrec error');
end;
}
end;
end; {select}
except end;

stmp := rfile + '.tmp.wav';
if tun.p^.record_prog <> 2 then begin
try fpSystem('wavfix ' + rfile); except end;
try fpSystem('mv ' + noext + '_REPAIRED.wav' + ' ' + rfile); except end;
end;
//try fpSystem('sox --norm ' + rfile + ' ' + stmp); except end;
//try fpSystem('wavnorm ' + rfile + ' ' + stmp); except end;

popen (fp, 'wavnorm ' + rfile + ' ' + stmp, 'r');
while not eof(fp) do begin
readln(fp,s);
//WriteLn('* ', s);
end;
pclose(fp);

If RenameFile (stmp, rfile) then Writeln ('Successfully renamed file.');

stmp := rfile;
rfile := noext + '.tta';
//try fpSystem('ttaenc -e ' + stmp + ' '  + sounddir); except end;
compress(stmp, rfile);

DeleteFile(stmp);

SetLength(tmp, 1);
rfile := ExtractFileName(rfile);
tmp[0] := rfile;
gev.sounds := tmp;
	end;
processrecord := false;
needafterrec := true;
//needrecord := false;
//end; {end if}
//Sleep(500);
//WriteLn('.');
//end; {wend}
end;

procedure tmainfo.onclockpanel(const sender: TObject);
begin
ShowClockPanel;
end;

procedure tmainfo.ShowAlarm;
begin
if efmsgfo then begin
msgfo.list;
end else begin
msgfo := tmsgfo.Create(nil);
efmsgfo := true;
end;
end;

procedure tmainfo.ShowYearList;
begin
if efyearlistfo then begin
end else begin
yearlistfo := tyearlistfo.Create(nil);
efyearlistfo := true;
end;
end;

procedure tmainfo.onneeds(const sender: TObject);
var tmp : filenamearty;
begin
if tun.noact then exit;
case needclockpanel of
1: begin ShowClockPanel; needclockpanel := 0; end;
2: begin if efclockpanel then clockpanelfo.close; needclockpanel := 0; end;
end;
if needalarm then begin ShowAlarm; needalarm := false; end;
if needshutdown > 0 then begin shutdown.cnt := 10; ShowShutdown; end;
if needeefoclose then begin eefo.Close; needeefoclose := false; end;
if needelefodisplay then begin elefo.Display; needelefodisplay := false; end;

if needafterrec then begin
if efEEfo then begin
try eefo.trichbutton6.visible := true; except end;
try eefo.tfilelistview1.path := sounddir; except end;
SetLength(tmp, 1);
tmp[0] := rfile;
try eefo.tfilelistview1.selectednames := tmp; except end;
end;
needafterrec := false;
end;

end;

procedure tmainfo.onele(const sender: TObject);
begin
ShowELE;
end;

procedure tmainfo.onsettings(const sender: TObject);
begin
ShowSettings;
end;

procedure tmainfo.onaddev(const sender: TObject);
begin
ShowEE(-1);
end;

procedure tmainfo.onnotebook(const sender: TObject);
begin
ShowNotebook;
end;

procedure tmainfo.onaddo(const sender: TObject);
begin
ShowO(-1);
end;
procedure tmainfo.onaddp(const sender: TObject);
begin
ShowP(-1);
end;

procedure tmainfo.onhelp(const sender: TObject);
begin
ShowHelp;
end;

procedure tmainfo.onyearlist(const sender: TObject);
begin
ShowYearList;
end;

procedure TTun.SetLangCode(Value : shortstring);
begin
if assigned(p) then p^.lang_code := Value;
end;

function TTun.GetLangCode : shortstring;
begin
if assigned(p) then result := p^.lang_code;
end;

function TTun.map : boolean;
var h : Int64;
begin
fFileName := tunfile;
h := do_SysCall(2 {Open}, Int64(PChar(fFileName)),2 {R W old 2}); if h < 0 then begin Exit(true); end;
fs  := do_SysCall(8 {GET LEN}, h,0{from begin},2{SEEK_END});
a := do_SysCall(9 {FILEMAP}, 0{from begin},fs,2{PROT_},1{MAP_},h,0{from begin file});
p := PTu(a);
if (do_SysCall(3 {Close}, h) < 0)  then begin WriteLn(stderr, 'Error: can''t map input file'); Exit(true); end;
if fs <> sizeof(TTu) then Exit(true);
Exit(false);
end;
function TTun.unmap : boolean;
begin
Exit(do_SysCall(11 {Unmap}, a,fs) <> 0);
end;

function TTun.Load : boolean;
var
fp : file of TTu;
tbool : boolean = false;
t : TTu;
begin
if fileexists(tunfile) then tbool := true;
if map then begin
	AssignFile(fp, fFileName);
	FileMode := 2;
	{$I-}
	ReWrite(fp, 1);
	{$I+} FilErr(IOResult, '[Rewrite Tun File]', false);
	{$I-}
with t do begin
 fmute := false;
 al_dlg_mem_action := 0;
 clip_mon          := false;
 sync_snd          := false;
 record_prog      := 0;
 engtrue_hour_fmt    := false;
 engtrue_calend_fmt := Byte(PChar(nl_langinfo(_NL_TIME_FIRST_WEEKDAY))^) <> 2; // false;
 engtrue_calend_layout := false;
 lang_code := DetectLang;
 if not tbool then
 begin
  main_fixation := false;
  fnoact := false;
  main_left := 100;  
  main_top := 10; 
  main_height := 56;  
 end; 
 flash_accmulate := false;
 main_doubleclick_action := 0;
 am_pm[true] := am_hour_pm[true];
 am_pm[false] := am_hour_pm[false];
 volume := 60;
 volumeon := false;
end;
	 blockwrite(fp, t, sizeof(t));
	{$I+} FilErr(IOResult, '[Write Tun File]', false);
	{$I-}
	Close(fp);
	{$I+} FilErr(IOResult, '[Close Tun File]', false);
if map then FilErr(IOResult, '[Map Tun File]', false);
end;
end;

procedure clipmon;
begin
mainfo.ShowFlash;
end;

procedure tmainfo.onbutton(const sender: TObject);
begin
doubleclick_action;
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;

type
 XErrorHandler = function(Display: PDisplay; ErrorEvent: PXErrorEvent): Longint; cdecl;
var
 ghkehandler: XErrorHandler;

procedure tmainfo.RegisterHotKeys;
var
 f : Int64;
 m : dword;
 appdisp: pdisplay;
function e : bytebool;
begin
if not ghklastf then WriteLn('Register GlobalHotkey failed (holded by other application?) ', f);
Exit(ghklastf);
end;
begin
appdisp := msedisplay;
ghkehandler:= xseterrorhandler(@globalhotkeyserrorhandler);

for f := 0 to High(fHotKeys) do begin
if fHotKeys[f].Active then begin
fHotKeys[f].key := XKeysymToKeycode(Pointer(appdisp), ghk_symb[fHotKeys[f].KeyIdx]);
 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
  fHotKeys[f].NumLock := true;
  fHotKeys[f].CapsLock := true;
  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('GlobalHotkey No ', f, ' disabled.');
	UnRegisterHotKey(fHotKeys[f]);
 end;
end; {next f}
// Need or not XSelectInput under mse?
XSelectInput(Pointer(appdisp), gui_getrootwindow, KeyPressMask);
end;

function tmainfo.CheckForGlobalHK(k : TXKeyEvent) : boolean;
var
 f : Int64;
 ctrl,shift,alt : bytebool;
begin
//WriteLn('CheckForGlobalHK 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
//WriteLn(' CheckForGlobalHK FOR f=',f);
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');
Action(fHotKeys[f].ActionNumber);
//WriteLn('CheckForGlobalHK ACTION');
Exit(true);
end;
end; {next f}
//WriteLn('CheckForGlobalHK END');
Exit(false);
end;

procedure tmainfo.LoadHotKeysFromFile(fn: utf8string);
var
 fp: Text;
 s: msestring;
 f, l, m1, m2: longint;
 tmp, strl, strr: msestring;
 h: TGlobalHotKey;
begin
AssignFile(fp, fn);
{$I-}
ReSet(fp);
{$I+} if IOResult <> 0 then Exit;
while not eof(fp) do begin
ReadLn(fp, s);
strr := ''; strl := strr; tmp := strr;
m1 := 0; m2 := 0;
l := length(s);
if  l < 1 then begin WriteLn('Bad line in GHK file'); continue; end;
for f := 1 to l do begin
  if (s[f] = #9) or (f = l) then begin
   m2 := f;
   if (f = l) and (s[f] <> #9) then inc(m2);
   if m2 > (m1 + 1) then tmp := copy(s, m1 + 1, m2 - m1 - 1);
   m1 := m2;
   if not StrToTwoStr(tmp, strl, strr, '=') then begin
      if strl = 'AnyMod' then begin
	h.AnyMod := strr[1] <> '0'; continue;
      end;
      if strl = 'Ctrl' then begin
	h.Ctrl := strr[1] <> '0'; continue;
      end;
      if strl = 'Alt' then begin
	h.Alt := strr[1] <> '0'; continue;
      end;
      if strl = 'Shift' then begin
	h.Shift := strr[1] <> '0'; continue;
      end;
      if strl = 'KeyIdx' then begin
	h.KeyIdx := StrToIdx(strr); continue;
      end;
      if strl = 'Action' then begin
	h.ActionNumber := uStrToInt(strr); continue;
      end;
   end; {end if}
  end; {end if}
end; {next f}
if h.AnyMod then begin h.Ctrl := false; h.Alt := false; h.Shift := false; end;
SetLength(fHotKeys,Length(fHotKeys)+1);
fHotKeys[High(fHotKeys)] := h;
end; {wend}
CloseFile(fp);
end;

procedure tmainfo.onmenuprog(const sender: TObject);
var
po: pointty;
begin
po.x := trichbutton1.left;
po.y := trichbutton1.top;
tpopupmenu2.show(self, po);
end;

procedure tmainfo.onm2execute(const sender: TObject);
begin
menuprog.a[tmenuitem(sender).Tag].run;
end;

procedure tmainfo.updatelang();
begin
DisplayMuteNoact;
Tun.SetFixation(Tun.fixation);
end;

procedure tmainfo.onpaintev(const sender: twidget; const acanvas: tcanvas);
var
rad : integer;
begin
   rad := mse_radiuscorner div 2;
   acanvas.linewidth:= 1;
   acanvas.drawrect(mr(0,0,Width-1,Height-1),cl_gray);
   acanvas.drawarc(mp(rad,rad), rad,  pi, -pi/2, cl_gray);
   acanvas.drawarc(mp(rad, Height - rad -1), rad, pi, pi/2 , cl_gray );
   acanvas.drawarc(mp(width - rad,rad), rad, 0, pi / 2, cl_gray );
   acanvas.drawarc(mp(width - rad, Height - rad -1), rad, 0, -pi/2 , cl_gray );
end;

procedure tmainfo.onloopev(const sender: TObject);
begin
if tun.p^.main_doubleclick_action = 0 then
mainfo.tbutton2.imagenr := 0 else
if tun.p^.main_doubleclick_action = 1 then
mainfo.tbutton2.imagenr := 5 else
if tun.p^.main_doubleclick_action = 2 then
mainfo.tbutton2.imagenr := 5 else
if tun.p^.main_doubleclick_action = 3 then
mainfo.tbutton2.imagenr := 6 else
if tun.p^.main_doubleclick_action = 4 then
mainfo.tbutton2.imagenr := 9 else
if tun.p^.main_doubleclick_action = 5 then
mainfo.tbutton2.imagenr := 4 else
if tun.p^.main_doubleclick_action = 6 then
mainfo.tbutton2.imagenr := 1 else
if tun.p^.main_doubleclick_action = 7 then
mainfo.tbutton2.imagenr := 2;
end;

procedure tmainfo.onsetheight(avalue: Integer);
var
ratio : double;
begin
ratio := avalue/56 ; 
height := avalue;
width := round(750 * ratio);
tlabel2.font.height := round(38 * ratio);
if round(38 * ratio) > 22 then tlabel2.font.style := [fs_bold]
else tlabel2.font.style := [];
tlabel2.width := round(656 * ratio);
tpaintbox1.height:= avalue div 2;
tpaintbox2.height:= avalue div 2;
tpaintbox1.width:= round(750 * ratio);
tpaintbox2.width:= round(750 * ratio);tpaintbox2.top:= avalue div 2;
//tbutton1.top := (avalue - tbutton1.height) div 2;
tbutton3.top := (avalue - tbutton3.height) div 2; 
tbutton2.height := round(40 * ratio);
tbutton2.width := round(40 * ratio);
tbutton2.top := (avalue - tbutton2.height) div 2;  
tbutton2.left := round(700 * ratio);
mse_radiuscorner := round(30 * ratio);
Window.RecreateWindow;
end;

// Обычная функция для обработки сигналов D-Bus
//function DBusSignalHandler(conn: PDBusConnection; msg: PDBusMessage; user_data: Pointer): DBusHandlerResult; cdecl;
//begin
  // Вызываем метод объекта, передавая ему сообщение
//  if Assigned(user_data) then
//    TMainFo(user_data).HandleDBusSignal(msg);
//  Result := DBUS_HANDLER_RESULT_HANDLED;
//end;

function DBusSignalHandler(conn: PDBusConnection; msg: PDBusMessage; user_data: Pointer): DBusHandlerResult; cdecl;
begin
  if Assigned(user_data) then
  begin
    // Проверяем тип сигнала и вызываем соответствующий метод
    if Boolean(dbus_message_is_signal(msg, 'org.freedesktop.login1.Manager', 'PrepareForShutdown')) then
    begin
      WriteLn('Получен сигнал PrepareForShutdown.');
      TMainFo(user_data).HandlePrepareForShutdown(msg);
    end
    else if Boolean(dbus_message_is_signal(msg, 'org.freedesktop.login1.Manager', 'PrepareForSleep')) then
    begin
      WriteLn('Получен сигнал PrepareForSleep.');
      TMainFo(user_data).HandlePrepareForSleep(msg);
    end
    else if Boolean(dbus_message_is_signal(msg, 'org.freedesktop.login1.Manager', 'Shutdown')) then
    begin
      WriteLn('Получен сигнал Shutdown.');
      TMainFo(user_data).HandleShutdown(msg);
    end
    else if Boolean(dbus_message_is_signal(msg, 'org.freedesktop.login1.Manager', 'Reboot')) then
    begin
      WriteLn('Получен сигнал Reboot.');
      TMainFo(user_data).HandleReboot(msg);
    end
// Обработка сигналов от org.gnome.SessionManager
    else if Boolean(dbus_message_is_signal(msg, 'org.gnome.SessionManager', 'QueryEndSession')) then
    begin
      WriteLn('Получен сигнал QueryEndSession.');
      TMainFo(user_data).HandleQueryEndSession(msg);
    end
  end;
  Result := DBUS_HANDLER_RESULT_HANDLED;
end;


procedure TMainFo.HandleQueryEndSession(msg: PDBusMessage);
begin
  WriteLn('Обработка сигнала QueryEndSession.');
  // Сохраняем данные
  AppClose;
  // Отправляем ответ, что готовы к завершению сессии
  SendEndSessionResponse(True);
end;

procedure TMainFo.HandleEndSession(msg: PDBusMessage);
begin
  WriteLn('Обработка сигнала EndSession.');
  // Завершаем работу программы
//  shutdownRequested := True;
AppClose;
end;

procedure TMainFo.SendEndSessionResponse(IsReady: Boolean);
var
  msg: PDBusMessage;
  reply: PDBusMessage;
  iter: DBusMessageIter;
  err: DBusError;
//  response: DBusBool;
  response: LongBool; // Используем LongBool
begin
  WriteLn('Отправка ответа на QueryEndSession...');
  dbus_error_init(@err);

  // Создаём сообщение для ответа
  msg := dbus_message_new_method_call('org.gnome.SessionManager', '/org/gnome/SessionManager', 'org.gnome.SessionManager', 'EndSessionResponse');
  if msg = nil then
  begin
    WriteLn('Ошибка создания D-Bus сообщения');
    Exit;
  end;

  // Подготавливаем аргументы для ответа
  response := IsReady; // 1 (True) или 0 (False)
  dbus_message_iter_init_append(msg, @iter);
  dbus_message_iter_append_basic(@iter, DBUS_TYPE_BOOLEAN, @response);

  // Отправляем сообщение и ждём ответа
  reply := dbus_connection_send_with_reply_and_block(fConn, msg, 1000, @err);
  if dbus_error_is_set(@err) <> 0 then
  begin
    WriteLn('Ошибка отправки EndSessionResponse: ', err.message);
    dbus_error_free(@err);
    dbus_message_unref(msg);
    Exit;
  end;

  WriteLn('Ответ на QueryEndSession отправлен.');
  dbus_message_unref(msg);
  dbus_message_unref(reply);
end;


// Инициализация блокировки
procedure TMainFo.InitializeInhibit;
var
  msg, reply: PDBusMessage;
  iter: DBusMessageIter;
  err: DBusError;
  what, who, why, mode: PChar;
begin
  WriteLn('Инициализация блокировки...');
  dbus_error_init(@err);

  msg := dbus_message_new_method_call(DBUS_SERVICE, DBUS_PATH, DBUS_INTERFACE, 'Inhibit');
  if msg = nil then
  begin
    WriteLn('Ошибка создания D-Bus сообщения');
    Exit;
  end;

  what := 'shutdown';
  who := 'Galaxy Organizer';
  why := 'Saving files before shutdown';
  mode := 'delay'; // Используем 'delay' вместо 'block'
  dbus_message_iter_init_append(msg, @iter);
  dbus_message_iter_append_basic(@iter, DBUS_TYPE_STRING, @what);
  dbus_message_iter_append_basic(@iter, DBUS_TYPE_STRING, @who);
  dbus_message_iter_append_basic(@iter, DBUS_TYPE_STRING, @why);
  dbus_message_iter_append_basic(@iter, DBUS_TYPE_STRING, @mode);

  reply := dbus_connection_send_with_reply_and_block(fConn, msg, 1000, @err);
  if dbus_error_is_set(@err) <> 0 then
  begin
    WriteLn('Ошибка вызова Inhibit: ', err.message);
    dbus_error_free(@err);
    dbus_message_unref(msg);
    Exit;
  end;

  dbus_message_iter_init(reply, @iter);
  dbus_message_iter_get_basic(@iter, @inhibit_fd);
  WriteLn('Блокировка активирована. Дескриптор: ', inhibit_fd);

  dbus_message_unref(msg);
  dbus_message_unref(reply);
end;

// Снятие блокировки
procedure TMainFo.ReleaseInhibit;
begin
  if inhibit_fd <> -1 then
  begin
    if FpClose(inhibit_fd) = -1 then begin
      WriteLn('Ошибка снятия блокировки: ', SysErrorMessage(fpGetErrNo));
      Halt;
    end else begin
      WriteLn('Блокировка снята.');
    end;
    inhibit_fd := -1;
  end;
end;

procedure TMainFo.HandlePrepareForShutdown(msg: PDBusMessage);
begin
  WriteLn('Обработка сигнала PrepareForShutdown.');
  AppClose; // Сохраняем данные
  shutdownRequested := True; // Устанавливаем флаг завершения
end;

procedure TMainFo.HandlePrepareForSleep(msg: PDBusMessage);
begin
  WriteLn('Обработка сигнала PrepareForSleep.');
  // Дополнительные действия, если нужно
end;

procedure TMainFo.HandleShutdown(msg: PDBusMessage);
begin
  WriteLn('Обработка сигнала Shutdown.');
  AppClose; // Сохраняем данные
  shutdownRequested := True; // Устанавливаем флаг завершения
end;

procedure TMainFo.HandleReboot(msg: PDBusMessage);
begin
  WriteLn('Обработка сигнала Reboot.');
  AppClose; // Сохраняем данные
  shutdownRequested := True; // Устанавливаем флаг завершения
end;

procedure TMainFo.InitializeSignalHandler;
var
  err: DBusError;
  rule: PChar;
begin
  WriteLn('Инициализация подписки на сигналы D-Bus...');
  dbus_error_init(@err);

  // Подписка на сигнал PrepareForShutdown
  rule := 'type=''signal'',interface=''org.freedesktop.login1.Manager'',member=''PrepareForShutdown''';
  dbus_bus_add_match(fConn, rule, @err);
  if dbus_error_is_set(@err) <> 0 then
  begin
    WriteLn('Ошибка подписки на PrepareForShutdown: ', err.message);
    dbus_error_free(@err);
  end;

  // Подписка на сигнал PrepareForSleep
  rule := 'type=''signal'',interface=''org.freedesktop.login1.Manager'',member=''PrepareForSleep''';
  dbus_bus_add_match(fConn, rule, @err);
  if dbus_error_is_set(@err) <> 0 then
  begin
    WriteLn('Ошибка подписки на PrepareForSleep: ', err.message);
    dbus_error_free(@err);
  end;

  // Подписка на сигнал Shutdown
  rule := 'type=''signal'',interface=''org.freedesktop.login1.Manager'',member=''Shutdown''';
  dbus_bus_add_match(fConn, rule, @err);
  if dbus_error_is_set(@err) <> 0 then
  begin
    WriteLn('Ошибка подписки на Shutdown: ', err.message);
    dbus_error_free(@err);
  end;

  // Подписка на сигнал Reboot
  rule := 'type=''signal'',interface=''org.freedesktop.login1.Manager'',member=''Reboot''';
  dbus_bus_add_match(fConn, rule, @err);
  if dbus_error_is_set(@err) <> 0 then
  begin
    WriteLn('Ошибка подписки на Reboot: ', err.message);
    dbus_error_free(@err);
  end;


 // Подписка на сигналы от org.gnome.SessionManager
  rule := 'type=''signal'',interface=''org.gnome.SessionManager'',member=''QueryEndSession''';
  dbus_bus_add_match(fConn, rule, @err);
  if dbus_error_is_set(@err) <> 0 then
  begin
    WriteLn('Ошибка подписки на QueryEndSession: ', err.message);
    dbus_error_free(@err);
  end;

  rule := 'type=''signal'',interface=''org.gnome.SessionManager'',member=''EndSession''';
  dbus_bus_add_match(fConn, rule, @err);
  if dbus_error_is_set(@err) <> 0 then
  begin
    WriteLn('Ошибка подписки на EndSession: ', err.message);
    dbus_error_free(@err);
  end;


  // Устанавливаем фильтр для обработки сигналов
  dbus_connection_add_filter(fConn, @DBusSignalHandler, Self, nil);
  WriteLn('Подписка на сигналы выполнена.');
end;

// Основной цикл обработки событий D-Bus
procedure {TMainFo.}StartDBusLoop;
begin
  WriteLn('Запуск цикла обработки событий D-Bus...');
  while {(not shutdownRequested) and} (not application.terminated) do
  begin
    dbus_connection_read_write_dispatch(fConn, 100);
//    WriteLn('Цикл обработки событий D-Bus...');
  end;
  WriteLn('Цикл обработки событий D-Bus завершен.');
end;

procedure tmainfo.onmousereboot(const sender: twidget;
               var ainfo: mouseeventinfoty);
begin
if (ainfo.eventkind = ek_buttonrelease) then begin
case ainfo.button of
mb_right: begin tun.NoAct := false; needshutdown := 2; end;
mb_middle: begin AppClose; end;
end;
end;
end;

procedure tmainfo.onshutdown(const sender: TObject);
begin
tun.NoAct := false;
needshutdown := 1;
end;

procedure tmainfo.onproclistexec(const sender: TObject);
begin
proglist.windows[tmenuitem(sender).Tag].Activate(proglist.d);
end;

procedure tmainfo.onproclist(const sender: TObject);
var
po: pointty;
f:Int64;
begin
RefreshProcList;
tpopupmenu3.menu.submenu.count := Length(proglist.windows);
for f := 0 to tpopupmenu3.menu.submenu.count - 1 do begin
tpopupmenu3.menu.submenu[f].Caption := proglist.windows[f].WindowName;
tpopupmenu3.menu.submenu[f].Tag := f;
tpopupmenu3.menu.submenu[f].onexecute := @onproclistexec;
end;
po.x := trichbutton2.left;
po.y := trichbutton2.top;
tpopupmenu3.show(self, po);
end;

initialization
GetLocalTime(t);
year := t.Year; month := t.Month; day := t.Day;
hour := t.Hour; minute := t.Minute;
sf_queue := tqueue.Create;
end.