unit proglist;
{$mode objfpc}{$H+}

{
    Running programs list unit.
    For GNU/Linux 64 bit version.
    Version: 1.
    Written on FreePascal (https://freepascal.org/).
    Copyright (C) 2025  Artyomov Alexander
    Used https://chat.deepseek.com/
    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/>.
}

interface

uses
  Classes, SysUtils, X, XLib, XUtil, XAtom, DateUtils,ctypes;

type
  TWindowInfo = object
    WindowID: TWindow;
    WindowName: String;
    ClassName: String;
    PID: Cardinal;
    CreatedTime: TDateTime;
    IsActive: Boolean;  // Новое поле: активно ли окно
    
    procedure Init(d: PDisplay; w: TWindow);
    procedure PrintInfo;
    procedure Activate(d: PDisplay);
    procedure Close(d: PDisplay);
    procedure Maximize(d: PDisplay);
    procedure Minimize(d: PDisplay);
    procedure Restore(d: PDisplay);
    procedure SetAlwaysOnTop(d: PDisplay; Enabled: Boolean);
  end;

  TWindowList = array of TWindowInfo;

var
  windows: TWindowList;
  d: PDisplay;

procedure RefreshProcList;

implementation

procedure TWindowInfo.Init(d: PDisplay; w: TWindow);
var
  net_wm_name, net_wm_pid, net_active_window, utf8_string: TAtom;
  type_ret: TAtom;
  format_ret: cint;
  nitems_ret, after_ret: culong;
  data: PChar;
  pid_data: PCardinal;
  class_hint: TXClassHint;
  active_win: TWindow;
begin
  WindowID := w;
  WindowName := '';
  ClassName := '';
  PID := 0;
  CreatedTime := Now;
  IsActive := False;

  // Получаем активное окно
  net_active_window := XInternAtom(d, '_NET_ACTIVE_WINDOW', False);
  if (XGetWindowProperty(d, XDefaultRootWindow(d), net_active_window, 0, 1, False,
      XA_WINDOW, @type_ret, @format_ret, @nitems_ret, @after_ret, @data) = Success) and
     (data <> nil) then
  begin
    active_win := PWindow(data)^;
    IsActive := (active_win = w);
    XFree(data);
  end;

  // Получаем стандартные атомы
  net_wm_name := XInternAtom(d, '_NET_WM_NAME', False);
  net_wm_pid := XInternAtom(d, '_NET_WM_PID', False);
  utf8_string := XInternAtom(d, 'UTF8_STRING', False);

  // Получаем имя окна
  if (XGetWindowProperty(d, w, net_wm_name, 0, 1024, False,
       utf8_string, @type_ret, @format_ret, @nitems_ret,
       @after_ret, @data) = Success) and (data <> nil) then
  begin
    if (nitems_ret > 0) and (data^ <> #0) then
      WindowName := data;
    XFree(data);
  end
  else
  begin
    // Fallback для старых приложений
    if XFetchName(d, w, @data) <> 0 then
    begin
      if (data <> nil) and (StrLen(data) > 0) then
        WindowName := data;
      XFree(data);
    end;
  end;

  // Получаем класс окна (исправленная версия)
  FillChar(class_hint, SizeOf(class_hint), 0);
  if XGetClassHint(d, w, @class_hint) <> 0 then
  begin
    if class_hint.res_class <> nil then
    begin
      ClassName := class_hint.res_class;
      XFree(class_hint.res_class);
    end;
    if class_hint.res_name <> nil then
      XFree(class_hint.res_name);
  end;

  // Получаем PID процесса
  if (XGetWindowProperty(d, w, net_wm_pid, 0, 1, False,
       XA_CARDINAL, @type_ret, @format_ret, @nitems_ret,
       @after_ret, @pid_data) = Success) and (pid_data <> nil) then
  begin
    if nitems_ret > 0 then
      PID := pid_data^;
    XFree(pid_data);
  end;
end;

procedure TWindowInfo.PrintInfo;
begin
  if IsActive then
    Write('[*] ')
  else
    Write('    ');
    
  WriteLn('ID: ', WindowID);
  WriteLn('  Имя: ', WindowName);
  WriteLn('  Класс: ', ClassName);
  if PID > 0 then
    WriteLn('  PID: ', PID);
  WriteLn('  Создано: ', DateTimeToStr(CreatedTime));
  WriteLn('----------------------------------');
end;

procedure TWindowInfo.Activate(d: PDisplay);
var
  net_active_window: TAtom;
  ev: TXClientMessageEvent;
begin
  net_active_window := XInternAtom(d, '_NET_ACTIVE_WINDOW', False);
  
  ev._type := ClientMessage;
  ev.window := WindowID;
  ev.message_type := net_active_window;
  ev.format := 32;
  ev.data.l[0] := 1; // Source indication (1 = normal application)
  ev.data.l[1] := CurrentTime;
  ev.data.l[2] := 0;
  
  XSendEvent(d, XDefaultRootWindow(d), False,
             SubstructureRedirectMask or SubstructureNotifyMask,
             PXEvent(@ev));
  XFlush(d);
end;

procedure TWindowInfo.Close(d: PDisplay);
var
  net_close_window: TAtom;
  ev: TXClientMessageEvent;
begin
  net_close_window := XInternAtom(d, '_NET_CLOSE_WINDOW', False);
  
  ev._type := ClientMessage;
  ev.window := WindowID;
  ev.message_type := net_close_window;
  ev.format := 32;
  ev.data.l[0] := CurrentTime;
  ev.data.l[1] := 0;
  
  XSendEvent(d, XDefaultRootWindow(d), False,
             SubstructureRedirectMask or SubstructureNotifyMask,
             PXEvent(@ev));
  XFlush(d);
end;

procedure TWindowInfo.Maximize(d: PDisplay);
var
  net_wm_state: TAtom;
  net_wm_state_maximized_vert, net_wm_state_maximized_horz: TAtom;
  ev: TXClientMessageEvent;
begin
  net_wm_state := XInternAtom(d, '_NET_WM_STATE', False);
  net_wm_state_maximized_vert := XInternAtom(d, '_NET_WM_STATE_MAXIMIZED_VERT', False);
  net_wm_state_maximized_horz := XInternAtom(d, '_NET_WM_STATE_MAXIMIZED_HORZ', False);
  
  ev._type := ClientMessage;
  ev.window := WindowID;
  ev.message_type := net_wm_state;
  ev.format := 32;
  ev.data.l[0] := 1; // _NET_WM_STATE_ADD
  ev.data.l[1] := net_wm_state_maximized_vert;
  ev.data.l[2] := net_wm_state_maximized_horz;
  ev.data.l[3] := 0;
  
  XSendEvent(d, XDefaultRootWindow(d), False,
             SubstructureRedirectMask or SubstructureNotifyMask,
             PXEvent(@ev));
  XFlush(d);
end;

procedure TWindowInfo.Minimize(d: PDisplay);
var
  wm_change_state: TAtom;
  ev: TXClientMessageEvent;
begin
  wm_change_state := XInternAtom(d, 'WM_CHANGE_STATE', False);
  
  ev._type := ClientMessage;
  ev.window := WindowID;
  ev.message_type := wm_change_state;
  ev.format := 32;
  ev.data.l[0] := 3; // IconicState
  ev.data.l[1] := 0;
  
  XSendEvent(d, XDefaultRootWindow(d), False,
             SubstructureRedirectMask or SubstructureNotifyMask,
             PXEvent(@ev));
  XFlush(d);
end;

procedure TWindowInfo.Restore(d: PDisplay);
var
  net_wm_state: TAtom;
  ev: TXClientMessageEvent;
begin
  net_wm_state := XInternAtom(d, '_NET_WM_STATE', False);
  
  ev._type := ClientMessage;
  ev.window := WindowID;
  ev.message_type := net_wm_state;
  ev.format := 32;
  ev.data.l[0] := 0; // _NET_WM_STATE_REMOVE
  ev.data.l[1] := 0;
  ev.data.l[2] := 0;
  ev.data.l[3] := 0;
  
  XSendEvent(d, XDefaultRootWindow(d), False,
             SubstructureRedirectMask or SubstructureNotifyMask,
             PXEvent(@ev));
  XFlush(d);
end;

procedure TWindowInfo.SetAlwaysOnTop(d: PDisplay; Enabled: Boolean);
var
  net_wm_state: TAtom;
  net_wm_state_above: TAtom;
  ev: TXClientMessageEvent;
begin
  net_wm_state := XInternAtom(d, '_NET_WM_STATE', False);
  net_wm_state_above := XInternAtom(d, '_NET_WM_STATE_ABOVE', False);
  
  ev._type := ClientMessage;
  ev.window := WindowID;
  ev.message_type := net_wm_state;
  ev.format := 32;
  if Enabled then
    ev.data.l[0] := 1  // _NET_WM_STATE_ADD
  else
    ev.data.l[0] := 0; // _NET_WM_STATE_REMOVE
  ev.data.l[1] := net_wm_state_above;
  ev.data.l[2] := 0;
  ev.data.l[3] := 0;
  
  XSendEvent(d, XDefaultRootWindow(d), False,
             SubstructureRedirectMask or SubstructureNotifyMask,
             PXEvent(@ev));
  XFlush(d);
end;

function GetWindowList(d: PDisplay): TWindowList;
var
  root: TWindow;
  net_client_list: TAtom;
  type_ret: TAtom;
  format_ret: cint;
  nitems, after_ret: culong;
  data: PWindow;
  i: Integer;
begin
  SetLength(Result, 0);
  root := XDefaultRootWindow(d);
  net_client_list := XInternAtom(d, '_NET_CLIENT_LIST', False);

  if XGetWindowProperty(d, root, net_client_list, 0, 1024, False,
     XA_WINDOW, @type_ret, @format_ret, @nitems, @after_ret, @data) <> Success then
    Exit;

  if (data = nil) or (nitems = 0) then
    Exit;

  SetLength(Result, nitems);
  for i := 0 to nitems - 1 do
    Result[i].Init(d, data[i]);

  XFree(data);
end;

procedure SortWindowListByCreation(var List: TWindowList);
var
  i, j: Integer;
  temp: TWindowInfo;
begin
  for i := 0 to High(List) - 1 do
    for j := i + 1 to High(List) do
      if List[i].CreatedTime > List[j].CreatedTime then
      begin
        temp := List[i];
        List[i] := List[j];
        List[j] := temp;
      end;
end;

procedure FilterWindowList(var List: TWindowList; const ExcludeClasses: array of String);
var
  i, j: Integer;
  exclude: Boolean;
begin
  i := 0;
  while i < Length(List) do
  begin
    exclude := False;
    for j := 0 to High(ExcludeClasses) do
      if List[i].ClassName = ExcludeClasses[j] then
      begin
        exclude := True;
        Break;
      end;

    if exclude then
    begin
      if i < High(List) then
        List[i] := List[High(List)];
      SetLength(List, Length(List) - 1);
    end
    else
      Inc(i);
  end;
end;

procedure RefreshProcList;
begin
  try
    windows := GetWindowList(d);
    if Length(windows) = 0 then
    begin
      Writeln('Не найдено ни одного окна');
      Exit;
    end;
    // Фильтруем окна (исключаем оконные менеджеры)
    FilterWindowList(windows, ['GORG64', 'marco', 'xfwm4', 'kwin', 'metacity']);
    // Сортируем по времени создания
    SortWindowListByCreation(windows);
  finally
  end;
end;

initialization
d := XOpenDisplay(nil);
  if d = nil then
  begin
    Writeln('proglist: Не удалось подключиться к X-серверу');
  end;
end.