unit UFormMain;

{$mode delphi}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, Menus,
  ComCtrls, ExtCtrls, ActnList, Process, Contnrs;

type
  TSystemProcess = class
    Name: string;
    Path: string;
    Parameters: string;
    Cpu: Single;
    Drive: Single;
    Memory: Single;
    Network: Single;
    User: string;
    PID: Integer;
    Description: string;
  end;

  TSystemProcesses = class(TObjectList)
  end;

  TSystemServiceState = (ssStopped, ssRunning);

  TSystemService = class
    Name: string;
    Description: string;
    State: TSystemServiceState;
  end;

  TSystemServices = class(TObjectList)
  end;

  { TFormMain }

  TFormMain = class(TForm)
    AAlwaysOnTop: TAction;
    AServiceStart: TAction;
    AServiceRestart: TAction;
    AServiceStop: TAction;
    ARestore: TAction;
    ARun: TAction;
    AExit: TAction;
    ActionList1: TActionList;
    ImageList1: TImageList;
    ListViewOnStartup: TListView;
    ListViewDetails: TListView;
    ListViewProcesses: TListView;
    ListViewServices: TListView;
    MainMenu1: TMainMenu;
    MenuItem1: TMenuItem;
    MenuItem10: TMenuItem;
    MenuItem11: TMenuItem;
    MenuItem12: TMenuItem;
    MenuItem13: TMenuItem;
    MenuItem14: TMenuItem;
    MenuItem15: TMenuItem;
    MenuItem16: TMenuItem;
    MenuItem17: TMenuItem;
    MenuItem18: TMenuItem;
    MenuItem19: TMenuItem;
    MenuItem2: TMenuItem;
    MenuItem20: TMenuItem;
    MenuItem21: TMenuItem;
    MenuItem22: TMenuItem;
    MenuItem23: TMenuItem;
    MenuItem24: TMenuItem;
    MenuItem25: TMenuItem;
    MenuItem26: TMenuItem;
    MenuItem27: TMenuItem;
    MenuItem28: TMenuItem;
    MenuItem29: TMenuItem;
    MenuItem3: TMenuItem;
    MenuItem30: TMenuItem;
    MenuItem31: TMenuItem;
    MenuItem32: TMenuItem;
    MenuItem33: TMenuItem;
    MenuItem34: TMenuItem;
    MenuItem35: TMenuItem;
    MenuItem36: TMenuItem;
    MenuItem37: TMenuItem;
    MenuItem38: TMenuItem;
    MenuItem39: TMenuItem;
    MenuItem4: TMenuItem;
    MenuItem40: TMenuItem;
    MenuItem41: TMenuItem;
    MenuItem42: TMenuItem;
    MenuItem43: TMenuItem;
    MenuItem44: TMenuItem;
    MenuItem45: TMenuItem;
    MenuItem46: TMenuItem;
    MenuItem47: TMenuItem;
    MenuItem5: TMenuItem;
    MenuItem6: TMenuItem;
    MenuItem7: TMenuItem;
    MenuItem8: TMenuItem;
    MenuItem9: TMenuItem;
    MenuItemRun: TMenuItem;
    MenuItemExit: TMenuItem;
    PageControl1: TPageControl;
    Panel1: TPanel;
    PopupMenuServices: TPopupMenu;
    PopupMenuProcesses: TPopupMenu;
    PopupMenuDetails: TPopupMenu;
    PopupMenuTrayIcon: TPopupMenu;
    TabSheet1: TTabSheet;
    TabSheet2: TTabSheet;
    TabSheet3: TTabSheet;
    TabSheetServices: TTabSheet;
    TabSheetDetails: TTabSheet;
    TabSheetUsers: TTabSheet;
    TabSheetProcesses: TTabSheet;
    TimerRefresh: TTimer;
    TrayIcon1: TTrayIcon;
    procedure AExitExecute(Sender: TObject);
    procedure ARestoreExecute(Sender: TObject);
    procedure ARunExecute(Sender: TObject);
    procedure AServiceRestartExecute(Sender: TObject);
    procedure AServiceStartExecute(Sender: TObject);
    procedure AServiceStopExecute(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure FormWindowStateChange(Sender: TObject);
    procedure ListViewDetailsData(Sender: TObject; Item: TListItem);
    procedure ListViewProcessesData(Sender: TObject; Item: TListItem);
    procedure ListViewServicesData(Sender: TObject; Item: TListItem);
    procedure PageControl1Change(Sender: TObject);
    procedure PopupMenuServicesPopup(Sender: TObject);
    procedure TimerRefreshTimer(Sender: TObject);
    procedure TrayIcon1Click(Sender: TObject);
  private
    Processes: TSystemProcesses;
    Services: TSystemServices;
    function Cut(var Text: string; Separator: string): string;
    procedure ReloadTab;
    procedure ReloadProcessList;
    procedure ReloadServiceList;
    function ExecuteAnWaitForOutput(Executable: string; Parameters: array of string): string;
  public

  end;

var
  FormMain: TFormMain;

implementation

{$R *.lfm}

resourcestring
  SRunNewTask = 'Run new task';
  SRunNewTaskPrompt = 'Enter name of a program, service, document or resource from the Internet and Windows system will open it.';

  var
    SystemServiceStatusText: array [TSystemServiceState] of string = ('Stopped', 'Running');
{ TFormMain }

procedure TFormMain.AExitExecute(Sender: TObject);
begin
  Application.Terminate;
end;

procedure TFormMain.ARestoreExecute(Sender: TObject);
begin
  Show;
  BringToFront;
end;

procedure TFormMain.ARunExecute(Sender: TObject);
var
  Text: string;
  Process: TProcess;
begin
  Text := InputBox(SRunNewTask, SRunNewTaskPrompt, '');
  if Text <> '' then begin
    Process := TProcess.Create(nil);
    try
      Process.Executable := Text;
      Process.Options := [poNoConsole];
      Process.Execute;
    finally
      Process.Free;
    end;
  end;
end;

procedure TFormMain.AServiceRestartExecute(Sender: TObject);
begin
  if Assigned(ListViewServices.Selected) then begin
    ExecuteAnWaitForOutput('/bin/systemctl', ['restart',
      TSystemService(ListViewServices.Selected.Data).Name])
  end;
end;

procedure TFormMain.AServiceStartExecute(Sender: TObject);
begin
  ExecuteAnWaitForOutput('/bin/systemctl', ['start',
    TSystemService(ListViewServices.Selected.Data).Name])
end;

procedure TFormMain.AServiceStopExecute(Sender: TObject);
begin
  ExecuteAnWaitForOutput('/bin/systemctl', ['stop',
    TSystemService(ListViewServices.Selected.Data).Name])
end;

procedure TFormMain.FormCreate(Sender: TObject);
begin
  Processes := TSystemProcesses.Create;
  Services := TSystemServices.Create;
end;

procedure TFormMain.FormDestroy(Sender: TObject);
begin
  Services.Free;
  Processes.Free;
end;

procedure TFormMain.FormShow(Sender: TObject);
begin
  PageControl1.TabIndex := 0;
  ReloadTab;
  TimerRefresh.Enabled := True;
end;

procedure TFormMain.FormWindowStateChange(Sender: TObject);
begin
  ARestore.Visible := WindowState = wsMinimized;
end;

procedure TFormMain.ListViewDetailsData(Sender: TObject; Item: TListItem);
begin
  if Item.Index < Processes.Count then
  with TSystemProcess(Processes[Item.Index]) do begin
    Item.Data := Processes[Item.Index];
    Item.Caption := Name;
    Item.SubItems.Add(IntToStr(PID));
    //Item.SubItems.Add(Status);
    Item.SubItems.Add(User);
    Item.SubItems.Add(FloatToStr(Cpu));
    Item.SubItems.Add(FloatToStr(Memory));
    Item.SubItems.Add(Description);
  end;
end;

procedure TFormMain.ListViewProcessesData(Sender: TObject; Item: TListItem);
begin
  if Item.Index < Processes.Count then
  with TSystemProcess(Processes[Item.Index]) do begin
    Item.Data := Processes[Item.Index];
    Item.Caption := Name;
    Item.SubItems.Add(FloatToStr(Cpu));
    Item.SubItems.Add(FloatToStr(Memory));
    Item.SubItems.Add(FloatToStr(Drive));
    Item.SubItems.Add(FloatToStr(Network));
  end;
end;

procedure TFormMain.ListViewServicesData(Sender: TObject; Item: TListItem);
begin
  if Item.Index < Services.Count then
  with TSystemService(Services[Item.Index]) do begin
    Item.Data := Services[Item.Index];
    Item.Caption := Name;
    Item.SubItems.Add('');
    Item.SubItems.Add(Description);
    Item.SubItems.Add(SystemServiceStatusText[State]);
    Item.SubItems.Add('');
  end;
end;

procedure TFormMain.PageControl1Change(Sender: TObject);
begin
  ReloadTab;
end;

procedure TFormMain.PopupMenuServicesPopup(Sender: TObject);
begin
  AServiceRestart.Enabled := Assigned(ListViewServices.Selected) and
    (TSystemService(ListViewServices.Selected.Data).State = ssRunning);
  AServiceStop.Enabled := Assigned(ListViewServices.Selected) and
    (TSystemService(ListViewServices.Selected.Data).State = ssRunning);
  AServiceStart.Enabled := Assigned(ListViewServices.Selected) and
    (TSystemService(ListViewServices.Selected.Data).State = ssStopped);
end;

procedure TFormMain.TimerRefreshTimer(Sender: TObject);
begin
  ReloadTab;
end;

procedure TFormMain.TrayIcon1Click(Sender: TObject);
begin

end;

function TFormMain.Cut(var Text: string; Separator: string): string;
begin
  if Pos(Separator, Text) > 0 then begin
    Result := Copy(Text, 1, Pos(Separator, Text) - 1);
    Delete(Text, 1, Length(Result) + Length(Separator));
  end else begin
    Result := Text;
    Text := '';
  end;
end;

procedure TFormMain.ReloadTab;
begin
  if PageControl1.ActivePage = TabSheetProcesses then begin
    ReloadProcessList;
    ListViewProcesses.Items.Count := Processes.Count;
    ListViewProcesses.Refresh;
  end;
  if PageControl1.ActivePage = TabSheetDetails then begin
    ReloadProcessList;
    ListViewDetails.Items.Count := Processes.Count;
    ListViewDetails.Refresh;
  end;
  if PageControl1.ActivePage = TabSheetServices then begin
    ReloadServiceList;
    ListViewServices.Items.Count := Services.Count;
    ListViewServices.Refresh;
  end;
end;

procedure TFormMain.ReloadProcessList;
var
  Lines: TStringList;
  I: Integer;
  NewProcess: TSystemProcess;
  Line: string;
begin
  Processes.Clear;
  Lines := TStringList.Create;
  try
    Lines.Text := ExecuteAnWaitForOutput('/bin/ps', ['auxw']);
    for I := 1 to Lines.Count - 1 do begin
      Line := Lines[I];
      while Pos('  ', Line) > 0 do
        Line := StringReplace(Line, '  ', ' ', [rfReplaceAll]);
      NewProcess := TSystemProcess.Create;
      NewProcess.User := Cut(Line, ' ');
      NewProcess.PID := StrToInt(Cut(Line, ' '));
      NewProcess.Cpu := StrToFloat(Cut(Line, ' '));
      NewProcess.Memory := StrToFloat(Cut(Line, ' '));
      Cut(Line, ' ');
      Cut(Line, ' ');
      Cut(Line, ' ');
      Cut(Line, ' ');
      Cut(Line, ' ');
      Cut(Line, ' ');
      NewProcess.Name := Cut(Line, ' ');
      if Copy(NewProcess.Name, 1, 1) = '/' then begin
        NewProcess.Path := ExtractFileDir(NewProcess.Name);
        NewProcess.Name := ExtractFileName(NewProcess.Name);
      end else NewProcess.Path := '';
      NewProcess.Parameters := Line;
      Processes.Add(NewProcess);
    end;
  finally
    Lines.Free;
  end;
end;

procedure TFormMain.ReloadServiceList;
var
  Process: TProcess;
  Lines: TStringList;
  I: Integer;
  NewService: TSystemService;
  Line: string;
  State: string;
begin
  Services.Clear;
  Process := TProcess.Create(nil);
  Lines := TStringList.Create;
  try
    Lines.Text := ExecuteAnWaitForOutput('/bin/systemctl', ['list-units',
      '--no-pager', '--all', '--plain', '--no-legend']);
    for I := 0 to Lines.Count - 1 do begin
      Line := Lines[I];
      while Pos('  ', Line) > 0 do
        Line := StringReplace(Line, '  ', ' ', [rfReplaceAll]);
      NewService := TSystemService.Create;
      NewService.Name := Cut(Line, ' ');
      Cut(Line, ' ');
      Cut(Line, ' ');
      State := Cut(Line, ' ');
      if State = 'running' then NewService.State := ssRunning
        else NewService.State := ssStopped;
      NewService.Description := Line;
      Services.Add(NewService);
    end;
  finally
    Lines.Free;
  end;
end;

function TFormMain.ExecuteAnWaitForOutput(Executable: string;
  Parameters: array of string): string;
var
  Process: TProcess;
  OutputStream: TMemoryStream;
  I: Integer;
  BytesRead: Integer;
  Buffer: array[0..2047] of Byte;
begin
  Process := TProcess.Create(nil);
  OutputStream := TMemoryStream.Create;
  try
    Process.Executable := Executable;
    for I := 0 to Length(Parameters) - 1 do
      Process.Parameters.Add(Parameters[I]);
    Process.Options := [poNoConsole, poUsePipes];
    Process.Execute;

    repeat
      BytesRead := Process.Output.Read(Buffer, Length(Buffer));
      OutputStream.Write(Buffer, BytesRead)
    until BytesRead = 0;

    SetLength(Result, OutputStream.Size);
    if OutputStream.Size > 0 then begin;
      OutputStream.Position := 0;
      OutputStream.Read(Result[1], OutputStream.Size);
    end else Result := '';
  finally
    OutputStream.Free;
    Process.Free;
  end;
end;

end.

