program clockpanel;

{
    Clock panel for X.Org.
    For GNU/Linux.
    Version: 2.
    Written on FreePascal (https://freepascal.org/).
    Copyright (C) 2021-2022  Artyomov Alexander
    http://self-made-free.ru/ (Ex http://aralni.narod.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/>.
}

{$MODE OBJFPC}
{$LONGSTRINGS ON}
{$RANGECHECKS ON}
{$SMARTLINK ON}
{$ASMMODE INTEL}
//{$CODEPAGE UTF8}
 
uses cthreads,classes,sysutils,x,xlib,xutil,strings,wdnrukoi;

type
    TGt  = class(TThread)
    public
         procedure AfterConstruction; override;
         procedure Execute; override;
    end;
 
const
 
 WND_X=300;
 WND_Y=50;
 WND_WDT=800;
 WND_HGH=600;
 WND_MIN_WDT=50;
 WND_MIN_HGH=50;
 WND_BORDER_WDT=5;
 WND_TITLE='Clock panel';
 WND_ICON_TITLE='Clock panel';
 PRG_CLASS='ClockPanel';

var
    year : LongInt;
    month, day, hour, minute : byte;
    inittime : boolean = false;
    atom1, atom2 : TAtom;

function inttofix2str(i : integer) : string;
begin result := inttostr(i);if 10 > i then result := '0' + result; end;
function Weekday(year, month, day: LongInt): byte;
begin
    if month < 3 then
    begin
	year := year - 1;
	month := month + 10;
    end
    else
	month := month - 2;
    Weekday := (day + 31 * month div 12 + year + year div 4 - year div 100 + year div 400) mod 7;
end;
function WeekdayRu(year, month, day: LongInt) : byte;
begin
result := Weekday(year, month, day);
if result = 0 then Exit(7);
end;

(* SetWindowManagerHints - процедура передает информацию о свойствах программы менеджеру окон. *)
procedure SetWindowManagerHints(
 prDisplay: PDisplay; (*Указатель на структуру TDisplay *)
 psPrgClass: PChar; (*Класс программы *)
 argv: PPChar; (*Аргументы программы *)
 argc: integer; (*Число аргументов *)
 nWnd: TWindow; (*Идентификатор окна *)
  x, (*Координаты левого верхнего *)
  y, (*угла окна *)
  nWidth,
  nHeight, (*Ширина и высота окна *)
  nMinWidth,
  nMinHeight:integer; (*Минимальные ширина и высота окна *)
 psTitle: PChar; (*Заголовок окна *)
 psIconTitle: PChar; (*Заголовок пиктограммы окна *)
 nIconPixmap: TPixmap (*Рисунок пиктограммы *)
);
 
var
 rSizeHints: TXSizeHints; (*Рекомендации о размерах окна*)
 rWMHints: TXWMHints;
 rClassHint: TXClassHint;
 prWindowName, prIconName: TXTextProperty;

begin
 
if (XStringListToTextProperty(@psTitle, 1, @prWindowName)=0) or
    (XStringListToTextProperty(@psIconTitle, 1, @prIconName)=0) then
 begin
  writeln('No memory!');
  halt(1);
end;
 rSizeHints.flags:= PPosition OR PSize OR PMinSize;
 rSizeHints.min_width:= nMinWidth;
 rSizeHints.min_height:= nMinHeight;
 rWMHints.flags:= StateHint OR IconPixmapHint OR InputHint;
 rWMHints.initial_state:= NormalState;
 rWMHints.input:= 1; // True;
 rWMHints.icon_pixmap:= nIconPixmap;
 rClassHint.res_name:= argv[0];
 rClassHint.res_class:= psPrgClass;
 XSetWMProperties(prDisplay, nWnd, @prWindowName, @prIconName, argv, argc, @rSizeHints, @rWMHints, @rClassHint);
end;

(* main - основная процедура программы *)
//void main(int argc, char *argv[])
 
var
 prDisplay: PDisplay; (* Указатель на структуру Display *)
 nScreenNum: integer; (* Номер экрана *)
 prGC: TGC;
 rEvent, sEvent: TXEvent;
 nWnd: TWindow;
 gt  : TThread;
  s : string;
Font : TFont;

procedure TGt.AfterConstruction;
begin
  inherited AfterConstruction;
  FreeOnTerminate := True;
end;

procedure TGt.Execute;
  var t : TSystemTime;
begin
while true do begin
GetLocalTime(t);
if (year <> t.Year) or (month <> t.Month) or (day <> t.Day) or (hour <> t.Hour) or (minute <> t.Minute) then begin
year := t.Year; month := t.Month; day := t.Day;
hour := t.Hour; minute := t.Minute;
inittime := true;
sevent.xexpose._type := Expose;
sevent.xexpose.x := 0;
sevent.xexpose.y := 0;
sevent.xexpose.width := 150;
sevent.xexpose.height := 150;
sevent.xexpose.count := 0;
XSendEvent(prdisplay, nwnd, true, 0, @sevent);
 XFlush(prdisplay);
end;
sleep(1000);
end;
end;
 
begin

XInitThreads;
 
(* Устанавливаем связь с сервером *)
 prDisplay:= XOpenDisplay(nil);
if prDisplay = nil then begin
  writeln('Can not connect to the X server!');
  halt (1);
 end;

 (* Получаем номер основного экрана *)
 nScreenNum:= XDefaultScreen(prDisplay);

//font := XLoadFont(prdisplay, '-cronyx-times-bold-r-normal-*-32-*-*-*-*-*-koi8-r');
font := XLoadFont(prdisplay, '-*-helvetica-bold-r-normal-*-230-*-*-*-*-*-koi8-r');
//font := XLoadFont(prdisplay, '-*-courier-medium-r-normal-*-32-*-*-*-*-*-*-*');
 
 (* Создаем окно *)
 nWnd:= XCreateSimpleWindow(prDisplay, XRootWindow (prDisplay, nScreenNum), WND_X, WND_Y, WND_WDT, WND_HGH, WND_BORDER_WDT, XBlackPixel (prDisplay, nScreenNum),
 XBlackPixel (prDisplay, nScreenNum));
 (* Задаем рекомендации для менеджера окон *)
 SetWindowManagerHints(prDisplay, PRG_CLASS, argv, argc, nWnd, WND_X, WND_Y, WND_WDT, WND_HGH, WND_MIN_WDT, WND_MIN_HGH, WND_TITLE, WND_ICON_TITLE, 0);
 (* Выбираем события, обрабатываемые программой *)
 XSelectInput(prDisplay, nWnd, ExposureMask OR KeyPressMask OR StructureNotifyMask OR ButtonPressMask);
 (* Показываем окно *)
 XMapWindow(prDisplay, nWnd);

gt := TGt.Create(True);
gt.Start;

atom1 := XInternAtom(prdisplay, 'WM_PROTOCOLS', 0);
atom2 := XInternAtom(prdisplay, 'WM_DELETE_WINDOW', 0);
XSetWMProtocols(prdisplay, nwnd, @atom2, 1);
 
 (* Цикл получения и обработки событий *)
while (true) do begin
  XNextEvent(prDisplay, @rEvent);
case (rEvent._type) of
  Expose:
begin
   (* Запрос на перерисовку *)
if (rEvent.xexpose.count <> 0) then continue;
if inittime = false then continue;
XClearWindow(prdisplay, nwnd);
    prGC:= XCreateGC (prDisplay, nWnd, 0, nil );
   XSetForeground(prDisplay, prGC, XWhitePixel (prDisplay, 0));
XSetFont(prdisplay, prgc, font);
s := IntToStr(hour) + ':' + IntToFix2Str(minute);
XDrawString(prDisplay, nWnd, prGC, 40, 175, PChar(s), strlen(PChar(s)));
s := Copy(mon_name[month],1,4) + IntToStr(month);
XDrawString(prDisplay, nWnd, prGC, 0, 360, PChar(s), strlen(PChar(s)));
s := Copy(wdn[WeekdayRu(year, month, day)], 1, 3) + ' ' + IntToStr(day);
XDrawString(prDisplay, nWnd, prGC, 0, 550, PChar(s), strlen(PChar(s)));
s := IntToStr(year);
XDrawString(prDisplay, nWnd, prGC, 100, 780, PChar(s), strlen(PChar(s)));
   XFreeGC (prDisplay, prGC);
end;
ButtonPress: if revent.xkey.keycode = 3 then XDestroyWindow(prdisplay, nwnd);
DestroyNotify,
 KeyPress:
   begin
    (* Нажатие клавиши клавиатуры *)
    XCloseDisplay(prDisplay);
    halt(0);
   end;
 ClientMessage: begin
 if((revent.xclient.message_type = atom1) and (revent.xclient.data.l[0] = atom2))
      then begin
          XDestroyWindow(prdisplay, nwnd);
      end;
break;
 end;
end;
 end;
end.