Posts Tagged ‘windows’
This post will demonstrate how to get a list of running processes in a windows pc and many extra details for each process.
This demo will be a good start if anybody want’s to write a windows task manager alternative.
At the end of the post you will find a demo with source code to download
The demo consists of one basic class TgtProcessManager.
It will do all the work here is the declaration part of the class(because it is pretty big to post the whole unit) :
{------------------------------------------------------------------------------}
TgtProcessManager = class(TComponent)
private
FHostApp : string;
FHostAppLoadedModules : TStrings;
FFileInfo : TgtFileInfo;
FRunningProcessList: TObjectList;
FOnAfterLaunch: TNotifyEvent;
procedure SetHostApp(const Value: string);
function GetRunningProcess(Index: Integer): TgtRunningProcess;
{ Private declarations }
protected
{ Protected declarations }
function GetProcessId(ProcessName : string):Cardinal;
function GetModuleLoaded(ModuleName : string):Boolean;
function InternalTerminateProcess(ExeName : string):Integer;
procedure InternalLaunch(ExeFileName: string;WaitTimeOut:Integer=5000;
ExeParams:string='';Wait: Boolean = False);
procedure GetHostAppLoadedModules;
function GetMemUsageForProcess(ProcId:Cardinal):Cardinal;
procedure GetCpuUsage(AProcess : TgtRunningProcess);
public
{ Public declarations }
constructor Create(AOwner : TComponent);override;
destructor Destroy;override;
procedure UpdateRunningProcessList;
function IsModuleLoaded(ModuleName:string;LookInCache : Boolean = False):Boolean;
function TerminateProcess(ExeName : string):Integer;overload;
function TerminateProcess(RunningProcess : TgtRunningProcess):Integer;overload;
function TerminateProcess(RunningProcessIndex : Integer):Integer;overload;
procedure Launch(ExeFileName: string;WaitTimeOut:Integer=5000;ExeParams:string='');
procedure LaunchAndWait(ExeFileName: string;WaitTimeOut:Integer=5000;ExeParams:string='');
public
property RunningProcesses[Index : Integer] : TgtRunningProcess read GetRunningProcess;
published
{ Published declarations}
property HostApp : string read FHostApp write SetHostApp;
property HostAppLoadedModules : TStrings read FHostAppLoadedModules;
property ModuleInfo : TgtFileInfo read FFileInfo;
property RunningProcessList : TObjectList read FRunningProcessList;
published
property OnAfterLaunch : TNotifyEvent read FOnAfterLaunch write FOnAfterLaunch;
end;
There is also a list view(TgtProcessListView) that has embedded a TgtProcessManager and does all the work by it self below a screenshot with TgtProcessListView in usage

This the declaration part of the TgtProcessListView class
type
{------------------------------------------------------------------------------}
TgtProcessListView = class;
{------------------------------------------------------------------------------}
TgtProcessInfoThread = class(TThread)
private
{ Private declarations }
FProcessListView : TgtProcessListView;
protected
{ Protected declarations }
FProcessManager : TgtProcessManager;
procedure UpdateUI;
public
{ Public declarations }
procedure Execute;override;
public
constructor Create(AProcessListView:TgtProcessListView);
destructor Destroy;override;
published
{ Published declarations}
end;
{------------------------------------------------------------------------------}
TgtProcessListView = class(TCustomListView)
private
FRefreshInterval: Cardinal;
{ Private declarations }
protected
{ Protected declarations }
FProcessInfoThread : TgtProcessInfoThread;
procedure CreateColumns;
procedure Initialize;
protected
procedure SetParent(AParent: TWinControl);override;
public
{ Public declarations }
constructor Create(AOwner:TComponent);override;
destructor Destroy;override;
published
{ Published declarations}
property RefreshInterval : Cardinal read FRefreshInterval write FRefreshInterval;
end;
{------------------------------------------------------------------------------}
The execute part of the TgtProcessInfoThread does update the listview with the running processes
{------------------------------------------------------------------------------}
procedure TgtProcessInfoThread.Execute;
begin
while not Terminated do
begin
WaitForSingleObject(Handle,FProcessListView.RefreshInterval);
FProcessManager := TgtProcessManager.Create(nil);
try
FProcessManager.UpdateRunningProcessList;
Synchronize(UPdateUI);
finally
FreeAndNil(FProcessManager);
end;
end;
end;
{------------------------------------------------------------------------------}
This post is about an old project of mine which i have stopped working for a long time now and i thought why not give the source code
somebody might be interested or even better might continue the idea.
The basic idea is to create an alternative to the classic Windows Explorer to provide more functionality and ease of use.
No installation will be need into the IDE
The code should compatible from Delphi6 and up
The project consists of 3 basic units
1) o_GTExtendedShellCtrls which has extended versions of Delphi’s ShellControls TShellListView,TShellTreeView,TShellComboBox
2) o_ExplorerTab.pas which has 3 basic classes : TgtSingleExplorerTab,TgtDualExplorerTab and TgtExplorerPageControl these classes automaticaly
create the UI.
3) o_Explorer.pas which has the TgtExplorer class which plays the role of the explorer can copy,delete,move files e.t.c.
This is the main screen

Download the source code
TgtMessageBox is a wrapper around the Windows MessageBox function
with many features and extended properties regarding the parameters of the function.
{*******************************************************}
{ }
{ GT Delphi Components }
{ TgtMessageBox }
{ }
{ Copyright (c) GT Delphi Components }
{ http://www.gtdelphicomponents.gr }
{ }
{ }
{*******************************************************}
unit o_Dialogs;
interface
uses
Classes
,Messages
;
type
{------------------------------------------------------------------------------}
//The Type of the dialog
TgtMessageBoxType = (
mbtOk
,mbtYesNo
,mbtOkCancel
,mbtAbortRetryIgnore
,mbtYesNoCancel
,mbtRetryCancel
);
{------------------------------------------------------------------------------}
{
The modality level of the dialog
mmlApplication,mmlTask = Modality level is confined with in the application
mmlSystem = Modality level is System wide meaning that the dialog will stay on top of all other
open windows.
}
TgtMessageModalityLevel = (
mmlApplication
,mmlSystem
,mmlTask
);
{------------------------------------------------------------------------------}
//The icon of the dialog
TgtMessageBoxIcon = (
mbiNone
,mbiInformation
,mbiQuestion
,mbiWarning
,mbiError
);
{------------------------------------------------------------------------------}
//Which button will have focus when the dialog is created
TgtMessageDefButton = (
mdbButton1
,mdbButton2
,mdbButton3
,mdbButton4
);
{------------------------------------------------------------------------------}
//The result of the dialog
TgtMessageExecResult = (
merNone
,merOk
,merCancel
,merAbort
,merRetry
,merIgnore
,merYes
,merNo
,merClose
,merTryAgain
,merContinue
);
{------------------------------------------------------------------------------}
//This event will run after the execution of the dialog parsing the result
TgtAfterExecuteEvent = procedure (Sender : TObject ; ExecutionResult : TgtMessageExecResult) of Object;
{------------------------------------------------------------------------------}
TgtMessageBox = class(TComponent)
private
FMessageBoxIcon : TgtMessageBoxIcon;
FMessageCaption : string;
FMessageText : string;
FAfterExecute : TgtAfterExecuteEvent;
FMessageDefButton : TgtMessageDefButton;
FMessageBoxType : TgtMessageBoxType;
FMessageModalityLevel: TgtMessageModalityLevel;
FOnHelpButtonClick: TNotifyEvent;
FShowHelpButton: Boolean;
FExecResult: TgtMessageExecResult;
{Private Declarations}
protected
{Protected Declarations}
FHandle : LongWord;
procedure WndProc(var Message: TMessage);
public
{Public Declarations}
constructor Create(AOwner : TComponent);override;
destructor Destroy;override;
public
procedure Execute;
published
{Published Declarations}
property MessageBoxType : TgtMessageBoxType read FMessageBoxType write FMessageBoxType;
property MessageModalityLevel : TgtMessageModalityLevel read FMessageModalityLevel write FMessageModalityLevel;
property MessageBoxIcon : TgtMessageBoxIcon read FMessageBoxIcon write FMessageBoxIcon default mbiNone;
property MessageDefButton : TgtMessageDefButton read FMessageDefButton write FMessageDefButton default mdbButton1;
property MessageText : string read FMessageText write FMessageText;
property MessageCaption : string read FMessageCaption write FMessageCaption;
property ShowHelpButton : Boolean read FShowHelpButton write FShowHelpButton;
property ExecResult : TgtMessageExecResult read FExecResult;
published
property AfterExecute : TgtAfterExecuteEvent read FAfterExecute write FAfterExecute;
property OnHelpButtonClick : TNotifyEvent read FOnHelpButtonClick write FOnHelpButtonClick;
end;
{------------------------------------------------------------------------------}
implementation
uses
Windows
,SysUtils
;
{ TgtMessageBox }
{------------------------------------------------------------------------------}
constructor TgtMessageBox.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
end;
{------------------------------------------------------------------------------}
destructor TgtMessageBox.Destroy;
begin
inherited;
end;
{------------------------------------------------------------------------------}
procedure TgtMessageBox.WndProc(var Message: TMessage);
begin
case Message.Msg of
WM_HELP :
begin
//Capturing the Help Button Click
if Assigned(FOnHelpButtonClick) then
FOnHelpButtonClick(Self);
end;
end;
end;
{------------------------------------------------------------------------------}
procedure TgtMessageBox.Execute;
var
ExecResult : TgtMessageExecResult;
MsgIcon : DWORD;
MsgDefBtn : DWORD;
MsgType : DWORD;
MsgModLevel: DWORD;
begin
MsgIcon :=0;
MsgDefBtn :=0;
MsgType :=0;
MsgModLevel :=0;
case MessageBoxIcon of
mbiNone :;
mbiInformation : MsgIcon := Windows.MB_ICONINFORMATION;
mbiQuestion : MsgIcon := Windows.MB_ICONQUESTION;
mbiWarning : MsgIcon := Windows.MB_ICONWARNING;
mbiError : MsgIcon := Windows.MB_ICONERROR;
end;
case MessageDefButton of
mdbButton1 : MsgDefBtn := Windows.MB_DEFBUTTON1;
mdbButton2 : MsgDefBtn := Windows.MB_DEFBUTTON2;
mdbButton3 : MsgDefBtn := Windows.MB_DEFBUTTON3;
mdbButton4 : MsgDefBtn := Windows.MB_DEFBUTTON4;
end;
case MessageBoxType of
mbtOk : MsgType := Windows.MB_OK;
mbtYesNo : MsgType := Windows.MB_YESNO;
mbtOkCancel : MsgType := Windows.MB_OKCANCEL;
mbtAbortRetryIgnore : MsgType := Windows.MB_ABORTRETRYIGNORE;
mbtYesNoCancel : MsgType := Windows.MB_YESNOCANCEL;
mbtRetryCancel : MsgType := Windows.MB_RETRYCANCEL;
end;
case MessageModalityLevel of
mmlApplication : MsgModLevel := Windows.MB_APPLMODAL;
mmlSystem : MsgModLevel := Windows.MB_SYSTEMMODAL;
mmlTask : MsgModLevel := Windows.MB_TASKMODAL;
end;
if ShowHelpButton then
try
FHandle := Classes.AllocateHWnd(WndProc);
ExecResult := TgtMessageExecResult(MessageBox(FHandle,PChar(MessageText),PChar(MessageCaption)
,MsgIcon+MsgDefBtn+MsgType+MsgModLevel+MB_HELP))
finally
Classes.DeallocateHWnd(FHandle);
end
else
ExecResult := TgtMessageExecResult(MessageBox(HWND(nil),PChar(MessageText),PChar(MessageCaption)
,MsgIcon+MsgDefBtn+MsgType+MsgModLevel));
if Assigned(FAfterExecute) then
FAfterExecute(Self,ExecResult);
end;
{------------------------------------------------------------------------------}
end.
TgtRegionalSettings is a class to easily retrieve regional settings for a windows PC
{*******************************************************}
{ }
{ GT Delphi Components }
{ TgtRegionalSettings }
{ }
{ Copyright (c) GT Delphi Components }
{ http://www.gtdelphicomponents.gr }
{ }
{ }
{ }
{*******************************************************}
unit o_RegionalSettings;
interface
uses
Classes
;
type
TgtRegionalSettings = class(TComponent)
private
{ Private declarations }
FLocaleBuffer : PChar;
FSysMonthNames: TStrings;
FSysDayNames: TStrings;
function GetSysCountryCode: Word;
function GetSysAnsiCodePage: string;
function GetSysLocalLanguageName: string;
function GetSysLanguageID:Word;
function GetSysDateSeparator: string;
function GetSysLongDateFormat: string;
function GetSysShortDateFormat: string;
function GetSysTimeSeparator: string;
function GetSysTimeFormat: string;
function GetSysDecimalSeparator: string;
function GetSysDigitGrouping: string;
function GetSysThousandSeparator: string;
function GetSysCountryName: string;
function GetSysOemCodePage: string;
function GetSysCurrencySymbol: string;
function GetSysCurrencyDecimalSeparator: string;
function GetSysCurrencyThousandSeparator: string;
function GetSysCurrenctyDecimalDigits: string;
function GetSysDecimalDigits: string;
protected
{ Protected declarations }
procedure GetMonthsAndDayNames;
public
{ Public declarations }
constructor Create(AOwner : TComponent);override;
destructor Destroy;override;
public
property SysLanguageId : Word read GetSysLanguageID;
property SysLocalLanguageName : string read GetSysLocalLanguageName;
property SysCountryCode : Word read GetSysCountryCode;
property SysCountryName : string read GetSysCountryName;
property SysAnsiCodePage : string read GetSysAnsiCodePage;
property SysOemCodePage : string read GetSysOemCodePage;
property SysDateSeparator : string read GetSysDateSeparator;
property SysTimeSeparator : string read GetSysTimeSeparator;
property SysTimeFormat : string read GetSysTimeFormat;
property SysShortDateFormat : string read GetSysShortDateFormat;
property SysLongDateFormat : string read GetSysLongDateFormat;
property SysDecimalSeparator : string read GetSysDecimalSeparator;
property SysThousandSeparator : string read GetSysThousandSeparator;
property SysDecimalDigits : string read GetSysDecimalDigits;
property SysDigitGrouping : string read GetSysDigitGrouping;
property SysCurrencySymbol : string read GetSysCurrencySymbol;
property SysCurrencyDecimalSeparator : string read GetSysCurrencyDecimalSeparator;
property SysCurrencyThousandSeparator: string read GetSysCurrencyThousandSeparator;
property SysCurrencyDecimalDigits : string read GetSysCurrenctyDecimalDigits;
property SysDayNames : TStrings read FSysDayNames;
property SysMonthNames : TStrings read FSysMonthNames;
published
{ Published declarations}
end;
implementation
uses
Windows
,SysUtils
;
{ TgtRegionalSettings }
{------------------------------------------------------------------------------}
constructor TgtRegionalSettings.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FLocaleBuffer := StrAlloc(255);
FSysDayNames := TStringList.Create;
FSysMonthNames:= TStringList.Create;
GetMonthsAndDayNames;
end;
{------------------------------------------------------------------------------}
destructor TgtRegionalSettings.Destroy;
begin
StrDispose(FLocaleBuffer);
FreeAndNil(FSysDayNames);
FreeAndNil(FSysMonthNames);
inherited;
end;
{------------------------------------------------------------------------------}
procedure TgtRegionalSettings.GetMonthsAndDayNames;
begin
FSysDayNames.Clear;
FSysMonthNames.Clear;
GetLocaleInfo(GetSystemDefaultLCID, LOCALE_SDAYNAME1,FLocaleBuffer, 255);// Monday
FSysDayNames.Add(FLocaleBuffer);
GetLocaleInfo(GetSystemDefaultLCID, LOCALE_SDAYNAME2,FLocaleBuffer, 255);// Tuesday
FSysDayNames.Add(FLocaleBuffer);
GetLocaleInfo(GetSystemDefaultLCID, LOCALE_SDAYNAME3,FLocaleBuffer, 255);// Wednesday
FSysDayNames.Add(FLocaleBuffer);
GetLocaleInfo(GetSystemDefaultLCID, LOCALE_SDAYNAME4,FLocaleBuffer, 255);// Thursday
FSysDayNames.Add(FLocaleBuffer);
GetLocaleInfo(GetSystemDefaultLCID, LOCALE_SDAYNAME5,FLocaleBuffer, 255);// Friday
FSysDayNames.Add(FLocaleBuffer);
GetLocaleInfo(GetSystemDefaultLCID, LOCALE_SDAYNAME6,FLocaleBuffer, 255);// Saturday
FSysDayNames.Add(FLocaleBuffer);
GetLocaleInfo(GetSystemDefaultLCID, LOCALE_SDAYNAME7,FLocaleBuffer, 255);// Sunday
FSysDayNames.Add(FLocaleBuffer);
GetLocaleInfo(GetSystemDefaultLCID, LOCALE_SMONTHNAME1,FLocaleBuffer, 255);// January
FSysMonthNames.Add(FLocaleBuffer);
GetLocaleInfo(GetSystemDefaultLCID, LOCALE_SMONTHNAME2,FLocaleBuffer, 255);// February
FSysMonthNames.Add(FLocaleBuffer);
GetLocaleInfo(GetSystemDefaultLCID, LOCALE_SMONTHNAME3,FLocaleBuffer, 255);// March
FSysMonthNames.Add(FLocaleBuffer);
GetLocaleInfo(GetSystemDefaultLCID, LOCALE_SMONTHNAME4,FLocaleBuffer, 255);// April
FSysMonthNames.Add(FLocaleBuffer);
GetLocaleInfo(GetSystemDefaultLCID, LOCALE_SMONTHNAME5,FLocaleBuffer, 255);// May
FSysMonthNames.Add(FLocaleBuffer);
GetLocaleInfo(GetSystemDefaultLCID, LOCALE_SMONTHNAME6,FLocaleBuffer, 255);// June
FSysMonthNames.Add(FLocaleBuffer);
GetLocaleInfo(GetSystemDefaultLCID, LOCALE_SMONTHNAME7,FLocaleBuffer, 255);// July
FSysMonthNames.Add(FLocaleBuffer);
GetLocaleInfo(GetSystemDefaultLCID, LOCALE_SMONTHNAME8,FLocaleBuffer, 255);// August
FSysMonthNames.Add(FLocaleBuffer);
GetLocaleInfo(GetSystemDefaultLCID, LOCALE_SMONTHNAME9,FLocaleBuffer, 255);// September
FSysMonthNames.Add(FLocaleBuffer);
GetLocaleInfo(GetSystemDefaultLCID, LOCALE_SMONTHNAME10,FLocaleBuffer, 255);// October
FSysMonthNames.Add(FLocaleBuffer);
GetLocaleInfo(GetSystemDefaultLCID, LOCALE_SMONTHNAME11,FLocaleBuffer, 255);// November
FSysMonthNames.Add(FLocaleBuffer);
GetLocaleInfo(GetSystemDefaultLCID, LOCALE_SMONTHNAME12,FLocaleBuffer, 255);// December
FSysMonthNames.Add(FLocaleBuffer);
end;
{------------------------------------------------------------------------------}
// Getters - Setters \\
{------------------------------------------------------------------------------}
function TgtRegionalSettings.GetSysLocalLanguageName: string;
begin
GetLocaleInfo(GetSystemDefaultLCID, LOCALE_SLANGUAGE,FLocaleBuffer, 255);
Result := FLocaleBuffer;
end;
{------------------------------------------------------------------------------}
function TgtRegionalSettings.GetSysAnsiCodePage: string;
begin
GetLocaleInfo(GetSystemDefaultLCID, LOCALE_IDEFAULTANSICODEPAGE,FLocaleBuffer, 255);
Result := FLocaleBuffer;
end;
{------------------------------------------------------------------------------}
function TgtRegionalSettings.GetSysOemCodePage: string;
begin
GetLocaleInfo(GetSystemDefaultLCID, LOCALE_IDEFAULTCODEPAGE,FLocaleBuffer, 255);
Result := FLocaleBuffer;
end;
{------------------------------------------------------------------------------}
function TgtRegionalSettings.GetSysCountryCode: Word;
begin
Result := GetLocaleInfo(GetSystemDefaultLCID, LOCALE_IDEFAULTCOUNTRY,FLocaleBuffer, 255);
end;
{------------------------------------------------------------------------------}
function TgtRegionalSettings.GetSysCountryName: string;
begin
GetLocaleInfo(GetSystemDefaultLCID, LOCALE_SCOUNTRY,FLocaleBuffer, 255);
Result := FLocaleBuffer;
end;
{------------------------------------------------------------------------------}
function TgtRegionalSettings.GetSysLanguageID: Word;
begin
Result := GetLocaleInfo(GetSystemDefaultLCID, LOCALE_IDEFAULTLANGUAGE,FLocaleBuffer, 255);
end;
{------------------------------------------------------------------------------}
function TgtRegionalSettings.GetSysDateSeparator: string;
begin
GetLocaleInfo(GetSystemDefaultLCID, LOCALE_SDATE,FLocaleBuffer, 255);
Result := FLocaleBuffer;
end;
{------------------------------------------------------------------------------}
function TgtRegionalSettings.GetSysLongDateFormat: string;
begin
GetLocaleInfo(GetSystemDefaultLCID, LOCALE_SLONGDATE,FLocaleBuffer, 255);
Result := FLocaleBuffer;
end;
{------------------------------------------------------------------------------}
function TgtRegionalSettings.GetSysShortDateFormat: string;
begin
GetLocaleInfo(GetSystemDefaultLCID, LOCALE_SSHORTDATE,FLocaleBuffer, 255);
Result := FLocaleBuffer;
end;
{------------------------------------------------------------------------------}
function TgtRegionalSettings.GetSysTimeSeparator: string;
begin
GetLocaleInfo(GetSystemDefaultLCID, LOCALE_STIME,FLocaleBuffer, 255);
Result := FLocaleBuffer;
end;
{------------------------------------------------------------------------------}
function TgtRegionalSettings.GetSysTimeFormat: string;
begin
GetLocaleInfo(GetSystemDefaultLCID, LOCALE_STIMEFORMAT,FLocaleBuffer, 255);
Result := FLocaleBuffer;
end;
{------------------------------------------------------------------------------}
function TgtRegionalSettings.GetSysDecimalSeparator: string;
begin
GetLocaleInfo(GetSystemDefaultLCID, LOCALE_SDECIMAL,FLocaleBuffer, 255);
Result := FLocaleBuffer;
end;
{------------------------------------------------------------------------------}
function TgtRegionalSettings.GetSysDigitGrouping: string;
begin
GetLocaleInfo(GetSystemDefaultLCID, LOCALE_SGROUPING,FLocaleBuffer, 255);
Result := FLocaleBuffer;
end;
{------------------------------------------------------------------------------}
function TgtRegionalSettings.GetSysThousandSeparator: string;
begin
GetLocaleInfo(GetSystemDefaultLCID, LOCALE_STHOUSAND,FLocaleBuffer, 255);
Result := FLocaleBuffer;
end;
{------------------------------------------------------------------------------}
function TgtRegionalSettings.GetSysDecimalDigits: string;
begin
GetLocaleInfo(GetSystemDefaultLCID, LOCALE_IDIGITS,FLocaleBuffer, 255);
Result := FLocaleBuffer;
end;
{------------------------------------------------------------------------------}
function TgtRegionalSettings.GetSysCurrencySymbol: string;
begin
GetLocaleInfo(GetSystemDefaultLCID, LOCALE_SCURRENCY,FLocaleBuffer, 255);
Result := FLocaleBuffer;
end;
{------------------------------------------------------------------------------}
function TgtRegionalSettings.GetSysCurrencyDecimalSeparator: string;
begin
GetLocaleInfo(GetSystemDefaultLCID, LOCALE_SMONDECIMALSEP,FLocaleBuffer, 255);
Result := FLocaleBuffer;
end;
{------------------------------------------------------------------------------}
function TgtRegionalSettings.GetSysCurrencyThousandSeparator: string;
begin
GetLocaleInfo(GetSystemDefaultLCID, LOCALE_SMONTHOUSANDSEP,FLocaleBuffer, 255);
Result := FLocaleBuffer;
end;
{------------------------------------------------------------------------------}
function TgtRegionalSettings.GetSysCurrenctyDecimalDigits: string;
begin
GetLocaleInfo(GetSystemDefaultLCID, LOCALE_ICURRDIGITS,FLocaleBuffer, 255);
Result := FLocaleBuffer;
end;
{------------------------------------------------------------------------------}
end.
Product Key Informer as the name states will retrieve product key information regarding the operating system.
It is mainly a tool for Network Administrators who want to keep in order the product key info for their network terminals organized.
But also it can be used by the simple or standalone user to store vital product key info in case of HD crash or data loss.



(7 votes, average: 4.00 out of 5)
