{$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
    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/>.
}

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

type
  TWindowInfo = packed object
    WindowID: TWindow;
    WindowName: String;
    ClassName: String;
    PID: Cardinal;
    IsActive: Boolean;
    IsVisible: Boolean;
    procedure PrintInfo;
  end;
  TWindowArray = array of TWindowInfo;

procedure TWindowInfo.PrintInfo;
begin
  if IsActive then Write('[*] ') else Write('    ');
  WriteLn('ID: ', WindowID.ToHexString(8));
  WriteLn('  Name: ', WindowName);
  WriteLn('  Class: ', ClassName);
  if PID > 0 then WriteLn('  PID: ', PID);
  WriteLn('  Visible: ', IsVisible);
  WriteLn('----------------------------------');
end;

function GetWindowsByNetClientList(d: PDisplay): TWindowArray;
var
  root: TWindow;
  net_client_list: TAtom;
  type_ret: TAtom;
  format_ret: cint;
  nitems, after_ret: culong;
  data: PWindow = nil;
  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, High(LongInt) div SizeOf(TWindow),
     False, XA_WINDOW, @type_ret, @format_ret, @nitems, @after_ret, @data) = Success then
  begin
    if (data <> nil) and (nitems > 0) then
    begin
      SetLength(Result, nitems);
      for i := 0 to nitems - 1 do
      begin
        Result[i].WindowID := data[i];
        // Остальные поля заполнятся позже
      end;
    end;
    if data <> nil then XFree(data);
  end;
end;

procedure GetWindowsByTree(d: PDisplay; parent: TWindow; var windows: TWindowArray);
var
  root, parent_root: TWindow;
  children: PWindow = nil;
  nchildren: cuint = 0;
  i: Integer;
begin
  if XQueryTree(d, parent, @root, @parent_root, @children, @nchildren) = 0 then Exit;

  if (children <> nil) and (nchildren > 0) then
  begin
    for i := 0 to nchildren - 1 do
    begin
      SetLength(windows, Length(windows) + 1);
      windows[High(windows)].WindowID := children[i];
      // Остальные поля заполнятся позже
      GetWindowsByTree(d, children[i], windows);
    end;
  end;
  if children <> nil then XFree(children);
end;

procedure FillWindowInfo(d: PDisplay; var win: TWindowInfo);
var
  net_wm_name, net_wm_pid, net_active_window, utf8_string: TAtom;
  type_ret: TAtom;
  format_ret: cint;
  nitems, after_ret: culong;
  data: Pointer = nil;
  class_hint: TXClassHint;
  attr: TXWindowAttributes;
begin
  // Проверка активного окна
  win.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, @after_ret, @data) = Success) and 
     (data <> nil) then
  begin
    win.IsActive := (PWindow(data)^ = win.WindowID);
    XFree(data);
  end;

  // Видимость окна
  win.IsVisible := False;
  if XGetWindowAttributes(d, win.WindowID, @attr) <> 0 then
    win.IsVisible := (attr.map_state = IsViewable);

  // Имя окна
  win.WindowName := '';
  net_wm_name := XInternAtom(d, '_NET_WM_NAME', False);
  utf8_string := XInternAtom(d, 'UTF8_STRING', False);
  if (XGetWindowProperty(d, win.WindowID, net_wm_name, 0, 1024, False,
       utf8_string, @type_ret, @format_ret, @nitems, @after_ret, @data) = Success) and 
     (data <> nil) then
  begin
    if nitems > 0 then win.WindowName := PChar(data);
    XFree(data);
  end
  else if XFetchName(d, win.WindowID, @data) <> 0 then
  begin
    if data <> nil then win.WindowName := PChar(data);
    XFree(data);
  end;

  // Класс окна
  win.ClassName := '';
  if XGetClassHint(d, win.WindowID, @class_hint) <> 0 then
  begin
    if class_hint.res_class <> nil then
    begin
      win.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 процесса
  win.PID := 0;
  net_wm_pid := XInternAtom(d, '_NET_WM_PID', False);
  if (XGetWindowProperty(d, win.WindowID, net_wm_pid, 0, 1, False,
       XA_CARDINAL, @type_ret, @format_ret, @nitems, @after_ret, @data) = Success) and 
     (data <> nil) then
  begin
    if nitems > 0 then win.PID := PCardinal(data)^;
    XFree(data);
  end;
end;

function GetAllWindows(d: PDisplay): TWindowArray;
var i,j:integer;
begin
  // Сначала пробуем стандартный метод
  Result := GetWindowsByNetClientList(d);
  
  // Если не сработал, используем рекурсивный обход
  if Length(Result) = 0 then
    GetWindowsByTree(d, XDefaultRootWindow(d), Result);
  
  // Заполняем информацию об окнах
  for i := 0 to High(Result) do
    FillWindowInfo(d, Result[i]);
  
  // Фильтруем только видимые окна с именами
  j := 0;
  for i := 0 to High(Result) do
    if Result[i].IsVisible and (Result[i].WindowName <> '') then
    begin
      if i <> j then Result[j] := Result[i];
      Inc(j);
    end;
  SetLength(Result, j);
end;

var
  d: PDisplay;
  windows: TWindowArray;
win:TWindowInfo;
begin
  d := XOpenDisplay(nil);
  if d = nil then
  begin
    WriteLn('Cannot open display');
    Halt(1);
  end;

  try
    windows := GetAllWindows(d);
    WriteLn('Found ', Length(windows), ' windows:');
    for win in windows do
      win.PrintInfo;
  finally
    XCloseDisplay(d);
  end;
end.